[SECURITY] PreToolUse hooks not enforced on subagent tool calls, allowing security bypass

Status Fixed / completed
Maintainer reply ✓ Yes — dicksontsai
Activity 16 comments · opened Jan 28, 2026 · closed May 29, 2026
💡 Likely answer: A maintainer (dicksontsai, collaborator) responded on this thread — see the highlighted reply below.

Summary

PreToolUse hooks configured in ~/.claude/settings.json are not enforced when subagents spawned via the Task tool make their own tool calls. This creates a security vulnerability where restrictions that apply to the main agent can be completely bypassed by spawning a subagent.

Steps to Reproduce

  1. Configure a PreToolUse hook to restrict file writes outside the project directory:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/restrict-operations.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
  1. Hook script blocks writes outside project directory (exits with code 1 for unauthorized paths)
  1. Try to write a file outside project with main agent:
  • Correctly blocked by PreToolUse hook
  1. Spawn a subagent with the same request:
Task tool with python-coder subagent
Prompt: "Create a file at /Users/username/test.py"
  • Bypasses the hook - file is created successfully

Expected Behavior

PreToolUse hooks should apply recursively to all agents in the execution tree:

  • Main agent's tool calls → hook enforced
  • Subagent's tool calls → hook enforced
  • Sub-subagent's tool calls → hook enforced

Actual Behavior

PreToolUse hooks only apply to the agent where settings.json is loaded:

  • Main agent's tool calls → hook enforced ✅
  • Subagent's tool calls → hook NOT enforced ❌

Security Impact

This allows complete bypass of security restrictions:

  1. File access controls - Can write/read files anywhere on the system
  2. Command restrictions - Can execute unrestricted bash commands
  3. Self-modification - Can modify or delete the hooks themselves
  4. Privilege escalation - Any security boundary can be bypassed

Attempted Workarounds

1. SubagentStart Hook

Added SubagentStart hook to intercept subagent spawning and analyze prompts for suspicious patterns. Limitation: Can only do heuristic analysis of the prompt text, cannot actually enforce tool-level restrictions within the subagent.

2. Intercept Task Tool

Added Task tool to PreToolUse matcher to block suspicious subagent prompts. Limitation: Prompt analysis is unreliable - many legitimate prompts contain file paths, and adversarial prompts can obfuscate intent.

Proposed Solutions

  1. Global Hook Enforcement (Preferred)
  • Hooks configured in ~/.claude/settings.json should apply system-wide to all agents
  • Each subagent inherits parent's hook configuration
  • Hooks execute in subagent's context when subagent makes tool calls
  1. Explicit Hook Inheritance Flag
  • Add inherit: true option to hook configuration
  • Allows opt-in recursive enforcement for security-critical hooks
  1. Subagent Security Context
  • Add security context parameter to Task tool
  • Explicitly declare which hooks must be enforced on subagent

Environment

  • Platform: macOS (Darwin 25.2.0)
  • Claude Code Version: Latest (using claude-sonnet-4-5-20250929)
  • Hook Types Affected: PreToolUse
  • Tools Affected: Write, Edit, Bash, NotebookEdit (any tool called by subagents)

Related Issues

  • #20221 - SubagentStop hooks don't prevent termination
  • #16424 - Expose Agent Context in Hook Event Payloads
  • #16126 - Add agent identity to PreToolUse hook data
  • #18653 - Tool result transform hook for content sanitization

Example Use Cases Requiring This Fix

  1. Corporate environments - Enforce write restrictions to prevent data exfiltration
  2. Sandboxed execution - Prevent agents from escaping sandbox via subagents
  3. Code review automation - Ensure read-only analysis can't be bypassed
  4. Credential protection - Block access to sensitive files/directories
  5. Audit logging - Ensure all tool calls are logged, not just main agent

Additional Context

This is a fundamental security architecture issue. While hooks provide excellent visibility and control for the main agent, the lack of recursive enforcement makes them insufficient for security-critical use cases. Any security boundary enforced via hooks can be trivially bypassed by using the Task tool.

View original on GitHub ↗

16 Comments

github-actions[bot] · 7 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/6305
  2. https://github.com/anthropics/claude-code/issues/18950
  3. https://github.com/anthropics/claude-code/issues/20946

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

evilfurryone · 7 months ago

This addresses a gap in the current hook architecture's security model. PreToolUse can block tool execution, but as you've demonstrated, this protection doesn't extend to subagents - any security boundary can be bypassed by spawning a Task.

Related: #18653 identifies another architectural gap where tool results cannot be sanitized before context ingestion. PreToolUse can block, PostToolUse can observe, but neither can transform content before it enters Claude's context window, leaving no mitigation layer against prompt injection via external content.

Together these suggest the hook system needs architectural attention for security use cases, not just point fixes.

Z-Lemke · 6 months ago

Confirmed: Reproduced with E2E Test

I've empirically confirmed this issue while testing a PreToolUse-based safety plugin.

Test Results

Setup: Plugin-level PreToolUse hook with empty matcher (applies to all tools), deny rule for curl

Test: Spawned 2 subagents in parallel via Task tool - one to run find, one to run curl

Findings:

  • ✅ Task tool successfully spawned subagents
  • ✅ Plugin loaded (visible in debug log)
  • ✅ Subagent attempted curl command
  • NO hook execution - no evidence in debug logs
  • ⚠️ curl blocked by sandbox network restriction, NOT by our deny rule

Conclusion: Plugin-level PreToolUse hooks do NOT fire for subagent tool calls.

Supporting Solution 1: Global Hook Enforcement

I strongly support the proposed "Global Hook Enforcement" solution - hooks configured in settings should apply recursively to all agents in the execution tree.

This is critical for security use cases where restrictions must be enforceable, not bypassable.

Resources

MechanicalTyler · 6 months ago

Has there been any prioritization of this? This is a non-starter for subagent usage for us, as all guardrails are ignored.

MaxwellCalkin · 5 months ago

This is a fundamental architectural issue — hooks run at the process level, but subagents spawn new processes that don't inherit hook configuration.

An alternative approach is scanning at the API/middleware layer rather than relying on hooks. Sentinel AI takes this approach — it can run as:

  1. MCP safety proxy — sits between Claude and any MCP server, scanning all tool calls regardless of whether they come from the main agent or a subagent:

``json
{
"mcpServers": {
"safe-server": {
"command": "sentinel",
"args": ["mcp-proxy", "--", "your-mcp-server"]
}
}
}
``

  1. API middleware — wraps the Anthropic/OpenAI client, scanning every request and response before it reaches the model

Since these approaches operate at the transport layer, they're immune to the subagent bypass — the scanning happens on every tool call, regardless of which agent initiated it.

The hook system is still valuable for quick policy enforcement, but for security-critical scanning, an external safety layer that can't be bypassed by spawning subprocesses is more robust.

ai-cre · 5 months ago

This is a real gap. We work around it by enforcing at the shell execution boundary rather than the tool call boundary.

Our hook (https://github.com/tech-and-ai/claude-rule-enforcer) intercepts commands at the Bash tool level. Since every subagent ultimately executes through the same shell, enforcement applies regardless of which agent tier spawned the command.

Two layers: L1 regex for instant blocks (<10ms), L2 LLM review for context-dependent decisions. The architecture means even if PreToolUse doesn't fire for subagents, the gate still catches destructive commands.

RichAyotte · 5 months ago

Yikes, my hooks aren't protecting me. This is a serious issue.

ai-cre · 5 months ago
ai-cre · 5 months ago
Yikes, my hooks aren't protecting me. This is a serious issue.

I built something for exactly this. CRE (Claude Rule Enforcer) is a two-layer enforcement system that sits between the agent and the OS via pre-tool hooks.

L1: regex gate, blocks dangerous patterns instantly (<10ms). fork bombs, disk writes, force push, production SSH.

L2: LLM reads the conversation and checks "did the user actually ask for this?" if no user approval exists, it blocks. no retry bypass. the user has to explicitly say "yes do it" in chat.

It also self-protects. the agent cannot turn CRE off even if instructed to. hooks auto-restore if tampered with.

It works with OpenClaw's new tool:pre hooks, plus Claude Code, Cursor, Windsurf, Copilot, Codex, Amp. ships as an MCP server too for the intelligence layer.

open source (BSL 1.1): https://github.com/tech-and-ai/claude-rule-enforcer
site: https://ai-cre.uk

mathnathan · 5 months ago

I've also reproduced this issue.

In addition, the PreToolUse and PostToolUse hook payloads don't include the calling agent_id. I'd like to have conditional logic for how the PreToolUse and PostToolUse hooks perform depending on which subagent called the tool. To accomplish this, I'm currently tracking the agent_id returned in the SubagentStart event payload and mapping it to my specified subagents in .claude/agents. Then, when a PreToolUse or PostTooleUse event arrives, I would check the agent_id in the payload, compare it to which of my agents are mapped to that id, and then run the conditional logic in my hook script.

Perhaps including agent_id in the payload of the PreToolUse and PostToolUse events should be a separate feature request... But then again, there are over 5000 of them right now, so... I'm slipping it in here! 😇

nialbima · 5 months ago

Claude wrote a bug report: worktree-permission-bypass-bug-report.md

I've reproduced the issue in a worktree using the new isolation: worktree mode.

yurukusa · 5 months ago

Hook-level workaround: enforce security at user level + defense-in-depth
The root cause is that project-level .claude/settings.json hooks only bind to the agent that loads them. User-level hooks (~/.claude/settings.json) inherit to subagents because they're loaded at the Claude Code process level.
Move your security hooks from project settings to ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/restrict-operations.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

User-level hooks fire for both the main agent and subagents.
Even with user-level hooks, a subagent can still be given overly broad permissions. This hook limits what files subagents can write:

INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -z "$FILE" ] && exit 0
SCOPE_FILE=".claude/agent-scope.txt"
[ -f "$SCOPE_FILE" ] || exit 0
SCOPE=$(cat "$SCOPE_FILE" | head -1 | tr -d '\n')
[ -z "$SCOPE" ] && exit 0
case "$FILE" in
    ${SCOPE}*) exit 0 ;;
    *)
        echo "BLOCKED: $FILE is outside agent scope ($SCOPE)." >&2
        exit 2
        ;;
esac

Then set scope per project: echo "src/" > .claude/agent-scope.txt
This is a workaround. The proper fix needs Claude Code to propagate hook bindings to all agents in the execution tree. But user-level hooks + scope files cover the most common bypass vectors.

willamhou · 4 months ago

Signet is now available on the official Claude Code plugin marketplace:

/plugin install signet@claude-plugins-official

Every tool call gets Ed25519-signed and appended to a hash-chained audit log at ~/.signet/audit/. This covers all agents in a session, including subagents that might bypass PreToolUse hooks.

Query the trail:

signet audit --since 1h
signet audit --verify # verify hash chain integrity

Not a replacement for fixing hook inheritance — but a useful defense-in-depth layer while that gets resolved.

https://github.com/Prismer-AI/signet

dicksontsai collaborator · 3 months ago

We tested both v2.1.22 (the release from the day this was filed) and current main: in both, a settings.json PreToolUse hook matching Write that exits code 2 fires and blocks when a subagent spawned via Task calls Write, identically to the main agent.

A note on the project-vs-user-level workaround mentioned upthread: both .claude/settings.json and ~/.claude/settings.json merge into the same startup snapshot, so there's no difference between them for subagent inheritance.

Related improvement since this was filed:
• v2.1.69+: hook payloads now include agent_id (set for subagent calls, absent for main-agent calls) and agent_type, so your hook script can branch on which agent made the call. Addresses @mathnathan's ask and #16126.

alexander-turner · 3 months ago

Thank you @dicksontsai !

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.