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.
By Luke Zulkarnain-Lemke ([email protected])
Plugin Structure
Installation
Install this plugin using the Claude Code CLI:
claude plugin install pii-redaction-guard@otc-awesome-llmVerification
After installation, verify the plugin is loaded:
claude plugin listDocumentation
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 event | What is redacted | Response field |
|---|---|---|
PreToolUse | tool_input | permissionDecision: allow + updatedInput |
PostToolUse | tool_response / tool output | updatedToolOutput |
PermissionRequest | tool_input | decision.behavior: allow + updatedInput |
ElicitationResult | elicitation content | action: accept + content |
Detection layers
- 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). - Layer 2 ā Presidio NER (required):
presidio_analyzer.AnalyzerEnginedetects 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:
PII_REDACTION_GUARD_PYTHONPII_REDACTION_GUARD_VENV/bin/python3~/.claude/pii-redaction-guard-venv/bin/python3~/.claude/plugins/pii-redaction-guard-venv/bin/python3${CLAUDE_PLUGIN_ROOT}/.venv/bin/python3- 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
updatedToolOutputwhose 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_valuereturns 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.comemail 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,
.npmrctokens, 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.

