FRL 自由研究ラボ

AIエージェントことはじめ — LAB 012

AIに勝手をさせない壁を、3層で作る(前編)

— 層1・層2:対象とやり方を止める —

AI に作業を任せるほど、「それは触らないでほしかった」が起きる余地も増えます。 「触らないで」と文章で頼むだけでは、守られたかどうかを誰も確かめていません。 この記事では、読ませたくないもの・やらせたくない操作を仕組みで止める壁を、 性質の違う3つの層で作り、本当に止まるかを自分の手で確かめます。 前編では、層1(設定の禁止リスト)と層2(フック)を作ります。

この記事を読むとできること

  • 「触らないで」と頼むだけでは AI の勝手が止まらない理由と、仕組みで止める3つの層の違いが分かります
  • 設定の禁止リスト(層1)で、読ませたくないものと危険な操作を止められます
  • フック(層2)で、禁止リストでは選べない「やり方」を止め、AI を使わずに試して確かめられます

想定している使い方

Claude Code(Mac の中でファイルを読み書きしたりコマンドを実行したりしてくれる AI エージェント)を入れた Mac で、 その設定ファイルを自分の手で書き足します。 ターミナル(文字で Mac に命令を打ち込むためのアプリ。「アプリケーション」の中の「ユーティリティ」にあります)に、 コピーしたコマンドを貼る場面も出てきます。 説明はすべて Mac を前提にしています。

この記事の道順

  1. はじめに — 「お願い」は壁ではない
  2. STEP 1 — 何を守るかを書き出す(3層の全体像)
  3. STEP 2 — 層1:設定の禁止リストで、対象を止める
  4. STEP 3 — 層2:フックで、やり方を止める
  5. 後編へ — 層3と、壁を試して配る

※ LAB 003(AI エージェントを入れる)、 LAB 005(戻れる仕組み)、 LAB 010(終了コードで合否を決める)を終えている前提です。 特にLAB 010 の「終了コード(0 なら成功、0 以外なら失敗)」を、STEP 3 で使います。

読む目安:約12分 ・ 手を動かす目安:約40分(STEP 1〜3)・ 図3枚

INTRO

「お願い」は、壁ではありません

AI に「このファイルは読まないで」「全部まとめて記録する操作は使わないで」と、文章で頼む。 とても自然な対策ですが、守られたかどうかを確かめる仕組みがありません。 読み違え・見落とし・長い作業の途中で忘れる、といったことは起こりえますし、起きても気づけません。

筆者の体験メモ

筆者は、AI 向けの行動ルールを文章で何本も書いていました。 ところが2026-08-30 の点検で、機械が強制しているルールが1つも無く、全部が「お願い」だった と分かりました。ルールを書いた安心感だけがあって、止める力は無かったわけです。

対策は、AI を疑い続けることではありません。 止める役を、AI の理解力から「実行の直前に確かめる仕組み」へ移すだけです。 これは LAB 010 で 「合否を AI の言葉から機械の番号に移した」のと同じ考え方です。

✕ 文章でお願いするだけ 「触らないで」と 文章で書いておく AI が読んで守る…はず 守ったかは、誰も見ていない 読み違えや見落としが あれば、そのまま実行 ○ 仕組みで止める AI が実行 しようとする 実行の直前に仕組みが 確かめる(AI の言葉は無関係) 許可された操作 → そのまま実行 禁止された操作 → 止める 止めた理由は AI にも返ります。

図の見方:上の段は、「触らないで」と文章で書いておくだけの状態です。AI が守ったかを誰も見ていないので、読み違えがあればそのまま実行されます。 下の段は、実行の直前に仕組みが確かめる状態です。許可された操作だけが実行され、禁止された操作は止まります。

壁といっても難しい仕組みではありません。設定ファイルに数行書く/小さなプログラムを1本置くだけです。 壁は1枚では穴が残るので、止める場所の違う3枚を重ねます。

01

STEP 1 — 目安 10分

何を守るかを書き出す(3層の全体像)

壁は、何を守りたいかが決まらないと作れません。 まず、次の3種類に分けて、思いつくものを紙かメモに書き出します。多くて5つで十分です。

① 読ませたくないもの

パスワード・鍵・.env(秘密の設定を入れるファイル)・財産や健康のデータ。

② 書き換えさせたくない場所

大事なノートや写真の置き場、クラウドで共有している保存先。

③ やらせたくない操作

管理者権限での実行(sudo)、履歴を書き換える強制 push(LAB 005 の git の操作)、中身を見ずに全部まとめて記録する git add -A。

5つまでにする理由:何でも止めると、AI も自分も作業が進まなくなり、 いずれ「面倒だから壁ごと外そう」となります。止めるのは壊れると取り返しがつかないものだけに絞ります。

書き出したものを、どの層で止めるかに割り振ります。3つの層は、止める場所が違います。

AI が何かをしようとすると、3つの壁を順に通る AI が 何かを しようとする 層1 設定の禁止リスト deny / ask 層2 フック 実行直前の検査 層3 サンドボックス 箱の外は触れない 実行 止めるもの:対象そのもの 例:秘密のファイルを読む 止めるもの:やり方 例:git add -A(全部足す) 止めるもの:すり抜け 例:Bash の cat で読む どの層にも穴があります。重ねるのは、前の層の抜け道を次の層が受け止めるためです。

図の見方:左から右へ、AI がしようとした操作が3つの壁を順に通り、すべて通ったものだけが実行されます。 層1は「どの場所・どの操作か」という対象、層2はコマンドの書き方(やり方)、 層3は前の2枚をすり抜けたものを止めます。 層3のサンドボックスとは、コマンドを「箱の中」だけで動かし、箱の外のファイルには触れなくする仕組みです(後編で作ります)。

守りたいもの まず使う層 受け止める層
読ませたくないファイル 層1(読み取りの禁止) 層3(コマンドでの読み取りを止める)
書き換えさせたくない場所 層3(書ける場所を絞る) 層1(編集の禁止)
やらせたくない操作 コマンド名で書けるなら層1、引数の組み合わせで決まるなら層2 —

何を書き出せばよいか迷うなら、Claude Code に質問してもらうのが早いです。 設定には触らせず、聞き取りだけにします。

私の Mac で、AI に「読ませたくないもの」「書き換えさせたくない場所」
「やらせたくない操作」を整理したい。
私に質問して、3つに分けて箇条書きにまとめて。
設定ファイルには書き込まず、まとめを見せるだけにして。
パスワードや鍵の中身そのものは、私に聞かないで(置き場所の名前だけでよい)。

「値は聞かない」と付ける理由:守りたいのは中身なのに、整理のために中身を会話へ書いては本末転倒だからです。 壁に書くのは置き場所の名前(パス)だけで、値は書きません。

✓ ここまでの確認: 守りたいものを3種類に分けて、合計5つ前後を書き出した。 それぞれに「層1/層2/層3」のどれで止めるか、目星を付けた(間違えていても、次から直せます)。

02

STEP 2 — 目安 15分

層1:設定の禁止リストで、対象を止める

いちばん単純な壁です。Claude Code の設定ファイルに、「これは読ませない」「これは実行させない」を書いておきます。 場所は ~/.claude/settings.json (どのフォルダで使っても効く、自分用の設定。~ は自分のホームフォルダ)です。

書き方は2種類あります。 deny は「拒否」(常に止める)、 ask は「たずねる」(その都度、人に確認する)です。

{
  "permissions": {
    "deny": [
      "Read(**/.env*)",
      "Edit(**/.env*)",
      "Write(**/.env*)",
      "Read(~/.ssh/**)",
      "Bash(sudo *)",
      "Bash(git push --force*)",
      "Bash(git push -f*)"
    ],
    "ask": [
      "Bash(git push*)"
    ]
  }
}

push を deny でなく ask にする理由:普段の push(手元の変更をネット上の保存先へ送る操作)は必要な作業なので、全部拒否すると困ります。 一方、強制 push は履歴を書き換える取り返しのつかない操作なので、たずねるまでもなく拒否にします。 「毎回あるが外へ出る操作」は ask、「やる理由がほぼ無い危険な操作」は denyが分けやすい目安です。

2-1 設定を足す(自分の手で貼る)

すでに設定ファイルがあるなら、中身を消さずに足します。 Claude Code に案だけ作ってもらい、貼るのは自分でやると確実です。 ファイルがまだ無い場合は、その旨を伝えて全文の案を頼みます(保存は自分で)。 .claude のように名前が「.」で始まるフォルダは Finder(Mac のファイル管理アプリ)では隠れているので、 ターミナルに open -e ~/.claude/settings.json と打つと、テキストエディット(Mac 付属のメモ帳のようなアプリ)で開けます。

~/.claude/settings.json に、次の禁止を足したい。
・.env を含む名前のファイルは、読む・直す・書くを拒否
・~/.ssh の中は読めないようにする
・sudo と、git push の強制(--force)は拒否
・git push は毎回たずねる
今のファイルの中身を読んで、既存の設定は消さずに、足したあとの全文を見せて。
ファイルへの書き込みはしないで。

貼ったら、ファイルが壊れていないかを機械に確かめさせます。 JSON(設定を書くときの決まった書式)は、カンマや括弧が1つずれただけで全体が読めなくなるためです。 ターミナルに貼ってください(python3 は Python というプログラミング言語を動かす命令で、 git が使える Mac には通常入っています。詳しくは STEP 3 で触れます)。

python3 -c "import json,os; json.load(open(os.path.expanduser('~/.claude/settings.json'))); print('JSON は読めた')"; echo $?

「JSON は読めた」と 0 が出れば合格です (LAB 010 と同じ、番号で決める形)。 エラーの文章が出て最後が 1 なら、書式のずれです。その文章を AI に見せれば直せます。 壊れた設定は、壁が1つも効かないまま気づかないことがあるので、この確認は省かないでください。

反映は次に起動したときから。 Claude Code は起動時に設定を読むので、開いているあいだの画面には効きません。いったん終了して、起動し直してください。

2-2 この壁の限界を知っておく

層1だけでは、穴が2つ残ります。残りの2層は、この穴のために作ります。

穴① やり方までは選べない

コマンドの禁止は「その文字で始まるもの」を止める書き方です。 git add -A だけを止めたくて Bash(git add*) と書くと、 普通の git add ファイル名 まで止まり、作業が回らなくなります。→ STEP 3 で解決

穴② 別の道具で読めてしまう

Read の禁止は、Read という道具にしか効きません。 ターミナルの cat(ファイルの中身を表示するコマンド)で読まれると素通りです。 → 後編の STEP 4 で解決

筆者の体験メモ

筆者は 2026-08-16 にサンドボックスを有効にしたとき、穴②(Read の禁止がターミナルの cat を素通りする点)を塞ぎました。

✓ ここまでの確認: 設定ファイルに禁止を足し、上の検査コマンドで「JSON は読めた」と 0 が出た。 Claude Code を起動し直した(実際に止まるかは 後編の STEP 5 で試します)。

03

STEP 3 — 目安 15分 / この記事の山場

層2:フックで、やり方を止める

フックとは、「ある瞬間に、自動で走る小さなプログラム」のことです。 Claude Code では、AI が道具を使う直前に走らせられます。 そこでコマンドの中身を検査して、止めるか通すかを決めます。

決め方は、LAB 010 でも使った終了コードです。 フックが 2 で終われば「止める」、0 で終われば「通す」。 止めるときに標準エラー出力(エラー用の出力先)へ書いた文章は、理由として AI に返ります。

層2:フックが働く流れ AI がコマンドを 実行しようとする 実行の直前に、フックが走る コマンドの中身を検査 終了コード 0 → そのまま実行 終了コード 2 → 止める 止める理由の文章が AI に返る フックは自分で書く小さなプログラム。AI の言葉ではなく、コマンドの中身で決めます。

3-1 いちばん小さなフックを置く

例として、層1では止められなかった git add -A (何が入るか見ずに全部まとめて記録する操作。秘密のファイルを巻き込みやすい)だけを止めるフックを作ります。 Python は Mac に最初から入っているものを使います(LAB 005 で git が使えていれば、通常入っています)。 ターミナルに、次を貼ってください。 mkdir -p はフォルダを作る命令、cat > ファイル <<'EOF' から EOF までは「この間の文章をそのファイルに保存する」という書き方です。

mkdir -p ~/.claude/hooks
cat > ~/.claude/hooks/guard.py <<'EOF'
#!/usr/bin/python3
import json
import shlex
import sys

payload = json.load(sys.stdin)
command = payload.get("tool_input", {}).get("command", "")

try:
    words = shlex.split(command)      # 引用符を解釈して、言葉ごとに分ける
except ValueError:
    words = command.split()

if "git" in words and "add" in words and ("-A" in words or "--all" in words or "." in words):
    sys.stderr.write("git add -A / git add . は使えません。ファイルを指定して git add <ファイル名> にしてください\n")
    sys.exit(2)                       # 2 = 止める

sys.exit(0)                           # 0 = 通す
EOF

次に、フックを呼び出す設定を、STEP 2 の settings.json に足します。 "permissions" と同じ階層(いちばん外側の { } の中)に、 カンマで区切って並べます。足したあとは、STEP 2 の検査コマンドを必ずもう一度走らせてください。

  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "/usr/bin/python3 ~/.claude/hooks/guard.py" }
        ]
      }
    ]
  }

読み方:PreToolUse は「道具を使う直前」、 matcher: "Bash" は「ターミナルのコマンドのときだけ」の意味です。

3-2 AI を使わずに、フックだけを試す

フックはただのプログラムなので、AI を呼ばずに、自分で入力を流し込んで確かめられます。 止めたいもの・通したいものを、1つずつ試します。

echo '{"tool_input":{"command":"git add -A"}}' | /usr/bin/python3 ~/.claude/hooks/guard.py; echo $?
echo '{"tool_input":{"command":"git add memo.txt"}}' | /usr/bin/python3 ~/.claude/hooks/guard.py; echo $?
echo '{"tool_input":{"command":"echo \"git add -A は使わない\""}}' | /usr/bin/python3 ~/.claude/hooks/guard.py; echo $?

1行目だけ理由の文章が出て最後が 2、2行目・3行目は 0 になれば合格です。 3行目は、引用符で包んだ文章の中に「git add -A」という文字があるだけで、走るコマンドは echo(文字を表示するだけ)なので、止めてはいけない例です。引用符が無いと別々の言葉に分かれてしまい、止まります(それが正しい動きです)。

言葉に分けて(shlex)から見る理由:コマンド全体を文字列として探すだけだと、 今のように「説明の文章の中に書いてあるだけ」のものまで止めてしまいます。 逆に git add "-A" のように引用符で隠した形は、分けて見れば捕まえられます。 それでもこの見本は最小の形で、取りこぼしや誤爆はゼロではありません。 誤爆を見つけたら、その例を1つ試験に足して直します(LAB 010 の「直した箇所を覆う確認を足す」と同じです)。

筆者の体験メモ

筆者も最初の版で、説明の文章の中に書いてあるだけのものを止めてしまう誤爆を起こしたので、同じ例を試験に固定しました。

フックは壊れても「止める」にはなりません。 フックが起動できなかったり、途中でエラーになったりしたとき、終了コードが 2 でなければ止まらずに素通りになります。 だから、フックは Mac 付属の /usr/bin/python3 で動かし(自分で用意した Python の作業環境〈venv〉が無い Mac でも起動できるようにするため)、 本数も1本だけに絞るのが安全です(増やすほど壊れる場所が増え、壊れると全作業が止まるか、黙って素通りするため)。 設定したら、必ず 3-2 の方法で「止まる」ことを自分の目で確かめます。

フックは「止める」だけでなく、「人にたずねる」も返せます。 普段は使わないが完全には禁止したくない操作(保護の仕組みを外して実行する、など)に向いた形です。 人が見ているときは確認が出て、人がいない実行ではたずねる=自動的に拒否になります (この挙動は設定の記録にあるものです。自分の環境では、起動して試して確かめてください)。 人がいない時間の実行は、次の LAB 014 で扱います。

筆者の体験メモ

筆者は、普段は使わないが完全には禁止したくない操作を、この「たずねる」の形にしています。上の挙動は、筆者の設定の記録にあるものです。

✓ ここまでの確認: 3-2 の3行を流して、1行目が 2、2行目・3行目が 0 になった。 設定ファイルに hooks を足したあと、STEP 2 の JSON 検査も 0 だった。

NEXT

前編のまとめと、後編へ

ここまでで、壁は2枚できました。ただし層1・層2は、こちらが数え上げたものしか止められません。 後編では、数え漏れを受け止める3枚目の壁を足し、3枚とも本当に止まるかをダミーで確かめます。

  • 「お願い」は壁ではない。止める役は、実行の直前に確かめる仕組みに移す
  • 層1(禁止リスト)は対象を、層2(フック)はやり方を止める
  • フックは AI を使わずに、自分で入力を流し込んで止まるかを試せる

LAB 013

層3:サンドボックスと、壁を試して配る

箱の中だけでコマンドを動かすサンドボックスで、すり抜けを止めます。 そのあと3枚の壁にダミーを当て、2台目の Mac へも配ります。 →