Repository navigation
fix(security): R-12 — GraphRAG's expansion reads only the caller's workspace - #527
Merged
Merged
Conversation
jwvanderstam
added a commit
that referenced
this pull request
Nov 8, 2026
From the separate review of #527: a re-ingest retires the old chunks but keeps the document id, and their entity_relations rows survive, so a name removed from a document stayed an expansion term. related_names now also requires the relation's chunk to be live. The repository docstring says entities rows belong to no workspace and are read only through a scoped relation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx
Owner
Author
Separate security review (CONTRIBUTING / QP-9)A separate agent session reviewed this PR read-only. No finding at Medium or above.
Low and Info findings:
🤖 Generated with Claude Code |
…rkspace Entities are shared across workspaces, keyed on name and type alone, so the 1-hop lookup returned names that co-occurred in any workspace's documents, retired ones included, and with GRAPH_RAG_ENABLED on appended them to the caller's query. related_names now takes scope=, opens a scoped connection, and counts a co-occurrence only through a live document of the scope. The scope is threaded from retrieve_context through QueryExpander and GraphStore. The integration test fails on the old code with the foreign and the retired name in the result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx
From the separate review of #527: a re-ingest retires the old chunks but keeps the document id, and their entity_relations rows survive, so a name removed from a document stayed an expansion term. related_names now also requires the relation's chunk to be live. The repository docstring says entities rows belong to no workspace and are read only through a scoped relation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx
jwvanderstam
force-pushed
the
fix/r12-graph-expansion-scope
branch
from
November 8, 2026 21:35
8550f33 to
e24a463
Compare
jwvanderstam
added a commit
that referenced
this pull request
Nov 11, 2026
…he (#533) * fix(security): retrieval no longer consults the scope-blind query cache QueryCache's key is (query, top_k, min_similarity, use_hybrid): no workspace, knowledge tier or document filter. retrieve_context looked it up first and stored into it last, on a path that was never reached only because _get_app_cache() returns None. Re-enabling it would have served one workspace's results to another. The dead read and write are removed, and QueryCache's docstring says it must never hold a scoped retrieval's results. Found by the separate review of #527. The regression test hands retrieval a cache answering with another workspace's chunk and fails on the old code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx * test: the query-cache regression misses on the embedding cache and checks the search's scope Review Low-1: the mock answered the embedding lookup too, so the search ran on a string as its embedding. It now answers only the query cache's four-argument lookup, and the test asserts that every search ran in the caller's workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
jwvanderstam
added a commit
that referenced
this pull request
Nov 11, 2026
workspace_ontology joined each relation to its document without asking whether the document, or the chunk the relation came from, was retired, so a retired document's entities, and those of chunks a re-ingest had replaced, kept shaping the ontology. Both queries now require a live document and a live chunk. Within one workspace only; found by the separate review of #527. Claude-Session: https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts were side-by-side additions to CHANGELOG, file-map and the docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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
QP-9 review finding R-12 (Low; GraphRAG is off by default).
EntitiesRepository.related_names— GraphRAG's 1-hop query expansion — ran on an unscoped connection and joinedentitiestoentity_relationswith no reference to a document. Entities are shared across workspaces (unique on name and type), so the lookup returned names that co-occurred in any workspace's documents, retired ones included, andretrieve_contextappended them to the caller's query.Change
related_names(..., *, scope)opensget_connection(scope=scope)and joinsdocuments d ON d.id = er.doc_id AND d.deleted_at IS NULLwithscope_predicate(scope, "d.workspace_id").Retriever.retrieve_context→QueryExpander.expand→GraphStore.get_related_entity_names.entities.related_namesadded to the object-authorisation matrix's list of workspace-scoped methods, so a call withoutscope=fails the AST check.The lookup reads the workspace only, not the global tier. That is conservative: expansion only adds search terms, and retrieval itself still reads the tier
knowledge_scopenames.Tests
tests/integration/test_graph_expansion_scope.py: a name co-occurring in another workspace's document is not returned; a retired document contributes nothing. Both fail on the old code: the result held…Foreignand…Retired(verified by running the same assertions withoutscope=againstmain'ssrc/).scope=None.Docs
SECURITY.mdmarks R-12 Fixed. CHANGELOG, ROADMAP and file-map are updated. CHANGELOG and ROADMAP will need a trivial rebase against #524, #525 and #526.🤖 Generated with Claude Code
https://claude.ai/code/session_01KkWPqAcWbxfqz1aCP6Qsdx