Skip to content

fix(app): restrict default CORS origins to deploy_url in prod mode - #7641

Open
FarhanAliRaza wants to merge 5 commits into
mainfrom
farhan/friendly-brahmagupta-e48jze
Open

FarhanAliRaza wants to merge 5 commits into
mainfrom
farhan/friendly-brahmagupta-e48jze

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Description

closes #7640

The ["*"] default for cors_allowed_origins let any site connect to the backend. This PR makes the default mode-aware, following the design in @masenf's review:

  • Dev mode: unchanged, any origin is allowed.
  • Prod mode, deploy_url unset (the new default): CORS is left unconfigured. No CORSMiddleware is installed and the Socket.IO server keeps Engine.IO's same-origin default, which is what a backend that serves its own frontend needs. Cross-origin requests are refused by the browser.
  • Prod mode, deploy_url set: only that origin is allowed, serialized the way browsers serialize Origin (lowercase host, no path, default port omitted; non-default ports and IPv6 hosts kept).
  • Explicit cors_allowed_origins (from rxconfig.py or REFLEX_CORS_ALLOWED_ORIGINS) is used as is in both modes, so ["*"] remains an opt-in to the old behavior. Explicitness is detected via Config._non_default_attributes.

Both enforcement points, the Starlette CORSMiddleware and the Socket.IO AsyncServer, resolve their origins through one helper, _cors_allowed_origins() in reflex/app.py.

Config changes

  • deploy_url now defaults to None instead of http://localhost:3000, and is no longer derived from frontend_port. It is only meant for frontends hosted on a different origin than the backend.
  • New Config.backend_url property: deploy_url when set, otherwise api_url. The sitemap plugin builds absolute links from it.

Notes

  • Integration tests are unaffected: AppHarness pins dev mode before building the app and sets deploy_url itself.
  • The docs site previously relied on the http://localhost:3000 default for its Helm sitemap rewrite; its sitemap now carries backend_url. See the review discussion.

Tests

Unit tests cover dev vs prod, unset deploy_url (no middleware, Socket.IO same-origin default), path-bearing and default-port deploy_url forms, explicit lists, explicit ["*"] via kwargs and via env, request-level enforcement through Starlette's TestClient and Engine.IO's origin resolution, the backend_url fallback, and the sitemap plugin. News fragments added for reflex and reflex-base (both breaking).

https://claude.ai/code/session_01C1xrGYmKsaacgEafTz9BTJ

The `["*"]` default for `cors_allowed_origins` let any site connect to
the backend. In prod mode the default now narrows to the origin of
`deploy_url` (or nothing when `deploy_url` is unset), while an explicit
`cors_allowed_origins`, from rxconfig or the environment, is used as is.
Dev mode keeps allowing any origin. Both the Starlette CORS middleware and
the Socket.IO server resolve their origins through one helper.

Closes #7640

Claude-Session: https://claude.ai/code/session_01C1xrGYmKsaacgEafTz9BTJ
@FarhanAliRaza
FarhanAliRaza requested review from a team and Alek99 as code owners October 22, 2026 19:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T19:55:55.451787Z ed3738c PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@FarhanAliRaza FarhanAliRaza added the breaking Public API facing change considered for minor release bump pre-v1.0 label Oct 22, 2026

@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: ed3738cb94

ℹ️ 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".

Comment thread reflex/app.py Outdated
@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; no new actionable issue was established.

Summary

Makes default CORS settings depend on the run mode and changes deploy_url to default to None.

  • Production apps limit default cross-origin access to their configured frontend.
  • Sitemaps use the backend URL when no frontend URL is set.

Reviews (5) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Comment thread reflex/app.py Outdated
Comment thread tests/units/test_app.py

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread reflex/app.py Outdated
Comment thread packages/reflex-base/news/+cors-prod-default.docs.md Outdated
Browsers serialize the Origin header with a lowercase host and without
the scheme's default port, so `https://Example.com:443/app` must allow
`https://example.com`. Also cover CORS enforcement with request-level
tests for the HTTP middleware and the Socket.IO origin check.

Claude-Session: https://claude.ai/code/session_01C1xrGYmKsaacgEafTz9BTJ

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread tests/units/test_app.py
@codspeed

codspeed Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 5.99%

2 warnings

⚠️ 9 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 147 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_state_access[write_inherited] 238.7 ms 225.2 ms +5.99%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing farhan/friendly-brahmagupta-e48jze (c54fef1) with main (2a2a4c1)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (16ca72d) during the generation of this report, so 2a2a4c1 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@masenf masenf added security on deck PRs lined up to review / merge next documentation Improvements or additions to documentation bug Something isn't working labels Oct 22, 2026

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With reflex run --env prod and the default deploy_url of http://localhost:3000, opening the app via 127.0.0.1:3000 is now rejected. Set deploy_url or cors_allowed_origins for alternate origins.

This is majorly problematic!

Reflex prod mode intentionally runs same origin now, serving the prod frontend build and the websocket from the same asgi stack. And with this change, those same origin connections to the websocket are blocked by socket.io.

I think we need to do the following:

  • change default for deploy_url in the config to be None. Add a property for backend_url that gives either deploy_url or api_url when the former is unset. Wherever we currently use deploy_url (like the sitemap plugin), we should move to backend_url instead.
  • when deploy_url is not set in production mode, then DO NOT set CORS options at all -- this defaults to only allowing same origin requests, which is different than allowing cross origin requests from a specific place. socket.io and asgi by default permit same origin requests to come through, which is the sane default that users want.

This keeps common deploy methods working without having to make any config changes, while bolstering security significantly.

Reflex prod mode serves the frontend and the websocket from the same
origin, so narrowing CORS to a default deploy_url blocked those same
origin connections. deploy_url now defaults to None and is no longer
derived from frontend_port; when it is unset in prod mode no CORS
middleware is installed and Socket.IO keeps its same-origin default.
Config gains a backend_url property (deploy_url, else api_url) and the
sitemap plugin builds absolute links from it.

Claude-Session: https://claude.ai/code/session_01C1xrGYmKsaacgEafTz9BTJ

Copy link
Copy Markdown
Contributor Author

@masenf thanks, you're right: with the frontend mounted into the backend, a default deploy_url narrowed CORS to an origin the browser never presents, and Socket.IO rejected the same-origin websocket. Pushed 509b30e implementing your proposal:

  • deploy_url now defaults to None and is no longer derived from frontend_port in _replace_defaults (otherwise reflex run --env prod, which persists frontend_port=backend_port=port, would have re-created a localhost:<port> deploy_url and narrowed CORS again for 127.0.0.1 / LAN access).
  • New Config.backend_url property: deploy_url when set, otherwise api_url. The sitemap plugin now builds absolute links from it.
  • In prod mode with deploy_url unset, _cors_allowed_origins() returns None: _add_cors installs no CORSMiddleware, and the Socket.IO server gets cors_allowed_origins=None, so Engine.IO's same-origin default (Host / X-Forwarded-* based) applies. With deploy_url set, only that origin is allowed; an explicit cors_allowed_origins is still used as is; dev mode stays ["*"].

Two things worth your eye:

  1. Docs site sitemap. docs/app/tests/test_config.py asserted the framework's http://localhost:3000 default, and the README noted the Helm sitemap rewrite expects that localhost origin. With backend_url, the docs sitemap now carries api_url, which in single-port prod is http://localhost:<port>. If the docs deployment does not run on port 3000, the rewrite pattern needs updating, or REFLEX_DEPLOY_URL should be set to the public origin. I updated the test and README to the new contract rather than pinning deploy_url in the docs rxconfig.py, since an explicit localhost deploy_url would narrow CORS in prod.
  2. reflex_site_shared.utils.url.public_url still reads deploy_url directly: with it unset it returns a relative URL, which seems to be the right semantic for canonical links (api_url would be wrong there), and switching it to backend_url would need a reflex-base .dev0 floor bump in reflex-site-shared. Left as is; happy to change if you'd rather have it uniform.

Tests: unit suites for app, config, sitemap, telemetry, export, hosting and site-shared pass; the docs-site suite (727 tests) passes. The full unit run has pre-existing failures in reflex_cli and reflex_bench browser tests that reproduce on a clean checkout in this environment and are unrelated.


Generated by Claude Code

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 12 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/plugins/sitemap.py
Comment thread news/+cors-prod-default.breaking.md Outdated
Comment thread tests/units/plugins/test_sitemap.py
Comment thread packages/reflex-base/src/reflex_base/config.py

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Public API facing change considered for minor release bump pre-v1.0 bug Something isn't working documentation Improvements or additions to documentation on deck PRs lined up to review / merge next security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cors_allowed_origins should default to restrictive in prod mode

2 participants