A Claude Code mod can redact sensitive data at the two places it moves: on the way into the session and on the way out. It can mask an API key you paste into a prompt, hand Claude a masked copy of a .env file a command just printed, and refuse a Slack or ticket MCP call that would carry a card number, with a reason so Claude does the job another way. If the secret lives in a file like .env, a Read deny rule that stops Claude reading it at all is the first step;6 the mod covers what slips through. Below: exactly where a mod can step in, a small tested mod that does all three, and what it will still miss.
Where sensitive data gets into a session, and out of it
Most leaks in a coding session are not dramatic. You paste an error message and the connection string with the password comes along. Claude runs cat .env or printenv to debug a config problem. A query result holds customer emails and phone numbers. A log line carries a bearer token. Each of these lands in the conversation, and from there it goes to the model with every following request.
The other direction matters as much. With MCP servers connected, Claude can post to Slack, write a ticket, or call an external API. If a key or a customer's IBAN is in the context, it can end up in that message without anyone meaning it to.
So the rule we want is simple: every session cleans the obvious things before they go in, and checks before anything goes out. That covers API keys and tokens, passwords, personal data such as emails and phone numbers, and payment details such as card numbers and IBANs.
You could write that rule in CLAUDE.md. But that is guidance, and the model can still miss it. A mod is code around the session: every prompt and tool call it hooks passes through it, so Claude cannot skip it. We cover that difference in more depth in rules Claude cannot ignore. If mods are new to you, start with Claude Code mods, explained simply.
Where a mod can step in
Before writing any code, we checked the docs and the type declarations for Claude Code v2.1.287 for each point where data moves. A mod hook gets the event, and next(e) hands it on to the rest of the chain and finally to Claude Code itself.2 What the hook does with next decides whether it observes, rewrites, or answers. Here is what is possible at each point:
| Where data moves | Mod event | What the hook can do |
|---|---|---|
| Your prompt, before Claude reads it | prompt.submit | Rewrite the text with next({ ...e, text }), add context only Claude reads, or stop the prompt with { drop: reason } |
| A tool call, before it runs | tool.call (before next) | Change the arguments with next({ ...e, ... }), or refuse with { deny: reason }. Claude reads the reason as the tool result. |
| A tool result, before Claude reads it | tool.call (after next) | Let the tool run with await next(e), then return { result } with your own copy. Claude reads that copy. |
| An MCP tool call | tool.call with tool: /^mcp__/ | Same as any tool: refuse, rewrite, or mask the result. Calls a subagent makes pass through too. |
| The start of a turn | turn.start | Observe only. Not a place to clean anything. |
The sources for each row: the events guide shows prompt rewriting, the deny answer, and that tool.call fires for MCP tools and for calls a subagent makes.2 The reference lists { result } as an answer to tool.call and turn.start as observe only.3 The type declarations say that when a hook returns its own { result }, Claude Code checks it against the tool's output schema, formats it for the model, and records it in the transcript as the tool's result (from the Claude Code 2.1.287 type declarations).
To be fair to settings hooks: they already do a lot of this. A PreToolUse hook can deny a call with a reason Claude sees, and a PostToolUse hook can replace the output Claude reads with updatedToolOutput.5 What a UserPromptSubmit hook cannot do is rewrite the prompt itself. It can only block it or add context. A mod can rewrite the prompt. It also keeps the three checks in one file that shares its patterns, runs as functions inside Claude Code's own process instead of as a shell command,1 and comes with a test runner.8
The mod: secret-scrub
A mod is a plugin folder with a manifest, a hooks.json that points at the code, and the code.1 Ours has one more file for the patterns, and a test file:
The patterns. Each rule is a regular expression, with an extra check where a shape alone is too loose. Card numbers must pass the Luhn checksum that valid card numbers pass, so an order id of 16 digits stays. IBANs must pass the mod 97 check. Phone numbers only match in international form, such as +49 ..., to keep version numbers and ids out. For KEY=value lines like the ones in a .env file, only the value is masked, so Claude still knows the key exists. The same goes for the password inside a connection string.
The hooks. Three of them, one per direction:
What each part does:
- The prompt hook masks your prompt before it enters the session and shows a short toast, so you know it happened. The message in the transcript shows the masked text.2
- The outbound hook covers every MCP tool,
WebFetch, and Bash commands that look like network calls. If the arguments hold anything sensitive, it returns{ deny }without callingnext, so the call never runs and no permission prompt appears. The reason is written as an instruction, because Claude reads it as the tool's result.2 - The
.catchon the outbound hook makes it fail closed. Without it, a hook that throws or times out is skipped and the call goes ahead.2 - The result hook lets every tool run, then walks the result and masks every string in it, keeping its shape. It returns a fresh
{ result }. It does not pass back the object it got: when a hook returns that object unchanged, Claude Code uses its own copy of the result (from the Claude Code 2.1.287 type declarations).
Hooks in one module run in the order they were registered, so for an MCP call the outbound check runs first and the result is masked on the way back.2
Check it before you trust it
A guard that does nothing looks exactly like a guard that works, until the day it matters. Two commands help. claude plugin validate reads the mod the way Claude Code will and lists what it hooks and what it calls.4 This is the real output for this mod on v2.1.287:
The calls: line is short on purpose. This mod reads no files, starts no processes, and makes no network requests. It only writes a dim line to the transcript and shows a toast. That is worth checking in any security mod you install, because a mod sees every prompt and every tool call.1
claude plugin test runs the .test.ts files against Claude Code's own engine, with stubs standing in for the tools.8 Our tests check the patterns, a pasted AWS key, a cat .env result, a Slack MCP call with a card number, and a clean Slack call that must go through. The fake key and card values are built at run time, and the card and IBAN are public test numbers:
What Claude sees
Suppose Claude runs cat .env while debugging. The command runs as usual, but Claude reads the masked copy:
Later, Claude tries to post a summary to Slack with a customer's card number in it. The call does not run, and Claude reads this as the result:
That is the point of a guard that blocks one call and gives a reason, instead of stopping the session. Claude still has the task, it knows what went wrong, and it can post the summary without the number. Whether it does depends on the model and the task, so watch the first few times.
One side effect to know about. If Claude reads a file with masked values and later edits it, its edit can contain the placeholder text, or fail to match the real line. Review diffs to files that hold secrets, or keep those files away from Claude entirely with a deny rule, as described below.
What it will not catch
This mod is a layer, not a guarantee. Be clear about where it stops:
- Patterns miss things. Custom token formats, names and street addresses, a key split across two lines, base64 or URL encoded values, a password without a
PASSWORD=label. Add rules for the formats your systems use. - It also masks things that are fine. An example email in documentation gets masked. So does the right side of a code line like
API_KEY = process.env.API_KEY, which can confuse Claude when it edits that code. And the Bash check matches words likehttporghanywhere in the command, so a localgrepfor anhttp://URL counts as outbound and is refused when it also holds, say, an email address. Tune the rules to your codebase. - What already went in stays in. Masking applies from the moment the mod loads. A key that reached the model in an earlier prompt or tool result cannot be recalled. If one did, rotate it.
- It only sees what passes through hooks. The outbound check reads tool arguments. A test script that Claude runs can still open a network connection on its own, and the Bash check only matches the command text. A mod's own file and process calls are also not tool calls.4
- It can be turned off.
claude --safe-modeand"disableAllHooks": truein your own settings stop the mods you installed.1 Mods are also not sandboxed: they run with your permissions.1 - Time limits. A hook gets 10 seconds of its own execution time per event.3 A huge tool result could in theory push past that. The outbound hook fails closed through
.catch. The result hook catches its own errors, but a timeout after the tool ran leaves the raw result standing.2
Use it with what Claude Code already has
We found no built-in step in the Claude Code docs that scans prompts or tool output for secrets. The docs do describe controls that work well next to a redaction mod:
- Read deny rules. A rule such as
Read(./.env)orRead(./secrets/**)blocks Claude's file tools. It also blocks file commands Claude Code recognizes in Bash, such ascat,headandtail. It does not cover a script that opens the file itself.6 Better to never read a secret than to mask it after. - The sandbox. For operating system level isolation of files and network for Bash commands, the docs point to sandboxing.67
- Network approval. Commands like
curlandwgetare not auto-approved by default, and you can add them topermissions.deny.7 - For teams. An administrator can ship the mod through managed settings so it counts as the organization's, run it before every user's mod with
prependPlugins, and keep users' own mods out with the built-in guard'sallowManagedModsOnlyoption.4 Note that--safe-modestill turns installed mods off, the organization's included.4
On the data side, Anthropic documents limited retention periods, restricted access to session data, and training controls in its privacy safeguards.7 Those cover what happens after data reaches Anthropic. The mod is about sending less of it in the first place.
One place this matters for us: logs. Logs often carry tokens and customer emails that nobody meant to log. Fixter is monitoring for teams that build with coding agents: you send your logs and traces over standard OpenTelemetry, and Fixter finds the issues and bugs in your system. When you pull those into Claude Code over MCP, the results pass through tool.call like any other MCP tool, so the same mod masks them before Claude reads them.
Key takeaways
- A mod can rewrite your prompt before it enters the session (
prompt.submit) - A
tool.callhook can refuse a call with a reason, or let it run and hand Claude a masked copy of the result - MCP tools and subagents' tool calls go through the same
tool.callevent - Block one call with a reason Claude can act on, do not stop the session
- Patterns miss things and data already sent cannot be recalled: pair the mod with Read deny rules and the sandbox
Frequently asked questions
Can a Claude Code mod redact secrets before Claude sees them?
Yes, at three points. A prompt.submit hook can rewrite the prompt you send before it enters the session. A tool.call hook can let a tool run, then hand Claude a masked copy of the result instead of the real one. And the same tool.call hook can refuse a call before it runs. Anything that reaches Claude some other way, or reached it before the mod loaded, is not covered.
Can a mod stop Claude from sending an API key to Slack or another MCP server?
Yes. MCP tool calls pass through the tool.call event like built-in tools, with names such as mcp__slack__post_message, and a matcher can catch them all with a regular expression like /^mcp__/. The hook checks the arguments and returns { deny: reason } without calling next, so the call never runs and Claude reads the reason as the tool's result.
Can a settings hook do the same thing without a mod?
Most of it. A PreToolUse hook can deny a call with a reason Claude sees, and a PostToolUse hook can replace the tool output Claude reads. A UserPromptSubmit hook can block a prompt or add context, but it cannot rewrite the prompt text. A mod can, and it keeps all three checks in one tested file that runs inside Claude Code.
Does Claude Code redact secrets on its own?
We found no built-in step in the Claude Code docs that scans prompts or tool output for secrets. What the docs do describe is Read deny rules such as Read(./.env), which keep Claude's file tools and common commands like cat away from a file, a sandbox for operating system level isolation of Bash, and approval for network commands like curl. A redaction mod is a layer on top of those.
Is pattern-based redaction enough to keep data safe?
No. Patterns catch keys with known prefixes, card numbers that pass a Luhn check, valid IBANs and similar shapes. They miss custom tokens, names, addresses, values split across lines or encoded, and anything a program sends without going through a tool call. Treat a redaction mod as one layer next to deny rules, a sandbox, and keeping real secrets out of the repo.
Sources
- Claude Code docs, Mods overview (what a mod can reach, not sandboxed, turning mods off, comparison with settings hooks)
- Claude Code docs, React to events with a mod (tool.call deny and rewrite, prompt.submit, MCP matchers, hook order, .catch to fail closed)
- Claude Code docs, Mods reference (events and what each can return, limits)
- Claude Code docs, Manage mods for your organization (claude plugin validate, prependPlugins, allowManagedModsOnly, safe mode)
- Claude Code docs, Hooks reference (PreToolUse, PostToolUse updatedToolOutput, UserPromptSubmit)
- Claude Code docs, Configure permissions (Read deny rules and what they cover in Bash)
- Claude Code docs, Security (sandbox, network command approval, privacy safeguards)
- Claude Code docs, Test a mod (claude plugin test and stubs)