Reject requests and policies that can detect but never redact - #421
Conversation
An empty label scope read oppositely on the two halves of the pipeline, so a request could come back looking processed while nothing was redacted. Two distinct defects, closed at the two granularities they live at. A request naming no labels anywhere now fails in `compile_catalog`. Since elide #239 an empty catalog is a request for no entity types, so such a request can only ever return an empty report — a clean answer a caller cannot tell apart from a clean document. Checking the compiled catalog rather than the policy slice catches every route to empty: no policies, or policies that name nothing. A single policy that declares scopes, names no labels in them and still carries an operator now fails in `validate_scope_references` via `PolicyDefinition::scopes_nothing_it_redacts`. Its rules match nothing, while any other policy's labels still reach it as entities it cannot act on. The request-wide check cannot catch this one: the union stays non-empty. Tests keep their subjects and gain the policies they need. The `anonymize_rejects_an_audit_that_never_ran_analyze` case mattered most: it asserts a specific message, so with no policies it would have kept passing while failing for the new reason instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LjYewekAhx4ZK7BReLSzj7
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change rejects policies with operators but no detectable labels. Catalog compilation also rejects empty label catalogs. Pipeline tests now use detection-only policies and cover the new configuration errors. ChangesCatalog and policy validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The PR intentionally rejects policy configurations that cannot produce meaningful detection or redaction, preventing misleading successful results. No actionable merge-blocking risk remains beyond normal checks and review. 🚥 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 |
Problem
An empty label scope read oppositely on the two halves of the pipeline. Scopes flatten into a request-wide
LabelCatalogfor analysis, but each policy's own scope gates its redaction — a rule matches an entity only when the scope contains its label. A scope that named nothing therefore detected without redacting, and the request returnedOkwith a document that looked processed.Two distinct defects hide behind that, at two different granularities:
Fix
Request-wide, in
compile_catalog. Since elide#239 an empty catalog is a request for no entity types, so the analyzer short-circuits and detects nothing. Such a request can only ever return an empty report — an answer a caller cannot distinguish from a clean document. It is now refused, naming the cause.Checking the compiled catalog rather than the policy slice catches every route to empty, not just
&[]. That is what surfacedanonymize_succeeds_when_policies_supply_catalog_afresh, whose policy hadscopes: Vec::new()and contributed nothing.Per-policy, in
validate_scope_references, via a newPolicyDefinition::scopes_nothing_it_redacts. The policy's rules match nothing, while any other policy's labels still reach it as entities it cannot act on. The request-wide check cannot catch this: add one scoped policy beside it and the union is non-empty, so the request compiles and the hole stays open.Deliberately still legal: a policy with no scopes at all (says nothing about coverage) and one that names labels but no operator (detect-only, which
unhandled()exists to report). Only "declares scopes, names no labels, carries an operator" is incoherent.Upstream
Cargo.lockmoves elide to7da9b52for #239. That PR fixes the all-empty case upstream — converting "detect everything, redact none" into "detect nothing" — but it does not reach either defect from our side: the first still returns a silent empty report, and the second it does not touch at all.Tests
Every test keeps its subject and gains the policies it needs; none was weakened.
audit.rs,multimodal.rs— a shareddetect_only()fixture. These exercise export writers and part round-tripping, not detection defaults.anonymize_rejects_an_audit_that_never_ran_analyze— the one that mattered. It asserts a specific message ("analyze must run first"), so with no policies it would have gone on passing while failing for the new reason instead. Now passes a real policy, so it still tests its own guard.review.rs:unhandled_names_a_detection_no_policy_acted_on— premise was "no policies at all"; now a policy withfallback: None, the same shape (detected, nothing acted on).catalog.rs:empty_policy_set_yields_empty_catalog→a_policy_set_naming_no_labels_is_rejected.a_request_naming_no_labels_is_rejected;a_policy_scoping_no_labels_is_rejectedanda_policy_with_no_scopes_or_no_operators_is_fineeach pair their shape with a label-contributing policy, so the request-level check cannot mask what is under test.Verification
114 passing (was 113).
clippy --workspace --all-targets --all-features,+nightly fmt --check,RUSTDOCFLAGS="--cfg docsrs -D warnings" +nightly doc,cargo machete,cargo deny check, and a--no-default-featuresbuild all clean.Note for review
Engine::analyzenow requires at least one label-naming policy, so exploratory "analyze with no policies" no longer works. The shape for that use case is an explicit detect-everything policy (scopes the builtins, no operators) rather than a special case in the empty check — not assumed here.🤖 Generated with Claude Code
https://claude.ai/code/session_01LjYewekAhx4ZK7BReLSzj7
Summary by CodeRabbit
New Features
Bug Fixes