Input Controls
HIPAA PHI Detector
hipaa-phi-detector replaces or blocks text that looks like protected health information before the model receives it: the base PII identifiers, plus names, medical record numbers, health plan IDs, and other HIPAA identifiers.
It is a text detector, not a guarantee of complete de-identification. The gateway also applies the HIPAA rules to streamed and buffered answers.
Configuration
Save this as policy-config.yaml. Start with synthetic data on a test agent, with the config-first workflow.
pack:
name: hipaa-phi-detector
version: "1.0.0"
enabled: true
policies:
chain:
- hipaa-phi-detector
policy:
hipaa-phi-detector:
action: redactFields
| Field | Type | Default | Description |
|---|---|---|---|
action | redact or block | redact | What happens to a match in the request. redact replaces it and continues. block stops the request. For the response behavior of each value, see Output behavior. |
action is the only field. Lint rejects the removed fields mode and safe_harbor_method.
What the detector finds
Both sets are heuristic and text-only.
Base PII engine
- Email addresses
- Social Security numbers
- Phone numbers
- Public IP addresses
- URLs with paths or query strings
- Dates
- Account numbers
- ZIP codes
- License plates
- MAC addresses, IMEI numbers, and vehicle identification numbers
HIPAA-oriented extensions
- Person names
- Street addresses and city terms
- Fax numbers
- Medical record numbers
- Health plan or policy identifiers
- Certificate and driver's license numbers
- Device identifiers and serial numbers
- Biometric identifier terms in text
- Photo or facial image terms in text
How it works
- The gateway joins the request messages into one string.
- It runs the base PII detector and the HIPAA rules on it.
- No match:
allowwithhipaa_phi.clean. - A match: the verdict that
actionselects, with the reason code of the first match. - With
redact, each match is replaced with a marker before dispatch. - For a response, the gateway applies the output behavior of
action. See Output behavior. - The event records the first match type, its reason code and confidence, the detection count, and the confidence counts.
Output behavior
With gateway 0.1.26 or later, the gateway applies these rules to the model response:
| Configuration | Response |
|---|---|
action: redact | The shared redaction pipeline runs with the HIPAA rules on. This applies to streamed and buffered Chat Completions, Responses, and Messages responses. A changed response has a nested hipaa_phi.detected result and the top-level reason redact.applied. |
action: block | PHI in a streamed or buffered Chat Completions, Responses, or Messages response blocks the response. The reason code is the code of the first match, as on a request. A streamed response sends no text before the block. |
A buffered response block returns HTTP 400 with the error code content_policy_violation. A buffered Messages block uses the Anthropic error shape. A streamed response block ends the stream with the error event of its API, with the code content_policy_violation. The request records the usage that the provider reports.
The nested redaction result names hipaa-phi-detector only when the chain has no pii-detector. See Output redaction reason codes.
Phase and verdicts
| Item | Value |
|---|---|
| Input phase | The joined request messages |
| Output phase | Response text, through the shared redaction pipeline or the block check |
| Verdicts | allow, redact, block |
| Reason code without a match | hipaa_phi.clean |
| Reason codes for a match | The code of the first match, such as phi.name_detected, phi.mrn_detected, or pii.ssn_detected |
| Reason code for output redaction | hipaa_phi.detected |
Examples
Redact matching PHI-like content
Test the configuration above offline:
testing:
suites:
- name: hipaa-phi-detector
cases:
- name: redacts-ssn
input: {messages: [{role: user, content: "My SSN is 123-45-6789."}]}
expected: {verdict: redact, reason_code: redact.applied}
- name: redacts-mrn
input: {messages: [{role: user, content: "Patient MRN: 12345678 needs a follow-up."}]}
expected: {verdict: redact, reason_code: redact.applied}
- name: allows-greeting
input: {messages: [{role: user, content: "Hello"}]}
expected: {verdict: allow, reason_code: ok}All three pass. The nested results name the matches: pii.ssn_detected and phi.mrn_detected. A redact verdict does not show the forwarded text, so also send a synthetic identifier through a test gateway with a test upstream, and check that a marker replaced it.
Block PHI-like input before the model receives it
policy:
hipaa-phi-detector:
action: blockSend the synthetic identifier again. The request stops before the provider call, and the runtime event names the match, such as pii.ssn_detected.
Limits and failure behavior
- This is a heuristic text detector. It does not prove HIPAA Safe Harbor de-identification.
- Images, audio, and other non-text data are not examined.
- It does not control access. Use RBAC for minimum-necessary rules.
- In the response pass, the gateway turns the HIPAA rules on and the PCI rules off. This applies to streamed and buffered responses.
- For streamed responses, the gateway buffers the whole answer before it sends it. With
action: redact, the gateway redacts the answer. Withaction: blockand gateway 0.1.26 or later, PHI in the answer blocks the stream. - With
action: block, the gateway examines a streamed Chat Completions or Responses answer when the provider marks the end of the output. If the provider closes the stream without that marker, the gateway redacts the answer and does not block it. A Messages stream is always examined at the end. - When the chain also has
pii-detector, the response redaction event namespii-detectoras the policy type, withpii.detected.
Next steps
- PII Detector: markers and the base PII rules.
- Healthcare Compliance: medical output controls.
- RBAC: which roles may send PHI.