Claude Code permissions, sandboxing, and protected paths — reading three official specs to find the real gate that stops an AI rewriting its own settings

AI Claude Code セキュリティ 権限管理 検証

Introduction

I keep several agreements with my personal AI coding assistant. One of them is that “the AI does not rewrite its own settings files (the ruleset of what is permitted and what is refused)”. An agreement is not settled once and left; it needs continual checking that it really holds. This article records reading three official primary sources — on permissions, sandboxing (isolated execution environments), and protected paths — for that check.

Before reading, I thought “using the isolation mechanism called a sandbox must give the hardest protection”. By the end, that prediction was wrong. And both the reason it was wrong and the answer found in its place were the kind of thing you cannot know without actually checking.

The order of evaluation, and one rule that is never consulted

Permission rules are evaluated in the order deny, ask, allow. The official text states:

Claude Code checks file permissions against Edit(path) and Read(path) rules only. If you write a path rule for Write, NotebookEdit, Glob, or the legacy MultiEdit tool instead, Claude Code accepts the rule but never consults it, and warns at startup.

That is, rules against file paths are actually consulted for only two kinds, Edit and Read. Write a path rule against Write and it is accepted as configuration yet never once used in a decision. Reviewing my own settings turned up one such Write path rule. There was no actual harm — an Edit rule covering the same location sat next to it and covered the Write tool’s share too. But the state of “a rule you thought you wrote has never once been evaluated” is one that stays put indefinitely if unnoticed.

There was another trap, in how paths are written.

A pattern like /Users/alice/file isn’t an absolute path. The single leading slash anchors at the settings source, not the filesystem root.

A path with a single leading slash is interpreted not as an absolute path on disk but as relative to “where that settings file lives”. To point at an absolute path on disk you need the form with two slashes. The same string beginning with ”/” means something completely different — which is to say the rule you wrote may be pointing somewhere other than where you aimed it.

A setting I had “removed” had been back for four days

The biggest finding was not a matter of the permissions specification but that an agreement we had supposedly settled in the past had in fact rolled back.

Some time ago I made the decision to “remove the permission to rewrite settings files from the AI’s allow-list”. The record of the decision said “removed”, in the present tense. But cross-checking the actual settings file and its change history told a different story.

[実測] 設定ファイルの変更履歴(要旨)
1本目のコミット: 設定書き換え権限を許可リストから外す(判断どおり)
   ↓ 3時間14分後
2本目のコミット: 別の不具合を切り分けるため、設定ファイルを編集する必要が生じ
                 書き換え権限が許可リストに戻る

以後の変更でも、外れたままにはならず今日まで許可リストに残存

The record of the decision still said “removed” and was never updated, while the actual setting had come back 3 hours and 14 minutes later. Look at only one of the two and you never notice. The record of a decision and the state in which that decision was actually reflected have to be checked separately, or the divergence goes unseen.

What I took for the hardest wall was not even available in my environment

Permission rules have a “shell bypass”. Allow and deny rules for file operations work on commands Claude itself recognises, but cannot close the route by which an arbitrary program opens and writes files internally.

They don’t apply to arbitrary subprocesses that read or write files indirectly, like a Python or Node script that opens files itself. For OS-level enforcement that blocks all processes from accessing a path, enable the sandbox.

Reading that sentence, I took “the mechanism that enforces at OS level = the sandbox” for the real answer. But opening the sandbox specification, its first few lines said:

The sandbox is built into Claude Code and runs on macOS, Linux, and WSL2. Native Windows is not supported. On Windows, run Claude Code inside a WSL2 distribution.

My working environment is a terminal running directly on Windows (a configuration that does not go through WSL, the mechanism for running Linux inside Windows). The mechanism I had taken for “the real answer” was unavailable in my environment before I ever checked. I had decided my next move from a single sentence in the first document and written down “the answer” before opening that mechanism’s own specification — a failure in the order of reading, not a failure of the specification. You have to read a specification to the end before calling it “the answer”.

In place of the unavailable wall, another had been standing all along

After learning the sandbox was unusable, there was a clue elsewhere in that same specification.

The permission system has its own protected paths, which control what Claude Code approves before a tool runs; the sandbox’s list applies to a command that is already running.

Separate from the sandbox, a mechanism called “protected paths” exists independently on the permission system’s side. Being not OS-level isolation, it may work on Windows too. Checking the specification confirmed exactly that.

Writes to a small set of paths are never auto-approved, except in bypassPermissions mode and in planning sessions with bypass permissions available.

permissions.allow rules in settings files do not pre-approve protected-path writes. The safety check runs before Claude Code evaluates allow rules from settings, so an entry such as Edit(.claude/**) in ~/.claude/settings.json or .claude/settings.json does not change the per-mode outcome in the table above.

Writes to protected paths are never auto-approved, whatever the allow-list says. By design this safety check runs before allow rules are evaluated. The directory holding my own settings files was covered by that protection in its entirety.

And even that wall was not, in the end, “a human gate”

The story does not end there. Even if writes to protected paths are “never auto-approved”, the handling varies by operating mode. In the mode I use, it is processed as follows.

Actions matching your allow, ask, or deny rules resolve immediately. Writes to protected paths route to the classifier even when an allow rule matches. … Everything else goes to the classifier.

A write to a protected path is routed not to a confirmation dialog for a human but to the “classifier” (the mechanism by which the AI itself decides on the basis of prior exchanges). The classifier is the model’s own judgement, not a gate at which a person picks yes or no on the spot. And the official text carries a pointed note about the classifier’s judgement.

the classifier blocks matching actions even when the default rules would allow them … Claude’s own judgment that a condition was met does not lift it … a boundary can be lost if context compaction removes the message that stated it. For a hard guarantee, add a deny rule instead.

A boundary conveyed only in conversation — “please don’t touch this” — can be lost along with the old messages when the conversation gets summarised. The one reliably effective means the official documentation names is writing an explicit deny rule. Conveying an agreement in conversation and writing it into a settings file as a deny rule differ in the strength of guarantee.

The route through the documents

start: does the "do not let it rewrite settings files" agreement really hold

      ├─▶ read permissions.md
      │        ├─ confirm the evaluation order (deny→ask→allow)
      │        ├─ discover that Write rules are in fact never consulted
      │        ├─ discover that how a path is written changes where it points
      │        └─ cross-check the past decision against the actual settings file
      │              └─ ⚠️ what was "removed" had come back 3 hours 14 minutes later

      ├─▶ find the sentence that makes the sandbox look like "the real answer"
      │        └─▶ open sandboxing.md and check
      │                 └─ ⛔ native Windows unsupported = unavailable in my environment
      │                       └──(correction, return edge)──▶ withdraw the previous conclusion

      ├─▶ find the clue in the same document that "protected paths are a separate mechanism"
      │        └─▶ open permission-modes.md and check
      │                 └─ ✅ protected paths are OS-independent = they work on Windows too
      │                       └─ but the handling varies by operating mode
      │                             └─ in my mode it goes to the classifier
      │                                   └─ the classifier is not a human gate
      │                                         └─ the reliable means the official docs name = a deny rule

The difference between an “agreement” and a “wall”, learned only by measuring

What remained after the three documents was not a single “this makes it safe” answer but material for stacking walls of differing strength in the right order. A boundary stated in conversation can disappear. An allow-list means nothing at the stage before protected paths. The classifier is the model’s own judgement, not approval by a person. And the means the official documentation names as most reliable is an explicit deny rule written into a settings file.

There is one more takeaway, apart from the documents’ contents: the discipline of “do not decide your next move from a single sentence of a primary source”. At the point I wrote that the sandbox was the answer, I had not yet opened its specification. Do not call something a conclusion until you have finished reading what the citation points to. That is why the answer flipped once here.

Building a mechanism that makes an AI coding assistant “not do it” — from advice to enforcement, and the side-door left open in the gate itself

I set out looking for one strong wall, and what I got was a drawing for lining up walls of differing strength in the right order. Knowing which one is the last sheet works better than making that sheet thicker.

Feel free to send a message

Job offers, project referrals, feedback, questions — anything is welcome. I sincerely hope to connect with people who share high ambitions. I will keep taking on the challenges I have staked my life on. Thank you very much.