Skip to content

Authorize browser state through session-aware transports - #7362

Open
masenf wants to merge 5 commits into
codex/codex/session-token-primitivesfrom
codex/session-transport
Open

masenf wants to merge 5 commits into
codex/codex/session-token-primitivesfrom
codex/session-transport

Conversation

@masenf

@masenf masenf commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Bind HTTP requests, Reflex socket events, custom WebSocket routes, and uploads to verified browser sessions before accessing state. Cookie-less sockets receive session/client tokens in-band so hydration can start immediately. Fresh clients in off mode also receive a routing token.

POST /_reflex/session installs an HttpOnly cookie only from a trusted Origin when exchanging an in-band credential; wildcard CORS alone cannot authorize that exchange. Existing valid cookies take precedence. Cookies use Secure, SameSite=None, and Partitioned with the documented development fallback. Enterprise's /_reflex/auth/token contract is unchanged.

Event contexts inherit browser authority, explicit None means anonymous, and trusted server work can use SessionToken.SYSTEM. Custom WebSockets no longer inherit SYSTEM accidentally. Authenticated live sockets can renew their own credential after cookie expiry while preserving owner and tab bindings. HTTP responses advertise the refresh interval. Streamed uploads use a distinct transaction context.

The internal transport module is named reflex.session_middleware to preserve the public rx.session() database helper across import orders. Dependency floors and deprecation versions match current main.

Depends on #7361; followed by #7363 and #7364. Rebased on main d02ba5409.

Validation: 390 transport/context/upload tests against an isolated real Redis server, two fresh-process database import regressions, full stack unit tests (12,147 passed; 80.15% coverage), and clean pyright reflex tests. Regressions reproduce the custom-WebSocket authority, off-mode token, and idle-renewal failures before their fixes. Real Uvicorn/Granian/browser coverage is in #7364.

Related to ENG-12911, ENG-12912, ENG-12913, ENG-12914, ENG-12915, ENG-9088.

@masenf
masenf added this pull request to stack #7365 September 30, 2026 00:41
@masenf masenf changed the title Authorize browser state through session-aware HTTP and WebSocket transports Authorize browser state through session-aware transports Sep 30, 2026
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Critical impact] Adds session authorization to event processing and state access.

The repeated session key must be extracted into a shared constant before merging to satisfy the repository requirement.

Summary

This PR ties browser sessions to HTTP requests, socket events, and uploads. The latest change renames the transport module to preserve the public rx.session database helper.

  • Browser sessions authorize client state across HTTP and sockets.
  • Event contexts check whether a session owns the client state.
  • Uploads check the request session before processing client data.
  • Buffered uploads get their own event transaction.

Reviews (8) · Last reviewed commit: "Preserve the public database session hel..." · Reviewed by Greptile

Comment thread reflex/app.py
Comment thread reflex/app.py
Comment thread reflex/app.py
@masenf
masenf force-pushed the codex/session-transport branch from 9eb0a30 to 9e6efe3 Compare October 5, 2026 21:24
@linear-code

linear-code Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

@masenf
masenf force-pushed the codex/session-transport branch from 9e6efe3 to 1ea0399 Compare October 8, 2026 17:55
@masenf
masenf force-pushed the codex/session-transport branch from 1ea0399 to 4962d46 Compare October 8, 2026 18:24
@masenf
masenf force-pushed the codex/session-transport branch 2 times, most recently from b23f99f to a931b1c Compare October 8, 2026 19:03
@masenf
masenf force-pushed the codex/session-transport branch from a931b1c to 9120624 Compare October 14, 2026 09:35
Comment thread reflex/session_middleware.py
@masenf
masenf marked this pull request as ready for review October 14, 2026 09:54
@masenf
masenf requested a review from a team as a code owner October 14, 2026 09:54

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

3 issues found across 22 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="reflex/session_middleware.py">

<violation number="1" location="reflex/session_middleware.py:285">
P2: Deeply nested JSON can make `json.loads` raise `RecursionError`, bypassing this handler and turning an invalid session request into a 500. Catch it with the parse errors so the endpoint returns its 400 response.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/event/context.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/event/context.py:136">
P2: An empty client token bypasses enforcement even when `session_token` is `None`, but state access can still use that token as its state key. Reject or otherwise guard state access from anonymous tokenless contexts; only explicitly trusted `SessionToken.SYSTEM` work should bypass authorization.</violation>
</file>

<file name="reflex/app.py">

<violation number="1" location="reflex/app.py:2278">
P2: A failed connect can leave `_sessions` and `_session_refresh_at` entries behind because they are populated before `link_token_to_sid`, while cleanup only covers boot-event failures. Wrap the link/state initialization in the same cleanup path and remove the token mapping before re-raising; rejected connects do not receive `on_disconnect`.</violation>
</file>

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

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

)

try:
payload = json.loads(await request.body() or b"{}")

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.

P2: Deeply nested JSON can make json.loads raise RecursionError, bypassing this handler and turning an invalid session request into a 500. Catch it with the parse errors so the endpoint returns its 400 response.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At reflex/session_middleware.py, line 285:

<comment>Deeply nested JSON can make `json.loads` raise `RecursionError`, bypassing this handler and turning an invalid session request into a 500. Catch it with the parse errors so the endpoint returns its 400 response.</comment>

<file context>
@@ -0,0 +1,330 @@
+        )
+
+    try:
+        payload = json.loads(await request.body() or b"{}")
+    except (ValueError, UnicodeError):
+        payload = None
</file context>
Suggested change
payload = json.loads(await request.body() or b"{}")
except (ValueError, UnicodeError, RecursionError):

Raises:
SessionAuthorizationError: If enforcement rejects the client token.
"""
if not self.token or (

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.

P2: An empty client token bypasses enforcement even when session_token is None, but state access can still use that token as its state key. Reject or otherwise guard state access from anonymous tokenless contexts; only explicitly trusted SessionToken.SYSTEM work should bypass authorization.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/reflex-base/src/reflex_base/event/context.py, line 136:

<comment>An empty client token bypasses enforcement even when `session_token` is `None`, but state access can still use that token as its state key. Reject or otherwise guard state access from anonymous tokenless contexts; only explicitly trusted `SessionToken.SYSTEM` work should bypass authorization.</comment>

<file context>
@@ -119,20 +127,55 @@ class EventContext(BaseContext):
+        Raises:
+            SessionAuthorizationError: If enforcement rejects the client token.
+        """
+        if not self.token or (
+            self.session_token is not None and self.session_token.authorizes(self.token)
+        ):
</file context>

Comment thread reflex/app.py
issue_session = session is None
if session is None:
session = manager.create()
self._sessions[sid] = session

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.

P2: A failed connect can leave _sessions and _session_refresh_at entries behind because they are populated before link_token_to_sid, while cleanup only covers boot-event failures. Wrap the link/state initialization in the same cleanup path and remove the token mapping before re-raising; rejected connects do not receive on_disconnect.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At reflex/app.py, line 2278:

<comment>A failed connect can leave `_sessions` and `_session_refresh_at` entries behind because they are populated before `link_token_to_sid`, while cleanup only covers boot-event failures. Wrap the link/state initialization in the same cleanup path and remove the token mapping before re-raising; rejected connects do not receive `on_disconnect`.</comment>

<file context>
@@ -2199,14 +2263,36 @@ async def on_connect(self, sid: str, environ: dict, auth: Any = None):
+            issue_session = session is None
+            if session is None:
+                session = manager.create()
+            self._sessions[sid] = session
+            self._session_refresh_at[sid] = session.issued_at + manager.refresh_interval
+            if not token or (mode == "enforce" and not session.authorizes(token)):
</file context>

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant