Security for Claude Code
Every shell command, edit, read, fetch and MCP call in Claude Code passes the adapter before it runs.
A denial in Claude Code
- The denial, in context
- The hook answers deny with the policy and the reason. Claude Code shows it and the model reads it, so the agent moves on to the rest of the task.
- The escalation id
- Every denial carries its id, and the person asks for an exception from the record. A retry before approval is denied again.
- The retry that passes
- Once the exception is on the record, the same command answers allow, with the exception named in the log.
One command on each machine
Run it once per machine. It enrols the person, starts the adapter and writes these hook entries into Claude Code’s settings:
$ specify adapter install claude-code- PreToolUse on Bash, Edit, Write, Read, WebFetch and every MCP tool: the decision
- PostToolUse: the input scanner over what the agent read or fetched
- PermissionDenied and PermissionRequest: Claude Code’s own decisions, onto the record
- SessionStart: inventory. Stop: the event spool flushes
"hooks": {
"PreToolUse": [{
"matcher": "Bash|Edit|Write|Read|WebFetch|mcp__.*",
"hooks": [{ "type": "command", "command": "specify-hook" }]
}]
}What is checked in Claude Code
- Shell commandsChecked
- Bash, with the whole command line.
- File edits and writesChecked
- Edit and Write, by path.
- File readsChecked
- Read, by path, and the input scanner on what came back.
- MCP tool callsChecked
- Every call to every server, by server and tool name.
- Web fetchChecked
- WebFetch, by host.
- SubagentsChecked
- A call from a subagent carries its id onto the record.
- IdentitySigns in once
- The person signs in once on the machine; every event names them from then on.
What the org can pin
Claude Code’s managed policy settings keep the hook in place on every machine, whatever a person sets locally.
{
"allowManagedHooksOnly": true,
"hooks": { "PreToolUse": [ … ] },
"permissions": {
"deny": ["Bash(vercel deploy --prod:*)", "Read(./.env*)"]
}
}- allowManagedHooksOnly keeps user, project and plugin hooks from taking the place of the managed one, and a managed hook cannot be switched off from outside managed settings.
- The org’s deny rules also compile to managed permissions.deny, so the plainest denials hold even with no adapter on the machine.
When something is missing
- If the hook does not answer in time
- Claude Code proceeds through its normal permission flow. That is Claude Code’s design, and it is why the adapter decides from the cached bundle on the machine and never waits on the network to answer.
- If the adapter is not running
- The shim answers from the last signed bundle: the org’s flagged classes are denied, everything else is allowed, and an adapter-down event is logged. Managed deny rules hold on their own.
- If the person removes the hook entry
- Nothing on the machine can answer. Inventory shows hooks removed and when the adapter was last seen. An org that pins hooks through managed settings keeps this from happening.