Skip to content

feat(cors): always trust the service's own RAILWAY_PUBLIC_DOMAIN origin - #85

Draft
PetrefiedThunder wants to merge 1 commit into
mainfrom
claude/inflow-lab-ux-onboarding-uwr3dc
Draft

feat(cors): always trust the service's own RAILWAY_PUBLIC_DOMAIN origin#85
PetrefiedThunder wants to merge 1 commit into
mainfrom
claude/inflow-lab-ux-onboarding-uwr3dc

Conversation

@PetrefiedThunder

Copy link
Copy Markdown
Collaborator

Summary

Makes the demo service trust its own platform-issued origin (https://$RAILWAY_PUBLIC_DOMAIN) in addition to whatever REGENGINE_CORS_ORIGINS resolves to. This is the code half of the August cutover lesson — the docs half landed in #84.

Why a union and not a fallback: in the incident, the variable was set — to the previous service's URL, via a Railway reference variable. A fallback that only applies when the variable is missing would have changed nothing. With the union, a stale configured value can no longer lock the service out of its own domain, which is exactly what kept both nightly smokes red for three days (#80, #81) — the same list also gates state-changing requests in auth_middleware, so browser writes failed too.

Why it's safe: the appended origin is only the service's own canonical domain, injected by Railway. Anyone who can forge RAILWAY_PUBLIC_DOMAIN already controls the deployment, so this widens trust by nothing. Wildcards remain rejected; explicit origins for any other dashboard host still require configuration (the cutover checklist keeps that step, with a note about the new behavior).

Robustness: a malformed platform value degrades to "no extra origin" rather than raising — cors_origins_from_env() runs while the ASGI app is constructed, and a platform-injected string must never be able to crash startup.

Changes

  • app/cors.py — append the normalized platform origin post-parse; new _platform_origin() helper with a never-raise contract.
  • tests/test_api.py — three scenarios: platform origin joins defaults when unconfigured; stale-explicit-list can't exclude the own domain (the Remote Smoke is failing against the shared demo #80/Remote Browser Smoke is failing against the shared demo #81 regression, now pinned); no duplicate when already listed. Plus a never-crashes test for malformed/blank platform values.
  • DEPLOYMENT_PROFILES.md — one-line note on the self-trust behavior in the cutover checklist.

Test Plan

  • Full suite: 143 passed locally (uv run --frozen pytest)
  • New tests pin the incident regression by name so it cannot silently return

🤖 Generated with Claude Code

https://claude.ai/code/session_014ykFQkKR1XmCtSkDRCT4sT


Generated by Claude Code

The August 2026 cutover proved that a *configured* CORS allowlist can be
worse than none: REGENGINE_CORS_ORIGINS was set — to the previous
service's URL, via a Railway reference variable — so the new service
rejected every browser request from its own domain for three days, and
because auth_middleware gates state-changing requests on the same list,
writes failed too (#80, #81).

Append the platform-issued origin (https://$RAILWAY_PUBLIC_DOMAIN) to
whatever cors_origins_from_env() resolves. A union rather than a
fallback, deliberately: the incident had the variable set-but-stale, so
a fallback that only applies when the variable is missing would have
changed nothing. Trusting the platform domain widens nothing beyond the
service's own canonical origin, and anyone who can forge that variable
already controls the deployment.

A malformed platform value degrades to "no extra origin" instead of
raising — this path runs while the ASGI app is constructed, and a
platform-injected string must never be able to crash startup.

Explicit origins for third-party dashboard hosts still require
configuration; the cutover checklist keeps that step and notes the
self-trust behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ykFQkKR1XmCtSkDRCT4sT
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.

2 participants