fix(storage): fail closed on an empty partition scope (cross-partition leak) - #760
Conversation
…ilter
An empty partition list (`{"partition": []}`) made _build_filter_expr drop the
partition clause and return an empty expression, so the search ran UNFILTERED
across every partition — a cross-tenant read. A scope resolving to no accessible
partition (e.g. a user with zero memberships hitting `openrag-all`) returned
other tenants' documents, where the pre-refactor line failed closed
(`partition in []` matched nothing).
Fail closed: an empty (non-wildcard) partition list now short-circuits to
_MATCH_NOTHING_EXPR and dominates any other filter. The explicit `all` wildcard
remains the only way to run an intentionally unscoped search. Rewrites the test
that had enshrined the fail-open behaviour so it revert-proves the fix, and adds
empty-tuple and scope-dominates-other-filters cases.
Refs #759
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
ChangesPartition filter enforcement
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
hedhoud
left a comment
There was a problem hiding this comment.
LGTM. The empty partition scope now fails closed, and the focused regression coverage looks good.
Closes #759.
Problem
MilvusVectorStore._build_filter_exprfailed open on an empty partition scope:{"partition": []}dropped the partition clause and returned an empty expression, so the search ran unfiltered across every partition — a cross-tenant read. A scope that resolves to no accessible partition (e.g. a user with zero memberships hittingopenrag-all) got other tenants' documents back, where the pre-refactor line failed closed (partition in []matched nothing). The fail-open was even enshrined in a test.Fix
An empty (non-wildcard) partition list/tuple now short-circuits to
_MATCH_NOTHING_EXPR(1 == 0) — fail closed — and dominates any co-filter (early return beforeexpr/other keys are read). The explicit"all"wildcard remains the only way to run an intentionally unscoped search._build_filter_expris the sole filter builder for all read/delete paths (dense + hybrid search, scalar query, delete), so the guarantee holds everywhere. Verified the hybrid path applies the expr to both the dense and BM25 sparse legs.None/missing partition is deliberately left unscoped: the search routers already 403 an empty scope upstream (get_partition_name,search.py), and the partition-less path is required by the authz-gated global-_idlookup inConversionService.get_chunk(extractText/download). Closing it there would break that flow with no isolation benefit.["all"], never[]; no caller passes an empty partition list meaning all.delete_by_filter({"partition": []})now safely no-ops instead of erroring.Tests
Rewrote the test that had enshrined the fail-open into a revert-prove (
test_empty_partition_list_matches_nothingasserts1 == 0; reverting the fix yields""and fails). Added empty-tuple and two co-filter-dominance cases (exprandfile_id).uv run pytest tests/unit/green; fulltest_milvus_store.py(78) green; ruff check/format + layer-import-guard clean.Scope / follow-ups (separate, noted in #759)
user_partitionsscope even earlier at the resolution layer.vector_store_searcher.search/multi_query_searchaccept but never readfilter_params, so Workspace scoping is inert: filter_params is threaded through retrieval and dropped at the vector store #706 workspace file-scoping doesn't reach Milvus (within-partition over-retrieval, not this cross-partition leak).Summary by CodeRabbit