Maps each ranked finding from 06-FINAL-SECURITY-REVIEW-REPORT.md to remediation, automated coverage, and residual risk. “Closed” means addressed in code/config with tests or guardrails unless noted.
| ID | Finding (short) | Remediation (where) | Test / guardrail coverage | Residual risk |
|---|---|---|---|---|
| H1 | CORS permissive when allowlist missing | parseCorsOriginsWithStartupEnforcement — fail startup when NODE_ENV ∉ {development, test} and allowlist missing/empty (packages/helpers/src/startup/cors-and-cookies.ts); API/management config use it |
cors-and-cookies.test.ts; cors-path.test.ts for Standard Endpoint permissive path |
Misconfigured NODE_ENV in production could weaken enforcement; deployments must set env correctly |
| H2 | RSS fetch SSRF | Central rss-safe-fetch.ts: DNS/IP block, redirects, body limits; buckets + sync use fetchRssFeedXmlWithTimeout |
rss-safe-fetch.test.ts; npm run security:check RSS wiring script |
Legitimate feeds on edge networks may need policy tuning; redirect chains capped |
| M1 | Open returnUrl sinks |
Shared helpers safeReturnUrl.ts; web/management role flows use resolveReturnUrlFromQuery / safeReturnPathOrFallback |
safeReturnUrl.test.ts; Playwright role specs from remediation wave |
New pages must continue to use helpers at every sink |
| M2 | Standard Endpoint HTTPS + trust-proxy | requireHttpsForStandardEndpoints + httpsScheme.ts; startup rejects unsafe STANDARD_ENDPOINT_TRUST_PROXY + STANDARD_ENDPOINT_REQUIRE_HTTPS=false |
standard-endpoint-https-enforcement.test.ts; startup-validation-standard-endpoint-policy.test.ts |
Operational: ingress must terminate TLS and set forwarded headers consistently |
| M3 | Tokens in query | Body-only token acceptance for verify-email / confirm-email-change (apps/api/src/controllers/authController.ts, Joi + routes) |
auth-mailer.test.ts |
Legacy bookmarks with query tokens no longer work |
| M4 | Auth rate limit gaps | Moderate limiters on POST /auth/refresh, POST /auth/logout (API); management refresh, logout, change-password |
auth-route-rate-limit-wiring.test.ts (API); auth-route-rate-limit-wiring.test.ts (management) |
Threshold tuning under abuse; optional Valkey store for multi-instance (*_AUTH_RATE_LIMIT_USE_KEYVALDB) |
| M5 | Management-web gate on cookie only | Proxy session restore + hasSession / invalidate paths aligned with web; stale cookie clears |
auth-stale-cookies-redirect-login.spec.ts |
Edge cases in cookie/session timing |
| M6 | Registry/provider trust | Hostname allowlists + extras env; startup validation (API + management-api); exchange provider URL checks | startup-validation-standard-endpoint-policy.test.ts; management mirror test file |
Ops dependency on third-party uptime; mirror hosts require *_EXTRA_HOSTS |
| L1 | JWT iss/aud optional | Optional API_JWT_* / MANAGEMENT_API_JWT_*; sign/verify use shared claim options |
jwt.claims.test.ts |
Rolling iss/aud requires coordinated token rotation |
| L2 | In-memory rate limits | Optional Valkey-backed store (API_AUTH_RATE_LIMIT_USE_KEYVALDB, MANAGEMENT_API_AUTH_RATE_LIMIT_USE_KEYVALDB) |
Covered by implementation + ENV-REFERENCE; integration left to staging | Valkey connectivity becomes a prod dependency when enabled |
| L3 | x-auth-user spoofing |
Proxies strip inbound x-auth-user before session restore; response headers built from stripped request |
Implementation in apps/web/src/proxy.ts, apps/management-web/src/proxy.ts |
Defense in depth only within Next layer; edge auth remains separate |
| SQL drift | Dynamic SQL fragments (date_trunc, etc.) |
Typed timeBucket union in BucketMessageService; parameterised LIKE %${escaped}% |
npm run security:check SQL fragment scanner + reviewer checklist |
Future edits to dynamic SQL must keep allowlists / types |
- None listed as permanently accepted without mitigation in the final report; remaining items are operational (env correctness, enabling optional features).
- Contributor checklist: SECURITY-REVIEW-CHECKLIST.md.
- Env catalog: ENV-REFERENCE.md.