Skip to content

feat(api): CORS restrito a origens confiáveis (ENG-1666) - #427

Merged
limaronaldo merged 1 commit into
mainfrom
rm/eng-1666-medium-restringir-cors-para-origens-confiaveis
Aug 11, 2026
Merged

feat(api): CORS restrito a origens confiáveis (ENG-1666)#427
limaronaldo merged 1 commit into
mainfrom
rm/eng-1666-medium-restringir-cors-para-origens-confiaveis

Conversation

@limaronaldo

Copy link
Copy Markdown
Owner

Resumo

  • Remove Access-Control-Allow-Origin: * do router e do endpoint SSE (/api/logs/stream)
  • Novo módulo packages/api/src/core/cors.ts: allowlist via env ALLOWED_ORIGINS (comma-separated)
  • Echo do Origin apenas quando presente na allowlist + Vary: Origin
  • Wildcard apenas com opt-in explícito (ALLOWED_ORIGINS=*); sem config, nenhum Allow-Origin é emitido (secure by default)
  • Aplicado a todos os endpoints: preflight OPTIONS, respostas de rota, erros 404/500/429 e SSE

Testes

  • bun test src/core/cors.test.ts: 9 pass / 0 fail
  • bunx tsc --noEmit: limpo

Closes ENG-1666

…G-1666)

- Remove Access-Control-Allow-Origin: * (router e endpoint SSE)
- Novo módulo core/cors.ts: allowlist via ALLOWED_ORIGINS (comma-separated)
- Echo do Origin apenas para origens confiáveis + Vary: Origin
- Wildcard só com opt-in explícito; secure by default sem config
- Aplicado a todos os endpoints (preflight, respostas, erros, SSE)
- 9 testes de conformidade em core/cors.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6051c8c0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Access-Control-Allow-Origin é emitido — secure by default.

function getAllowedOrigins(): string[] {
return (process.env.ALLOWED_ORIGINS ?? "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Configure frontend origins before default-denying CORS

When deploying with the checked-in Fly workflow, neither fly.toml nor packages/api/.env.example configures the new ALLOWED_ORIGINS variable, while the separately deployed dashboard calls https://multiplai.fly.dev (packages/web/.env.production:1). This empty default therefore omits Access-Control-Allow-Origin from every dashboard API and SSE response, causing browsers to block the application unless an untracked Fly secret is manually provisioned; the documented Vite-on-5173 development setup is similarly affected by pages that call port 3000 directly. Add the trusted production/development origins to the deployment and sample configuration, or fail startup when the required setting is absent.

Useful? React with 👍 / 👎.

@limaronaldo limaronaldo left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Light Review — PR #427 (ENG-1666): CORS restricted to trusted origins

Verdict: ok-to-merge (one MEDIUM hardening gap noted, not blocking)

Summary

corsHeadersFor/addCorsHeaders in packages/api/src/core/cors.ts replace the old always-wildcard CORS_HEADERS object. Default behavior is now secure-by-default (no Access-Control-Allow-Origin emitted unless the request's Origin matches ALLOWED_ORIGINS, with an explicit * opt-in still supported). All four call sites in router.ts (the /api/logs/stream route, the OPTIONS preflight 204, the rate-limit-rejected path, the route-handler success/catch path, and the 404 fallback) were migrated consistently. Access-Control-Allow-Credentials is never set anywhere in this diff or on the pre-existing router.ts, so the classic "reflected origin + credentials" CORS vulnerability class does not apply here.

Findings

  • [MEDIUM] packages/api/src/core/cors.ts:38-45 (addCorsHeaders) — stale/pre-existing Access-Control-Allow-Origin on a Response is not stripped, only conditionally overwritten.
    addCorsHeaders builds newHeaders from the existing response headers, then only sets keys that corsHeadersFor returns. For a disallowed/absent origin, corsHeadersFor never includes Access-Control-Allow-Origin in its return value, so if a Response object already carried Access-Control-Allow-Origin: * (e.g., set by a future handler, a library, or a copy-pasted route), that stale value would survive untouched and bypass the new allowlist policy. Confirmed against current router.ts (fetched from main) that no route handler in this diff's scope currently sets its own Access-Control-Allow-Origin — so this is not exploitable via any code path present today — but it's a real defense-in-depth gap since the function's contract ("apply the CORS policy to this response") is not actually enforced; it's merged. Recommend explicitly deleting Access-Control-Allow-Origin from newHeaders before applying corsHeadersFor's result, and adding a regression test that pre-seeds a Response with Access-Control-Allow-Origin: * to prove the override actually clears it. (Flagged independently by cross-model review via codex exec.)

  • [LOW] packages/api/src/core/cors.ts:24 — comma-split allowlist has no validation of malformed entries.
    getAllowedOrigins() blindly splits/trims ALLOWED_ORIGINS; a misconfigured env var (e.g., trailing comma producing an empty string after .filter(Boolean), or a typo'd scheme) fails safely (no match → no header), so this is low risk, but there's no startup-time validation/logging if the env var is misconfigured, which could mask an ops mistake silently. Not a blocker.

Not found

  • No wildcard-by-default regression, no null-origin bypass, no credentials/wildcard combination, no missing Vary: Origin — all handled correctly and covered by the new cors.test.ts (9 tests).

Cross-model review

codex exec -m gpt-5.6-terra reviewed the diff independently and returned a single MEDIUM finding matching the one above (the stale-header bypass), corroborating this analysis. Codex noted its own scope limitation (no local packages/api checkout available), so its review was diff-only static analysis, same constraint as this review.

@limaronaldo
limaronaldo merged commit 352412d into main Aug 11, 2026
4 checks passed
@limaronaldo
limaronaldo deleted the rm/eng-1666-medium-restringir-cors-para-origens-confiaveis branch August 11, 2026 00:35
limaronaldo added a commit that referenced this pull request Aug 11, 2026
…ddCorsHeaders (#431)

Follow-up to #427. addCorsHeaders re-applied corsHeadersFor via
Headers.set, but when the evaluated origin is not allowed no ACAO is
computed, so a stale/upstream Access-Control-Allow-Origin (and
Allow-Credentials) survived on the response and could leak. Delete both
CORS headers before re-applying, guaranteeing at most one correct value
and no ACAO for disallowed origins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant