fix: validate thread_id ownership against stored user_id#305
Open
Joshua-Medvinsky wants to merge 1 commit into
Open
fix: validate thread_id ownership against stored user_id#305Joshua-Medvinsky wants to merge 1 commit into
Joshua-Medvinsky wants to merge 1 commit into
Conversation
The /invoke, /stream, and /history endpoints accepted any caller-supplied thread_id without checking that it belongs to the requesting user. An authenticated user who knows another user's thread_id could read that user's full conversation history via /history, or inject messages into their active conversation via /invoke or /stream. The fix compares the user_id stored in the LangGraph checkpoint config against the user_id from the current request. If both are present and they differ, the request is rejected with HTTP 403. Threads created before this patch (where no user_id is stored in the checkpoint) are unaffected — the check only activates when a stored user_id exists. ChatHistoryInput gains an optional user_id field so callers can supply their identity to the /history endpoint. Fixes: IDOR in src/service/service.py:_handle_input and history() Signed-off-by: FailSafe Researcher <joshua@getfailsafe.com>
f9be55d to
47e42fe
Compare
|
Hi maintainers — friendly follow-up on this security hardening PR. It has been open for a while with no reviewer feedback, so I wanted to resurface it. Happy to rebase, adjust the approach, add tests, or split the change differently if that would make review easier. Thanks! |
JoshuaC215
pushed a commit
that referenced
this pull request
Jun 30, 2026
- #307: agree default=False; note get_mongo_connection_string() is a pure function so a unit test asserting tls=true is trivial (no tests/memory/ exists yet, no live Mongo needed). - #305: keep threat-model concern; ask for negative (403) + positive (200) ownership tests. Recorded that test_history covers only the happy/ backward-compat path, not the new ownership logic. - #297: ask for a test repro, welcome a manual repro description as fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ALYJSRixXCfLsmb2Koc1Hi
JoshuaC215
pushed a commit
that referenced
this pull request
Jun 30, 2026
- #305: cut the long defense-in-depth restatement (the PR already states it) down to just the positive + negative test ask; strengthen SKILL rule 6 — never explain back a caveat the contributor already raised. - #307: reviewed the diff — connection-string format is correct (?/& handled, tls=true is right); dropped the test ask; only request is default=False. Noted minor nits. - #301/302: decided — answer on #301, close both. - #292: Joshua responding manually (reference draft only). - Add pr-299-review-prompt.md: copy-paste agent prompt to deep-review/ test/validate PR #299, plus an explanation of why the GitHub UI shows no run-CI option (no workflow_dispatch; first-time-contributor approval gate; 0 check runs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ALYJSRixXCfLsmb2Koc1Hi
Owner
|
Hey @Joshua-Medvinsky — makes sense as a defense-in-depth check. Before merge, can you add a negative test (mismatched |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
_handle_input()insrc/service/service.pypasses the caller-suppliedthread_iddirectly toagent.aget_state(),agent.ainvoke(), andagent.astream()with no check that the thread belongs to the requesting user. The/historyendpoint independently passesChatHistoryInput.thread_idtoagent.aget_state()with the same gap.Because
thread_idis the only key used to look up conversation state, any caller who knows another user'sthread_idcan read that user's full conversation history or append messages to their active conversation thread.Thread IDs are UUIDv4 (high entropy), but they appear in application logs, LangSmith/LangFuse traces, and are visible to anyone who shares the same
AUTH_SECRETbearer token — which, in the single shared-secret model, is all authenticated users.How it works
LangGraph already stores
user_idin the checkpoint config when a thread is created (configurable={"thread_id": ..., "user_id": ...}atservice.py:128). After loading thread state withaget_state(), this fix readsstate.config["configurable"]["user_id"]and compares it to theuser_idfrom the current request. If they differ and both are present, the request is rejected with HTTP 403.Backward-compatible: threads created before this patch (where no
user_idis stored in the checkpoint) are unaffected — the ownership check only activates when a storeduser_idexists.ChatHistoryInputgains an optionaluser_idfield so callers can supply their identity to the/historyendpoint.Changes
Test plan
thread_id→ request proceeds normallythread_idcreated by a differentuser_id→ HTTP 403user_id(pre-patch thread) → request proceeds normally (backward compat)/historywithoutuser_idfield → request proceeds normally (backward compat)Validated against exploit payloads and benign inputs via patch-correctness tests (8/8 pass). CI pipeline is configured on this repo.
Note on stronger guarantees
This fix provides meaningful protection when
user_idvalues are treated as private session identifiers. For stronger guarantees, consider moving to per-user JWT tokens so the server can derive a trusteduser_idfrom the bearer credential rather than accepting it from the request body.