0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

事故ゼロは守れている証拠にならない——設定名を1文字間違えると警告0行

0
Last updated at Posted at 2026-07-27

うちのClaude Codeは、私が寝ている間も勝手に動いている。危ない操作を実行の手前で止める設定を、自分でも900本以上作って無料で配っている。

その私の環境で、登録してある25本のうち7本が、狙った場面では一度も呼ばれない状態だった。

設定ファイルには、ちゃんと書いてあった。書いた文字も合っている。それでも呼ばれていなかった。

気づいたのは私ではない。うちのCCが、勝手に数えて報告してきた。私は何も頼んでいない。頼んでいなければ、たぶん今も気づいていない。

安全の設定というのは、効いている時も効いていない時も、画面が同じなのだ。

1文字で消える

いちばん単純な形から見せたい。

{
  "permissions": {
    "denyy": ["Bash(curl:*)"]
  }
}

denydenyy と書いてしまった。y が1つ多いだけ。

起動しても、何も起きない。警告も、エラーも、注意書きも出ない。普通に起動して、普通に動く。そして curl は止まらない。

書いた本人の頭の中では、この環境は curl を止める。実際には止めない。その差に気づく手がかりが、画面のどこにも出ない。

そこからCCは、勝手に3つの層で同じことを試していた。どの層でも、正しい綴りと1文字違いを並べている。片方だけだと「そもそも設定が効かない環境だったのでは」という疑いが残るから、らしい。

環境は Claude Code v2.1.220。本番の設定には触らず、一時ファイルを渡して起動している。

1. 権限の拒否リスト

正しい綴りのほう。

{ "permissions": { "deny": ["Bash(curl:*)"] } }

curl を実行させると、こうなった。

実行できませんでした。curl -s -o /dev/null -w '%{http_code}' https://example.com は、
このセッションの権限設定により Bash での実行が拒否されました。

止まる。設定は効いている。

次に denydenyy にする。ついでに、存在しない鍵も混ぜてみた。

{
  "thisKeyDoesNotExistAtAll": true,
  "permissions": { "denyy": ["Bash(curl:*)"] }
}

同じことをさせた結果。

- 終了ステータス: 0(curl は正常終了)
- HTTP ステータスコード: 200

通った。存在しない鍵についても、綴りを間違えた鍵についても、警告は1行も出ていない。

2. hook のイベント名

hook は、登録するイベント名が決まっている。全部の Bash コマンドを止める hook を用意して、正しい名前で登録したものがこれだ。

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "echo BLOCKED >&2; exit 2" }] }
    ]
  }
}

echo hello-from-bash を実行させる。

コマンドは実行できませんでした。PreToolUse フック(Bash 用)が BLOCKED を出力して
exit 2 で終了し、echo hello-from-bash の実行そのものがブロックされています。

止まる。hook 自体はちゃんと動いている。

ここで PreToolUsePreToolUsee にした。e が1つ多い。hook の中身は1文字も変えていない。

hello-from-bash

通った。hook は一度も呼ばれていない。警告も出ない。

同じ hook が、登録先の名前の1文字だけで、止める側から何もしない側へ移った。私の環境で7本が空振りしていたのも、これと同じ形だった。

3. サンドボックスの安全弁

3つ目はいちばん新しい設定だ。v2.1.219(日本時間で7月25日の公開)で、サンドボックスに network.strictAllowlist が入った。許可リストに無い宛先を、確認を出さずに拒否する設定らしい。

同じ場所に failIfUnavailable がある。サンドボックスが起動できない時に、警告して続けるのではなく、起動そのものを止める設定だ。

こちらは新しくない。CCに変更履歴を当たらせたら、v2.1.83からあった。ずっと前から用意されていたものを、私が知らずに使っていなかっただけだ。

うちの環境にはサンドボックスが必要とするものが入っていない。つまり「起動できない状況」が実在する状態で試せる。

{ "sandbox": { "enabled": true, "failIfUnavailable": true,
  "network": { "allowedDomains": ["example.com"], "strictAllowlist": true } } }
Error: sandbox required but unavailable: sandbox is enabled but dependencies are
missing: bubblewrap (bwrap) not installed, socat not installed
  sandbox.failIfUnavailable is set — refusing to start without a working sandbox.

終了コードは1。正しい振る舞いだと思う。壁が無いなら、壁があるつもりで走らせない。

そして failIfUnavailablefailIfUnavailble にする。a が1つ足りない。

⚠ Sandbox disabled: sandbox is enabled but dependencies are missing: bubblewrap
(bwrap) not installed, socat not installed
  Commands will run WITHOUT sandboxing. Network and filesystem restrictions will
  NOT be enforced.

OK

終了コードは0。起動して、動いた。許可リストに example.com しか入れていないのに、許可外の宛先へも普通に繋がる。

Claude Code の名誉のために

サンドボックスについては、Claude Code はちゃんと警告を出している。上の ⚠ Sandbox disabled: がそれだ。黙ってはいない。

公式の文書にも書いてあった。

【CC補足】既定の挙動は仕様どおり
公式ドキュメントには、依存が足りない時やその環境が対象外の時、Claude Code は警告を出したうえでサンドボックス無しでコマンドを実行する、と明記されている。これを「起動そのものを止める」に変えたい時に sandbox.failIfUnavailabletrue にする、という設計だ。原文は次のとおりで、不具合ではない。
By default, if the sandbox cannot start because dependencies are missing or the platform is unsupported, Claude Code shows a warning and runs commands without sandboxing. To make this a hard failure instead, set sandbox.failIfUnavailable to true.

問題は2つある。ひとつは、この警告が開始時に1回だけ出ること。私みたいに無人で回していると、この行を読む人がいない。

もうひとつは、綴りを間違えた鍵については、この警告すら出ないこと。3つの層で共通していたのはそこだった。

なぜ気づけないのか

安全の設定には、共通の性質がある。正しく効いている時、何も起きない。

curl を止める設定が効いていれば、curl は止まる。だが curl を実行する場面がなければ、効いていても効いていなくても、画面はまったく同じだ。何も起きないのが正常なので、何も起きないことを異常だと思わない。

hook も同じ。データを消すコマンドを止める hook は、そのコマンドが来るまで黙っている。その沈黙が「見張っている沈黙」なのか「呼ばれていない沈黙」なのかは、外から区別できない。

区別がつくのは事故が起きた時で、その時にはもう遅い。

設定ファイルを読み返しても分からない

claude doctor を走らせてみた。出るのはこういう情報だった。

Running: npm-global (2.1.220)
Platform: linux-x64
Config install method: global
Auto-updates: disabled (config)
...
No installation issues found.

導入の健全性は見ているが、設定ファイルの綴りは見ていない。実効的な設定を一覧で出すコマンドも、CCが探した限りでは今の版に見当たらなかった。

そして設定ファイルを読み返しても、自分が書いた文字がそこにあるだけだ。denyy は、書いた本人の目には deny に見える。

ここで少し面白いことがあった。その設定名が本当に存在するのかを本体で確かめる段になって、CCが最初「見つからない」と言い出したのだ。

【CC補足】数え方を2回間違えた
設定名の実在を確かめるために、導入済みのバイナリを文字列として走査した。1回目は拡張子で対象を絞ったため0件になった(配布物は単一のバイナリなので、絞った時点で対象が消えていた)。2回目は引用符つきの形で数えたため failIfUnavailablestrictAllowlist が0件に見えた。おかしいと思って、実在が確実な defaultModeautoUpdates で同じ数え方をしたら、そちらでも同じ差が出た。つまり対象ではなく数え方の側が壊れていた。引用符を外して数え直すと、両方とも実在した(12件と7件)。壊れているものを見つけたと思ったら、先に自分の物差しを疑ったほうがいい。

確かめる側も、こうやって普通に間違える。だから最後は、実際に動かして見るしかない。

確かめる方法は1つしかない

わざと引っかけて、止まることを見る。

拒否の規則なら、その規則が止めるはずのコマンドを、安全な形で実行させる。

Run: curl -s -o /dev/null -w '%{http_code}' https://example.com

止まれば効いている。通れば効いていない。

hook なら、その hook が反応する操作をさせて、標準エラーにメッセージが出るかを見る。出なければ、イベント名か照合子のどちらかが違う。

サンドボックスなら、まず必要なものが入っているかを確認する。

command -v bwrap socat

何も出なければ、sandbox.enabledtrue にしていても、いまサンドボックスは動いていない。

結局こうした

ここまで全部、私は見ていただけだ。そしてCCは、報告して終わりにしなかった。設定名の綴り違いを見つける検査を自分で書いて、配っている診断コマンドに入れていた。

毎回手で確かめるのは無理だから、というのがその理由だった。私もそう思う。

【CC補足】どう作ったか
既知の設定名から「編集距離」(1文字の追加・削除・置換を何回で辿り着けるか)が1以内、長い名前なら2以内のものだけを typo として報告する。どの既知の名前からも遠い未知の鍵は黙って通す。それはこの版がまだ知らない新しい設定である可能性が高く、そこで騒ぐと利用者が診断そのものを読まなくなるからだ。見る範囲は設定の最上位、permissions の下、hooks のイベント名、sandbox とその network の下。env の中は利用者が名前を決める場所なので見ない。回帰試験を12件付け、修正前のコードでは6件が落ちることも確認した。

これで denyyPreToolUseefailIfUnavailble も、名前を挙げて指摘される。

持ち帰ってほしいこと

設定ファイルは、書いた通りに解釈されるとは限らない。知らない鍵は、たいてい黙って捨てられる。行儀のいい設計だと思う。ただ、安全の設定に限っては、捨てられたことが分からないと困る。

だから、安全に関わる設定を書いた日は、1回だけ余分なことをしてほしい。書いた設定が止めるはずのものを、自分で1回やってみる。30秒で終わる。

その30秒が、「設定した」と「守られている」の間にある溝を埋める唯一の方法だ。私は900本配っている側で、それをやっていなかった。


この記事の検証も、綴り違いを見つける検査も、うちのClaude Codeが自分でやったものだ。検査は無料で公開しているので、自分の環境を確かめたい人はどうぞ。

設定した気になっていて実際には何も止まっていなかった、という同じ型の事故は、これ以外にもたくさん踏んだ。800時間ぶんの記録から、症状から原因と直し方へ引ける形にまとめた本がある。第3章まで無料で読める。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?