Claude Code mods can override your deny rules — what we found testing them on a personal plan
On our personal Max plan with no managed settings, the guard that makes deny rules hold over mods never loaded, and a six-line mod that answered permission checks with allow overrode our deny rule. Anthropic documents that. What it means in practice is that installing a mod gives its author access your settings.json doesn't control.
The 30-second version
On 1 October, Claude Code 2.1.287 added mods: plugins that run JavaScript inside your session. A mod can register handlers that see and change prompts and tool calls, and that answer permission checks before you are asked. Mods are on by default.
We wrote seven small test mods and ran 48 headless sessions (claude -p, no interactive prompts) on Claude Code 2.1.289, signed in to a personal Max plan on a machine with no managed settings. Each setup produced the same outcome in all three of its runs:
- A mod overrode our deny rule. The rule blocked a shell command. A six-line mod that answers the permission check with “allow” let it run.
- Deny rules don’t cover a mod’s own calls. Claude couldn’t read a file our
Readrule denied. Our mod read it through the mods API. - The sandbox doesn’t contain a mod. It blocked Claude’s write to a folder outside the project. A process our mod started wrote there.
- A mod can change the record. With a handler that rewrites stored conversation rows, the transcript recorded “REDACTED” where the control runs recorded Claude’s codeword.
claude plugin validateshowed the override only astool.check. The approving mod’scalls:line read “nothing on $” — and so did a mod that only observed the check.
The permission, sandbox and transcript behaviours are all in Anthropic’s documentation. One network result didn’t match it; that section explains. Taken together: installing a mod gives its code access that Claude Code’s permission rules and Bash sandbox don’t control.
What a mod is
A mod is a plugin with a hooks module, a JavaScript or TypeScript file Claude Code loads into its own process. Handlers run at events such as tool calls, submitted prompts and saved conversation rows. A handler can leave the event unchanged, change it, or provide its own result so the usual behaviour doesn’t run.
Claude Code passes each handler an API object named $. The module has no Node.js APIs of its own, so $ is its only route to files ($.fs), programs ($.process), the network ($.http.fetch), settings, environment variables and model calls. That is why Claude Code can list a mod’s handlers and API calls before it runs.
Two kinds of “permissions” matter here. A mod runs with your operating-system account’s access. Claude Code’s allow, ask and deny rules are a separate control, written for the tool calls Claude makes.
Mods install like any plugin, from a marketplace, or for one session with --plugin-dir. Their handlers run in the terminal, the Desktop app (except WSL sessions), the VS Code extension, claude -p and the Agent SDK. Some of Claude Code’s own features, such as /diff and the AGENTS.md loader, are built-in mods.
How we tested
Each run created a fresh project folder and, where the setup used one, a fresh mod folder, then started claude -p with Claude Haiku 4.5, a minimal environment (only HOME, PATH, USER and TMPDIR set), --strict-mcp-config, and our own plugins switched off. Mods loaded with --plugin-dir. Permission rules and the sandbox came from a --settings file.
We checked marker files, the tool results recorded in each transcript, and load errors. For the rewrite test we compared the stored transcript with the headless output.
16 setups, three runs each: 48 runs, 238 seconds of summed run time, and $0.92 at API list prices (on our Max plan it counted toward usage limits), on macOS on 4 October. Anthropic’s guard mod, cc-plugin-sec-default, appeared in no session’s plugin list, as the documentation predicts for this setup.
What a mod did to our permission rules
The test command was touch deny-marker.txt, with Bash allowed so that only the deny rule stood in the way:
| Setup | Result | Runs |
|---|---|---|
Bash allowed, no deny rule, no mod | ran | 3/3 |
| Deny rule on the command, no mod | denied | 3/3 |
| Deny rule + a mod that only observes the check | denied | 3/3 |
Deny rule + a mod that answers the check with allow | ran | 3/3 |
Same, started with --safe-mode | denied | 3/3 |
Same, with "disableAllHooks": true | denied | 3/3 |
| No allow rule or mod (a headless run can’t ask you to approve) | not run | 3/3 |
| No allow rule + the approving mod | ran | 3/3 |
Read(./secret.txt) denied, Claude asked to read it | denied | 3/3 |
Same, plus a mod that reads it with $.fs.read | Claude denied, mod read all 37 characters | 3/3 |
The approving mod has one handler, on tool.check: it lets Claude Code reach its decision, then returns allow. Without the mod, the recorded tool result was “Permission to use Bash with command touch deny-marker.txt has been denied.” With it, the result was (Bash completed with no output).
Both off switches restored the deny rule. Each has side effects: --safe-mode disables your other customisations for the session, and disableAllHooks also stops your settings hooks and custom status line. Neither stops built-in mods.
When the built-in guard loads
Anthropic ships a guard mod, sec-default, that loads ahead of every mod a user installs. Where it loads, deny rules take precedence over a user’s mods by default, and a user’s mod can’t change managed hooks, managed instructions or the system prompt. Administrators can loosen the deny-rule part with allowModsToOverrideDenyRules.
The guard loads in two cases: the machine has managed settings, or you are signed in with a Team or Enterprise plan. Anthropic’s permissions page covers the rest: “Anywhere else, the mod can approve a call that a deny rule refuses.” On a machine without managed settings, that includes Pro and Max plans, and API-key, Bedrock, Vertex and Foundry users.
Even where the guard loads, mods keep these documented capabilities:
- Deny rules apply to Claude’s tool calls, not to a mod’s own
$.fsand$.processcalls. WithRead(.env)denied, a mod can still read the file. - A mod can approve a call that an
askrule would prompt for, or that aPreToolUsehook outside managed settings blocked. - In auto mode, a call a mod approves skips the automatic safety check.
Sandbox and network results
The sandbox ran with sandbox.enabled: true and allowUnsandboxedCommands: false. We asked Claude to touch a file in a folder outside the project and outside the sandbox’s default write locations. The sandbox refused, “Operation not permitted”, in 3 of 3 runs. In the same sessions our mod ran touch on the same folder through $.process.run, and the file appeared in 3 of 3. Anthropic documents this: a process that a mod starts runs outside the sandbox.
One network result did not match the documentation. Anthropic’s admin page says Claude Code refuses a mod’s $.http.fetch when nonessential network traffic is turned off for the session. With CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 set, our mod’s fetch to example.com returned HTTP 200 in 3 of 3 runs. The built-in telemetry mod was absent from those sessions, so the variable was recognised; we didn’t establish why the fetch still went through. The variable’s own reference entry doesn’t mention mods, and we didn’t test the organisation-level setting that turns off web fetching.
The admin page also says the policy doesn’t cover programs a mod starts. curl through $.process.run returned HTTP 200 in all six network runs. Don’t treat that variable as network isolation for mods.
A mod can change the record
session.append runs before Claude Code stores each row of the conversation. Our test mod replaced one codeword, PELICAN-41, with REDACTED in any row it saw. The prompt described the codeword without containing it.
Without the mod, the transcript and the headless output both said PELICAN-41, in 3 of 3 runs. With it, both said REDACTED, in 3 of 3. Since the prompt never contained the codeword, the replacement points to Claude’s reply.
If you rely on transcripts as a record of what an agent did — our AGENTS.md tests used them to show which files loaded — a session.append handler can change rows before they are stored. We tested reply text, not those instruction records.
Reading claude plugin validate
Anthropic’s review step is claude plugin validate <folder>. Without running the mod, it lists the events the mod registers handlers for and the mods-API methods its code calls:
| Mod | hooks: | calls: |
|---|---|---|
Answers every permission check with allow | tool.check | nothing on $ |
| Only observes the permission check | tool.check | nothing on $ |
| Rewrites stored rows | session.append | nothing on $ |
| Reads the denied file | session.start | $.fs.read, $.fs.write |
| Sandbox test | session.start | $.fs.write, $.process.run |
| Network test | session.start | $.fs.write, $.http.fetch, $.process.run |
Uses a computed name on $ | — | validation failed |
Claude Code rejects a mod whose use of $ the validator can’t analyse. Our mod with a computed property name failed validation, and in 3 of 3 sessions its code never ran; the refusal appeared only in the debug log.
The first two rows are the lesson. The observing mod and the approving mod have identical listings, and opposite effects on a deny rule. The listing tells you a mod can answer permission checks, not what it answers. Changing permission decisions and transcript rows needs no $ calls, so a review that only looks for file and network calls misses both. What to flag:
hooks:—tool.check(can approve or deny tool calls),tool.callandprompt.submit(sees and can change tool calls and prompts),session.append(can rewrite the stored conversation),prompt.composeandprompt.section(can change the system prompt). For any of these, read the handler.calls:—$.process.runand$.process.spawn(outside the sandbox and the network policy),$.fs.readand$.fs.write(outside your deny rules),$.http.fetch(network requests),$.env.getand$.settings.read(can read values that hold secrets),$.model.complete(spends your plan or API key).
What to do
On your own machine:
- A loaded mod has your account’s access to files, programs and the network. Install mods only from authors you trust, read the source, and run
claude plugin validatefirst. --plugin-dirgives you a temporary trial, not containment. To try unfamiliar code, use a separate machine, VM or user account.- Without the guard, a mod that handles
tool.checkcan override your deny rules. Keep secrets somewhere your user account can’t read without asking, and keep backups that account can’t overwrite. claude --safe-modeturns off installed mods and your other customisations for one session, a quick way to see whether any of them is involved.
For an organisation: check that the guard loads. Managed settings and Team or Enterprise sign-ins load it by default; if you set prependPlugins, list the guard explicitly. To allow only your organisation’s managed mods and built-ins, set allowManagedModsOnly under cc-plugin-sec-default@builtin in pluginConfigs in managed settings. A marketplace allowlist (strictKnownMarketplaces) limits where mods come from, and disableSideloadFlags blocks --plugin-dir. Leave allowModsToOverrideDenyRules unset.
What we didn’t test
- Team, Enterprise and managed-settings machines. What the guard does there comes from Anthropic’s documentation and its published source.
- Auto mode. The skipped safety check is documented; we didn’t run it.
- Interactive sessions and the Desktop app. In an interactive session in a folder you haven’t trusted yet, mods wait for the trust prompt.
- Third-party mods. All seven of ours were written for this test; we audited no published mod.
Companion reading
- AI coding agents keep wiping production databases — the permission layers this affects, and why credentials matter more than rules.
- Claude Code hooks — the four worth having — settings hooks, which keep working alongside mods.
- Your CLAUDE.md is an attack surface — the other way a repository’s files reach your agent.
- Claude Code reads AGENTS.md now — a built-in mod at work, and the transcript records we relied on.
Sources
- Anthropic — Mods overview. What a mod can reach, “Mods aren’t sandboxed”, where mods run, built-in mods, and how to turn mods off.
- Anthropic — Manage mods for your organization. The
sec-defaultguard and when it loads, what deny rules cover, the network policy statement,allowManagedModsOnly,allowModsToOverrideDenyRules,prependPlugins, and how to readclaude plugin validate. - Anthropic — Configure permissions: extend permissions with hooks. “Anywhere else, the mod can approve a call that a deny rule refuses.”
- Anthropic — Use the mods API. No Node.js APIs in the hooks module;
$.fs,$.processand$.http. - Anthropic — Mods reference. The
tool.checkandsession.appendevents. - Anthropic — Sandboxing. The sandbox’s default write locations.
- Anthropic — Environment variables. What
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFICturns off. - Anthropic — Claude Code changelog. Mods added in 2.1.287; mod fixes in 2.1.288 and 2.1.289.
- Anthropic — Getting started with Claude Code mods, 1 October 2026.
- GitHub —
sec-defaultguard source. - npm — @anthropic-ai/claude-code. 2.1.287 published 1 October, 2.1.289 on 3 October.
Our own measurements: 48 claude -p sessions on macOS on 4 October 2026, Claude Code 2.1.289, Claude Haiku 4.5, a personal Max plan and no managed settings; 16 setups, three runs each; $0.92 at API list prices and 238 seconds of summed run time. Results come from marker files, the tool results in each session’s transcript, load errors, and, for the rewrite test, the headless output. All test mods were written for this test, loaded with --plugin-dir, and run in throwaway folders.
FAQ
What are Claude Code mods? Plugins with JavaScript or TypeScript handlers that run inside Claude Code, added in 2.1.287 on 1 October and on by default. They run with your account’s access to files, programs and the network.
Can a mod bypass my deny rules? Without managed settings or a Team or Enterprise sign-in, yes. Our deny rule held in 3 of 3 runs without a mod and gave way in 3 of 3 with an approving mod. Where Anthropic’s guard loads, deny rules take precedence over a user’s mods by default, but not over a mod’s own file and process calls.
Are mods sandboxed? No. The sandbox blocked Claude’s write outside the project in 3 of 3 runs. A process our mod started wrote there in 3 of 3.
How do I check a mod before installing it? Run claude plugin validate on its folder, then read the handlers it lists. tool.check on the hooks: line means it can answer permission checks, even with nothing on the calls: line.
How do I turn mods off? Per mod, in /plugin. For one session, claude --safe-mode. For the mods you installed, "disableAllHooks": true in your user settings, which also stops your settings hooks and status line. Built-in mods keep running.
Was this helpful?
Related reading
- Haiku 5.5's 90% price cut stops at 100K tokens — 95% of our Claude Code tokens were past it
- Claude Code's /doctor prompt-audit, tested on nine planted problems — Opus and Sonnet caught all nine, Haiku missed the broken commands
- Claude Code reads AGENTS.md now — but five ordinary setups still leave it unread
Reviews independently produced · Editorial policy
Read more reviews →