React Native

AI Coding Agent Hooks Security: 7 Controls for 2026

AI coding agent hooks are both a guardrail and an attack surface. Use 7 controls to stop malicious repo hooks, fail closed, and enforce policy that holds.

October 20, 2026 • 15 min • Mikołaj Gramowski

AI coding agent hooks security means treating hooks as two things at once: the strongest deterministic guardrail you can put around an agent, and executable code that an attacker can plant in a repository. Secure them by locking down who can define hooks, making blocking hooks fail closed, matching normalized intent instead of raw strings, and backing every hook with a sandbox, scoped credentials, and logs.

Claude Code, Cursor, GitHub Copilot, Gemini CLI, and Codex all expose some form of lifecycle hook: a script that runs when a session starts, before a tool call, after a file edit, or when the agent stops. Teams use them to block rm -rf, protect .env files, run Semgrep on every edit, and log every shell command. That is exactly why they matter for security, and exactly why they became one of the most exploited configuration surfaces of the past year.

This guide is the hooks-specific companion to the broader AI coding agent security checklist. It covers both sides: how attackers abuse hooks, and how to write hooks that actually enforce policy.

What are AI coding agent hooks?

Hooks are user-defined commands, HTTP endpoints, or prompts that the agent harness runs automatically at fixed points in its loop. The harness passes a structured JSON description of the event on stdin, for example the tool name and the exact shell command the model wants to run, and reads back a decision such as allow, deny, or ask.

The important property is determinism. Instructions in CLAUDE.md or AGENTS.md are suggestions the model may reinterpret. A PreToolUse hook that denies a command runs every time, regardless of what the reasoning trace said. In Claude Code, a PreToolUse deny is evaluated before the permission mode check, so it still blocks under bypassPermissions and --dangerously-skip-permissions.

The same property cuts the other way. A hook is a shell command that runs with the developer's privileges, and hooks can be defined in files that live inside the repository.

How do attackers abuse AI coding agent hooks?

The clearest example is the Check Point Research disclosure on Claude Code project files. Because .claude/settings.json is committed with the repository, anyone with commit access, or anyone whose pull request gets merged, can add a hook. Check Point showed that a SessionStart hook in a cloned repository could execute shell commands when the developer launched the agent, before consent was meaningfully given.

IssueVectorImpactFixed in
Hooks consent bypass (GHSA-ph6w-f82w-28w6)Project hooks in .claude/settings.jsonCode execution on project openClaude Code 1.0.87
CVE-2025-59536 (CVSS 8.7)Repo-defined hooks and MCP settings such as enableAllProjectMcpServersShell commands run at tool initializationClaude Code 1.0.111
CVE-2026-21852 (CVSS 5.3)ANTHROPIC_BASE_URL set in project settingsAPI key sent to an attacker endpoint before trustClaude Code 2.0.65
CVE-2026-40068Spoofed .git/commondir pointing at a trusted folderTrust dialog skipped, hooks executedClaude Code 2.1.84

All four were patched. The lesson is not that one vendor shipped bugs. It is that agent configuration files are executable, and they arrive through the same channel as untrusted code: a clone, a pull request, or a compromised maintainer account. Developers review package.json lifecycle scripts with suspicion, but a JSON settings file in a dot-directory rarely gets the same scrutiny.

The second class of problem is weaker than an exploit but far more common: hooks that look like controls and are not. A deny hook that matches the literal string cat .env does nothing against less .env, python -c "print(open('.env').read())", or a base64-encoded command. A hook that crashes or times out may silently let the action through. Those gaps are covered in the controls below.

AI coding agent hooks security: 7 controls at a glance

ControlWhat it preventsQuick check
1. Review hook config as executable codeMalicious hooks merged through pull requests.claude/, .cursor/, .gemini/, .github/hooks require code-owner review
2. Restrict who can define hooksRepo or plugin hooks running on developer machinesManaged settings enforce allowManagedHooksOnly where policy requires it
3. Open untrusted repos with hooks disabledCode execution on clone and openHeadless and review runs use disableAllHooks in a disposable container
4. Make blocking hooks fail closedCrashes, timeouts, and wrong exit codes allowing actionsA broken hook denies instead of allowing
5. Match normalized intent, not raw stringsTrivial bypasses of deny listsPaths are canonicalized and interpreters are inspected
6. Keep secrets and side effects out of hooksHooks leaking or exfiltrating dataNo secrets in hook output, no hook network calls without an allowlist
7. Log decisions and layer containmentSilent bypasses with no evidenceEvery allow and deny has a timestamp, rule ID, and command

1. Review hook configuration as executable code

Every file that can register a hook deserves the review you give a Dockerfile or a CI workflow. For Claude Code that means .claude/settings.json, .claude/settings.local.json if anyone commits it, plugin hooks/hooks.json files, and .mcp.json. For other agents it means .cursor/hooks.json, Gemini CLI settings, Codex config, and Copilot hook definitions.

Add those paths to CODEOWNERS so a security-aware reviewer must approve changes, and add a CI check that fails when a pull request introduces a new hook or changes an API endpoint variable:

# .github/workflows/agent-config-guard.yml (step)
git diff --name-only origin/main...HEAD \
  | grep -E '^(\.claude|\.cursor|\.gemini|\.codex)/|(^|/)\.mcp\.json$|hooks\.json$' \
  && echo "Agent config changed: security review required" && exit 1 || exit 0

Flag these patterns explicitly in review: SessionStart hooks, hooks that call curl, wget, or nc, any override of ANTHROPIC_BASE_URL or similar endpoint variables, and auto-approval of all project MCP servers. The same reasoning applies to MCP configuration, covered in depth in MCP server security controls.

2. Restrict who can define hooks

Hooks merge from several sources. In Claude Code these are user settings in ~/.claude/settings.json, project settings, local project settings, plugins, and managed policy settings deployed by an administrator. The more sources that can add a hook, the more places an attacker can hide one.

For organizations, put security hooks in managed settings so developers cannot remove them, and decide deliberately whether other sources may add hooks at all. Claude Code supports allowManagedHooksOnly, which narrows execution to managed hooks and blocks hooks from user settings, project settings, and user-installed plugins. Setting disableAllHooks in managed settings goes further and turns off every hook, including your own managed ones, so it is a kill switch rather than a policy.

SettingEffectUse it when
allowManagedHooksOnly (managed)Only organization hooks runYou ship central guardrails and do not want repo hooks on laptops
disableAllHooks (managed)No hooks run at allIncident response or a locked-down environment with no hook policy
disableAllHooks (user or flag)Stops your own and project hooks, managed hooks keep runningOpening an unfamiliar repository

Solo developers get most of the benefit by keeping guardrail hooks in user settings, not in each repository, and treating any project-level hook as untrusted until read.

3. Open untrusted repositories with hooks disabled

Workspace trust dialogs are a useful prompt, but the CVEs above show they are also a target. The safest default for a repository you did not write is to inspect it before any agent runs inside it, then run the agent in a disposable container or VM where the worst case is a rebuilt image.

# Inspect agent config before launching any agent
find . -path ./node_modules -prune -o \
  \( -path '*/.claude/*' -o -name '.mcp.json' -o -name 'hooks.json' -o -path '*/.cursor/*' \) -print

# Launch Claude Code without hooks for a first look
claude --settings '{"disableAllHooks": true}'

Headless runs deserve extra care. Non-interactive modes such as claude -p in CI do not show an interactive trust dialog, so disable hooks explicitly when the checkout comes from a fork or an external contributor. If an agent runs inside GitHub Actions, combine this with the controls in AI agents in GitHub Actions: prompt injection fixes. And keep every agent CLI updated, because three of the four issues in the table above were fixed by a version bump.

4. Make blocking hooks fail closed

The most common hook bug is a guardrail that fails open. In Claude Code, exit code 2 from a PreToolUse hook blocks the tool call and returns stderr to the model. Exit code 0 means no objection. Most other non-zero exit codes are treated as a non-blocking error, which means the tool call proceeds. A security hook that crashes on malformed JSON, a missing jq binary, or an unexpected field can therefore wave through the exact command it was written to stop.

Write blocking hooks so that any unexpected state denies:

#!/usr/bin/env bash
# PreToolUse guard: any failure path blocks (exit 2)
set -uo pipefail
trap 'echo "guard error: blocking by default" >&2; exit 2' ERR

input="$(cat)" || exit 2
cmd="$(jq -er '.tool_input.command // empty' <<<"$input")" || exit 2

if grep -Eq '(^|[;&|[:space:]])(rm[[:space:]]+-[a-z]*r|curl|wget|nc)([[:space:]]|$)' <<<"$cmd"; then
  echo "Blocked by policy: $cmd" >&2
  exit 2
fi
exit 0

Prefer the structured JSON response where the harness supports it, for example permissionDecision: "deny" with a permissionDecisionReason, because it is explicit and auditable. Test the failure paths: delete jq from the PATH, send malformed input, and set a tiny timeout, then confirm the command is blocked. Cursor's community forum has documented the opposite problem too, where fail-closed hooks that never start hard-lock the agent, so test both directions on every OS your team uses.

5. Match normalized intent, not raw strings

A regex on a command line is deterministic, but it is not complete. Agents and attackers both find alternatives: bash -c, sh -c, python -c, node -e, xargs, command substitution, environment variable expansion, and relative paths such as ../../.ssh/id_ed25519.

  • Canonicalize paths before comparing them. Resolve symlinks and .. segments, and anchor file rules to ${CLAUDE_PROJECT_DIR} or the equivalent workspace root.
  • Hook every tool, not only the shell. Protect .env with rules on read, edit, and write tools as well as Bash, and on MCP tools that touch files.
  • Treat interpreters as high risk. Inline code via -c or -e should usually require approval.
  • Prefer allowlists for autonomous runs. A short list of permitted commands such as npm test and npm run lint is far harder to bypass than a long deny list.

Accept the limit: a PreToolUse hook sees one tool call at a time. It cannot remove a secret the agent already read into its context, and it cannot anticipate every exfiltration path. That is why the sandbox boundary checks still matter even when the hooks are well written.

6. Keep secrets and side effects out of hooks

Hooks receive rich context, including prompts, file paths, command strings, and sometimes file content. A logging hook that ships that payload to a third-party endpoint can become the leak it was meant to detect. Review what each hook sends, and where.

  • Quote every variable ("$VAR") and never pass model-controlled strings to eval or an unquoted shell expansion.
  • Redact secrets before logging. Tools such as GitGuardian's ggshield hooks or Semgrep can scan prompts, commands, and edits, but keep the raw payload local unless you have a data agreement.
  • Restrict HTTP hooks to an allowlist of URLs and environment variables. Claude Code exposes settings such as allowedHttpHookUrls and httpHookAllowedEnvVars for this.
  • Keep hooks fast and side-effect free. A hook that installs packages, pushes to Git, or calls production APIs is a second agent with no review.

When a hook needs credentials, scope them to that hook alone with a short lifetime, and never place them in the repository's settings file.

7. Log every decision and layer containment around hooks

Hooks only govern cooperative software. They do not cover an agent without hook support, a developer who removed the config, or a process the agent spawned in the background. Use hooks as the policy layer, not the whole system.

  • Start with logging. A PreToolUse and PostToolUse pair that records tool name, normalized command, decision, rule ID, and timestamp will show real agent behavior within days. Enforcement is then a config change. OpenTelemetry-based hook exporters can send this into the same pipeline as your application observability.
  • Sandbox the agent so a missed rule has a small blast radius: scoped filesystem, restricted egress, no Docker socket.
  • Put a package firewall or verification step in front of installs, as described in the hallucinated dependency guide.
  • Keep CI as an independent gate. Secret scanning, SAST, and human review on pull requests catch what local hooks miss.

Which AI coding agents support blocking hooks?

AgentPre-action hookConfig location
Claude CodePreToolUse (plus UserPromptSubmit, Stop, and more)~/.claude/settings.json, .claude/settings.json, plugins, managed settings
CursorbeforeShellExecution, beforeMCPExecution, beforeReadFile, preToolUse~/.cursor/hooks.json and project .cursor/hooks.json
GitHub CopilotpreToolUseRepository and user hook definitions
Gemini CLIBeforeToolGemini CLI settings files
Codex CLIPreToolUseCodex configuration

Names and capabilities differ, and there is no shared hook standard yet. If your team uses several agents, write the policy once as a small script with a stable input format and adapt a thin wrapper per tool, so the rules stay consistent.

How should you test your hook setup?

Run a short red-team pass before trusting hooks with autonomous work. Clone a test repository that contains a harmless SessionStart hook writing a marker file, and confirm it does not run under your policy. Ask the agent to read .env through Bash, through the read tool, and through an inline Python one-liner, and confirm all three are blocked. Break the hook on purpose and confirm the action is denied. Finally, check that each attempt appears in the log with a reason.

If you are inheriting an AI-built codebase where agents already run with broad permissions, pair this with a full AI-generated codebase audit before giving them more autonomy.

Frequently asked questions about AI coding agent hooks security

Are AI coding agent hooks a security risk?

Yes, when they come from untrusted sources. A hook is a command that runs with your privileges, and project-level hook files travel with the repository. Treat them as executable code and restrict which sources can define them.

Can a PreToolUse hook block commands in bypass permissions mode?

In Claude Code, yes. A PreToolUse deny is evaluated before the permission mode check, so it blocks the tool call even under bypassPermissions or --dangerously-skip-permissions.

Why did my security hook let a command through?

The usual causes are a wrong exit code, a crash, or a timeout. In Claude Code only exit code 2 or an explicit deny decision blocks a PreToolUse call. Other errors are non-blocking, so the tool runs.

Should I disable all hooks?

Disable hooks when opening an unfamiliar repository or running headless jobs on external code. For daily work, keep guardrail hooks in user or managed settings and block hooks from repositories and unvetted plugins.

Are hooks enough to secure an AI coding agent?

No. Hooks see one tool call at a time and only govern cooperative tools. Combine them with a sandbox, scoped credentials, restricted egress, dependency verification, CI checks, and audit logs.

Sources: Check Point Research: RCE and API token exfiltration through Claude Code project files, Claude Code hooks reference, Claude Code settings reference, CVE-2026-40068 overview, The Hacker News: Claude Code flaws, OWASP AI Agent Security Cheat Sheet.