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

Status Fixed / completed
Maintainer reply None cached
Activity 6 comments · opened Mar 22, 2026 · 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 ↗

6 Comments

yurukusa · 6 months ago

This is the same VS Code extension pattern as #37518 and #37533 — the extension has its own tool execution layer that bypasses the CLI's permission and hook system for certain tools.
In the CLI, both \permissions.deny\ and PreToolUse hooks work correctly for Read. The issue is specific to the VS Code extension.
Workaround (limited): Since hooks don't fire for Read in the extension, the only defense is at the OS/filesystem level:
\\\bash
chmod 600 env/.env
\
\\
This doesn't solve the fundamental issue (hooks should fire for all tools in all environments), but it prevents the file contents from being returned.
For Codespaces specifically, you could also use \.gitignore\ patterns in \.claude/settings.json\ to exclude sensitive directories from Claude's file discovery — though this won't block explicit Read calls, it reduces the chance Claude attempts to read them.

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/33742
  2. https://github.com/anthropics/claude-code/issues/24846
  3. https://github.com/anthropics/claude-code/issues/34569

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

cbini · 6 months ago
This is the same VS Code extension pattern as #37518 and #37533 — the extension has its own tool execution layer that bypasses the CLI's permission and hook system for certain tools. In the CLI, both permissions.deny and PreToolUse hooks work correctly for Read. The issue is specific to the VS Code extension. Workaround (limited): Since hooks don't fire for Read in the extension, the only defense is at the OS/filesystem level: ``bash chmod 600 env/.env ` This doesn't solve the fundamental issue (hooks should fire for all tools in all environments), but it prevents the file contents from being returned. For Codespaces specifically, you could also use .gitignore patterns in .claude/settings.json` to exclude sensitive directories from Claude's file discovery — though this won't block explicit Read calls, it reduces the chance Claude attempts to read them.

Appreciate the fast response, but that workaround doesn't work. In a Codespace, Claude runs as the vscode user, which owns the file, so filesystem permissions can't block it. Instituting Sandbox mode isn't an option either, which was what I was initially trying to implement, because bubblewrap can't work inside a Codespace either.

yurukusa · 6 months ago

Good point — in Codespaces where Claude runs as the file owner, \chmod\ can't help.
The remaining options are more creative:

  1. Move secrets out of the workspace entirely — store \.env\ in a mounted volume or external secret manager (Doppler, Vault, etc.) and reference via environment variables set at Codespace startup
  2. Encrypt at rest — use \sops\ or \age\ to encrypt \.env\ files. Claude can read the ciphertext but can't decrypt without the key
  3. Use Codespace secrets — GitHub Codespace secrets are injected as environment variables, not files. No \.env\ file to read

None of these are hook-based solutions, and that's the core issue — the VS Code extension needs to respect the same hook/deny pipeline as the CLI.

cbini · 6 months ago

Closing — root cause identified. This was not a VSCode extension bug.

The commented-out JSON block in settings.json caused the JSONC parser to silently reject the entire file, falling back to default settings (no hooks, no deny rules). Once the invalid JSONC was removed, both permissions.deny and PreToolUse/PostToolUse hooks work correctly in the VSCode extension with exit 0 + deny JSON on stdout.

Apologies for the noise. One suggestion: it would be helpful if Claude Code logged a warning when settings.json fails to parse, rather than silently falling back to defaults.

github-actions[bot] · 6 months 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.