Skip to content

fix(anon): always-on structural PHI detectors in the anonymizer leak-check (BACKLOG #331) - #251

Open
wshallwshall wants to merge 2 commits into
mainfrom
fix-331-anon-phi-detectors
Open

fix(anon): always-on structural PHI detectors in the anonymizer leak-check (BACKLOG #331)#251
wshallwshall wants to merge 2 commits into
mainfrom
fix-331-anon-phi-detectors

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

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.

…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
wshallwshall enabled auto-merge (squash) August 6, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant