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?

Claude Code パーミッションシステム詳細解説

0
Posted at

パーミッションの仕組みが全くわからなかったので調査しました。
以下調査結果です。

※Claude Code v2.1.44 のバイナリをリバースエンジニアリングして得られた調査結果に基づく。


目次

  1. パーミッションモード
  2. 設定ファイルの構造
  3. ツール別のパーミッション判定
  4. Bashコマンドの判定フロー(重要)
  5. ファイル操作ツールの判定フロー
  6. MCPツールの判定フロー
  7. Allowパターンの書式とマッチング
  8. よくある誤解と落とし穴
  9. 実践的なパターン設計ガイド

1. パーミッションモード

Claude Code には5つのパーミッションモードがある。

モード 説明 Bash Edit/Write Read/Glob
default 標準動作。危険な操作時に確認を求める 確認あり 確認あり cwd内は自動許可
acceptEdits ファイル編集を自動承認 確認あり 自動許可 cwd内は自動許可
plan 分析のみモード。実際のツール実行なし 実行不可 実行不可 実行不可
bypassPermissions すべての権限チェックをバイパス 自動許可 自動許可 自動許可
dontAsk 確認プロンプトを出さず、未承認は拒否 拒否 拒否 拒否

設定方法

# 起動時オプション
claude --permission-mode acceptEdits

# settings.json で設定
{
  "permissions": {
    "defaultMode": "acceptEdits"
  }
}

注意事項

  • bypassPermissionsallowDangerouslySkipPermissions が有効な場合のみ使用可能
  • 組織ポリシー(Statsig gate tengu_disable_bypass_permissions_mode)や設定(disableBypassPermissionsMode: "disable")で無効化可能
  • acceptEdits はファイル編集のみを自動許可する。Bashコマンドには効果がない

2. 設定ファイルの構造

設定ファイルの場所と優先順位

パーミッションルールは複数ソースから読み込まれ、以下の順序で評価される:

ソース 説明
policySettings 組織ポリシー(読み取り専用)
flagSettings フィーチャーフラグ(読み取り専用)
userSettings ユーザー設定(~/.claude/settings.json
projectSettings プロジェクト設定(.claude/settings.json
localSettings ローカル設定(.claude/settings.local.json
cliArg CLI引数
command コマンドライン(読み取り専用)
session セッション中に追加された一時ルール

settings.json の基本構造

{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(git *)",
      "Read(/home/user/**)",
      "Edit(/home/user/**)",
      "mcp__serena__*"
    ],
    "deny": [
      "Bash(rm -rf /*)"
    ]
  }
}

3. ツール別のパーミッション判定

Claude Code のツールは、パーミッション判定の仕組みがツールの種類によって大きく異なる

ツール名の内部マッピング

表示名 内部名 パーミッション判定方式
Bash Bash コマンドパターンマッチング
Edit Edit パスベース
Write Write パスベース
Read Read パスベース
Search(UI表示)/ Glob(内部名) Glob パスベース
Grep Grep パスベース
MCPツール mcp__{server}__{tool} ツール名マッチング

4. Bashコマンドの判定フロー(重要)

Bashコマンドのパーミッション判定は最も複雑で、多段階の処理を経る。

全体フロー(xgA 関数)

入力コマンド
    │
    ▼
[1] 構文パース(fM関数)── 失敗 → ask
    │
    ▼
[2] サンドボックスチェック(有効時)── サンドボックス内 → allow
    │
    ▼
[3] 完全一致チェック(nS$関数)── deny/ask/allow → 即返却
    │                              passthrough → 続行
    ▼
[4] プロンプトルール評価(AI判定)
    │
    ▼
[5] ヒアドキュメント展開(YHB関数)
    │
    ▼
[6] コマンドインジェクションチェック(tb関数)
    │
    ▼
[7] コマンド分割(nJ関数)★超重要★
    │    ↓
    │  「&&」「||」「;」「|」で分割
    │  リダイレクション(2>/dev/null等)を除去
    │    ↓
    │  結果: 個別コマンドの配列
    │
    ▼
[8] cd コマンドのカウント(複数cd → ask)
    │
    ▼
[9] 各サブコマンドを個別に判定(mHB関数) ★超重要★
    │
    ▼
[10] パスセキュリティチェック(KN$関数)
    │
    ▼
[11] 最終判定

コマンド分割の詳細(nJ 関数)

これが最も重要な発見事項。 Bashコマンドは、Allowパターンとのマッチングに分割・加工される。

分割の例

# 入力コマンド
ls /home 2>/dev/null && ls /tmp 2>/dev/null

# nJ関数による処理後
["ls /home", "ls /tmp"]
#  ↑ 「&&」で分割され、「2>/dev/null」は除去される
# 入力コマンド
find /home -name "*.php" 2>/dev/null | head -20

# nJ関数による処理後
["find /home -name \"*.php\"", "head -20"]
#  ↑ 「|」で分割され、「2>/dev/null」は除去される

分割ルール

演算子 動作
&& コマンド境界として分割
|| コマンド境界として分割
; コマンド境界として分割
| コマンド境界として分割
2>/dev/null 除去される
2>&1 除去される
> /dev/null 除去される

サブコマンドの個別判定(mHB 関数)

分割された各サブコマンドは mHB 関数で個別に判定される。

サブコマンド(例: "ls /home")
    │
    ▼
[1] 完全一致チェック(nS$) ── deny/ask → 即返却
    │
    ▼
[2] プレフィックスマッチング(tgA, "prefix"モード)
    │   deny/ask → 即返却
    │
    ▼
[3] ★パスセキュリティチェック(KN$)★ ── ask/deny → 即返却
    │   ※ Allowルールより先に評価される!
    │
    ▼
[4] 完全一致のallow判定
    │
    ▼
[5] プレフィックスのallow判定
    │
    ▼
[6] 危険コマンドチェック(lvD)── rm等の危険操作
    │
    ▼
[7] ファイル書き込みチェック(wHB)
    │
    ▼
[8] 読み取り専用コマンド判定 ── 読み取りのみ → allow
    │
    ▼
[9] passthrough(最終的にaskへ)

重大な発見: KN$(パスセキュリティチェック)はAllowルールより先に評価される。
つまり、cwd外のパスにアクセスするコマンドは、Allowパターンに一致していてもブロックされる可能性がある。

パターンマッチング関数(rgA 関数)

// パターンマッチング前の前処理
let commandWithoutRedirections = vZ(command).commandWithoutRedirections;
// ↑ リダイレクションを除去

let normalizedCommand = kyA(commandWithoutRedirections);
// ↑ timeout, time, nice, nohup, 環境変数プレフィックスを除去

パターンの3つのタイプ(ogA 関数でパース):

タイプ パターン例 マッチング方式
exact Bash(ls) 完全一致のみ
prefix Bash(git:*) ※旧形式 前方一致(startsWith
wildcard Bash(git *) グロブマッチング(agA関数)

ワイルドカード *任意の文字列にマッチする(スペース含む)が、マッチング対象は分割後・リダイレクション除去後のコマンドである点に注意。


5. ファイル操作ツールの判定フロー

Read, Edit, Write, Glob(Search), Grep はすべてパスベースの判定を使用する。

判定フロー(Eo 関数 - 読み取り系、t1H 関数 - 編集系)

ファイルパス
    │
    ▼
[1] UNCパスチェック(\\\\、//で始まるパス → ask)
    │
    ▼
[2] Windowsパス不正パターンチェック
    │
    ▼
[3] deny ルール評価(eQ関数)
    │
    ▼
[4] ask ルール評価(eQ関数)
    │
    ▼
[5] モードチェック(acceptEditsモード等)
    │
    ▼
[6] ワーキングディレクトリチェック(ax関数)
    │   cwd内 → allow(defaultモード)
    │
    ▼
[7] allow ルール評価(eQ関数)
    │
    ▼
[8] ask(デフォルト)

パスルールのマッチング(eQ 関数)

eQ 関数は ignore npmパッケージ(gitignoreと同じ形式)を使用してパスマッチングを行う。

// 相対パスを計算
let relativePath = path.relative(baseDir, targetPath);

// 「../」で始まる場合はスキップ(マッチしない)
if (relativePath.startsWith(`..${separator}`)) continue;

// ignoreパッケージでマッチング
let result = ignore().add(patterns).test(relativePath);

重要: パスがベースディレクトリの外にある場合(相対パスが ../ で始まる場合)、ルールはスキップされる

パスパターンの書式

{
  "allow": [
    "Read(/home/user/**)",     // /home/user/ 以下すべて
    "Read(~/**)",              // ホームディレクトリ以下すべて
    "Edit(/tmp/**)",           // /tmp/ 以下すべて
    "Write(./.claude/**)"      // プロジェクト内 .claude/ 以下
  ]
}

Glob/Search ツールの特殊性

UI上は「Search」と表示されるが、内部名は「Glob」。パーミッション判定は Read と同じ Eo 関数を使用する。

Glob/Search ツール
    │
    ▼
  getPath() でパスを取得
    │
    ▼
  Eo() で Read と同じパス判定

そのため、Search(**)Glob(**) のようなパターンはパラメータベースではなくパスベースで判定される。
ただし Glob(**) のようなワイルドカード記法がうまく機能するかはパスの解決方法に依存する。


6. MCPツールの判定フロー

MCP(Model Context Protocol)ツールは独自のシンプルな判定フローを使用する(yHB 関数)。

MCPツール呼び出し
    │
    ▼
[1] サーバー名・ツール名の文字種チェック(英数字、ハイフン、アンダースコアのみ)
    │
    ▼
[2] 内部名を構築: mcp__{server}__{tool}
    │
    ▼
[3] deny ルール評価 → deny
    │
    ▼
[4] ask ルール評価 → ask
    │
    ▼
[5] allow ルール評価 → allow
    │
    ▼
[6] デフォルト → ask

MCPツールのパターン例

{
  "allow": [
    "mcp__serena__*",           // serenaサーバーの全ツール
    "mcp__playwright__*",       // playwrightサーバーの全ツール
    "mcp__gdrive__read_file"    // gdriveサーバーのread_fileのみ
  ]
}

7. Allowパターンの書式とマッチング

基本書式

ToolName(specifier)
要素 説明
ToolName ツール名 Bash, Read, Edit, Write, Glob, Grep
specifier マッチング対象 コマンド文字列 or パス

パターンタイプ

タイプ 書式 マッチ対象
完全一致 Bash(ls) ls のみ 完全に同一のコマンド
プレフィックス(旧形式) Bash(git:*) gitで始まるコマンド : の後が *
ワイルドカード Bash(git *) git status, git diff * は任意の文字列

ワイルドカード * の動作

* はグロブスタイルで任意の文字列(スペース含む)にマッチする。

パターン: Bash(git *)
マッチ:   git status           ✅
マッチ:   git diff --cached    ✅
不一致:   ls                   ❌

ただし、Bashの場合、マッチング対象は分割後・リダイレクション除去後のサブコマンド。


8. よくある誤解と落とし穴

誤解1: 複合コマンドはそのままマッチングされる

実際の動作: &&, ||, ;, | で分割され、各部分が個別にマッチングされる。

❌ 誤った想定:
パターン: Bash(ls * && ls *)
コマンド: ls /home && ls /tmp
→ パターン全体でマッチングされる

✅ 実際の動作:
コマンド: ls /home && ls /tmp
→ 分割: ["ls /home", "ls /tmp"]
→ "ls /home" を Bash(ls *) でマッチ → ✅
→ "ls /tmp" を Bash(ls *) でマッチ → ✅
→ 結果: 両方マッチすれば allow

※ Bash(ls * && ls *) というパターンは永遠にマッチしない

誤解2: リダイレクション付きパターンが必要

実際の動作: リダイレクション(2>/dev/null, 2>&1, > file 等)はマッチング前に除去される。

❌ 不要なパターン:
Bash(find * 2>/dev/null)
Bash(ls * 2>/dev/null && ls * 2>/dev/null)

✅ 必要なパターン:
Bash(find *)   ← これだけで十分
Bash(ls *)     ← これだけで十分

※ リダイレクション付きパターンは害はないが、マッチすることもない

誤解3: for/if等の複合構文のパターン

実際の動作: forifwhile 等のシェル構文はシェルパーサー(qYH 関数)によって1つの構文単位として認識され、内部の ; ではトップレベルの分割は行われない。ただし、以下の不確実性がある。

不確実な点: glob の *; にマッチするかは実装依存。

パターン: Bash(for * in *; do *; done)
コマンド: for f in *.txt; do echo $f; cat $f; done

もし * が ; にマッチするなら:
  → * が "echo $f; cat $f" にマッチ → ✅(このパターンだけで十分)

もし * が ; にマッチしないなら:
  → マッチしない → ❌
  → Bash(for * in *; do *; *; done) が必要

安全策: *; にマッチしない場合に備え、本体の ; の数が異なるバリエーションパターンを追加しておく。

"Bash(for * in *; do *; done)",
"Bash(for * in *; do *; *; done)",
"Bash(for * in *; do *; *; *; done)",
"Bash(for * in *; do *; *; *; *; done)"

なお、構文の外側done の後)に && がある場合は通常のトップレベル分割が適用されるため、Bash(for * in *; do *; done && *) は無効。

誤解4: Glob/Search はパラメータベース

実際の動作: Glob/Search のパーミッションは Read と同じパスベースの判定を使用する。

❌ 効果が不明確:
Search(**)
Glob(*, *)

✅ 実際に効くのはパスベースルール:
Read(/home/user/**)   ← これがGlob/Searchにも適用される

誤解5: Allow に書けばすべて許可される

実際の動作: Bash の mHB 関数では、KN$(パスセキュリティチェック)が Allow ルールより先に評価される。

判定順序:
1. deny ルール
2. ask ルール
3. ★ KN$(パスセキュリティチェック)★  ← ここでブロックされる可能性
4. allow ルール(完全一致)
5. allow ルール(プレフィックス/ワイルドカード)

つまり:
Bash(find *) を Allow に設定しても、
find /etc -name "*.conf" のようにcwd外のパスにアクセスすると
KN$ でブロックされる可能性がある

9. 実践的なパターン設計ガイド

基本原則

  1. Bashパターンはシンプルなコマンド単位で書く(複合コマンドのパターンは不要)
  2. リダイレクションは書かない(自動除去される)
  3. パスアクセスにはRead/Editのパスルールを使う
  4. MCPツールはサーバー単位のワイルドカードが便利

推奨パターン集

{
  "permissions": {
    "allow": [
      // === Bash: シンプルなコマンド単位 ===
      "Bash(git *)",
      "Bash(ls *)",
      "Bash(cat *)",
      "Bash(find *)",
      "Bash(grep *)",
      "Bash(curl *)",
      "Bash(echo *)",
      "Bash(mkdir *)",
      "Bash(cp *)",
      "Bash(mv *)",
      "Bash(head *)",
      "Bash(tail *)",
      "Bash(wc *)",
      "Bash(jq *)",
      "Bash(sed *)",

      // === ファイル操作: パスベース ===
      "Read(/home/user/**)",
      "Read(/tmp/**)",
      "Edit(/home/user/projects/**)",
      "Write(/tmp/**)",

      // === MCPツール: サーバー単位 ===
      "mcp__serena__*",
      "mcp__playwright__*"
    ],
    "deny": [
      "Bash(rm -rf /*)",
      "Bash(rm -rf ~/*)"
    ]
  }
}

不要なパターンの例(追加しても意味がない)

{
  "allow": [
    //  複合コマンドパターン(分割されるため無意味)
    "Bash(ls * && ls *)",
    "Bash(find * | head *)",
    "Bash(cat * > /tmp/* && echo *)",

    //  リダイレクション付き(除去されるため無意味)
    "Bash(find * 2>/dev/null)",
    "Bash(ls * 2>/dev/null && ls * 2>/dev/null)",
    "Bash(cat * 2>&1 | grep *)",

    //  for/if の複雑なパターン(内部の&&で分割される可能性)
    "Bash(for * in *; do * && *; done)",
    "Bash(if *; then * && *; else *; fi)"
  ]
}

パスセキュリティチェック(KN$)の回避

cwd 外のパスへのアクセスが KN$ でブロックされる場合:

  1. additionalDirectories を設定に追加する

    {
      "permissions": {
        "additionalDirectories": ["/home/user/other-project"]
      }
    }
    
  2. 起動時の --add-dir オプションを使用する

    claude --add-dir /home/user/other-project
    

付録: ソースコード上の主要関数マッピング

関数名 役割
xgA Bash全体のパーミッション判定(エントリポイント)
nJ コマンド分割(&&, |, ; で分割)+ リダイレクション除去
mHB サブコマンド個別判定
nS$ 完全一致ルールチェック
rgA パターンマッチング(exact/prefix/wildcard)
tgA deny/ask/allow ルール一括評価
ogA パターン文字列のパース(exact/prefix/wildcard判定)
agA / OHB ワイルドカード(glob)マッチング
kyA コマンド正規化(timeout, nice, nohup, 環境変数除去)
vZ リダイレクション除去
KN$ パスセキュリティチェック(cwd外アクセスのブロック)
tb コマンドインジェクションチェック
Eo Read/Glob パーミッション判定
t1H Edit/Write パーミッション判定
eQ パスルールマッチング(ignore npmパッケージ使用)
yHB MCPツールパーミッション判定
bS1 全ツール共通のトップレベル判定
cF パーミッションモード考慮付き最終判定

調査環境

  • Claude Code バージョン: 2.1.44
  • バイナリ形式: ELF 64-bit (Linux x86-64)
  • 調査手法: strings コマンドによる文字列抽出 + 関数ロジックの手動デコンパイル
  • 調査日: 2026-02-17

注意: この文書はリバースエンジニアリングに基づくため、バージョンアップにより動作が変更される可能性がある。

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?