Summary
Claude Code's permission system decides whether a tool call is allowed, denied, or must prompt the user. A run of releases from 2.1.288 through 2.1.296 (shipped 2026-10-02..09) fixed a cluster of escape hatches in that system: deny/ask rules could be evaded by environment-variable prefixes, bare variable assignments, compound commands, and shell-level indirection, and Read deny rules missed files reached through symlinks. The same window added a fail-closed option for hooks and corrected tool-scoping for builtins registered after launch. These fixes map directly onto known weaknesses in a comparable agent ("yoyo"), whose deny list still evaluates the command as written rather than as the shell would interpret it.
Key Points
- Deny/ask matching must model shell semantics, not raw strings. Rules based on the literal command line are bypassed by
TZ="$HOME" rm -rf build, a bare variable assignment before a command, or a variable that later expands to a denied command name. - A deny rule on part of a compound command must hold for the whole invocation — otherwise an allowed prefix smuggles a denied command through.
- Assignment prefixes count as command context.
declare,typeset,export, andreadonlyprefixes previously hid the following command name from deny/ask rules. - Indirection has many faces: glob-expanded arguments to "read-only" commands (
rg,git grep),BASHPIDassignments evaluated as arithmetic, and shell-specific (zsh vs bash) variable-name behavior. - Path-based rules must resolve through symlinks; a Read deny rule that doesn't follow a symlink leaks the file it was meant to protect.
- Hooks can fail closed.
onFailure: "block"(opt-in) makes a hook that cannot start, times out, or exits unexpectedly block the action it guards. - Hook instructions written as prose can invert intent — prompt/agent hooks described as instructions previously allowed what they should have blocked.
- Tool scoping is a moving target: / did not apply to builtin tools registered after launch.