Read tool bypasses PreToolUse hooks and permissions.deny rules in VSCode extension
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:
permissions.denyonly — addedRead(/workspaces/teamster/env/**)to deny, removed all broad Read allow rules, restarted session. Read still succeeded.- PreToolUse hook only — hook correctly returns
exit 0with{"permissionDecision": "deny"}on stdout when tested via shell. Read still succeeded in the extension. - 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:
- The
permissions.denyrule (evaluated before allow), or - 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
- Create
.claude/settings.jsonwith 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"
}
]
}
]
}
}
- Create
.claude/hooks/check-sensitive.shthat denies access toenv/:
``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
- 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
- Restart the VSCode extension session.
- Ask Claude to read
env/.env— it succeeds with full contents returned.
Additional Findings
- The suggested
chmod 600workaround 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-addis silently stripped fromrunArgs, 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
6 Comments
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.Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
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.
Good point — in Codespaces where Claude runs as the file owner, \
chmod\can't help.The remaining options are more creative:
.env\in a mounted volume or external secret manager (Doppler, Vault, etc.) and reference via environment variables set at Codespace startupsops\or \age\to encrypt \.env\files. Claude can read the ciphertext but can't decrypt without the key.env\file to readNone 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.
Closing — root cause identified. This was not a VSCode extension bug.
The commented-out JSON block in
settings.jsoncaused 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, bothpermissions.denyand PreToolUse/PostToolUse hooks work correctly in the VSCode extension withexit 0+ deny JSON on stdout.Apologies for the noise. One suggestion: it would be helpful if Claude Code logged a warning when
settings.jsonfails to parse, rather than silently falling back to defaults.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.