fix(anon): always-on structural PHI detectors in the anonymizer leak-check (BACKLOG #331) - #251
Open
wshallwshall wants to merge 2 commits into
Open
fix(anon): always-on structural PHI detectors in the anonymizer leak-check (BACKLOG #331)#251wshallwshall wants to merge 2 commits into
wshallwshall wants to merge 2 commits into
Conversation
…floor signal on the leak-check (BACKLOG #331) The fail-closed leak-check verified only that MAPPED fields were pseudonymized; PHI sitting in a field no rule mapped would pass the check clean on first deployment (a real MRN is not a denylisted string). Scoped to the fields anonymize did NOT rewrite, add: - high-precision structural detectors over unmapped fields: dashed SSN, punctuated NANP phone, and CX MR/MRN-typed identifier. Deliberately narrow, to avoid the mass false-positives a broad digit-run search produces on HL7 bodies dense with dates/order-numbers/set-ids (ADR 0030 section 5). - LeakReport: an unmapped-field coverage report (addresses only, never a value) plus token_tables_live / token_floor_reason. Reasons name the shape + field ADDRESS only, so a raised LeakError / log line never carries PHI. - token_floor_failure() folded into the fail-closed decision under the require_live_denylist opt-IN lever (default off, so token-less CI/OSS/fork runs stay green with the structural detectors as the live backstop). The whole structural block is mirrored byte-identical into tee/anon/leak.py; a new engine/tee leak_report parity test pins it. Each detector was falsified (removed it, watched the unmapped-PHI dataset slip through, restored); a false-positive guard proves a benign unmapped field (14-digit EVN timestamp, order number, coded observation id) does not trip. Docs are written to the shipped DEFAULT behaviour, not an overclaim: the coverage report and token-floor reason are RECORDED and surfaced on a refusal or via the on_report hook (not an unconditional clean-path catch-all), and the strict refusal is opt-IN. anon/__init__.py (both copies) necessarily changed to export LeakReport/leak_report/coverage_clause and wire the lever. ADR 0030 section 5 / section 7 / Consequences amended (the "deferred" phrasing was stale against the shipped code). NOT-DEPLOYED beta: worded as "would let unmapped PHI through on first deployment", no present-tense exposure claim.
…aviour (BACKLOG #331) Banner-only flip of #331 to SHIPPED. Worded to the shipped DEFAULT behaviour, not an overclaim: the coverage report and token_floor_reason are RECORDED and surfaced on a refusal or via the on_report hook (not an unconditional clean-path catch-all), and require_live_denylist is the strict opt-IN lever (default off). Census NOT recomputed: only the #331 banner line changed. The ranked table, the four census distribution lines, and every other item's banner are untouched.
wshallwshall
enabled auto-merge (squash)
August 6, 2026 14:16
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.
Fixes BACKLOG #331. anon/ and the vendored tee/anon/, plus an ADR 0030 amendment.
The anonymizer's leak-check gains always-on STRUCTURAL PHI detectors, so detection no longer depends
on a configured denylist being present and correct.
⭐ WHY require_live_denylist DEFAULTS OFF, and it is deliberate rather than an oversight: the
structural detectors are the always-on fail-closed backstop. Making the live denylist mandatory would
put the fail-closed property behind a configuration flag -- the weaker arrangement. The ADR 0030
amendment records that reasoning.
FOLLOW-ON TO FILE (not in this PR): a --require-live-denylist tee-CLI flag. The lever exists but is
currently kwarg-only, so it cannot be exercised from the CLI.
Verified in-lane, and the ADR 0030 amendment was verified fair. Banner gate OK.