Read tool bypasses PreToolUse hooks and permissions.deny rules in VSCode extension

Resolved 💬 6 comments Opened Mar 22, 2026 by cbini Closed Mar 22, 2026

What's Wrong?

permissions.deny rules and PreToolUse/PostToolUse hooks are not enforced for the Read tool in the VSCode extension. Files that should be blocked by both layers are read successfully with full contents returned to the conversation.

We tested each layer independently:

  1. permissions.deny only — added Read(/workspaces/teamster/env/**) to deny, removed all broad Read allow rules, restarted session. Read still succeeded.
  2. PreToolUse hook only — hook correctly returns exit 0 with {"permissionDecision": "deny"} on stdout when tested via shell. Read still succeeded in the extension.
  3. PostToolUse hook — output scanner for secret patterns never fires because the Read completes without triggering any hooks.

The file in question (env/.env) contains 160 lines of plaintext production credentials (SFTP passwords, API keys, database credentials, OAuth secrets).

What Should Happen?

The Read call should be blocked by either:

  1. The permissions.deny rule (evaluated before allow), or
  2. The PreToolUse hook returning a deny decision on stdout with exit 0

Error Messages/Logs

No errors — the Read silently succeeds and returns full file contents.

Steps to Reproduce

  1. Create .claude/settings.json with deny rules and a PreToolUse hook:

``json
{
"permissions": {
"deny": ["Read(/workspaces/teamster/env/**)"],
"allow": ["Read(~/.claude/plugins/**)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Edit|Write|Bash|Grep|Glob",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/check-sensitive.sh"
}
]
}
]
}
}
``

  1. Create .claude/hooks/check-sensitive.sh that denies access to env/:

``bash
#!/bin/bash
input=$(cat)
file_path=$(echo "${input}" | jq -r '.tool_input.file_path // empty')
if [[ "${file_path}" =~ env/.env ]]; then
echo '{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "blocked"}}'
exit 0
fi
``

  1. Verify the hook works in the shell:

``bash
$ echo '{"tool_name":"Read","tool_input":{"file_path":"env/.env"}}' \
| bash .claude/hooks/check-sensitive.sh
{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "blocked"}}
$ echo $?
0
``

  1. Restart the VSCode extension session.
  2. Ask Claude to read env/.env — it succeeds with full contents returned.

Additional Findings

  • The suggested chmod 600 workaround does not help when Claude Code runs as the file owner (vscode), which is the default in GitHub Codespaces.
  • Sandbox (sandbox.enabled) is not viable in Codespaces — --cap-add is silently stripped from runArgs, so bwrap/unshare-based sandboxing cannot work. Hooks and permissions are the only enforcement layers available.
  • Zero layers currently block Read in the VSCode extension.

Environment

  • Claude Code Version: 1.0.33
  • Model: Opus
  • Platform: Anthropic API
  • OS: Ubuntu/Debian Linux (GitHub Codespace, Linux 6.8.0-1044-azure)
  • Terminal: VS Code integrated terminal
  • Regression?: Unknown
  • Related issues: #37518, #37533

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗