Files
sim/docker
WaleedandClaude Opus 4.7 ef14b2b580 fix(security): remove localhost CORS origin, consolidate CORS in proxy (#4658)
* fix(security): remove localhost CORS origin, consolidate CORS in proxy

Move all /api/* CORS handling from next.config.ts to proxy.ts so the
runtime can resolve allowed origin per-request instead of baking it at
build time (which produced "Access-Control-Allow-Origin: http://localhost:3000"
with credentials:true in production).

- proxy.ts: per-route CORS policy table covering auth, MCP, form, and
  workflow execute endpoints; OPTIONS preflight short-circuit; Vary:
  Origin when origin is not '*'; form routes defer to route handler's
  addCorsHeaders to avoid double-setting
- next.config.ts: drop all /api/* Access-Control-Allow-* headers; keep
  COEP/COOP/CSP
- deployment.ts: addCorsHeaders sets Vary: Origin alongside reflected
  Allow-Origin
- Dockerfile: drop NEXT_PUBLIC_APP_URL build placeholder (Zod has
  skipValidation:true; build path doesn't read it)
- Remove 8 dead OPTIONS handlers and their preflight tests now that the
  proxy handles preflight uniformly

* refactor(cors): consolidate API CORS into proxy as single source of truth

Move CORS for /api/chat/* and /api/form/* into the proxy policy table with
reflected-origin + credentials:false, and delete the per-route addCorsHeaders
helper. Routes no longer set CORS headers — the proxy is the only writer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(cors): convert proxy CORS policy chain to a rule table + add tests

Replace the if/else chain in resolveApiCorsPolicy with a CORS_RULES table
so each route's policy lives in one place and is trivially scannable.
Add proxy.test.ts covering each rule and the wildcard-with-credentials
invariant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cors): scope embed CORS rule to /api/{chat,form}/[identifier] only

The embed policy (reflected origin, credentials:false) was matching
workspace-internal session-authed routes — /api/chat, /api/chat/manage/*,
/api/chat/validate, and the form equivalents — which need the default
credentialed policy. Tighten the matcher to the embed paths only and add
tests covering the exclusion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(cors): replace embed-path regex with explicit segment check

The regex form `^/api/(chat|form)/(?!manage|validate)[^/]+(/(otp|sso))?$`
was opaque on review and would silently exclude any future identifier
subroute outside the hard-coded (otp|sso) group from the embed policy.
Replace it with an imperative segment check and a named
EMBED_RESERVED_SEGMENTS Set, so the policy boundary is visible at the
top of the function and adding a reserved subpath is a one-line diff.
Add a test asserting that future identifier subroutes also get the
embed policy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cors): allow PUT in embed CORS policy for OTP verification

Both /api/chat/[identifier]/otp and /api/form/[identifier]/otp export
PUT for OTP code verification. The embed policy advertised only
GET/POST/OPTIONS, so cross-origin embed clients failed preflight on
verify. Add PUT and assert it in the embed policy test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:04:23 -07:00
..