feat(policy): ACL egress verifier — defense-in-depth re-check (Step 6.4) - #151
Merged
Merged
Conversation
Add a post-retrieval ACL re-check that backstops the Step 6.3 push-down. AclEgressVerifier.verify(ctx, refs) keeps only returned ChunkRefs whose acl_labels overlap the principal's — same overlap semantics as the push-down (no-op on correct results), reading ChunkRef.acl_labels (no re-hydration), and independent of the PolicyEngine — so a filter-translation bug, a backend that ignores the predicate, or a path wired without the engine cannot leak an over-privileged chunk past the boundary. Wired at the gateway as a SupportsRoute wrapper (AclEgressVerifyingRouter) around app.state.retrieval_router, so query/retrieve/corpus/OpenAI/agent all inherit it a layer above HybridRetriever. Opt-in via cfg.acl.verify_egress (default on, gated by cfg.acl.enabled). Emits acl.egress_violation on a caught leak (PII-free: ids + counts). A red-team gate bypasses the push-down (no engine + leaky backend) and proves a zero escaped-violation rate. Co-Authored-By: Claude Opus 4.7 <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.
Summary
Step 6.4 adds the ACL egress verifier — a post-retrieval re-check that backstops the Step 6.3 ACL push-down. The push-down is the primary, source-side layer (re-implemented by each backend translator); this is the independent second layer the
tests/policy/coverage.pylinter docstring already anticipated.AclEgressVerifier.verify(ctx, refs)(rag_policy.egress) keeps only returnedChunkRefs whoseacl_labelsoverlap the principal's — the same overlap semantics as the push-down, so it's a no-op on a correctly-filtered result and only ever acts on a genuine leak. ReadsChunkRef.acl_labels(populated by every backend regardless of the filter applied) → no re-hydration, O(results) set-intersection.ctx.principal.acl_labels), so a filter-translation bug, a backend that ignores the predicate, or a path wired without the engine can't leak an over-privileged chunk.SupportsRoutewrapper (AclEgressVerifyingRouter, kept in the gateway sorag-retrievalstays free ofrag-policy) aroundapp.state.retrieval_router— query / retrieve / corpus / OpenAI / agent all inherit it, a layer aboveHybridRetriever.cfg.acl.verify_egress(defaulttrue) gated oncfg.acl.enabled; setfalseto run the push-down alone.app.state.acl_verify_egressreports the effective state.acl.egress_violationevent (EVT_ACL_EGRESS_VIOLATION,errorlevel, PII-free: tenant / principal / counts / dropped chunk ids) on a caught leak — the unexpected push-down failure, distinct from 6.3's expectedacl.egress_denied. A clean pass is silent.Scope / deferred: covers the gateway retrieval surfaces; trusts the labels the backend reports on each
ChunkRef(catching a mislabelling backend needs authoritative re-hydration — deferred); per-tenant/per-label violation metrics deferred to the 6.x governance dashboards.Tests & gates
tests/policy/test_acl_egress.py), red-team zero-violation-rate gate (tests/redteam/test_acl_egress_verifier.py— bypassed push-down + a leaky backend → verifier drops every violation), gateway wiring + behavioral drop (apps/gateway/tests/test_acl.py), event-schema registry.mypy --strict(296 files), RAG001, policy-coverage linter, log-schema/event-registry, config schema-drift (dist/rag.schema.*regenerated forverify_egress).Documentation
Test plan
uv run pytest tests/ packages/ -m "not integration"— 2299 passed, 1 skippeduv run ruff check+ruff format --check— cleanuv run mypy packages/ apps/gateway/— no issues (296 files)🤖 Generated with Claude Code