技術 約5分で読めます

Claude Codeの権限設定、なんもわからん

Claude Codeで「毎回Askされるのをなんとかしたい」と思って設定ファイルをいじり始めたら、沼にハマった話。

結論: 公式が「バイパスされうる」「tricky」と言ってる時点で、完璧な制御は諦めた方がいい。

やりたかったこと

  • プロジェクト内のファイル読み込みでAskされるのを止めたい
  • Dockerにコマンド打つときに毎回Askが入るのを止めたい
  • ただし本番環境へのアクセスは防ぎたい

最初に試したこと

.claude/settings.json に許可ルールを書く方法:

{
  "permissions": {
    "allow": [
      "Read(*)",
      "Bash(docker *)",
      "Bash(docker-compose *)"
    ]
  }
}

これで行けるはずだった。

地獄の始まり

しばらく作業していると、settings.local.json が勝手に肥大化していた:

{
  "permissions": {
    "allow": [
      "Bash(echo:*)",
      "Bash(npx playwright install chromium)",
      "Bash(curl:*)",
      "Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\")",
      "Bash(npx playwright test:*)",
      "Bash(node test-login.js:*)",
      "Bash(npm install:*)",
      "Bash(docker exec:*)",
      "Bash(docker-compose down:*)",
      "Bash(__NEW_LINE__ echo \"=== POST /reports ===\")",
      "Bash(__NEW_LINE__ echo -e \"\\n\\n=== POST /messages ===\")"
    ]
  }
}

何がまずいのか

  • JWTトークンがそのまま記録されている(期限切れとはいえパターンとしてよくない)
  • フルパスが入っている(c:\Users\username\...
  • エコー文やワンライナーがそのまま(意味のない許可が大量に)
  • __NEW_LINE__ という内部表現がそのまま記録されている

パターンマッチの謎

Bash(npm *) を設定しているのに、なぜか Bash(npm run build:*) が追加される。

試したこと

スペースの有無で挙動が変わる?

// 記事で見た書き方
"Bash(npm install*)"

// 自分の書き方
"Bash(npm install *)"

両方の形式を書いてみる

"Bash(npx *)",
"Bash(npx:*)"

それでもダメ。

__NEW_LINE__ 対策

複数行コマンドの内部表現がそのまま記録されるので:

"Bash(__NEW_LINE__*)"

これを追加してみたが…

結局どうなったか

いろいろ追加した結果:

{
  "permissions": {
    "allow": [
      "Read(*)",
      "Write(*)",
      "Bash(docker *)",
      "Bash(docker-compose *)",
      "Bash(docker compose *)",
      "Bash(git *)",
      "Bash(npm *)",
      "Bash(npx *)",
      "Bash(node *)",
      "Bash(php *)",
      "Bash(composer *)",
      "Bash(cat *)",
      "Bash(ls *)",
      "Bash(dir *)",
      "Bash(find *)",
      "Bash(grep *)",
      "Bash(tar *)",
      "Bash(powershell *)",
      "Bash(powershell.exe *)",
      "Bash(npx playwright test:*)",
      "Bash(npm run build:*)",
      "Bash(tar:*)",
      "Bash(docker exec:*)"
    ],
    "deny": [
      "Bash(*https://example.com*)",
      "Bash(*http://example.com*)",
      "Bash(*curl*example.com*)",
      "Bash(*scp*)",
      "Bash(*ssh*example.com*)",
      "Bash(*rsync*)"
    ]
  }
}

「ほぼ止まらない」状態にはなった。

でもその後も増え続ける:

"Bash(__NEW_LINE__ echo \"*\")",
"Bash(__NEW_LINE__ echo \"=== * ===\")",
"Bash(__NEW_LINE__ echo \"\")",
"Bash(docker cp:*)",
"Bash(docker-compose restart:*)",
"Bash(__NEW_LINE__ echo \"=== Some API ===\")"

Redditを見てみた

みんな同じ苦労をしていた。

公式ドキュメントを確認

設定ファイルの優先順位

公式ドキュメントによると、設定ファイルには階層がある:

  1. ~/.claude/settings.json - ユーザーレベル(全プロジェクト共通)
  2. .claude/settings.json - プロジェクト設定(チーム共有、git管理対象)
  3. .claude/settings.local.json - 個人用オーバーライド(gitignore対象)
  4. Managed settings(Enterprise)- 最優先

settings.local.json が肥大化していたのは、Askで「Allow always」を選ぶたびにここに追記されていたから。

公式の例

{
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test:*)",
      "Read(~/.zshrc)"
    ],
    "deny": [
      "Bash(curl:*)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ]
  }
}

そして重要な注記:

Bash rules use prefix matching, not regex

Bash patterns are prefix matches and can be bypassed

FAQにも:

Wildcard syntax can be tricky - test permissions thoroughly.

公式が「バイパスされうる」「tricky」と認めている。

現実的な選択肢

1. 全許可してdenyで守る

{
  "permissions": {
    "allow": [
      "Read(*)",
      "Write(*)",
      "Bash(*)"
    ],
    "deny": [
      "Bash(*本番ドメイン*)",
      "Bash(*scp *)",
      "Bash(*rsync *)"
    ]
  }
}

ただし deny もバイパスされる可能性がある。

2. --dangerously-skip-permissions で起動

claude --dangerously-skip-permissions

全ての確認をスキップ。名前が怖すぎて使う気になれないが、ローカル開発なら現実的らしい。

3. 自動追加を止める

claude config set --global autoAddPermissions false

勝手に追加されなくなる。Askされたら毎回「Allow once」で対応。

4. ネットワークレベルで制限

Claude Code側ではなく、hostsファイルやファイアウォール、Dockerのネットワーク設定で本番アクセスを防ぐ。これが一番確実。

5. sandbox機能を使う

claude --sandbox で起動すると、OSレベル(Linux bubblewrap / macOS seatbelt)でファイルシステム・ネットワークを制限できる。

{
  "sandbox": {
    "enabled": true,
    "excludedCommands": ["docker"],
    "network": {
      "allowUnixSockets": ["~/.ssh/agent-socket"]
    }
  }
}

許可するファイルパスやドメインを事前に定義しておく方式。権限設定のパターンマッチより確実。

まとめ

Claude Codeの権限設定は:

  • パターンマッチの仕様が不透明
  • 内部表現(__NEW_LINE__ 等)がそのまま記録される
  • ワイルドカードの挙動が不安定
  • allow に書いても Ask される場合がある
  • 公式が「バイパスされうる」と認めている

設定ファイルでの完璧な制御は諦めて、--dangerously-skip-permissions か、ネットワークレベルでの制限、または --sandbox を併用するのが現実的。

感想

なんもわからん、とりあえず言うこと聞いてくれない……

参考