Blog

Claude Code 2.1.268: What Was Fixed in deny Rules and MCP

Markdown

Claude Code 2.1.268–2.1.269 fixed four permission issues and a secret leak in /mcp. Who is affected, how to update, and how to check your rules.

Read full article

Claude Code version 2.1.268 was released on September 10, 2026, followed the next day by 2.1.269. You should update right away: in two cases, deny rules that you use to block the agent from accessing .env and service folders were silently ineffective. In addition, the /mcp command could print secrets from your configs in the terminal.

Everything below is based on Anthropic’s official changelog and documentation as they stood on September 17, 2026, the date of the source material. The latest version on that date was 2.1.274. We describe how the errors worked based on those entries; the bypasses were not reproduced on the vulnerable version.

For additional instructions on working with Ailoot, see the documentation.

What was fixed in Claude Code 2.1.268 and 2.1.269?

Key takeaway

Four permission issues were fixed. The deny and ask rules did not work on paths through symlinks, and deny on Read or Edit could fail on lines containing env -C or eval. A rule with “!” applied beyond its settings file. The tee command wrote files without being blocked by deny on Edit. In addition, /mcp and claude mcp list displayed secrets from configs, while plugin errors displayed tokens from Git URLs.

Four fixes are relevant to permission rules:

  1. Denials on symlinks did not work. Deny and ask rules written for symlinked directories (/etc, /tmp, /var on macOS, /bin on Linux) did not trigger when a path was specified by its real location. Bash commands also ignored deny rules written for a path through a symlink.
  2. Denial on reading and editing failed alongside env -C or eval. If the same command line contained a construct that the permission check could not parse, deny on Read or Edit could fail to trigger.
  3. A rule with “!” applied more broadly than intended. A deny or ask rule that started with ! applied beyond the settings file where it was written. Now it applies only within its own source, and a bare negation ! is ignored.
  4. tee wrote around the denials. Deny rules for Edit() and the write-path check were not applied to a file created by the Bash tee command. The Bash(tee:*) permission no longer covers writes outside working directories.

These versions also fixed two issues involving secret exposure. WebFetch no longer waits indefinitely for a response from a server that keeps the connection open: the request now fails after 300 seconds, and the timeout can be configured with the CLAUDE_CODE_WEBFETCH_DEADLINE_MS variable. They also removed persistent high CPU usage in long idle sessions.

How do denials work in Claude Code?

Key takeaway

Rules are written in the permissions block of settings.json: deny blocks, ask requests confirmation, and allow permits an action without confirmation. Checks run in this order: deny, then ask, then allow. A rule has the format Tool or Tool(detail), for example Read(./.env).

Claude Code has three lists of rules: deny (always prohibited), ask (ask before taking the action), and allow (permitted without confirmation). When the agent is about to do something, rules are checked strictly in that order. A matching deny cancels any allow for the same action: an allowing rule cannot make an exception to a denial.

Rules are stored in three places: user rules in ~/.claude/settings.json, project rules in .claude/settings.json inside the repository, and local rules in .claude/settings.local.json. Here is an example of settings that deny reading a file with keys:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)",
      "Bash(rm *)"
    ]
  }
}

You can see all active rules and the file each one came from by running /permissions in a session. If a rule blocks reading, the denial names that rule, which will be useful for the check below.

Which errors affected denials and the scope of rules?

The errors affected path and command checks, as well as the scope of rules from different settings files.

First case: symlinks. On macOS, /etc, /tmp, and /var are symlinks to real folders under /private; on Linux, /bin is commonly a similar symlink. If you wrote a deny rule for one spelling of a path but the agent accessed it using another (for example, its real location instead of the symlink), the rule did not trigger. Bash commands also ignored a denial written for a path through a symlink. In 2.1.268, both cases were fixed: the rule now applies regardless of which spelling of the path was used.

Second case: env -C and eval on the same line. The permission check parses a command to figure out which files it will access. It cannot parse constructs such as env -C or eval. The bug was that when such a construct appeared on the line, deny on Read or Edit might not be applied. As a result, the command could bypass the denial on reading or editing a protected file.

Third case: a rule with “!” applied beyond its boundaries. A rule beginning with ! should apply only within the settings file where it is written. Before 2.1.269, it also applied to other settings sources. Now its scope is limited to its own file, and a rule consisting of a bare ! is ignored.

Fourth case: writing through tee. The Bash tee command can write to a file. The write-path check and Edit() deny rules did not apply to that file. At the same time, the Bash(tee:*) permission covered writes outside working directories. In 2.1.269, these checks began to apply to writes through tee: a denial on the file takes effect, and Bash(tee:*) covers only working directories.

If you use targeted denials—to block .env, a couple of service folders, or dangerous commands—you should check those too. The steps are below.

Why could /mcp show your keys?

Key takeaway

Keys in MCP configs are often supplied through ${VAR} variables. The /mcp, /plugin, claude mcp list, and claude mcp get commands, as well as MCP login errors, displayed the substituted values—that is, the keys themselves. In 2.1.268, the output is redacted.

MCP server configs commonly keep keys in environment variables and refer to them through ${VAR} so the file can be stored in a repository without the secrets themselves. The problem was that viewing commands (/mcp, /plugin, claude mcp list, claude mcp get) and MCP login error messages displayed the config with the values already substituted. The key itself appeared on screen.

Another fix concerns plugin and marketplace errors: they could display a token or password from the source Git URL.

If output from /mcp or claude mcp list on an older version appeared on a stream, in a screenshot, in a message to a colleague, or in a log that left your machine, including automatically, assume that the keys from your MCP configs were exposed and rotate them. If the output never left your machine, updating is enough. We have a separate guide to MCP servers and ready-made setups: Claude Code MCP servers.

Other changes in these versions

WebFetch and artifacts. Previously, a WebFetch rule without details in deny or ask also applied to reading and modifying published artifacts. Starting with 2.1.268, it no longer applies to them. To block artifacts, you need a separate Artifact rule or a rule for the domain, such as WebFetch(domain:claude.ai). If you relied on a general WebFetch denial to protect against reading artifacts, recheck your settings: the previous rule no longer blocks these actions.

WebFetch now has a timeout. A request to a server that keeps the connection open and does not finish its response now fails after 300 seconds. Previously, such a request could hang indefinitely. The behavior can be configured with the CLAUDE_CODE_WEBFETCH_DEADLINE_MS variable; setting it to 0 disables the timeout.

Persistent high CPU usage in idle sessions was removed. In long idle sessions, an idle loop in Claude Code could cause persistent high CPU usage. This bug was fixed in 2.1.268. It could also affect people who keep sessions open all day.

Install the latest version. A new bug appeared in 2.1.269: after a session had been running for a long time, Bash Git commands that only read data began asking for permission. It was fixed in 2.1.270. So update to the latest version (as of September 17, 2026, that was 2.1.274); do not stop at 2.1.268 or 2.1.269.

How do you update and check that denials work?

Key takeaway

Check the version with claude --version, update to the latest release, then test using a safe file with no secrets: add a deny rule for it, ask the agent to read the file, and make sure the denial refers to your rule.

  1. Find out your current version. Run claude --version in the terminal. The fixes for the issues described above were released in 2.1.268 and 2.1.269. If the version number is lower than 2.1.268, your version does not include these fixes yet, though whether each issue applies depends on the paths, commands, and rules you use.
  2. Update. The guide how to update Claude Code to the latest version has step-by-step instructions and troubleshooting. Choose the latest version; do not stop at 2.1.268.
  3. Test the denial. Do not test a rule on a real .env: if the denial fails, the agent will see real keys. Create a test file, for example secret-test.txt, containing any text such as “this is a test”; add the rule "Read(./secret-test.txt)" to the deny list in the project’s .claude/settings.json; restart the session; and ask: “read the file secret-test.txt and tell me what is inside.” The expected behavior is a denial that names your rule. If the agent reads the file, the denial did not work in this test. Start by checking the syntax in the section above and open /permissions to see the active rules and the files they were loaded from. The test result alone does not identify the cause. One denial shows that the rule for this file works; it says nothing about your other rules, so check those one at a time in the same way.
  4. Check the symlink case if you use paths like these. If you have denials for /tmp, /etc, or /var on macOS, or /bin on Linux, repeat the test for these paths after updating.
  5. Review rules with “!”. If you used any, check that each one is written in the file where it is meant to apply.

If the denial works but Claude Code still cannot see the files you need, see the separate explanation: why Claude Code cannot see project files.

If you cannot update right now

Sometimes you have to postpone an update: you cannot change the environment, the machine is corporate-owned, or you need to finish the current task. Until you update, you can reduce risk this way:

  • write deny rules for both spellings of the path—the symlink and the real location (on macOS, real paths are under /private);
  • do not combine multiple commands on one line, and avoid env -C and eval in commands you ask the agent to run;
  • do not show anyone the output of /mcp, /plugin, claude mcp list, or claude mcp get, and rotate keys from MCP configs that may have fallen into someone else’s hands;
  • remember that tee writes around deny on Edit—do not consider files protected if Bash is allowed.

These measures reduce the likelihood of specific bypasses, but they do not replace the fix. Update and run the test from the previous section as soon as you can.

Brief summary

Versions 2.1.268 and 2.1.269 fixed four Claude Code permission issues: symlinks, lines with env -C and eval, rules with “!” applying beyond their own file, and writes through tee. Separately, they fixed the exposure of secrets in /mcp output and tokens in plugin errors. Check your version number, update to the latest release, and run the test with a test file from the section above. A denial that names your rule shows that the denial worked in this test. The rule’s presence in settings alone is not enough to verify it.

Sources

To continue working with models, you can go to Ailoot.

More details

Questions about Claude Code 2.1.268: What Was Fixed in deny Rules and MCP

How can I test a deny rule without exposing a real secret?

Create a file containing harmless text, deny Read access to that test file, restart the session, and ask the agent to read it. Expect a refusal naming your rule. Never use a real .env file for a test that might fail.

Does one successful denial prove all my permissions are safe?

No. It verifies only the tested file and access path. Review active rules in /permissions, then test the other paths and cases you rely on, including symlink spellings where relevant.

All articles