← Community
bugfixed

phi_guard strict mode wholesale-redacts free text only above 40 chars, so a short name+diagnosis field leaks into the receipt/card

ShwetaShweta#112d ago · 45 views
affected: station-v0.99fixed in: station-v1.4.0

Reproduction steps:

  1. Scrub short PHI-bearing free text in STRICT mode (set by the operator's

phi_mode toggle, or auto-set by payload_is_clinical):
phi_guard.scrub("call John Doe re HIV results", strict=True) # 30 chars
phi_guard.scrub("Maria Garcia tested positive for HIV", strict=True) # 36 chars
phi_guard.scrub("admitted 2026-01-05, Jane Doe, sepsis", strict=True) # 37, has a date

  1. Compare with the same content over 40 chars:

phi_guard.scrub("x"*45 + " Jane Doe sepsis", strict=True)

Expected:
On a PHI-bearing flow, unrecognised free text is redacted wholesale — the
strict-mode contract, whose rationale is "personal names in narrative cannot be
detected by pattern." A name is as undetectable at 30 chars as at 300.

Actual:
"call John Doe re HIV results" -> unchanged (LEAK)
"Maria Garcia tested positive for HIV" -> unchanged (LEAK)
"admitted 2026-01-05, Jane Doe, sepsis" -> "admitted [date], Jane Doe, sepsis"
(date masked; name + diagnosis LEAK)
"xxxx... Jane Doe sepsis" (>40 chars) -> "[redacted — free text on a PHI-bearing flow]"
scrub() applies wholesale redaction only when len(value) > FREE_TEXT_CHARS (40);
at or below 40 it falls back to identifier masking, which cannot catch a name or
a free-text diagnosis. phi_guard.scrub feeds the approval-airlock card and the
signed command receipt (command_registry.redact_output, approval_airlock.redact),
so the name/diagnosis lands in both — under the mode the operator enabled to
prevent exactly that. This reintroduces the community #1fde22 leak below the
length threshold.

Suggested fix:
Drop the length floor in strict mode; redact any residual free text, but keep
precise masking for a value that is ONLY a recognised identifier:

if isinstance(value, str):
if strict:
stripped = value
for _name, _rx in _DETECTORS:
stripped = _rx.sub("", stripped)
if stripped.strip(): # free text beyond identifiers
return REDACTED_FREE_TEXT
return _scrub_text(value) # value was only identifiers -> mask them
hits = detect(value)
if hits:
return _scrub_text(value)
return value

In strict mode over-redaction is the safe failure, so the 40-char floor (meant to
spare short non-PHI values) is the wrong default there. Non-strict behaviour is
unchanged.

Station version (railcall version): station-v0.99
Module slug + version: N/A (platform — workbench/phi_guard.py)

5 pts

1 reply

Fixed in station-v1.4.0. Strict mode no longer has a length floor: it strips the recognised identifiers, and if any free text remains it redacts the value wholesale; a value that was ONLY identifiers is masked precisely. "call John Doe re HIV results" (28 chars) now redacts instead of passing through. Over-redaction is the safe failure on a PHI-bearing flow; non-strict behaviour is unchanged.

Thanks for the report — credited.

Sign in to reply.