Skip to content

Keep request credentials out of frontend router data - #7360

Merged
masenf merged 4 commits into
mainfrom
codex/session-cookie-privacy
Oct 8, 2026
Merged

masenf merged 4 commits into
mainfrom
codex/session-cookie-privacy

Conversation

@masenf

@masenf masenf commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

WebSocket request cookies, including HttpOnly credentials, were copied into frontend router state. Frontend on_load events could also return the raw server router metadata, exposing cookies even after the header serializer filtered them.

Keep request credentials on the server in both paths: omit Cookie, Authorization, Proxy-Authorization, Cloudflare Access assertions, OAuth2 Proxy access-token headers, AWS ALB OIDC tokens, and Google IAP assertions from serialized router headers, and stop copying state.router_data onto generated on-load events. Backend chains inherit the originating view through EventContext. This is a one-line source fix in reflex/state.py; StateUpdate serialization performs no added event filtering or cloning. Explicitly constructed application events retain any router_data supplied by the application.

Backend handlers and server-side state persistence retain the original headers. Frontend .headers.cookie and literal .headers["cookie"] access warn and render an empty string. Filtered raw_headers keys are absent; the docs explain indexed access and .get(..., "") fallbacks.

Validation on current main (749dd55b3):

  • Regressions failed before the fixes for indexed cookie access, HttpOnly cookies in frontend on-load event metadata, and authorization/proxy credentials in hydration. The AWS ALB/Google IAP regressions also failed in unit tests and real browser frames before adding the three header names. Header filtering covers nine names in three case variants.
  • The full unit suite passed 11,459 tests, with 159 skipped and 79.22% coverage, including state/event-processor routing and producer-level privacy regressions.
  • All 10 router browser cases passed in dev and production, including actual HttpOnly cookies, authorization/proxy credentials, AWS ALB tokens, and Google IAP assertions in WebSocket handshakes, backend access, and checking all received frames for secrets.
  • pyright reflex tests passed. Workspace pyright targeting Linux passed, and the remaining pre-commit hooks passed. The macOS workspace-wide pyright hook reports six pre-existing CPU-affinity typing errors in benchmark files.

The review's duplicate-header parsing report reproduces on main and is tracked separately in #7521, as the reviewer permits. This PR preserves the existing backend decoding behavior.

Covers ENG-12909. First layer of the token-protected endpoints project.

Next: #7361 → #7362 → #7363 → #7364. Enterprise companion: reflex-dev/reflex-enterprise#240.

Related to ENG-12909, ENG-9088.

@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Critical impact] The PR appears safe to merge; no outstanding finding or new blocking issue was identified.

Summary

The PR keeps request credentials out of frontend router data and stops generated on-load events from copying request metadata. Since the previous review, it also filters AWS ALB and Google IAP credential headers and extends the related tests and documentation.

Reviews (5) · Last reviewed commit: "Withhold AWS ALB and Google IAP credenti..." · Reviewed by Greptile

Comment thread reflex/istate/data.py
Comment thread reflex/istate/data.py Outdated
@codspeed

codspeed Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 11.16%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 149 untouched benchmarks
⏩ 18 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ test_hydration_metadata[200] 1.7 ms 1.9 ms -11.16%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing codex/session-cookie-privacy (d45cef9) with main (749dd55)

Open in CodSpeed

Footnotes

  1. 18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@masenf
masenf force-pushed the codex/session-cookie-privacy branch from 27416d7 to 33cad32 Compare October 5, 2026 21:24
@masenf masenf added documentation Improvements or additions to documentation breaking Public API facing change considered for minor release bump pre-v1.0 bug Something isn't working labels Oct 6, 2026
@linear-code

linear-code Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

ENG-12909

ENG-9088

@masenf
masenf marked this pull request as ready for review October 8, 2026 17:13
@masenf
masenf requested review from a team and Alek99 as code owners October 8, 2026 17:13

@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.

No issues found across 7 files

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

Comment thread reflex/istate/data.py Outdated
Comment thread docs/utility_methods/router_attributes.md
Comment thread reflex/istate/data.py

@FarhanAliRaza FarhanAliRaza 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.

I tested this PR in a small app against main (the PR's parent commit).

I sent raw websocket handshakes with custom header lists:

  • one Cookie field
  • an upper-case COOKIE field
  • two cookie fields, as HTTP/2 can send
  • Cookie and COOKIE together
  • Authorization and Cf-Access-Jwt-Assertion
  • two X-Forwarded-For fields

Then I searched the hydrate payload for the secret values. On main, every cookie case leaks. With this PR, no cookie value reaches the frontend in any case. Server code still reads the cookie.

In Chrome, State.router.headers.cookie renders "", and raw_headers has no cookie. An rx.Cookie set from the UI stays after a reload. The console shows no errors. The deprecation warning prints at compile time.

I also compared the cookie parsers of Starlette, Django, Werkzeug, Node cookie and universal-cookie. rx.Cookie takes the first value of a duplicate cookie name, as Node does. That follows the order in RFC 6265 §5.4, and this PR does not change it.

My findings are in the inline comments.

FarhanAliRaza
FarhanAliRaza previously approved these changes Oct 8, 2026
masenf added 2 commits October 8, 2026 10:52
Preserve server-side cookie access while omitting Cookie from serialized router headers and raw_headers. Deprecate frontend router cookie reads in favor of rx.Cookie.

Tests: 411 related unit tests passed; regression tests reproduced the leak before the fix. Ruff, formatting, and targeted pyright passed.

Refs: ENG-12909
Apply the deprecated empty-string fallback to indexed cookie access. Strip server-only router metadata from outbound events, and omit standard authorization and Cloudflare/OAuth2 Proxy token headers from frontend header serialization. Preserve backend access and document missing raw header keys.

Regression tests reproduced indexed access failures, HttpOnly cookie exposure in on-load events, and authorization header exposure in dev and production WebSocket frames before the fixes.

@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 8 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 reflex/istate/data.py
Comment thread news/+router-cookie-privacy.bugfix.md Outdated
@masenf masenf changed the title Keep request cookies out of frontend state Keep request credentials out of frontend router data Oct 8, 2026
Stop copying state.router_data onto generated on-load events. Backend chains already inherit the originating view through EventContext, and frontend actions do not need request routing metadata. Restore the original StateUpdate serializer to remove event filtering and cloning from serialization.

Replace the serializer-level regression with a producer-level test that failed before this change. State/event processor tests (458), dev/prod browser tests (10), framework type checks, and changed-file hooks pass.
@greptile-apps

greptile-apps Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings could not be posted inline.

  • P1 Security Outbound events expose credentials reflex/state.py:2945 ▶

    When a handler returns an Event carrying request router data, this serializer now sends that data to the browser without filtering its headers. Cookies and authorization headers can therefore be exposed even though frontend router state filters them. Returned Event objects retain their router data, and frontend events are emitted in a StateUpdate; the outbound event data needs filtering too.

    How this was verified: Returned events retain their router data, and the outbound StateUpdate now serializes those events without filtering their headers.

@masenf

masenf commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Addressing the latest outside-diff finding about explicit Event(router_data=...): this PR prevents the framework from automatically forwarding request credentials. Event.from_event_type creates events with empty router data unless the caller supplies it, and _load_events_for_page no longer supplies state.router_data. Backend chains stay on the server and inherit routing through EventContext.fork; only frontend actions are emitted. The producer regression and real dev/prod browser tests cover on-load frontend actions and verify all received frames contain no request credentials.

An application can still explicitly attach server data to an outgoing Event, just as it can put a credential in an event payload or a public state field. We are preserving that explicit application behavior. At the author's request, the former per-update iteration, router-data filtering, and event cloning were removed in favor of fixing the automatic copy at its source. serialize_state_update is unchanged from main, and the PR description states this scope.

@masenf
masenf merged commit beef0ae into main Oct 8, 2026
145 of 146 checks passed
@masenf
masenf deleted the codex/session-cookie-privacy branch October 8, 2026 19:03
masenf added a commit that referenced this pull request Oct 8, 2026
The State.router.headers.cookie deprecation added in #7360 shipped in the
0.10 alpha line, so the warning should name 0.10.0 rather than 0.9.13.


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

Co-authored-by: Claude <noreply@anthropic.com>
masenf pushed a commit that referenced this pull request Oct 9, 2026
One up-to-date, parameterized copy of each app, driver, probe and run
script from the eight passes, in four areas with a README each (findings
re-verified, run commands, expected result on 0.10.0):

- hydration: browser storage and hydration, sync=True cross-tab storms
  and convergence, router data / credential exposure (#7360).
- class_state: the state class model, set_default / copy-at-definition,
  add_var and auto-setter collisions, state-store compatibility.
- enterprise: OIDC cross-tab logout, auth suites and matrix + MCP, AG
  Grid, demo smoke, reflex-azure-auth.
- upgrade: installs, in-place upgrades of reflex-examples apps,
  third-party packages, events suite, CLI lifecycle, upgrade guide.

Upstream apps, enterprise demos and the enterprise wheel are fetched or
supplied at run time, never committed. Representative fixtures of every
area were re-run on reflex 0.10.0 (+ reflex-enterprise 0.9.7) and match
the 0.10.0a5 records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjCrbdguMkfe4bhbaREiwo
@masenf masenf mentioned this pull request Oct 20, 2026
10 of 13 tasks
masenf pushed a commit that referenced this pull request Oct 23, 2026
One up-to-date, parameterized copy of each app, driver, probe and run
script from the eight passes, in four areas with a README each (findings
re-verified, run commands, expected result on 0.10.0):

- hydration: browser storage and hydration, sync=True cross-tab storms
  and convergence, router data / credential exposure (#7360).
- class_state: the state class model, set_default / copy-at-definition,
  add_var and auto-setter collisions, state-store compatibility.
- enterprise: OIDC cross-tab logout, auth suites and matrix + MCP, AG
  Grid, demo smoke, reflex-azure-auth.
- upgrade: installs, in-place upgrades of reflex-examples apps,
  third-party packages, events suite, CLI lifecycle, upgrade guide.

Upstream apps, enterprise demos and the enterprise wheel are fetched or
supplied at run time, never committed. Representative fixtures of every
area were re-run on reflex 0.10.0 (+ reflex-enterprise 0.9.7) and match
the 0.10.0a5 records.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants