Raxx · internal docs

internal · gated ↑ index

ADR 0047 — Track B: CORS origin allowlist for raxx.app on raxx-api-prod

Status: Accepted
Date: 2026-05-03
Deciders: software-architect
Context doc: raxx-app-track-b.md §4
Parent epic: #94


Context

raxx-api-prod (Heroku) already reads FRONTEND_ORIGIN env var to populate flask_cors.CORS(origins=...). The fallback is http://localhost:3000. On production, if FRONTEND_ORIGIN is not set, every cross-origin request from https://raxx.app is rejected by the browser before it reaches Raptor, because the CORS preflight response does not include Access-Control-Allow-Origin: https://raxx.app.

The code is correct. The config-var is missing.


Decision

Set FRONTEND_ORIGIN=https://raxx.app on the raxx-api-prod Heroku app.
Set FRONTEND_ORIGIN=https://raxx-app.pages.dev on the raxx-api-staging Heroku app.

No code change is required. The existing CORS init in backend_v2/api/__init__.py handles this correctly via _frontend_origins_raw.split(',').


Consequences

Positive

Negative / risks


Alternatives considered

A: Wildcard CORS (origins='*')

Rejected. Wildcard CORS is incompatible with supports_credentials=True (browsers reject credentialed requests with a wildcard origin header). It also removes the ability to scope which domains can call the API. The 2026-04-24 security review flagged this as finding C1; it was already fixed.

B: Hard-code https://raxx.app in source

Rejected. The FRONTEND_ORIGIN env var pattern is already established and rotatable without redeploy. Putting the origin in source would make staging/prod parity a code branch rather than a config-var difference.

C: Use CF Access to restrict cross-origin calls instead of CORS

Rejected. CF Access is a surface-gating mechanism, not a browser-facing CORS mechanism. The browser performs CORS preflight before CF Access has any involvement. CF Access cannot substitute for CORS headers.