Specify Work · Security · Claude Code
Security for Claude Code.
One install writes the hooks Claude Code already honours. Every shell command, edit, read, fetch and MCP call passes the adapter before it runs, and a denial reaches the model as a reason, not a crash.
A denial in Claude Code
- Ship release 24.3 and deploy it to production.
- Bash(deploy 24.3 --env production)
- Denied by Specify · Production changes · logged as d_4f81
- Production deploys need an exception from your org admin. Ask from the record, or run: specify escalate d_4f81
- Skipping the deploy. Continuing with the changelog and the release notes.
- Four hours later, after D. Marsh grants the exception
- Bash(deploy 24.3 --env production)
- Allowed by exception e_812 · D. Marsh · once · logged
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 prints its id and the one command that escalates it. The person can do the same from the record, and a retry before approval is denied again and counted.
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.
$ specify adapter install claude-codeRun it once per machine. It enrols the person, starts the adapter and writes these hook entries into Claude Code’s settings:
- 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 commands — covered
Bash, with the whole command line.
File edits and writes — covered
Edit and Write, by path.
File reads — covered
Read, by path, and the input scanner on what came back.
MCP tool calls — covered
Every call to every server, by server and tool name.
Web fetch — covered
WebFetch, by host.
Subagents — covered
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(deploy * --env production)", "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.
Hold the line without slowing the team.
Standard policies come with every plan. Your own policies, simulation and exceptions come with Org.
See pricing