feat: privacy shield operator deny-lists, miss queue, idnum, eval gate (#760) - #766
Merged
Conversation
…ivacy-shield # Conflicts: # docs/CHANGELOG.md # docs/middleware-agent-handoff.md # middleware/src/index.ts # web-ui/app/_components/Nav.tsx # web-ui/messages/de.json # web-ui/messages/en.json
…ivacy-shield # Conflicts: # docs/CHANGELOG.md # docs/middleware-agent-handoff.md
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.
Closes #760.
What
Four gaps in the prompt-masking layer's operator story, found during an external audit-readiness review: no operator-defined terms, no path for a human to report a missed value,
idnumexplicitly ungated ("informationally only"), and detection quality with zero CI regression protection.How
Operator deny-list (
custom_terms/custom_patterns)'a'*n + 'b'for growing n) under a 50 ms budget — a catastrophic-backtracking pattern is caught at small n before it could hang anything. Rejected patterns are reported loudly (customPatternRejected), never silently dropped — an operator who typed a protection must learn it is not active.custom/ detectorcustom-termsin the receipt and ride the existing fail-closed surrogate machinery unchanged —findIdentityLeakscovers custom values automatically. Detector construction is fingerprint-cached; no custom config ⇒ byte-identical baseline-only behaviour (test-asserted).Miss-report catch basin
PrivacyReceiptCard("report a value the shield missed") →privacy_miss_reports(migration0040;0039is Persist per-turn audit receipts (receipt store) #757/PR feat: persist per-turn privacy receipts with operator API (#757) #763) → review queue at/operator/privacy-reports(copy term → paste intocustom_terms→ resolve). Kernel routes auth-gated under/api/v1/operator/privacy/miss-reports, session actor server-side, zod at the boundary.idnumpromoted from informational to gated[A-Z]{2}on purpose — over-match beats a leak), FR n° de sécurité sociale. All eight committed fixture shapes across the six locales detect (verified by test and by the eval).validation/README.md).Detection quality as a CI gate
promptDetectorEval.ts --check: deterministic C0-only run compared against committed per-locale floors (validation/ci-baseline.json). de/en/es/fr/it sit at the official gates (0.97/0.85); nl's floor (0.88) records its pre-existing C0 gaps (de-centric address pattern + BSN) so a regression below today fails without permanently reddening CI. Floors are exact — the eval is pure regex over committed fixtures, so any drop is a real regression, not noise.Tests
test/privacyCustomTermsAndIdnum.test.ts(19): detector units (word boundaries incl. umlauts, both rejection reasons — syntax and too_slow via(a+)+$), all 8 idnum shapes + negative, service wiring (term masked, receipt span typecustom, patterns fail-closed, no-config byte-identical), manifest↔code key wiring (the Wave-L declared-but-never-read lesson),--checksmoke via spawnSync incl. the non-zero-locale-count assertion.test/privacyMissReports.test.ts(5): create with session actor, 400s before the pool on bad input, status filter threading, resolve-once + 409, malformed id.dist/): removing the custom-detector wiring frommaskUserPromptturns 2 tests red.typecheck,typecheck:test, i18n parity + validator green.Pre-PR review round (self-review findings, all addressed)
(\d+)+$passed vetting and a user prompt with a digit run could wedge a turn. Fixed twice over: the probe escalation now runs across letters, digits, mixed-alphanumeric, unicode and punctuation (test:(\d+)+$is rejectedtoo_slow), AND a runtime backstop bounds every operator pattern per turn — over budget throwsCustomPatternRuntimeError, which the service's tier-2 catch converts into a BLOCKED turn. Deliberately no auto-disable: skipping the pattern on later turns would be fail-open for exactly the values it protects; the operator gets a greppablecustomPatternRuntimeExceededlog instead. Residual risk stated in the CHANGELOG (a polynomial pattern's first over-budget turn is slow before the block lands).2Adepartment). Documented over-match surface (bare 11-digit runs, NINO-shaped tokens) stays a design choice per the fail-closed philosophy.turnIdis now actually threaded chat page → receipt card → miss-report form (the declared-but-never-threaded defect shape).package.jsonandmanifest.yaml(the hub reads the manifest);docs/security-architecture.mdgained the deny-list/miss-queue posture paragraph.Deliberately out (named in CHANGELOG)
mask_user_promptdefault (product decision — tracked in Privacy Shield: operator deny-lists, miss-report review queue, idnum coverage, eval CI gate #760 discussion).Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.