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 Auto Modeの権限設定をPythonで点検

0
Posted at

Claude Code Auto Modeの権限設定をPythonで点検する

結論から言うと、Claude CodeのAuto Modeを使う前に見るべきなのは、普段は意識しないsettings.local.jsonです。便利だからと残したBash(git *)やBash(python *)は、Auto Modeの安全策とは別に、チームの運用ルールとして一度棚卸しした方がいいです。

Auto Modeは、承認ボタンを連打してしまう問題を減らすための仕組みです。Anthropicは、従来の許可プロンプトの93%が承認されていたと説明しています。これはかなり実感があります。昨日、検証用の設定を作ってスクリプトを回したら、ローカル設定にdefaultMode: bypassPermissionsを入れたケースが一発で赤く出ました。テスト用だから平気、と言いかけて止まりました。設定は残ります。

Q. Auto Modeがあれば、権限設定は見なくていい?

見ないままは危ないです。

Auto Modeは、ツール出力に混じるプロンプトインジェクションを検査し、実行前の操作は別の分類器で判定します。さらに、Bash全体やBash(python *)のような任意コード実行につながる広い許可は、Auto Modeに入ると持ち越さない設計です。Anthropicの技術解説には、このあたりがかなり具体的に書かれています。

ただし、設定ファイルが消えるわけではありません。Claude Codeはユーザー設定、共有プロジェクト設定、プロジェクトローカル設定を読みます。permissions.allowのようなリストは上書きではなく併合されます。公式のsettings解説を読むと、ローカル設定は共有設定より優先される一方、許可ルールは足し合わされると明記されています。

Auto Modeを安全装置にするなら、その前に「何を常時許可しているか」を読める状態にしておく。僕はここを分けて考えています。

Q. どの設定を拾えばいい?

次のスクリプトは、指定した設定JSONから広すぎる許可を拾います。Bash全体、git全体、スクリプト実行、パッケージ操作、外向き通信を伴うコマンドを対象にしました。denyも一緒に出すので、.envを読ませない設定が入っているかも確認できます。

監査対象はユーザー設定、共有プロジェクト設定、ローカル設定の3か所です。

# claude_permission_audit.py
import argparse
import json
import re
from pathlib import Path

RISKY_ALLOW = [
    (re.compile(r"^Bash(?:\(\*\))?$"), "Bash全体を常時許可"),
    (
        re.compile(r"^Bash\((?:python|python3|node|ruby|perl)(?:\s|:|\*).+\)$"),
        "スクリプト実行を広く許可",
    ),
    (re.compile(r"^Bash\((?:npm|pnpm|yarn)\s+\*\)$"), "パッケージ操作を広く許可"),
    (re.compile(r"^Bash\(git\s+\*\)$"), "gitの全操作を許可"),
    (re.compile(r"^Bash\((?:curl|wget)\s+\*\)$"), "外部送信を伴うコマンドを広く許可"),
]
REVIEW_ALLOW = [
    (re.compile(r"^WebFetch(?:\(\*\))?$"), "取得先を絞らないWebFetch"),
    (re.compile(r"^mcp__.+__\*$"), "特定MCPサーバーの全ツールを許可"),
]

def classify(rule: str):
    for pattern, reason in RISKY_ALLOW:
        if pattern.fullmatch(rule):
            return "HIGH", reason
    for pattern, reason in REVIEW_ALLOW:
        if pattern.fullmatch(rule):
            return "REVIEW", reason
    return None

def get_rules(permissions: dict, key: str):
    rules = permissions.get(key, [])
    if not isinstance(rules, list):
        print(f"  ERROR: permissions.{key} は配列である必要があります")
        return []
    return rules

def inspect(label: str, path: Path):
    print(f"[{label}] {path}")
    try:
        settings = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        print("  SKIP: ファイルなし")
        return
    except json.JSONDecodeError as exc:
        print(f"  ERROR: JSONが不正です (line {exc.lineno}, column {exc.colno})")
        return

    mode = settings.get("defaultMode")
    if mode == "bypassPermissions":
        print("  HIGH: defaultMode=bypassPermissions (隔離環境以外では使わない)")
    elif mode == "auto":
        print("  OK: defaultMode=auto")

    permissions = settings.get("permissions", {})
    if not isinstance(permissions, dict):
        print("  ERROR: permissions はオブジェクトである必要があります")
        return

    for rule in get_rules(permissions, "allow"):
        if not isinstance(rule, str):
            print(f"  ERROR: allow に文字列以外があります: {rule!r}")
            continue
        result = classify(rule)
        if result:
            level, reason = result
            print(f"  {level}: allow {rule} ({reason})")

    for rule in get_rules(permissions, "deny"):
        if isinstance(rule, str):
            print(f"  PROTECT: deny {rule}")

def main():
    parser = argparse.ArgumentParser(
        description="Claude Code settings の許可ルールをざっくり点検する"
    )
    parser.add_argument(
        "--scope",
        action="append",
        default=[],
        metavar="名前=PATH",
        help="例: --scope user=~/.claude/settings.json",
    )
    args = parser.parse_args()
    if not args.scope:
        parser.error("--scope 名前=PATH を少なくとも1つ指定してください")

    for item in args.scope:
        if "=" not in item:
            parser.error("--scope は 名前=PATH の形式です")
        label, raw_path = item.split("=", 1)
        inspect(label, Path(raw_path).expanduser())

if __name__ == "__main__":
    main()

実行例です。ファイルがない場所はSKIPになるので、個人設定しかない人もそのまま実行できます。

python3 claude_permission_audit.py \
  --scope user=~/.claude/settings.json \
  --scope project=.claude/settings.json \
  --scope local=.claude/settings.local.json

検証用のJSONでは、こう出ました。

[user] /tmp/claude-check/settings.json
  OK: defaultMode=auto
  HIGH: allow Bash(git *) (gitの全操作を許可)
  HIGH: allow Bash(python *) (スクリプト実行を広く許可)
  REVIEW: allow mcp__github__* (特定MCPサーバーの全ツールを許可)
  PROTECT: deny Read(./.env)
  PROTECT: deny Bash(curl *)
[local] /tmp/claude-check/settings.local.json
  HIGH: defaultMode=bypassPermissions (隔離環境以外では使わない)

Q. HIGHが出たら、全部消す?

消す前に、許可の粒度を小さくします。たとえばBash(git *)を置いたままにせず、必要な作業だけBash(git log *)やBash(git diff *)にします。公式ドキュメントでも、Bash(git *)はpushを含むすべてのgit操作に一致すると説明されています。権限ルールの仕様を読むと、ワイルドカードがサブコマンドの前にあるか後ろにあるかで、許可範囲が大きく変わります。ここ地味に効きます。

このスクリプトは設定の優先順位を完全再現するものではありません。--settings付きの起動や組織のmanaged settingsまで含めた実効値は、Claude Code内の/statusと/permissionsで確認してください。JSONにコメントや末尾カンマがあると設定自体が無効になるので、そのエラーも先に直します。

で、現場でどう使うか

僕なら、Auto Modeへ切り替える日と、MCPサーバーを増やす日にはこの点検を一度回します。HIGHは即削除ではなく、誰が何の作業で必要なのかを決めて、狭いルールに置き換えます。共有の.claude/settings.jsonは便利な設定ファイルである前に、チーム全員に効く権限表です。

Auto Modeが承認疲れを減らしてくれるのは事実です。だからこそ、常時許可だけは人が読める大きさにしておく。これで使い始めの安心感がかなり変わります。

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?