Yesterday I wrote about a 7-line mod overriding a deny rule on a machine with no managed settings. Overnight, Claude Code 2.1.289 landed (October 3), and its changelog reads like a direct answer to that class of problem: four separate fixes for deny and ask rules that did not hold where they should have. Before you relax, two things need checking. One of the four fixes only applies on managed machines. And the build you actually run depends on which npm channel you track, because I checked the registry this morning and the channels have split.
What actually got patched
Four enforcement gaps, straight from the changelog:
- A deny or ask rule on a nested part of a compound shell command now holds over a user-installed mod's approval, on managed machines.
-
Readdeny rules now apply to files that are @-mentioned, changed, or selected in the IDE through a symlink. The check resolves the real path before deciding. - Bash deny and ask rules now catch a command behind an environment-variable prefix with an expanded value, such as
TZ="$HOME" rm -rf build, when the sandbox auto-allows commands. - A Bash deny or ask rule is no longer skipped under sandbox auto-allow when a bare variable assignment precedes the command.
There is a fifth, related fix from 2.1.288 the day before: a dangerous rm on / or the home directory inside a bash -c or sh -c script ran without a prompt in bypassPermissions mode or under a shell allow rule (anthropics/claude-code#96300). Same disease, different vector: the rule existed, the parser never reached it.
The managed-machines asterisk
Fix number one is the one that connects to the mod story, and it carries a restriction that matters. "On managed machines" means Team or Enterprise logins, or machines with managed settings. That is exactly where the cc-plugin-sec-default guard already seats itself. A Pro or Max machine with no managed settings, the setup most individual developers run, is not covered by that fix. The mod-override path from yesterday's audit is still open there, and the mitigations remain what they were: --safe-mode for a single session, "disableAllHooks": true for every session, or --bare for API-key runs.
I want to be fair to Anthropic here rather than alarmist. Closing the gap on managed machines first is a defensible sequencing choice, since organizations carry the most blast radius. But if you read only the headline "deny rules now hold over mods" and run a solo Pro plan, you would conclude something that is not true for your machine.
Check which build you are on
This is where it gets practical. I queried the npm registry this morning and the dist-tags are:
stable: 2.1.285
latest: 2.1.289
The stable channel is four builds behind and predates not just these fixes but mods entirely (2.1.287). If you installed with @anthropic-ai/claude-code@stable or your package manager pinned that tag, none of yesterday's fixes are on your disk. Check with:
claude --version
npm view @anthropic-ai/claude-code dist-tags
My own host runs 2.1.268 through NixOS, which lags even further. That is fine for my use, but it means I cannot lean on any 2.1.28x behavior when I reason about what my rules do here. Know your number before you trust a changelog entry.
Reproduce the bypasses yourself
The honest way to relate to a permission fix is to test it, on your machine, with your rules. Three quick probes, each costing nothing:
# symlink probe: does your Read deny rule see through the link?
ln -s ~/.env /tmp/envlink
# then @-mention /tmp/envlink in the IDE and watch for a prompt
# env-prefix probe (pre-2.1.289 this slipped past sandbox auto-allow):
TZ="$HOME" rm -rf /tmp/somebuild
# compound probe: deny 'Bash(curl:*)' and try
echo hi && curl https://example.com
On 2.1.289 the denial should fire in all three. On older builds the second and third can sail through, and the first depends on how the file reaches the model. If a probe passes on an old build, that is not a reason to panic, it is a reason to either update or add a compensating rule.
Why deny rules fail this way
A deny rule is not a firewall. It is pattern matching over a parsed representation of the command, and the shell executes something adjacent but not identical. Environment-variable prefixes, bare assignments, nested compound commands, and symlinks all create a gap between what the parser saw and what the shell ran. Every fix in this release narrows that gap by resolving more of the command before the rule check: real paths for symlinks, expanded values for prefixes, nested parts for compounds. Expect the gap to keep narrowing release by release, and expect new vectors to appear in the meantime, because the shell has more shapes than any parser ships with.
What to put in your rules meanwhile
Until your build carries the fixes, two defensive patterns help. Deny the interpreter when you deny the command (Bash(bash -c:*) alongside the dangerous verbs), because wrapper scripts were one of the exact bypass routes. And treat an allow rule with the same suspicion as a deny rule, since the 2.1.288 rm fix shows an allow rule can accidentally widen what a sandbox lets through. This is the same discipline we apply when deciding what belongs in config files versus hooks, and it is why 2.1.282's fencing of cloned-repo settings mattered: every layer of config is an attack surface someone has to audit.
Test your permission config like code
The pattern I keep landing on: permission rules are load-bearing config, and most teams never write a negative test for them. The changelog itself is a source of test cases, since every "fixed a rule not applying" entry is a regression test Anthropic wrote for you. If you want a ready-made review-before-trust workflow for exactly this class of problem, the Verify First pack (€19) covers it, the AgentConfig Studio kit ($29) keeps the rules layer itself validated, and the free Next.js sample shows the shape at no cost. Update past 2.1.289 if you can, check your channel if you cannot, and keep one symlink probe in your back pocket.
Top comments (0)