HookStackGitHub
Back to catalogue
SecurityMessageDisplay

Redact secrets from displayed output

Secrets never show on screen during a share

Intercepts assistant message text as it streams to the screen and replaces patterns matching API keys, GitHub tokens and Bearer credentials with [REDACTED] placeholders. The transcript and model context retain the original text; only the rendered display is sanitized.

What does the Redact secrets from displayed output hook do?

Redact secrets from displayed output is a Claude Code MessageDisplay hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Secrets never show on screen during a share.

Use cases

  • Prevent API keys from being visible on screen during pair programming or screen shares
  • Sanitize assistant output that echoes back tool results containing credentials
  • Compliance screen recording: ensure no secrets appear in recorded sessions

Tags

#security#redaction#secrets#display#compliance

settings.json fragment

{
  "hooks": {
    "MessageDisplay": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/message-display-redact-secrets.mjs",
            "type": "command"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/message-display-redact-secrets.mjs

#!/usr/bin/env node
// Caviarde les secrets dans le contenu affiché (MessageDisplay)
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';

export function run(input) {
  const delta = input.delta ?? '';

  const redacted = delta
    .replace(/sk-(?:ant-api03-|proj-)[A-Za-z0-9_-]{20,}/g, '[REDACTED-ANTHROPIC-KEY]')
    .replace(/sk-[A-Za-z0-9]{20,}/g, '[REDACTED-API-KEY]')
    .replace(/ghp_[A-Za-z0-9]{36}/g, '[REDACTED-GH-TOKEN]')
    .replace(/ghs_[A-Za-z0-9]{36}/g, '[REDACTED-GH-TOKEN]')
    .replace(/Bearer [A-Za-z0-9_\-.]{20,}/g, 'Bearer [REDACTED]');

  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));
}