創刊準備号 · 2026年秋English · RSS
AgentKoji

AIエージェントの権限設計

本番DBを読ませたいが、書かせたくない。読み取り専用のSQLラッパーを実際に書き、拒否ルールとhookの順番を公式仕様で確かめた。

PR:この記事には、当サイトが参加する(予定の)アフィリエイトプログラムの紹介枠(VPS)があります。紹介する基準は料金とリージョンだけです。

エージェントに本番を見せたい場面は多くあります。ログを読ませたい、DB の件数を数えさせたい、といった場面です。ただし、書き込みまでは許したくありません。この記事では、Claude Code の権限ルールと hook の評価順を公式ドキュメントで確かめたうえで、読み取り専用のラッパーを実際に書いて試します。

試した環境は、編集部の検証環境(Linux、Claude Code 2.1.301、Python 3.13.5、SQLite 3.46.1、2026-10-16)です。

評価の順番を知っておく

公式ドキュメントの要点です(出典:https://code.claude.com/docs/en/permissions 、2026-10-16確認)。

つまり、止める仕組みは何重にも重ねられます。一方で、許可を足して止める仕組みを外すことはできません。設計は「deny を広めに、allow を狭く」から始めます。

hook の実装と、素通りした例は「Claude Code hooks実践」にあります。

生の curl や psql を allow しない

本番の読み取りを毎回承認するのは手間なので、つい Bash(curl *) や Bash(psql *) を allow したくなります。しかし同じエンドポイントで書き込みも通ってしまいます。編集部では、読み取りしかできないラッパーを1本作り、そのフルパスだけを allow に登録しています。

読み取り専用ラッパーを書いて試す

SELECT か WITH で始まらない文、書き込みの語を含む文、複数の文を拒否します。接続そのものも読み取り専用モードで開き、二重に守ります。

#!/usr/bin/env python3
# /usr/local/bin/ro-sql(デモは SQLite。本番では読み取り専用ロールで接続する)
import re, sys, sqlite3
sql = sys.argv[1].strip().rstrip(';')
WRITE = r'\b(insert|update|delete|drop|alter|create|replace|truncate|attach|pragma|vacuum|grant)\b'
if ';' in sql:                                     sys.exit("reject: 複数文")
if not re.match(r'(?is)^\s*(select|with)\b', sql): sys.exit("reject: SELECT/WITH以外")
if re.search(WRITE, sql, re.I):                    sys.exit("reject: 書き込み語")
db = sqlite3.connect('file:demo.db?mode=ro', uri=True)
for row in db.execute(sql): print(row)
入力結果
SELECT id, selected_at FROM jobs実行された
select * from jobs where deleted_flag=0実行された
WITH x AS (SELECT 1) SELECT * FROM x実行された
SELECT 1; DELETE FROM jobsreject: 複数文
DELETE FROM jobsreject: SELECT/WITH以外
select * from jobs where 'delete' = 'x'reject: 書き込み語

語の境界(\b)で判定しているので、selected_at や deleted_flag のような列名では誤って拒否しません。文字列リテラルの中の 'delete' は拒否されます。安全側に倒れる誤判定なので、そのままにしています。

設定には、ラッパーのフルパスを書きます。

{
  "permissions": {
    "allow": ["Bash(/usr/local/bin/ro-sql *)"],
    "deny": ["Bash(psql *)", "Bash(curl *)"]
  }
}

ラッパーをシェルの関数や変数で包むと、許可ルールの前方一致に当たらなくなります。呼び出しは、パスをそのまま書く形にそろえます。

「確認のつもり」の probe で消した

外部サービスの API で、どのメソッドに対応しているかを確かめようとして、for m in GET POST PUT DELETE を回したことがあります。実在するデータを1件、削除してしまいました。

いまは、probe 用のスクリプトが GET・HEAD・OPTIONS しか受け付けないようにしています。調べるときは、先に OpenAPI の定義を読みます。書き込みを試す必要があるなら、ダミーのデータを作ってから、それを対象にします。

直すなら「足す」方向で

auto mode の分類器は、同じ修復でも方向で扱いを変えました。ツールを追加でインストールする修復は通り、設定行を削除する修復は「セキュリティを弱める変更」として止まりました。遠回りに見えても、足す方向の直し方を先に探します。

無人実行には人間のゲートを残す

鍵と信頼できない入力を同じプロセスに置かない理由は「AIエージェントをVPSで24時間動かす」に書きました。ここでは、その先の話をします。OS の鍵保管庫も、シェルから読めるのであれば、この脅威に対する守りになりません。本番への書き込みは、対話の端末から人が起動したときだけにしています。人間のゲートは、パスフレーズを人が手で打つ形で作りました。

本番に書き込めたことで実際に起きた静かな事故は、「デプロイとマイグレーションの事故集」にまとめました。

MCP サーバーのツールを、使うものだけ許可する書き方は「MCPサーバーの選び方」にまとめました。

まとめ