Describe the bug
#7068 split State.router into five per-field vars (rx_router_url, rx_router_page, rx_router_session, rx_router_headers, rx_router_route_id) and made router a property with no backing field, so it no longer appears in state.dict() or in deltas. Downstream code that looked up the router key now finds nothing. reflex-enterprise 0.9.5's redact_router_session() (reflex_enterprise/plugins/event_handler_api.py:733) is exactly such code: it blanks router.session.client_token / session_id before state is returned over the REST, MCP and agent-delta surfaces. On 0.9.12a1 the redaction silently does nothing, and POST /_reflex/retrieve_state plus the ndjson event deltas of /_reflex/event/<state>/<handler> return the server-side rx_router_session.client_token and session_id — identifiers the caller never presented (not its bearer). On 0.9.11.post1 the same responses carry router with client_token blanked.
Today this is masked by #7211 (reflex-enterprise 0.9.5 apps cannot start on 0.9.12a1 at all); it becomes live the moment #7211 is fixed in reflex against the already-published reflex-enterprise 0.9.5, which pins reflex[db]>=0.9.6 with no upper bound. No released stable combination is affected today.
To Reproduce
In a venv with reflex 0.9.12a1 + reflex-enterprise 0.9.5 (plus a test-only shim for #7211 so the module imports), from a neutral cwd:
import reflex as rx
from reflex.istate.data import RouterData
from reflex.state import State
from reflex_enterprise.plugins.event_handler_api import redact_router_session, router_data_for_token
st = State(_reflex_internal_init=True)
st.router_data = dict(router_data_for_token("SECRET-CLIENT-TOKEN", headers={"host": "x"}, query={"a": "1"}, path="/tickets"))
st.router = RouterData.from_router_data(st.router_data)
d = redact_router_session(st.dict())
print(sorted(d[State.get_full_name()]))
# 0.9.12a1: is_hydrated + the five rx_router_* keys, no 'router'; searching d for "SECRET-CLIENT-TOKEN"
# finds it under rx_router_session.client_token.
# 0.9.11.post1: 'router' present with client_token blanked; the secret appears nowhere.
Over HTTP: the unmodified tickets demo (with the #7211 shim) on 0.9.12a1, an anonymous app bearer, POST /_reflex/retrieve_state and POST /_reflex/event/<state>/seed — both bodies contain the real rx_router_session.client_token; blanked on 0.9.11.post1.
Expected behavior
Session identifiers are never returned over the REST surface, whatever reflex version reflex-enterprise runs on.
Where the fix belongs / what 0.9.12 needs
- reflex-enterprise: redact the
rx_router_session entry (and keep redacting router for reflex < 0.9.12), released in lockstep with 0.9.12, ideally with a reflex>=0.9.12 lower bound in the version that carries it.
- reflex: a changelog / breaking-change note that
router is no longer a key in state.dict() or in deltas and that downstream code keyed on it must switch to the rx_router_* entries (there is no such note today). Whether reflex should additionally offer a redaction hook, or refuse to run with a known-incompatible reflex-enterprise, is a maintainer decision.
Specifics (please complete the following information):
- Python Version: 3.11.15
- Reflex Version: 0.9.12a1 / reflex-base 0.9.12a1 (regression vs 0.9.11.post1); reflex-enterprise 0.9.5
- OS: Linux (Ubuntu 24.04 container)
Additional context
0.9.12a1 pre-release QA campaign FINDING-011, confirmed over HTTP by an adversarial verifier. Evidence and scripts: prerelease-testing/2026-09-18-v0.9.12a1/ent_map_dnd_flow_mantine/ (scripts/probe_router_redact.py, verification/v_leak_http.py, NOTES.md) and FINDINGS.md in the parent directory.
Describe the bug
#7068 split
State.routerinto five per-field vars (rx_router_url,rx_router_page,rx_router_session,rx_router_headers,rx_router_route_id) and maderoutera property with no backing field, so it no longer appears instate.dict()or in deltas. Downstream code that looked up therouterkey now finds nothing. reflex-enterprise 0.9.5'sredact_router_session()(reflex_enterprise/plugins/event_handler_api.py:733) is exactly such code: it blanksrouter.session.client_token/session_idbefore state is returned over the REST, MCP and agent-delta surfaces. On 0.9.12a1 the redaction silently does nothing, andPOST /_reflex/retrieve_stateplus the ndjson event deltas of/_reflex/event/<state>/<handler>return the server-siderx_router_session.client_tokenandsession_id— identifiers the caller never presented (not its bearer). On 0.9.11.post1 the same responses carryrouterwithclient_tokenblanked.Today this is masked by #7211 (reflex-enterprise 0.9.5 apps cannot start on 0.9.12a1 at all); it becomes live the moment #7211 is fixed in reflex against the already-published reflex-enterprise 0.9.5, which pins
reflex[db]>=0.9.6with no upper bound. No released stable combination is affected today.To Reproduce
In a venv with reflex 0.9.12a1 + reflex-enterprise 0.9.5 (plus a test-only shim for #7211 so the module imports), from a neutral cwd:
Over HTTP: the unmodified
ticketsdemo (with the #7211 shim) on 0.9.12a1, an anonymous app bearer,POST /_reflex/retrieve_stateandPOST /_reflex/event/<state>/seed— both bodies contain the realrx_router_session.client_token; blanked on 0.9.11.post1.Expected behavior
Session identifiers are never returned over the REST surface, whatever reflex version reflex-enterprise runs on.
Where the fix belongs / what 0.9.12 needs
rx_router_sessionentry (and keep redactingrouterfor reflex < 0.9.12), released in lockstep with 0.9.12, ideally with areflex>=0.9.12lower bound in the version that carries it.routeris no longer a key instate.dict()or in deltas and that downstream code keyed on it must switch to therx_router_*entries (there is no such note today). Whether reflex should additionally offer a redaction hook, or refuse to run with a known-incompatible reflex-enterprise, is a maintainer decision.Specifics (please complete the following information):
Additional context
0.9.12a1 pre-release QA campaign FINDING-011, confirmed over HTTP by an adversarial verifier. Evidence and scripts:
prerelease-testing/2026-09-18-v0.9.12a1/ent_map_dnd_flow_mantine/(scripts/probe_router_redact.py,verification/v_leak_http.py, NOTES.md) andFINDINGS.mdin the parent directory.