The plugin is in beta. It is tested on macOS. Linux runs the same code, and Windows is untested.
Requirements
- Claude Code 2.1.295 or newer
- Node.js 18 or newer on your
PATH - A FrostWolf API key from frostwolf.app
Install
Run one command in your shell, with your API key:settings.json. Start a new session to load the hooks.
A key typed into a shell command stays in your shell history. To be prompted for the key instead, install from a Claude Code session:
How a value travels
1
Claude reads a file
customers.csv holds ada,ada@example.com. Before the result reaches Claude, FrostWolf replaces the address. Claude sees ada,[[email:e64094f527c3]].2
Claude uses the token
Claude writes
copy.csv with the content ada,[[email:e64094f527c3]].3
FrostWolf restores it
Before the write runs, FrostWolf puts the address back. The file on disk holds
ada,ada@example.com.4
You see the real value
Your terminal shows the address where Claude wrote the token. Claude and the session transcript keep the token.
What each hook does
Tool output
Every tool result is checked, whichever tool produced it: file reads, searches, shell output, web fetches, and MCP tools. Personal data and secrets become tokens. Every category is tokenized, including credentials and card codes, which the SDKs redact by default. In a coding session Claude writes files from what it read, and a redacted secret would be written back into your file as the redaction label. A token comes back as the original. An injected instruction is taken out and replaced with[FROSTWOLF_REMOVED], and Claude is told the rest of the result is untrusted. A write that contains the marker is refused, so Claude cannot overwrite your original text with it.
Tool calls
Before a tool runs, FrostWolf restores every token in its arguments. The call then goes through your permission rules as usual, evaluated against the restored arguments. Restoring a value never approves a call. A call that uses a token FrostWolf does not hold is refused, rather than written into a file as literal text. That happens when Claude alters a token, or uses one from a session that has ended. Claude is told to read the source again. A token-shaped string that was already in your files, such as an example in documentation, is recognised and let through. Shell, PowerShell, web fetch, and MCP calls are validated before they run, the same check asvalidateToolCalls. They are validated as Claude wrote them, with tokens, so the values never travel for validation. File edits are not validated: code under test legitimately contains strings an argument check would flag.
Prompts
A hook can refuse a prompt but cannot rewrite it. A prompt with a prompt injection is blocked. A prompt that carries personal data is refused by default, and the refusal offers the cloaked prompt to send instead. Its tokens are already held, so they restore like any other:prompt_personal_data to warn to send such a prompt as typed and be told about it instead.
A username inside your working directory or home directory path does not refuse a prompt. Claude Code gives Claude the working directory in every session, so it is not new exposure.
Files attached with @
Claude Code attaches a file you mention with@ to the prompt itself, without a tool call, so no tool hook sees it. FrostWolf reads the file when you submit the prompt. If it carries personal data, or an injected instruction, the prompt is refused, and the version offered names the file as a plain path, so Claude reads it with a tool and FrostWolf cloaks it:
warn mode. A file larger than 256 KB is not checked, and is refused unless on_unavailable is allow.
The batch check
PostToolBatch runs after every tool in a turn has finished and before Claude Code sends the results to the model. It sees each result exactly as Claude will, and it stops the turn if one should not go through:
- A rewrite was dropped. Claude Code discards a rewritten result whose shape does not match the tool’s. FrostWolf keeps the shape by construction, and this check confirms it.
- A result was not checked. FrostWolf could not reach the control plane, or the hook failed.
- A failed call printed personal data or an injection. A command that exits non-zero goes to a different hook, which cannot rewrite its output. The batch check reads it instead.
/rewind and go back to before that step to remove it. The stop message names the tool and the types found, never a value.
Settings
Set these in/plugin or /config. When the hooks are installed without the plugin, use the environment variable.
Failure handling
The plugin fails closed by default, like the SDKs. See Failure handling.
The gate hooks are registered with
onFailure: "block", so a hook that crashes, cannot start, or times out refuses the action rather than letting it through. Without an API key, every prompt is refused with a message saying how to set one.
Privacy
Values stay on your machine
Tokenized values are held in memory by a local service, behind a socket only your account can open. They never touch disk.
Values are never echoed
The control plane returns types and ranges. Block messages, stop messages, and logs name types and counts only.
Erased when you are done
A session’s values are erased when it ends. The service exits after two hours without activity, and values expire after twelve hours.
Telemetry is counts only
The console sees how many values of each type were cloaked, never the values or the tokens.
What a device reports
When a session starts, the plugin checks in with this device’s name, operating system and architecture, and the plugin, Claude Code, and Node.js versions, under a random installation id it generates once. The console’s Integrations page lists each device with its session count and when it was last seen. The person behind a device comes from the console member who created its key, not from the device. A check-in carries no prompt, file, or value, and a failed check-in never delays or blocks a session.What reaches the control plane
To be checked, text has to reach the control plane: tool results, prompts, and files attached with@ are sent for detection, as they are when you use an SDK. See Security and privacy.
Claude Code keeps its own session transcripts and prompt history on disk. They hold what Claude saw, with tokens. Output the batch check stopped, and a prompt FrostWolf refused, are kept there as they were.
Limits
Content loaded without a tool call
Content loaded without a tool call
CLAUDE.md and other memory files are loaded into Claude’s context directly, and no hook sees them. Keep personal data and secrets out of them. Files attached with @ are the exception: FrostWolf reads those itself.Prompts are refused, not rewritten
Prompts are refused, not rewritten
A hook cannot change a prompt. FrostWolf refuses one that carries personal data and offers the cloaked version, which you send yourself.
Resumed sessions
Resumed sessions
Tokens live only as long as the local service and the session. In a session resumed after it ended, or after the service restarted, earlier tokens cannot be restored. Claude is told to read the source again, which gives it current tokens.
Latency and usage
Latency and usage
Each tool result is a control-plane request, so expect a short delay after each tool runs. Text over 12,000 characters is split across requests. Text read again unchanged within ten minutes, such as a file read twice, reuses the earlier answer. Requests count toward your plan like any other scan.
Detection accuracy
Detection accuracy
Detection is a model, so it has false positives and false negatives. See Cloak limits.
Without the plugin
Clone frostwolf-skills and point your own hooks at itsclaude-code/dist/hook.mjs. Every event uses the same command:
hooks/hooks.json, with the same timeouts and onFailure settings, and set FROSTWOLF_API_KEY in the environment.
Troubleshooting
The local service writes errors, never values, to
service.log in ~/.claude/plugins/data/ under the plugin’s directory.
