auditing-deidentification-runs
GitHub生成不可抵赖的脱敏审计轨迹,用于HIPAA/GDPR合规。返回无PHI的审计报告,包含偏移量、哈希和证据,支持签名验证与完整性校验,确保脱敏过程可追溯且安全。
Trigger Scenarios
Install
npx skills add maziyarpanahi/openmed --skill auditing-deidentification-runs -g -y
SKILL.md
Frontmatter
{
"name": "auditing-deidentification-runs",
"license": "Apache-2.0",
"metadata": {
"pairs": "after",
"project": "OpenMed",
"version": "1.0",
"category": "de-identification"
},
"description": "Produce a signed, reproducible, no-PHI audit trail for an OpenMed de-identification run via deidentify(audit=True). Use when the user needs compliance evidence, a tamper-evident record of what was redacted and why, to verify nothing was changed, to retain proof for HIPAA\/GDPR audits, or to review de-id decisions without exposing plaintext PHI. Covers the AuditReport \/ AuditSignature \/ AuditSpan \/ DetectorInfo fields, why audits store offsets+hashes+provenance+residual-risk and never plaintext, signing with .sign(key), and verifying with .verify(key). Pairs with OpenMed deidentifying-clinical-text and auditing-safe-harbor-checklist."
}
Auditing de-identification runs
deidentify(..., audit=True) returns an AuditReport instead of the rewritten
text: a deterministic, PHI-free record of every redaction decision —
offsets, label, detector confidence and threshold, the action taken, content
hashes, model provenance, and projected residual risk. Sign it to make it
tamper-evident, retain it as compliance evidence, and verify it later without
ever touching the original text. Runs on-device.
When to use this skill
Use it whenever a de-identification needs to be defensible: regulatory retention, internal review, reproducibility checks, or proving to an auditor that identifiers were detected and handled — all without storing or exposing the PHI itself.
Quick start
import openmed
note = "Patient John Doe (MRN 1234567) seen 2024-03-02. SSN 123-45-6789."
# Returns an AuditReport, NOT a DeidentificationResult, when audit=True.
report = openmed.deidentify(note, policy="hipaa_safe_harbor", audit=True)
# Make it tamper-evident with a release HMAC key (keep the key in a vault).
report.sign(b"my-release-hmac-key", key_id="release-2026")
# Persist the no-PHI report (no plaintext identifiers inside).
import json
with open("deid_audit.json", "w") as fh:
json.dump(report.to_dict(), fh, indent=2)
# Later: verify integrity. Optionally bind to the exact texts via their hashes.
ok = report.verify(b"my-release-hmac-key", original_text=note)
assert ok
What an AuditReport contains
AuditReport (from openmed.core.audit) carries no plaintext PHI. Key fields:
| Field | Meaning |
|---|---|
policy |
policy profile name in effect |
resolved_profile |
the concrete settings applied (method, model, thresholds, language, sweep flags) |
detectors |
list[DetectorInfo] — provenance of each detector |
safety_sweep |
structured-ID sweep metadata (patterns version, source) |
spans |
list[AuditSpan] — one entry per detected identifier |
thresholds |
per-label confidence thresholds used |
residual_risk |
projected leakage / re-identification risk summary |
openmed_version |
library version that produced the report |
manifest_hash |
hash of the model manifest used |
document_length |
character length of the input |
input_hash |
sha256: hash of the original text |
deidentified_text_hash |
sha256: hash of the de-identified output |
repro_hash |
deterministic hash over the canonical payload |
signature |
AuditSignature once .sign() is called (else None) |
AuditSpan (per identifier, no plaintext): start, end, label,
canonical_label, sources, confidence, threshold, action, surrogate,
text_hash (hash of the span text), plus evidence and a redacted context.
DetectorInfo: source, model_id, model_format, commit, metadata.
AuditSignature: key_id, algorithm (HMAC-SHA256), value (hex digest).
Why audits store offsets + hashes + provenance, never plaintext
The whole point of de-identification is to not retain identifiers. An audit log full of raw names and MRNs would itself be a PHI breach. So OpenMed records:
- offsets (
start/end) — where an identifier was, not what it was; - hashes (
input_hash,deidentified_text_hash, per-spantext_hash) — prove what was processed without revealing it; - provenance (
detectors,manifest_hash,openmed_version) — which model/version made each call, for reproducibility; - residual risk (
residual_risk) — projected leakage so a reviewer can judge whether the output is safe to release.
This lets an auditor confirm coverage and integrity while the report stays shareable.
Workflow
- Run with
audit=Trueand apolicy=to get anAuditReport. - Inspect
residual_riskbefore releasing the output — non-zero projected leakage means review (cross-check withauditing-safe-harbor-checklist). - Sign with
report.sign(key, key_id=...)using a secret release HMAC key stored in a vault (never in the repo). - Persist
report.to_dict()(JSON). For a reviewer view without full context, usereport.export_review_bundle(). - Verify on retrieval with
report.verify(key). Passoriginal_text=/deidentified_text=to also confirm the texts match the recorded hashes. - Retain per your retention schedule alongside the de-identified output (but keep any reversible mapping separate and encrypted).
Hand-off to / from OpenMed
- Produce the de-id:
deidentifying-clinical-text(deidentify); addaudit=Trueto get the report from the same call. - Coverage check:
auditing-safe-harbor-checklistmaps span labels to the 18 HIPAA identifier categories and flags gaps. - Policies:
configuring-privacy-policies—resolved_profilereflects the chosenpolicy=. - Other surfaces: MCP
openmed_deidentifyand RESTPOST /pii/deidentifydrive the same engine; audit output is available through them.
Edge cases & gotchas
audit=Truechanges the return type toAuditReport. You get the report, not.deidentified_text. Run a separate normal call if you also need the text.- An unsigned report is not tamper-evident.
.sign()is a deliberate second step;signatureisNoneuntil you call it. Empty/None keys are rejected. verifyneeds the same key. It checks the HMAC and therepro_hash; optionally it re-hashes texts you pass to confirm they are the audited ones.- Never put plaintext PHI back into the report. Do not stuff raw identifiers
into
metadata/evidence; the design is hash-and-offset only. - Store the signing key in a secret manager, not in source or the report.
- Residual risk is advisory, not a pass/verdict. Combine it with the Safe Harbor checklist and human review for release decisions.
Standards & references
- HIPAA de-identification & documentation, 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html
- HMAC-SHA256: RFC 2104 / FIPS 198-1.
- OpenMed source:
openmed/core/audit.py(AuditReport,AuditSpan,AuditSignature,DetectorInfo,sign,verify),openmed/core/pii.py(deidentify(audit=True)).
Version History
- f213557 Current 2026-07-23 00:43


