PII redactor for display
Prevents credit card numbers, IBANs, SSNs and emails from ever appearing on screen
Redacts hard-PII (credit card numbers, IBANs, Social Security Numbers) from display at every MessageDisplay event. Email addresses are intentionally excluded — too common in legitimate dev output (configs, error messages, docstrings). Patterns are chosen to never match valid code constructs, making this safe as a default-on compliance layer for PCI-DSS and GDPR-sensitive sessions.
What does the PII redactor for display hook do?
PII redactor for display is a Claude Code MessageDisplay hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Prevents credit card numbers, IBANs, SSNs and emails from ever appearing on screen.
Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.
Use cases
- GDPR/PCI-DSS compliance: prevent customer data from appearing during debugging sessions with database exports or log files
- Screen-recording safety: ensure no PII is visible when recording pair programming sessions or demos
- Audit trail: redact sensitive fields in assistant output that echoes back tool results from customer tables
Tags
settings.json fragment
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/message-display-redact-pii.mjs"
}
]
}
]
}
}Script · .claude/hooks/message-display-redact-pii.mjs
#!/usr/bin/env node
// Caviarde le hard-PII dans le contenu affiché (MessageDisplay) :
// numéros de cartes, IBANs, SSNs — jamais présents dans du code légitime.
// Les e-mails sont exclus volontairement (trop fréquents en contexte dev).
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
// Numéros de carte Visa/MC/Amex/Discover — séparateurs optionnels espace ou tiret
const CC_RE = /\b(?:4[0-9]{3}|5[1-5][0-9]{2}|3[47][0-9]{2}|6(?:011|5[0-9]{2}))[- ]?[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{3,4}\b/g;
// IBAN : 2 lettres, 2 chiffres de contrôle, puis blocs alphanum groupés
const IBAN_RE = /\b[A-Z]{2}[0-9]{2}(?:\s?[A-Z0-9]{4}){3,7}(?:\s?[A-Z0-9]{1,4})?\b/g;
// SSN US (NNN-NN-NNNN) et numéro de sécu français (13 chiffres + clé 2 chiffres)
const SSN_RE = /\b(?:\d{3}-\d{2}-\d{4}|\d{13}\s?\d{2})\b/g;
export function run(input) {
const delta = input.delta ?? '';
const redacted = delta
.replace(CC_RE, '[REDACTED-CARD]')
.replace(IBAN_RE, '[REDACTED-IBAN]')
.replace(SSN_RE, '[REDACTED-SSN]');
if (redacted === delta) return null;
return {
hookSpecificOutput: {
hookEventName: 'MessageDisplay',
displayContent: redacted,
},
};
}
/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const input = JSON.parse(readFileSync(0, 'utf8'));
const result = run(input);
if (result) process.stdout.write(JSON.stringify(result));
}
Learn more
Related hooks
- Secret detection before Bash executionCatch a leaked API key before it ever runs
- Destructive command blockingStops a disk-wiping shell command before it runs
- Sensitive file write protectionYour .env and keys stay untouched by the agent
- Lock file write protectionLock files stay intact - package manager only