TL;DR
- Claude Code の
settings.jsonの permission ルールは、構文が無効だとそのルールが黙ってスキップされることがある。 - スキップされても通常の操作中は気づきにくく、deny を書いたのに効いていない状態になりうる。
- よくある無効パターンは4つ:
-
defaultMode: "ask"("ask"という値は存在しない) -
Bash(cat:*.env*)のように:*を末尾以外で使う -
WebFetch(http://*)(WebFetch は URL ではなくドメイン形式) -
Bash(bash:<(*)のように 括弧が閉じないパターン
-
- 正しい構文は公式ドキュメントに明記されている。検知は対話起動 or
/permissionsが確実(ヘッドレス-pでは表面化しないことがある)。
この記事は社内のセキュリティ勉強会で Claude Code のベースライン設定を作っていて実際に踏んだ内容です。エラーメッセージは筆者環境(執筆時点の Claude Code)で実際に表示されたものを引用しています。挙動の一部は公式ドキュメントに明記が無く、観察ベースである点は本文で都度ことわります。
きっかけ:全社配布する直前に気づいた
Claude Code のセキュリティ設定(settings.json の permissions)を社内標準として配ろうとしていました。秘密ファイルの読取や curl | sh、rm -rf / などを deny で塞ぐ、よくあるやつです。
ところが、いざ実機に置いて claude を起動したら Settings Error / Warning が出ました。よく読むと「一部のルールは無効でスキップされた」と書いてある。つまり書いたつもりの deny が効いていなかったわけです。全社に配る前に気づけて冷や汗ものでした。
前提:permission は deny → ask → allow の3層
まず軽くおさらい。Claude Code の permission は3種類のルールで制御します(公式: Configure permissions)。
| ルール | 効果 |
|---|---|
deny |
そのツール使用を禁止(機械的にブロック) |
ask |
使用のたびに確認 |
allow |
無確認で許可 |
評価順は deny → ask → allow。最初にマッチしたものが勝ち、deny が最優先。
そして重要なのが、permission は「モデル」ではなく「Claude Code 本体」が enforce するという点(公式に明記)。CLAUDE.md の指示は「Claude が何をしようとするか」を変えるだけで、許可・禁止の境界そのものは settings.json 側が決めます。
この deny が無効構文でスキップされると、防御が穴になる——というのが本題です。
ハマった無効構文 4パターンと、正しい書き方
ここからが本編。筆者が実際に踏んだ4つです。正しい書き方は公式のPermission rule syntaxに準拠しています。
① defaultMode: "ask" — そんな値は無い
// ❌ 無効
{ "permissions": { "defaultMode": "ask" } }
実機で出たエラー:
defaultMode: Invalid value. Expected one of:
"acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"
"ask" という mode は存在しません。「未許可の操作は確認する」標準挙動が欲しいなら "default" です。
// ✅ 正しい
{ "permissions": { "defaultMode": "default" } }
有効値は公式のPermission modesの通り:default / acceptEdits / plan / auto / dontAsk / bypassPermissions。
② :* を末尾以外で使っている
// ❌ 無効(:* が途中にある)
"Bash(cat:*.env*)"
"Bash(git:*--no-verify*)"
実機で出たエラー:
Invalid permission rule "Bash(cat:*.env*)" was skipped:
The :* pattern must be at the end. Move :* to the end for prefix matching,
or use * for wildcard matching
公式の記述がそのまま答えです:
The
:*form is only recognized at the end of a pattern. In a pattern likeBash(git:* push), the colon is treated as a literal character and won't match git commands.
:* は「末尾のワイルドカード(前方一致)」の糖衣構文で、末尾でしか効きません。途中にワイルドカードを置きたいなら スペース+* を使います。ワイルドカードは任意位置に置けます(公式: "Wildcards can appear at any position")。
// ✅ 正しい(* を任意位置に)
"Bash(cat *.env*)"
"Bash(git * --no-verify*)"
ちなみに Bash(ls *)(スペースあり)は ls -la にマッチするが lsof にはマッチしない、Bash(ls*)(スペースなし)は両方にマッチ、というスペースの有無で語境界が変わる仕様も公式に書かれています。
③ WebFetch(http://*) — WebFetch は URL 形式が使えない
// ❌ 無効
"WebFetch(http://*)"
実機で出た警告:
Invalid permission rule "WebFetch(http://*)" was skipped:
WebFetch permissions use domain format, not URLs. Use "domain:hostname" format
WebFetch の specifier は ドメイン形式 domain:... のみ(公式の例も WebFetch(domain:example.com) だけ)。http:// のようなスキーム指定はできません。
// ✅ 正しい(ドメイン単位)
"WebFetch(domain:internal.example.com)"
「http だけ deny したい」は WebFetch ルールでは表現できない点に注意。
④ Bash(bash:<(*) — 括弧が閉じていない
プロセス置換 bash <(curl ...) を塞ごうとして書いたもの:
// ❌ 無効
"Bash(bash:<(*)"
実機で出た警告:
Invalid permission rule "Bash(bash:<(*)" was skipped:
Mismatched parentheses. Ensure all opening parentheses have matching closing parentheses
<( の ( が閉じ括弧なしと判定されます。permission パターンで素直に表現できないので、このケースは別レイヤ(CLAUDE.md の指示や PreToolUse hook、curl 系の deny/ask)で対応するのが現実的でした。
一番大事な話:「スキップ」の範囲は?
ここが記事のキモであり、注意が必要な部分です。
筆者の環境では、エラーの種類によって表示が違いました:
-
個別ルールが無効(例
WebFetch(http://*))→ Settings WarningThe values listed above were skipped; the rest of the file is in effect.
(= その無効ルールだけスキップ。残りは有効) -
defaultModeの値が無効など → Settings ErrorFiles with errors are skipped entirely, not just the invalid settings.
(= ファイル全体がスキップされる、と表示)
つまり、
「どんな構文ミスでも全 deny が吹き飛ぶ」わけではない。
個別ルールの無効はその行だけスキップ。ただし構造的な値(defaultMode等)の無効は影響が大きい(筆者環境では「ファイル全体スキップ」と表示)。
⚠️ 公式ドキュメントには、この「スキップの範囲(個別か全体か)」の明記が見当たりませんでした。 上記はあくまで筆者環境のダイアログ表示に基づく観察です。バージョンによって挙動が変わる可能性があるので、自分の環境で必ず確認してください。
いずれにせよ教訓は同じです:
無効な構文を1つでも残すと、少なくともそのルールは黙って無効になる。最悪ファイル全体が無効になりうる。
「deny を書いた=守られている」と思い込まず、実際にロードされているか確認することが必要です。
どうやって検知する?
確実なのは対話起動 or /permissions
-
claudeを対話モードで起動すると、無効ルールがあれば起動時に Settings Error/Warning ダイアログが出ます。これが一番確実でした。 - セッション中は
/permissionsで、現在有効なルールとどの settings.json 由来かを一覧できます(公式機能)。
ヘッドレス -p は当てにしない(観察)
筆者環境では、claude -p(ヘッドレス)だと無効設定があってもエラーが表面化せず、そのまま動いてしまう挙動でした。CI などヘッドレスで使っている場合、設定の妥当性検証には向かない点に注意(これも観察ベース)。
# JSON として壊れていないかの最低限チェック(構文ルール違反は検知できない点に注意)
jq empty ~/.claude/settings.json
jq は JSON の文法エラーは見つけますが、:* の位置や WebFetch 形式のようなClaude Code 固有のルール違反は検知できません。最終確認は対話起動が確実です。
おまけ:公式から拾った「誤解しやすい」トリビア
記事を書くにあたり公式を読み込んで、知っておくと得なものをいくつか。
-
bypassPermissions(いわゆる YOLO)でもrm -rf /やrm -rf ~は止まる。サーキットブレーカーとして残っていると公式に明記。「全部スキップ」と思いがちだが例外がある。 -
Read/Edit の deny は Bash の
cat/head/tail/sedにも適用される。なのでRead(.env)を deny すればcat .envも止まる。Bash(cat *.env*)は部分的に冗長(ただし Python/Node スクリプトが自前で開く読取は防げない)。 -
Bash の引数を絞るパターンは脆い。公式が「
curl http://github.com/ *はオプション順・リダイレクト・変数展開で回避できる」と警告し、ドメイン allowlist や PreToolUse hook を使えと推奨している。 -
Bash(括弧なし)やBash(*)を deny にすると、ツールごと Claude の文脈から消える(Claude はそのツールの存在を見えなくなる)。一方Bash(rm *)のようなスコープ付き deny はツールは残してマッチ時だけ止める。
まとめ
-
settings.jsonの permission ルールは構文が無効だとスキップされる。「書いた deny が効いていない」事故につながる。 - よく踏む無効パターンは
defaultMode:"ask"/:*の位置 /WebFetchの URL 形式 / 括弧不一致 の4つ。正しい書き方は公式のPermission rule syntaxに準拠。 - 「全 deny が無効化される」は言い過ぎ。個別ルールはその行だけスキップ、構造的な値の無効は影響大(範囲は公式に明記が無いので各自確認)。
-
検知は対話起動 or
/permissions。ヘッドレス-pは表面化しないことがある(観察)。 - 設定を配る前に、実機で一度ロードして確認するのが結局いちばん安全。
参考
本記事のエラーメッセージ・挙動は執筆時点の筆者環境での観察を含みます。Claude Code のバージョンにより異なる場合があるため、最新は公式ドキュメントと自身の環境でご確認ください。