無人で回している校閲エージェントには、読んで指摘だけしていてほしい。その「だけ」を保証するために、筆者は権限ルールの積み上げと、セッション前後のハッシュ比較という事後検出を重ねてきた。8月28日リリースの Claude Code に入った --restricted フラグは、この仕組みの一部を起動時の1フラグに畳めるように見える。9項目を決めて手元の v2.1.251 で当ててみたところ、畳めない理由のほうが先に出てきた。
目次
changelog の4点だけを読むと、守りの絵を描き間違える
最初に筆者が読んだのは changelog で、そこから4点を抜き出していた。コマンド・コード実行系ツールと WebFetch を外す。ファイルツールを起動ディレクトリ内に限定する。bypassPermissions を拒否する。settings ファイル類を無視する。
この4点で守りの絵を描くと、外部送信の経路が塞がったように見える。実際には塞がっていない。
claude --help が出す --restricted の説明文は、changelog より踏み込んでいる。原文にはこう書いてある。
Restricted mode: removes the built-in tools that run commands or code
(Bash, PowerShell, REPL and the other code-running tools) and WebFetch
unless --tools names them, and ignores user, project and local settings
files (managed settings and --settings still apply; add --strict-mcp-config
to skip MCP servers too). Also confines the file tools to the working
directories (--add-dir included), refuses bypassPermissions, and lets only
a person or the configured permission handler approve writes to settings,
git and tool-configuration files.
括弧の中に add --strict-mcp-config to skip MCP servers too と書いてある。MCP サーバーは --restricted だけでは止まらない、と読める一文で、changelog の要約からは落ちていた。ここを読み落としたまま「外部送信は塞がった」と結論すると、守りの設計そのものが狂う。
フラグの説明文は、changelog より --help のほうが情報量が多い。
ツール一覧の差分を取る
挙動を測る前に、何が消えるかを一覧で押さえた。同じディレクトリで、フラグの有無だけを変えて、自分に見えているツール名を列挙させる。対照群を取らないと、消えたのがフラグのせいなのか別の条件のせいなのか切り分けられない。
# 対照群(フラグなし)
claude -p "List every tool available to you right now, one name per line." --strict-mcp-config
# 実験群
claude --restricted -p "List every tool available to you right now, one name per line." --strict-mcp-config
28個が22個になった。消えた6個はこれだ。
| 消えたツール | 何を起こす経路か |
|---|---|
Bash |
シェルコマンドの実行 |
WebFetch |
URL の取得 |
Workflow |
複数エージェントの起動 |
CronCreate |
定時ジョブの新規登録 |
Monitor |
バックグラウンドでのコマンド常駐 |
RemoteTrigger |
遠隔の実行トリガ |
面白いのは CronDelete と CronList が残り、CronCreate だけが消えている点だった。TaskStop と TaskOutput が残って Monitor が消えるのも同じ形をしている。新しい実行を起こす側だけを抜いて、止める側と読む側は残す、という線の引き方になっている。
残った22個には Write・Edit・Agent・WebSearch が含まれる。
この4つに加えて、外へ向かう経路がもう2つ残っていた。SendMessage(他のエージェントやセッションへの送信)と PushNotification(通知の送出)だ。どちらもコマンドを走らせないので除外の条件に当たらない。無人の部署に何を持たせるかを設計するとき、消えたツールの一覧だけを見て「送る手段は無くなった」と読むと、この2つを数え落とす。
Agent が残っているのも効く。サブエージェントを起こす経路そのものは生きていて、起こされた側がどのツールを持つかは親と同じ制限に従う。制限は伝播するが、伝播したうえで Write と WebSearch は残る。
9項目の実測結果
環境は Windows 11・Claude Code v2.1.251、測定日は2026年8月31日。測ったのはリポジトリの外に作った使い捨てのディレクトリで、本番の週次ジョブ構成には触れていない。本番のリポジトリで測ると、検証そのものがゲートに引っかかる。
| # | 確かめたこと | 結果 |
|---|---|---|
| 1 | コマンド・コード実行系が外れるか | 外れる。実行時に拒否ではなく、一覧から消える方式。挙動でも確認し、指示したスクリプトは走らず生成物も残らなかった |
| 2 | WebFetch が外れるか | 外れる |
| 3 | WebSearch・MCP の扱い | どちらも残る。MCP(40ツール)は --strict-mcp-config で止まるが、WebSearch はどちらのフラグでも残る |
| 4 | 起動ディレクトリ外の読み取り | 拒否される。絶対パスで指定しても harness の側で止まる |
| 5 | 起動ディレクトリ内の書込 | 書込許可があれば通る(--permission-mode acceptEdits で実測)。読み取り専用ではない |
| 6 | settings 無視の範囲 | deny も消える(local スコープで実測)。user / project も対象だとヘルプにあるが未実測 |
| 7 | 環境変数との等価性 | 等価。CLAUDE_CODE_RESTRICTED=1 はフラグと同一の一覧を返す |
| 8 | 書込範囲ゲートとの併用 | ゲートの代わりにならない。書込許可のあるセッションなら、作業ディレクトリ内の担当外の場所にも書けた |
| 9 | 状態の報告が通るか | 通らない。Bash が無いので desk_status.py を実行できない |
項目4で返ってきた拒否の文面はこうだった。読み取りを断ったのが harness 側だと、応答自体が明かしている。
REFUSED — path is outside the working directory; `--restricted` confines
the file tools to ...
estricted-probe\inside, so the read was blocked
by the harness.
限定の対象として名前が出るのは起動時の作業ディレクトリで、絶対パスで外を指しても届かない。--add-dir で足したディレクトリはこの範囲に含まれるとヘルプに書いてあるので、範囲を広げたければそこから広がる。逆に言えば、--add-dir を無自覚に足すと限定はそのぶん緩む。
項目5と6は、最初の測り方を間違えて測り直している。
-p の非対話モードで書込を指示したら拒否された。ここで「--restricted は書込を止める」と書きかけたが、拒否の理由をよく読むと「書込の許可を求めたが与えられなかった」だった。非対話モードには承認する人がいないので、権限の要る操作は自動で落ちる。フラグの効果ではない。--permission-mode acceptEdits を足して測り直すと、ファイルは普通に作られた。
交絡を1つ残したまま結論を書くところだった。
項目6は、deny ルールを書いた settings.local.json を置いて、その対象ファイルを読ませた。読めた。
ここで止めると項目5と同じ穴に落ちる。「読めた」は「deny が無視された」でも「そもそもルールが当たっていなかった」でも同じ結果になるからだ。フラグを外して同じ読み取りを走らせ、ルールが効くことを先に確かめた。置いたルールは Read(./existing.txt) の1行で、対象はファイル1つ。
--restricted なし: REFUSED read of existing.txt is blocked by permission
settings (the directory is on the deny list).
--restricted あり: OK existing.txt contains: inside-marker
拒否側の文面が「the directory is on the deny list」と言っているのは応答の言葉づかいで、実際に書いたのはファイル単位のルールだ。止まったこと自体は変わらない。
対照側では止まる。同じルール・同じファイルで、フラグを付けたときだけ読める。無視されるのが allow だけなのか deny も含むのかは changelog から読めなかった箇所で、deny も消えることが確かめられた。許可を絞るつもりで書いた deny が効かなくなるので、settings で守りを作っている構成ほど影響が大きい。
測り方をそのまま置いておく
同じことを自分の環境で確かめたい人のために、実際に使った形を残す。使い捨てのディレクトリを1つ作り、その中と外にファイルを1つずつ置くだけで始められる。
# 準備: inside を作業ディレクトリにし、outside は範囲外の対照に使う
mkdir -p probe/inside probe/outside
echo "outside-marker" > probe/outside/outside.txt
echo "inside-marker" > probe/inside/existing.txt
# 項目6用: local settings に deny を書いておく(無視されるなら existing.txt が読める)
mkdir -p probe/inside/.claude
cat > probe/inside/.claude/settings.local.json <<'JSON'
{ "permissions": { "deny": ["Read(./existing.txt)"] } }
JSON
項目4と6は1回の指示でまとめて測れる。範囲外の絶対パスを読ませ、deny を書いた対象を読ませる。前者が拒否され、後者が読めれば、限定は効いていて settings は無視されている。
cd probe/inside
# 対照群を先に取る。deny が効くことを確かめないと、項目6は判定できない
claude --strict-mcp-config -p "Read existing.txt and quote it. Reply OK or REFUSED." # → REFUSED になるはず
# 実験群
claude --restricted --strict-mcp-config -p "1. Read ../outside/outside.txt 2. Read existing.txt Report each as OK or REFUSED."
項目5と8は、書込の許可を先に与えてから測る。ここを省くと非対話モードの権限拒否と区別が付かない。担当外のディレクトリを1つ混ぜておくと、範囲の広さがそのまま出る。
mkdir -p news/drafts logs
claude --restricted --strict-mcp-config --permission-mode acceptEdits -p "Create news/drafts/mine.html with 'mine' and logs/stray.log with 'stray'."
# モデルの自己申告ではなくファイルの有無で判定する
ls -l news/drafts/mine.html logs/stray.log
最後の ls が要る。応答が DONE と言っていてもファイルが無いことがあるし、REFUSED と言いながら書けていることもある。判定はディスクを見て決める。筆者は項目9で、スクリプトを実行させたつもりの応答に対して生成物の有無を確かめ、実際には走っていないことを確認した。
測定そのものは10分もかからない。
予想が外れた2点
下書きの段階で筆者が作っていた比較表には、「リポジトリ内への想定外の書込」を --restricted 側で「残る可能性(未実測)」と書いていた。可能性ではなかった。
作業ディレクトリの中に news/drafts/・news/review/・logs/ を模した構造を作り、記者役の指示で3箇所へ書かせてみた。自分の担当である news/drafts/ に書けたのは想定どおりで、どの部署も触るはずのない logs/ にもそのまま書けた。ファイルは実際に生成されている。
もう1つの外れは MCP だった。--strict-mcp-config を外して同じ一覧を取ると、mcp__ で始まるツールが40個ぶら下がってくる。中身には Gmail の送信・転送・下書き作成が含まれていた。Bash と WebFetch を抜いて外部送信の脚を断ったつもりでも、メールで送れる経路が残っている。
筆者の環境でぶら下がっていた40個の内訳は、メール・カレンダー・ドライブ・ブラウザ操作・ノート閲覧といった外部サービス側のものだった。どれも --restricted の説明文が言う「コマンドやコードを走らせる組み込みツール」には当たらないので、除外の対象になっていない。理屈は通っているし、ヘルプにも --strict-mcp-config を足せと書いてある。読み落とすと守りが1枚足りなくなるだけだ。
自分の環境で何が残るかは、フラグの有無で一覧を取って数えれば分かる。
# --restricted だけのとき、MCP が何個残るか
claude --restricted -p "List every tool available to you, one name per line." | grep -c '^mcp__'
# --strict-mcp-config を足すと 0 になるか
claude --restricted --strict-mcp-config -p "List every tool available to you, one name per line." | grep -c '^mcp__'
接続している MCP サーバーが多い環境ほど、この数は大きくなる。残った中に外へ出せるものが1つでもあれば、外部送信の脚は生きている。
穴を1つ塞いで安心すると、隣に開いたままの穴を数え忘れる。
| 経路 | 書込範囲ゲート(事後検出) | –restricted(事前制限・実測) |
|---|---|---|
| 起動ディレクトリの外への書込 | リポジトリの外は見ていない | 入口で拒否 |
| リポジトリ内への想定外の書込 | 検出する(事後) | 通る(書込許可があるとき・実測) |
| WebFetch による外部取得 | 関知しない | ツールごと外れる |
| MCP 経由の外部送信 | 関知しない | 残る。--strict-mcp-config の併用で閉じる |
WebSearch・SendMessage・PushNotification 経由の外部送信 |
関知しない | 残る。どちらのフラグでも閉じない |
| コマンド実行を経由した書込・外部送信 | 書込なら事後検出 | ツールごと外れる |
| 権限の緩め直し | 関知しない | bypassPermissions を拒否・settings を無視 |
重なるのはリポジトリ内の書込だけで、そこはフラグ側が素通しする。フラグを入れてもゲートは外せない。
無人編集部に当てると、報告の経路が切れる
筆者の無人AI編集部は部署ごとのサブエージェントで動いている(構成の制約はサブエージェントの入れ子深さを測った回で書いた)。部署セッションに渡しているコマンド実行の許可は3本しかない。desk_status.py(自分の状態を working / done で書く)、desk_office.py(画面を更新する)、git status の3つだ。Bash(python:*) のような広い許可は意図して書いていない。リポジトリ内の公開系スクリプトまで一緒に通るからで、絞り込んだ結果がこの3本になっている。
項目9で測ったのは、この3本がフラグの下で通るかどうかだった。通らない。
--restricted を当てた部署は、読んで指摘することはできても、自分が working であることも done であることも書けない。実行の完了判定は、親プロセスが status.json を読んで全部署が done になっているかで出している。報告できない部署が1つでもあれば、その週の実行は complete にならない。
読み取り専用にしたいという要求と、パイプラインの部品として状態を返せという要求が、同じフラグの上で正面から当たる。
縛れるのは工程であって、部署まるごとではない。
適用の設計はこう分ける。読んで指摘する工程(原稿の通読・表記の照合)に --restricted を当て、状態の報告は親側へ寄せる。書込範囲の検査を部署にやらせず親が持っているのと同じ理屈で、自分の状態を自分で書く経路は部署の外にあってよい。フラグを当てられるかどうかの前に、報告の持ち主を決め直す話になる。
実際に当てるときの起動の形
# 校閲工程だけに当てる。MCP も止めるなら --strict-mcp-config を必ず足す。
# ただしこの2つを足しても WebSearch / SendMessage / PushNotification は残る。
claude --restricted --strict-mcp-config -p "/editorial-run 2026-W36 proof"
# 環境変数でも同じモードに入る(実測で一覧が一致することを確認済み)
CLAUDE_CODE_RESTRICTED=1 claude --strict-mcp-config
# Windows の PowerShell から環境変数で入れる形
$env:CLAUDE_CODE_RESTRICTED = "1"
claude --strict-mcp-config
環境変数は --help に載っていない。載っていないのに、フラグと同一のツール一覧を返す。無人ジョブの起動スクリプトに書くなら使えるが、文書化されていない入口に依存する形になるので、筆者はフラグ側を使う。
隔離をうたう機能を発表文のまま信じない、というのは隔離の抜け穴を自分の環境で試した回からの持ち越しだ。権限まわりが表記どおりに動くとは限らないことも、引用符付きパスで権限チェックを検証した回で一度見ている。9項目を読んで終わらせず挙動で確かめたのは、この2回があったからだ。今回もそれで2つ拾えた。ただし項目6の settings は local スコープしか置いていないので、user と project はヘルプの記述を読んだだけになっている。ここだけは「動くはず」が残った。
一次ソース
- Claude Code changelog(公式) —
--restricted追加の項 claude --helpの--restricted説明文(v2.1.251 で取得)— changelog より記述が詳しい
関連記事
まとめ
筆者は校閲工程に --restricted を当てる。ただし --strict-mcp-config とセットで、状態の報告は親側へ移し、書込範囲ゲートはそのまま残す。フラグ単体で置き換えられるものは1つもなかった。
測って分かったのは、このフラグが「入口の数を減らす」ものであって「範囲を守る」ものではない、という性質だった。作業ディレクトリの中では担当外の場所にも書けるし、MCP を止めるには別のフラグが要る。減った入口の数を守りの強さと読み替えると、その差のぶんだけ危ない。
まだ測っていないこと。--tools で名指しすれば Bash が戻るとヘルプに書いてあるが、戻したときにディレクトリの限定が保たれるかは確かめていない。managed settings と --settings が生き残る範囲も、手元に managed settings を置いていないので測れていない。settings 無視の範囲も local スコープだけで、user と project は未実測だ。この3つは、本番の週次ジョブへ入れる前にもう1周やる。