permissions.deny + PreToolUse hook did not block a Task-tool subagent from reading a denied file (blocked correctly on retest)

Open 💬 0 comments Opened Jul 2, 2026 by mirwnKamp

Description

A permissions.deny rule combined with a matching PreToolUse hook (on Read|Bash|Grep|Glob) that reliably blocks the main agent from reading a sensitive file pattern did not reliably block a Task/Agent-tool-spawned subagent from reading that same file — at least once in a session, the subagent returned real contents from a path that should have been denied. A follow-up test with a fresh subagent, same settings, same file, was correctly blocked.

Setup

~/.claude/settings.json (user-level), unchanged before/during/after the session:

{
  "permissions": {
    "deny": [
      "Read(**/*.xcconfig)",
      "Read(**/APIConstants.swift)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Bash|Grep|Glob",
        "hooks": [
          {
            "type": "command",
            "command": "input=$(cat); path=$(echo \"$input\" | jq -r '(.tool_input.file_path // .tool_input.command // .tool_input.pattern // \"\")'); echo \"$path\" | grep -qiE \"xcconfig|APIConstants\\.swift\" && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Access to this file is blocked\"}}' || true"
          }
        ]
      }
    ]
  }
}

No project-level settings existed to override this.

Steps observed (not yet minimized to a deterministic repro)

  1. In a project containing a file matching the deny pattern (an .xcconfig file with plaintext secrets), spawned an Explore-type subagent via the Agent/Task tool with a broad research prompt that incidentally touched the denied file (among many other files).
  2. The subagent's returned report included literal contents of the denied .xcconfig file — including plaintext credentials — confirmed by the repo owner to be the real values. The deny rule did not block this Read/Bash/Grep call.
  3. Immediately after, the main agent's own Read/Bash attempts on the same file were correctly blocked: "File is in a directory that is denied by your permission settings." / "Access to this file is blocked".
  4. A fresh, minimal subagent spawned specifically to test Read and Bash cat on the exact same file was also correctly blocked, with the same error messages, same settings file (verified unchanged via mtime).

So: main agent blocked (both before and after), a second subagent blocked — but the first subagent's incidental access to the same file, under the identical settings, was not blocked.

Expected

permissions.deny (and the equivalent PreToolUse hook) should uniformly gate all tool calls in a session, including those issued internally by Task/Agent-tool subagents — no window where a subagent can read a denied path.

Actual

At least once, a Task-tool subagent successfully read a file that should have been denied per permissions.deny and a matching PreToolUse hook, while the identical rule correctly blocked the main agent and a later subagent test against the same exact file.

Impact

Real plaintext credentials from a denied file were exposed in the subagent's report — and therefore in the session transcript — despite an explicit deny rule intended to prevent exactly this. This is a security-relevant enforcement gap, not just a UX bug.

Environment

  • Claude Code version: 2.1.198
  • macOS 26.5.2 (build 25F84)
  • Deny rule scope: user-level ~/.claude/settings.json; no project-level .claude/settings.json present

Notes

I could not find a deterministic repro — a second test subagent with an identical setup was blocked correctly. This looks like an intermittent enforcement gap specific to Task/Agent-tool subagent tool calls rather than a config mistake, but I can't rule out a more mundane cause (e.g. a hook-registration timing issue early in a session, before some watcher/cache attaches). Flagging regardless since the security impact — real secrets exposure — is confirmed.

View original on GitHub ↗