Skip to content

pii-redaction-guard

Phase 1 beta: best-effort PII redaction for Claude Code model-facing tool-mediated context only; not enterprise redaction, secrets scanning, or cross-harness coverage.

IDE:
claude
Version:
11.3.0
plugin
claude-code
pii-redaction-guard

pii-redaction-guard

Phase 1 beta. This is a super beta, best-effort Claude Code-only guard for PII in model-facing, tool-mediated context. It is not enterprise redaction, does not scan secrets or tokens, and does not yet support Codex CLI, Kai, or other agent harnesses.

Claude Code plugin that redacts PII from model-facing, tool-mediated context before it reaches the model. It wires deterministic recognizers and required Microsoft Presidio NER into Claude Code lifecycle hooks so that sensitive values in tool inputs and tool outputs are masked while the non-sensitive diagnostic structure required for root-cause analysis is preserved.

What it does

The plugin registers four hooks (hooks/hooks.json) that all route to a single entry point (hooks/redact_hook.py). Each hook reads the event payload on STDIN, recursively redacts string values while preserving keys, types, and overall shape, and emits a hook-specific response that returns the redacted content to Claude Code.

Hook eventWhat is redactedResponse field
PreToolUsetool_inputpermissionDecision: allow + updatedInput
PostToolUsetool_response / tool outputupdatedToolOutput
PermissionRequesttool_inputdecision.behavior: allow + updatedInput
ElicitationResultelicitation contentaction: accept + content

Detection layers

  1. Layer 1 — deterministic recognizers (redaction_engine.py): regular expressions for well-structured identifiers (SSN, phone, email, IP, credit card, MRN, NPI, DEA) plus incident-triage recognizers (member ID, BigN values, superuser-derived markers, screenshot filenames).
  2. Layer 2 — Presidio NER (required): presidio_analyzer.AnalyzerEngine detects free-text entities (names, locations, dates). Setup and health checks fail if Presidio is not installed.

The default mode is mask: each detected span is replaced with a stable [CATEGORY_n] token. Masking is chosen over deletion so diagnostic structure survives for RCA while the underlying value is never exposed to the model.

Setup

The plugin requires Python 3.11-3.13 plus Presidio/spaCy. Presidio is required because regex-only redaction misses free-text names and incident contact context. Python 3.14+ is not accepted for installation yet because Presidio/spaCy wheels may lag newer Python releases.

Run the setup script from the plugin directory:

# Install NLP dependencies into the default venv
./install.sh --python python3.13

# Verify an existing setup without installing packages
./install.sh --check

By default, setup creates or reuses:

~/.claude/pii-redaction-guard-venv

Use PII_REDACTION_GUARD_VENV or --venv to choose a different virtual environment. The hook uses hooks/run_hook.sh, which selects Python in this order:

  1. PII_REDACTION_GUARD_PYTHON
  2. PII_REDACTION_GUARD_VENV/bin/python3
  3. ~/.claude/pii-redaction-guard-venv/bin/python3
  4. ~/.claude/plugins/pii-redaction-guard-venv/bin/python3
  5. ${CLAUDE_PLUGIN_ROOT}/.venv/bin/python3
  6. system python3

Dependencies

Setup installs pinned dependencies from requirements-full.txt:

./install.sh --python python3.13

It then installs the en_core_web_lg 3.8.0 spaCy model via pip from the official model wheel URL instead of spacy download. This keeps the model artifact explicit and lets enterprise environments replace the URL with an internally mirrored artifact:

./install.sh --python python3.13 \
  --spacy-model-url https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl

For automation using a pip index package spec, use:

./install.sh --python python3.13 \
  --spacy-model-requirement en-core-web-lg==3.8.0
./install.sh --python python3.13 \
  --spacy-wheel /path/to/en_core_web_lg-3.8.0-py3-none-any.whl

If you need to install Presidio/spaCy but defer model installation during an image build, use:

./install.sh --skip-spacy-model

Internal package sources

Do not bundle CA certificates or TLS workarounds in this repository. The supported approach is to install Presidio and spaCy from a managed internal pip index and, when needed, install the spaCy model from an internal wheel URL:

./install.sh --python python3.13 \
  --pip-index-url https://edgeinternal1uhg.optum.com/artifactory/api/pypi/glb-py-pypi-rem/simple \
  --spacy-model-url https://centraluhg.jfrog.io/artifactory/<repo>/en_core_web_lg-3.8.0-py3-none-any.whl

You can also set PII_REDACTION_GUARD_PIP_INDEX_URL and PII_REDACTION_GUARD_PIP_EXTRA_INDEX_URL in the environment if agent bootstrap should not pass CLI flags. Set PII_REDACTION_GUARD_SPACY_MODEL_URL if the internal model artifact URL differs from the default. Agent images and CI should receive trust and package source configuration from the platform, not from plugin source code.

Fail-open and fail-closed behavior

  • Built-in tool output is schema-validated. If a hook returns updatedToolOutput whose shape does not match the original tool output, Claude Code discards it and the original unredacted output leaks to the model. To avoid this fail-open path, redact_value returns a redacted payload with the exact keys, scalar types, and container structure of the original payload.
  • Errors never block tooling. Malformed payloads, analyzer failures, and unexpected exceptions are logged to STDERR and the event is allowed to proceed unchanged (exit 0). This keeps the plugin from breaking the host workflow, at the cost of not redacting that single event.

V1 limitations (spec task 3.5)

This Phase 1 beta is intentionally narrow: Claude Code hooks, tool-mediated context only, best-effort PII detection, and synthetic-fixture validation. Treat it as an early control, not a complete enterprise redaction layer.

Unresolved: direct user-prompt rewriting

This plugin covers tool-mediated boundaries only. Claude Code does not expose a supported hook that lets a plugin rewrite the raw user prompt text before the model sees it. As a result, PII that a user types directly into the chat prompt is not redacted by this plugin in V1.

Policy for V1: treat direct-prompt redaction as an unresolved control. Operators must rely on user training and upstream input controls for free-form prompt entry. If a future Claude Code release adds a supported user-prompt-submit transform hook, add a corresponding handler here.

Excluded production data (V1)

V1 is validated against synthetic fixtures only. It is not wired to, and must not be pointed at, live production data sources — production HCP, CSPA, superuser-derived stores, live Splunk, or live ServiceNow — in V1.

Coverage notes

  • Detection is best-effort. Layer 1 recognizers target structured identifiers; unusual formats may evade them. Presidio (Layer 2) provides required free-text name/location/date coverage.
  • Exact optum.com email addresses are intentionally allowed through so incident agents can read and update internal contact context for outreach. External email domains remain redacted.
  • Redaction applies to string values only. Sensitive data encoded in non-string scalars (for example a raw integer member number) is not detected.
  • Plain URLs are not redacted wholesale in V1 because operational tool output often includes package, model, documentation, or remediation links that the agent needs to use. Sensitive substrings inside a URL, such as an email in a query parameter, are still redacted by the relevant recognizer.
  • Redaction applies to values, not dictionary keys. Sensitive data embedded in object keys (for example { "[email protected]": {...} }) remains visible and should be normalized upstream before tool payloads reach Claude Code.

Future Enhancements

  • Proper enterprise redaction support with governed policies, auditability, configurable allow/deny rules, stronger failure-mode controls, and validated production operating guidance.
  • Secrets and token scanning for API keys, bearer tokens, PATs, private keys, cloud credentials, .npmrc tokens, and internal credential formats.
  • Cross-harness support for Codex CLI, Kai, and other supported agent runtimes beyond Claude Code hooks.

Files

pii-redaction-guard/
├── .claude-plugin/
│   ├── plugin.json        # generated from plugin.json.j2 at release
│   └── plugin.json.j2     # template (version/author injected at build)
├── hooks/
│   ├── hooks.json         # hook registration (4 events)
│   ├── run_hook.sh        # Python interpreter/venv selector wrapper
│   ├── redact_hook.py     # STDIN dispatcher + per-event response shaping
│   └── redaction_engine.py# two-layer detection + shape-preserving redaction
├── install.sh             # base/full setup helper
├── requirements-full.txt  # required Presidio/spaCy dependencies
└── README.md

Tests

pytest tests/plugins/test_pii_redaction_guard.py

The suite exercises Layer 1 detection, right-to-left redact_text, recursive shape-preserving redact_value, and the response shape of each of the four hook handlers, setup wrapper wiring, and the internal contact email allowlist.

Related Assets