0
1

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の権限設定を安全に設計する方法 ― Write/Edit・Ask/Denyを実際に検証して分かったこと

0
Posted at

動作確認環境

  • Claude Code 2.1.209
  • Ubuntu(WSL)
  • Manual Mode
  • コマンドラインで専用の settings.json を指定

本記事は、2026年7月時点で実際に確認した挙動をまとめたものです。Claude Codeは更新頻度が高いため、導入時は最新の公式ドキュメントと起動時の警告も確認してください。

はじめに

Claude Codeに調査やレビューを任せるとき、最初から広い権限を与えるのは避けたいと考えました。

今回やりたかったのは、単に「書き込みを許可する」ことではありません。目標は、次のように用途ごとに権限境界を分けることでした。

  • 通常の開発セッションでは、実ログやローカル出力を読ませない
  • 調査専用セッションだけ、必要な入力を読めるようにする
  • 調査レポートの保存先だけ、人間の承認後に書き込めるようにする
  • ソースコード、テスト、設定、Git管理領域への書き込みは拒否する
  • Bash経由のファイル作成やGit変更も禁止したままにする
  • 調査専用セッションを閉じた後、通常セッションへ一時許可を残さない

つまり、検証したかったのは次の境界です。

必要な読み取り
    +
指定した出力先への承認制書き込み
    -
ソースコード・秘密情報・Git・通常外部通信

本記事では、成功した設定だけでなく、実際にはまった次の2点も含めて記録します。

  1. パス付きの Write(path) を設定したところ、起動時に警告された
  2. 絶対パスのつもりで先頭を / にしたが一致せず、// に直す必要があった

公式ドキュメントで確認した権限の基本

Claude Codeの公式ドキュメントでは、権限ルールは次の順序で評価されます。

  1. deny
  2. ask
  3. allow

そのため、広い askallow と、禁止対象の deny が同時に一致した場合は、deny が優先されます。

今回の目的では、この優先順位が重要です。

保存先ディレクトリ     → ask
ソースコード領域       → deny

このように分けておけば、レポート保存は毎回確認しつつ、禁止領域への書き込みは確認画面を出さずに拒否できます。

また、公式ドキュメントには次の説明があります。

Edit rules apply to all built-in tools that edit files.

つまり、Edit ルールは、Claude Codeの組み込みファイル編集ツール全体に適用されます。


検証用Settingsを分離した理由

通常運用の設定を直接変更すると、検証中の許可が普段の開発セッションへ混ざる危険があります。

そこで、通常設定とは別に検証用Settingsを作り、次の形式で起動しました。

claude --setting-sources user --settings ~/.claude/xserver-log-analyzer-write-test.settings.json

--setting-sources user を付け、コマンドラインで指定したSettingsとUser設定だけを読み込ませる構成です。

検証用ファイルは本番運用へ残さず、検証終了後に削除しました。検証用の一時ディレクトリも /tmp 配下へ作成し、最後にすべて削除しています。


1. 最初は Write(path)ask に設定した

最初は、Writeツールだけを対象にすれば新規ファイル作成を限定できると考え、次のようなルールを設定しました。

{
  "permissions": {
    "ask": [
      "Bash",
      "Edit(/tmp/xserver-log-analyzer-claude-write-test/reports/**)",
      "Write(/tmp/xserver-log-analyzer-claude-write-test/reports/**)"
    ]
  }
}

/permissions のAskタブでは、Edit(...)Write(...) の両方が表示されました。

01_permissions_write_rule.png

Writeツールへ、指定ディレクトリ内に1行だけのMarkdownを作成するよう依頼すると、作成前の確認画面が表示されました。

02_write_tool_permission_prompt.png.png

ここでは、次の3択が表示されました。

  1. 今回だけ許可
  2. セッション中、対象ディレクトリへのすべての編集を許可
  3. 拒否

「今回だけ許可」を選ぶとファイルは作成され、内容も確認できました。

この時点では、一見すると Write(path) が機能しているように見えました。しかし、後で起動時の警告により、パス付きファイル権限の考え方を見直すことになります。


2. プロジェクトルートへの書き込みを試した

次に、許可対象ではないプロジェクトルートへファイルを作成するよう依頼しました。

03_write_denied_project_root_prompt.png

この段階では確認画面が表示されました。

04_write_project_root_prompt_masked.png

その後、プロジェクトルートに対する拒否ルールを追加して同じ操作を行うと、Error writing file となり、ファイルは作成されませんでした。

05_write_denied_result.png

この検証で、Denyルールに一致した場合は、Claudeの文章上の指示ではなく、Claude Code側の権限制御によって書き込みが止まることを確認できました。

公式ドキュメントにも、権限ルールはモデルではなくClaude Codeが強制すると記載されています。

プロンプトに「変更しないでください」と書くことも大切ですが、それだけでは権限境界にはなりません。実際の禁止は、SettingsのDenyルールで行う必要があります。


3. 起動時の警告で Write(path) の問題に気付いた

検証途中から、Claude Code起動時に黄色の警告が表示されるようになりました。

06_write_rule_warning.png

警告内容は次のとおりです。

Permission deny rule (...):
Write(...) is not matched by file permission checks
— only Edit(path) rules are.
Use Edit(...) instead
(Edit rules cover all file-editing tools).

Ask側の Write(path) にも同じ警告が表示されました。

ここで重要なのは、Writeツールそのものが存在しないわけでも、Writeを全面禁止しなければならないわけでもない点です。

今回確認したClaude Code 2.1.209では、パス単位でファイル編集系ツールを制御するときは Edit(path) を使うよう警告されました。公式ドキュメントにも、Edit ルールは組み込みのファイル編集ツール全体へ適用されるとあります。

そこで、パス付きの Write(...) をAskとDenyから削除し、Edit(...) に統一しました。


4. 先頭が / のパスでは、想定した絶対パスに一致しなかった

Write(path) の次にはまったのが、パスの先頭記号です。

最初は、一般的なLinuxの絶対パスと同じ感覚で、次のように書きました。

"ask": [
  "Edit(/tmp/xserver-log-analyzer-claude-write-test/reports/**)"
]

しかし、この指定では期待した絶対パスとして扱われませんでした。

Claude CodeのRead/Editルールにおけるパスは、通常のシェルパス表記とは意味が異なります。公式ドキュメントでは、次の4種類に分かれています。

パターン 意味
//path ファイルシステムルートからの絶対パス
~/path ホームディレクトリからの相対パス
/path Settingsファイルのスコープに対応するディレクトリからの相対パス
path / ./path Claude Codeを起動したカレントディレクトリからの相対パス

公式ドキュメントには、次のように明記されています。

A pattern like /Users/alice/file isn’t an absolute path.
Use //Users/alice/file for absolute paths.

今回のSettingsは、~/.claude/ 配下のファイルを --settings で指定していました。そのため、先頭が1つの /tmp/... は、OSの /tmp/... ではなく、Settingsファイルの場所を基準に解決されます。

そこで、次のように先頭を // へ変更しました。

"ask": [
  "Edit(//tmp/xserver-log-analyzer-claude-write-test/reports/**)"
]

プロジェクトの絶対パスをDenyする場合も同様です。

"deny": [
  "Edit(//home/USER/projects/example-project/**)"
]

今回の検証では、/ のままでは意図したパス制御にならず、// へ修正したことで一致するようになりました。


5. Edit(path) だけでWriteツールの新規作成を制御できるか

パス付き Write(...) を削除し、Askには Edit(...) だけを残しました。

{
  "permissions": {
    "ask": [
      "Bash",
      "Edit(//tmp/xserver-log-analyzer-claude-write-test/reports/**)"
    ]
  }
}

この状態でWriteツールによる新規作成を試すと、確認画面が表示されました。

07_claude-code-edit-rule-write-test-success.png

「今回だけ許可」を選ぶと、Writeツールは指定ファイルを作成しました。

作成後の結果では、次の点を確認できました。

  • Writeツールで1行だけ書き込まれた
  • Bashは使用されなかった
  • 指定したファイル以外は変更されなかった
  • 親ディレクトリが必要な場合はWriteツール側で作成された

08-claude-code-edit-rule-write-success.png

この結果から、Askの Edit(path) が、Writeツールによる新規作成にも適用されることを実測できました。


6. 実運用に近い保存先だけAskにする

一時ディレクトリでの基本検証後、実運用を想定した構成へ進みました。

保存先は、Git管理対象外とするローカルディレクトリです。

.local/
└── real-log-investigation/

検証用には、同じ構造を /tmp 配下へ作りました。

/tmp/xserver-log-analyzer-permission-test/
├── .local/
│   └── real-log-investigation/
└── src/

権限は次のように分けました。

{
  "permissions": {
    "ask": [
      "Bash",
      "Edit(//tmp/xserver-log-analyzer-permission-test/.local/real-log-investigation/**)"
    ],
    "deny": [
      "Edit(//tmp/xserver-log-analyzer-permission-test/src/**)"
    ]
  }
}

まず、許可対象の .local/real-log-investigation/ 配下へ新規作成を依頼しました。

Claude Codeは、Writeツールを使う前に確認画面を表示しました。

09-claude-code-local-report-write-permission-prompt-masked.png

ここでも「今回だけ許可」を選択しました。

「セッション中、このディレクトリへのすべての編集を許可」も選べますが、検証では許可範囲を広げず、1回ごとに確認する方を選びました。


7. Ask対象ディレクトリへの書き込み成功を確認

承認後、指定したMarkdownファイルが作成されました。

10-claude-code-local-report-write-success-masked.png

画面上では、次の内容を確認できました。

  • Writeツールが成功した
  • 内容は指定した1行だけ
  • Bashを使用していない
  • 他のファイルを変更していない
  • 書き込み前に権限プロンプトを通過した

ターミナル側でも、対象ファイルの内容を確認しました。

cat /tmp/xserver-log-analyzer-permission-test/.local/real-log-investigation/local-write-test.md

結果は、指定した1行だけでした。

この段階で、許可対象の保存先はAskを経て書き込めることを確認できました。


8. Deny対象の src/ は確認なしで拒否された

最後に、同じセッションでDeny対象の src/ 配下へ新規作成を依頼しました。

/tmp/xserver-log-analyzer-permission-test/src/deny-write-test.md

結果は Error writing file となり、次の理由で拒否されました。

File is in a directory that is denied by your permission settings.

11-claude-code-denied-directory-write-result-masked.png

この画像が、今回の最終確認です。

  • .local/real-log-investigation/** はAsk後に成功
  • src/** はDenyにより拒否
  • 同じWriteツールでも、対象パスによって結果が分かれた

公式ドキュメントにある deny → ask → allow の優先順位どおりの挙動を、実際のファイル作成で確認できました。


検証時のSettings構成

実際の設定には、ファイル編集だけでなく、Git変更、Bash経由の書き込み、外部通信、秘密情報の読み取り禁止も含めました。

以下は考え方を残した簡略版です。環境固有のユーザー名やプロジェクト名は置き換えてください。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "disableClaudeAiConnectors": true,
  "disableSkillShellExecution": true,
  "permissions": {
    "defaultMode": "plan",
    "disableAutoMode": "disable",
    "disableBypassPermissionsMode": "disable",
    "ask": [
      "Bash",
      "Edit(//home/USER/projects/example-project/.local/real-log-investigation/**)"
    ],
    "deny": [
      "Edit(//home/USER/projects/example-project/src/**)",
      "Edit(//home/USER/projects/example-project/tests/**)",
      "Edit(//home/USER/projects/example-project/docs/**)",
      "Edit(//home/USER/projects/example-project/.claude/**)",
      "Edit(//home/USER/projects/example-project/.github/**)",

      "NotebookEdit",
      "WebFetch",
      "WebSearch",
      "mcp__*",

      "Read(./config.yml)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./**/.env)",
      "Read(./**/.env.*)",
      "Read(./**/*.xlsx)",
      "Read(./**/*.sql)",
      "Read(./**/*.pem)",
      "Read(./**/*.key)",
      "Read(./**/*.token)",
      "Read(./**/*.secret)",

      "Bash(git switch *)",
      "Bash(git checkout *)",
      "Bash(git pull *)",
      "Bash(git fetch *)",
      "Bash(git add *)",
      "Bash(git commit)",
      "Bash(git commit *)",
      "Bash(git push)",
      "Bash(git push *)",
      "Bash(git merge *)",
      "Bash(git rebase *)",
      "Bash(git reset *)",
      "Bash(git restore *)",
      "Bash(git stash *)",
      "Bash(git clean *)",

      "Bash(touch *)",
      "Bash(mkdir *)",
      "Bash(cp *)",
      "Bash(mv *)",
      "Bash(rm *)",
      "Bash(tee)",
      "Bash(tee *)",
      "Bash(sed -i *)",
      "Bash(perl -i *)",
      "Bash(cat > *)",
      "Bash(cat >> *)",
      "Bash(*>*)",
      "Bash(*>>*)",
      "Bash(*<<*)",

      "Bash(curl)",
      "Bash(curl *)",
      "Bash(wget)",
      "Bash(wget *)",
      "Bash(ssh *)",
      "Bash(scp *)",
      "Bash(rsync *)"
    ]
  }
}

なぜ Bash をAskにしつつ、書き込み系コマンドをDenyするのか

Bash全体をDenyすると、読み取り確認や必要な検証コマンドまで使えなくなります。

一方で、BashをAskだけにすると、人間が誤って承認した場合に、リダイレクトや rmcp、Git変更などを実行できる可能性があります。

そこで、次の二段構えにしました。

Bash全体                → Ask
危険な変更系コマンド    → Deny

これにより、読み取り中心の単一コマンドは確認後に実行でき、明確な変更系コマンドは拒否できます。

ただし、公式ドキュメントでは、Read/Editルールは任意のPythonやNode.jsプログラムなど、間接的にファイルを開くすべての子プロセスまでは制御しないと説明されています。OSレベルで完全に遮断したい場合は、Sandboxも検討する必要があります。

今回の環境ではSandboxを有効にした起動が正常にできなかったため、必須要件には含めず、Claude Codeの権限ルールで検証可能な範囲を確認しました。


JSONの検証も毎回行った

Settings編集中、一度JSONへ // コメントを書いてしまい、次のエラーになりました。

Expecting value: line 13 column 5

JSONではコメントを使用できないため、コメント行を削除してから再確認しました。

構文確認には次のコマンドを使いました。

python3 -m json.tool ~/.claude/xserver-log-analyzer-write-test.settings.json

設定変更後は、Claude Codeを起動する前に毎回このコマンドを実行しました。

権限設計が正しくても、JSON自体が壊れているとSettings全体が読み込まれません。公式ドキュメントでも、User・Project・Local設定は厳密に扱われ、ファイルの検証に失敗すると全体が拒否されると説明されています。


Manual Modeで検証した理由

Settingsの defaultMode は当初 plan にしていました。

Plan Modeでは読み取りと計画が中心で、書き込み検証へ進めません。そのため、実際にWriteツールの確認画面と拒否結果を見る段階では、Manual Modeへ切り替えました。

公式ドキュメントでは、manualdefault の別名で、CLI上ではManualと表示されます。

今回の検証では、自動承認されるモードを使わず、必ず人間が結果を確認できる状態で進めました。


「今回だけ許可」を選んだ理由

書き込み確認画面には、セッション中の継続許可も表示されました。

しかし、今回は権限境界そのものの検証なので、「今回だけ許可」を選びました。

継続許可を選ぶと、その後の確認回数が減り、Askルールがどの操作で発火しているのか追いにくくなります。最小権限の検証では、多少手間が増えても、1回ずつ確認する方が安全です。

また、検証終了後には通常セッションを起動し、一時許可が残っていないことも確認しました。


検証後の後片付け

検証用の一時ファイルやSettingsを残さないよう、最後に削除しました。

rm -rf /tmp/xserver-log-analyzer-claude-write-test
rm -rf /tmp/xserver-log-analyzer-permission-test
rm ~/.claude/xserver-log-analyzer-write-test.settings.json

その後、プロジェクトの作業ツリーに変更がないことを確認しました。

git status --short

検証用Settingsが削除されていることも確認しました。

ls ~/.claude/xserver-log-analyzer-write-test.settings.json

検証では、成功条件だけでなく、終了後に不要な権限設定とテストファイルを残さないことまでを1セットにしています。


今回確認できたこと

確認項目 結果
パス付き Write(path) 起動時警告が出たため使用を中止
Edit(path) でWriteツールの新規作成を制御 成功
/tmp/... を絶対パスとして指定 想定どおり一致しなかった
//tmp/... へ修正 絶対パスとして一致
Ask対象への新規作成 確認画面後に成功
Deny対象への新規作成 Error writing file で拒否
Bashを使わずWriteツールだけで作成 成功
指定ファイル以外を変更しない 確認できた
JSON構文確認 python3 -m json.tool で実施
検証後の一時ファイル削除 実施
Git作業ツリーへの影響 なし

今回の結論

今回の検証で特に重要だったのは、次の4点です。

1. パス単位のファイル編集制御は Edit(path) を使う

Claude Code 2.1.209では、パス付き Write(path) に対して起動時警告が表示されました。

新規作成をWriteツールで行う場合でも、権限ルールは Edit(path) で制御できました。

2. 絶対パスの先頭は / ではなく //

Read/Editルールでは、先頭が1つの / はファイルシステムルートを表しません。

/path   → Settingsのスコープを基準
//path  → ファイルシステムルートからの絶対パス

Linuxの通常のパス表記と同じ感覚で書くと、意図しない場所を基準に解決されるため注意が必要です。

3. AskとDenyを役割分担させる

保存してよい場所    → Ask
触ってはいけない場所 → Deny

DenyがAskより優先されるため、許可対象と禁止対象を明確に分離できます。

4. プロンプトだけでなくSettingsで強制する

「ソースコードを変更しないでください」という指示は、Claudeの行動方針にはなりますが、強制的な権限境界ではありません。

実際の拒否は、Claude CodeのDenyルールで行います。


まとめ

今回の目的は、Claude Codeへ単に書き込みを許可することではありませんでした。

調査に必要な範囲だけを許可し、それ以外を実際に拒否できることを、小さな検証環境で確認することが目的でした。

検証の途中では、

  • Write(path) による起動時警告
  • /// の意味の違い
  • JSONコメントによる構文エラー
  • Plan Modeでは書き込み検証へ進めないこと
  • Sandboxを有効にした起動が環境上うまくいかなかったこと

など、設定例を眺めるだけでは分からない点がいくつもありました。

最終的には、

許可対象 → Edit(path)をAsk
禁止対象 → Edit(path)をDeny
絶対パス → 先頭を//
Bash      → Ask + 変更系コマンドを個別Deny

という構成で、指定ディレクトリへの承認制書き込みと、ソースコード領域への書き込み拒否を実測できました。

Claude Codeを実運用へ導入するときは、いきなり本番の権限を変更するのではなく、まず /tmp などの安全な場所で、AskとDenyの両方を実際に試すことをおすすめします。


参考資料

関連記事

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?