pediatric-ai-scribe-v3/src/utils/redact.js
2026-05-08 19:08:19 +02:00

42 lines
1.9 KiB
JavaScript

// ============================================================
// PHI redactor for audit log details
// ============================================================
// Audit details should describe *what happened*, not contain clinical
// text. This defensively strips obvious PHI patterns and caps length.
// ============================================================
var MAX_LEN = 500;
function redact(text) {
if (text == null) return text;
var s = typeof text === 'string' ? text : JSON.stringify(text);
// Credentials and tokens. These patterns intentionally run before PHI
// redaction so accidental pasted headers/URLs are removed from logs.
s = s.replace(/Authorization\s*:\s*Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Authorization: Bearer [REDACTED]');
s = s.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, 'Bearer [REDACTED]');
s = s.replace(/\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, '[JWT]');
s = s.replace(/\b(api[_-]?key|password|secret|token|app[_-]?password)(\s*[=:]\s*)([^\s&"']+)/gi, '$1$2[REDACTED]');
s = s.replace(/(https?:\/\/)([^\s/@:]+):([^\s/@]+)@/gi, '$1[REDACTED]@');
// SSN
s = s.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]');
// US phone numbers
s = s.replace(/\b\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g, '[PHONE]');
// Email addresses (except domain-only)
s = s.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '[EMAIL]');
// Dates of birth / MRN-ish long digit runs
s = s.replace(/\b\d{2}\/\d{2}\/\d{4}\b/g, '[DATE]');
s = s.replace(/\b\d{8,}\b/g, '[ID]');
// Note-body signal: many newlines or very long prose → indicates the
// caller passed a clinical note as `details`. Truncate aggressively.
var newlines = (s.match(/\n/g) || []).length;
if (newlines > 4 || s.length > MAX_LEN * 2) {
s = s.slice(0, 120) + ' [TRUNCATED:possible-note-body]';
}
if (s.length > MAX_LEN) s = s.slice(0, MAX_LEN) + '…';
return s;
}
module.exports = { redact: redact };