HookStack
Back to catalogue
SecurityMessageDisplay

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

#security#pii#compliance#gdpr#pci-dss#redaction

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