You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security(chat): the ownership gate still claims a session whose owner record is unreadable — #14033's contract is not honoured by SessionOwnershipValidator #17481
Found in review on #17426 (#17422), by CodeRabbit, and verified against current main.
Problem
#14033 gave ChatHistoryManager.get_session_owner an honest error contract: an unreadable session file raises SessionOwnerUnreadable (autobot-backend/chat_history/session.py, raise SessionOwnerUnreadable(session_id) from e) instead of returning None. api/conversation_files.py, api/ws_security.py and services/conversation_export.py catch it and treat it as unknown.
The chat ownership gate does not. SessionOwnershipValidator._durable_owner (autobot-backend/security/session_ownership.py) catches every exception and returns None:
exceptExceptionasexc:
logger.warning("Could not read the durable owner of session %s...: %s", session_id[:8], exc)
returnNone
_owner_when_cache_is_empty then reads that None as "no owner anywhere" and writes the caller as owner (set_session_owner(session_id, username, ...)), returning legacy_migration. So a caller who reaches a conversation while its file is unreadable (decrypt failure, corruption, IO error) with no Redis record is granted ownership of it, and the grant persists in Redis.
The docstring calls this fail-open deliberate. It also contradicts #14033's acceptance criterion "an unreadable session file does not result in a claim", and #14033 is closed as completed.
Blast radius
Every route guarded by validate_ownership. The grant is also an input elsewhere: services/agent_terminal/conversation_owner.py falls back to it. #17426 hardens the agent-terminal claim path so that an unreadable owner refuses instead of trusting the grant. That PR does not fix the gate itself.
Not measured
How often an unreadable-but-existing session file occurs in practice.
Whether any caller depends on the fail-open. The docstring points to "the PR" for the reasoning; I did not locate it.
Suggested approach
_durable_owner distinguishes SessionOwnerUnreadable from None. On unreadable, _owner_when_cache_is_empty denies (or defers) instead of claiming, and never calls set_session_owner.
Acceptance criteria
An unreadable session file with no Redis record does not produce a claim and does not write an owner. The test asserts the exact status code.
A genuinely ownerless (readable) session still takes legacy_migration as today.
Mutation: restoring the return None makes the unreadable-file test fail.
Witness from #14016's de-fork analysis: the conversation-files route check denies on an unreadable owner (#14033) while the canonical security/session_ownership.validate_session_ownership still allows and claims. That divergence is one of the 7 reasons #14016 can't merge the two validators until this is fixed.
Closure evidence (merged in merge train #18314, 5efe902; constituent #18258). Verified against main.
An unreadable owner yields no claim and no owner write, with the exact status asserted:autobot-backend/security/session_posture.py:73-87read_owner turns any lookup failure into deny_unreadable_owner. session_posture_equivalence_14016_test.py:222-227 asserts 403 (_verdict maps 403 to DENY) and redis.set.assert_not_awaited() for all 5 failure rows under both postures; line 238 pins the count.
A genuinely ownerless session still takes legacy_migration:session_ownership.py:534; the test at :230-235 asserts ALLOW under both postures, with the claim written under DEFAULT only.
Mutation: met by inspection only. The except … return None path is gone from read_owner, and restoring it would turn :222's DENY into ALLOW. There is no mutation harness, and the merger did not run one.
Activity
mrveiss commentedon Oct 10, 2026
Witness from #14016's de-fork analysis: the conversation-files route check denies on an unreadable owner (#14033) while the canonical
security/session_ownership.validate_session_ownershipstill allows and claims. That divergence is one of the 7 reasons #14016 can't merge the two validators until this is fixed.mrveiss commentedon Oct 11, 2026
Closure evidence (merged in merge train #18314, 5efe902; constituent #18258). Verified against
main.autobot-backend/security/session_posture.py:73-87read_ownerturns any lookup failure intodeny_unreadable_owner.session_posture_equivalence_14016_test.py:222-227asserts 403 (_verdictmaps 403 to DENY) andredis.set.assert_not_awaited()for all 5 failure rows under both postures; line 238 pins the count.legacy_migration:session_ownership.py:534; the test at :230-235 asserts ALLOW under both postures, with the claim written under DEFAULT only.except … return Nonepath is gone fromread_owner, and restoring it would turn :222's DENY into ALLOW. There is no mutation harness, and the merger did not run one.