AIエージェントことはじめ — LAB 012
— 層1・層2:対象とやり方を止める —
AI の勝手を止める役は、AI のやる気や理解力ではなく、実行の直前に確かめる仕組みに任せます。 設定の禁止リスト、フック(実行の直前に走る検査プログラム)、サンドボックス(箱の外に触れなくする仕組み)の 3つの層を重ねて、読ませたくないもの・やらせたくない操作を止めます。 「触らないで」と文章で頼むだけでは、守られたかどうかを誰も確かめていないからです。 前編では、層1(設定の禁止リスト)と層2(フック)を作り、本当に止まるかを自分の手で確かめます。
この記事を読むとできること
想定している使い方
Claude Code(Mac の中でファイルを読み書きしたりコマンドを実行したりしてくれる AI エージェント)を入れた Mac で、 その設定ファイルを自分の手で書き足します。 ターミナル(文字で Mac に命令を打ち込むためのアプリ。「アプリケーション」の中の「ユーティリティ」にあります)に、 コピーしたコマンドを貼る場面も出てきます。 説明はすべて Mac を前提にしています。
※ 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 が守ったかを誰も見ていないので、読み違えがあればそのまま実行されます。 下の段は、実行の直前に仕組みが確かめる状態です。許可された操作だけが実行され、禁止された操作は止まります。
壁といっても難しい仕組みではありません。設定ファイルに数行書く/小さなプログラムを1本置くだけです。 壁は1枚では穴が残るので、止める場所の違う3枚を重ねます。
STEP 1 — 目安 10分
壁は、何を守りたいかが決まらないと作れません。 まず、次の3種類に分けて、思いつくものを紙かメモに書き出します。多くて5つで十分です。
パスワード・鍵・.env(秘密の設定を入れるファイル)・財産や健康のデータ。
大事なノートや写真の置き場、クラウドで共有している保存先。
管理者権限での実行(sudo)、履歴を書き換える強制 push(変更を無理やりネット上の保存先へ送る操作。LAB 005 の git の操作)、中身を見ずに全部まとめて記録する git add -A。
5つまでにする理由:何でも止めると、AI も自分も作業が進まなくなり、 いずれ「面倒だから壁ごと外そう」となります。止めるのは壊れると取り返しがつかないものだけに絞ります。
書き出したものを、どの層で止めるかに割り振ります。3つの層は、止める場所が違います。
図の見方:左から右へ、AI がしようとした操作が3つの壁を順に通り、すべて通ったものだけが実行されます。 層1は「どの場所・どの操作か」という対象、層2はコマンドの書き方(やり方)、 層3は前の2枚をすり抜けたものを止めます。 層3のサンドボックスとは、コマンドを「箱の中」だけで動かし、箱の外のファイルには触れなくする仕組みです(後編で作ります)。
| 守りたいもの | まず使う層 | 受け止める層 |
|---|---|---|
| 読ませたくないファイル | 層1(読み取りの禁止) | 層3(コマンドでの読み取りを止める) |
| 書き換えさせたくない場所 | 層3(書ける場所を絞る) | 層1(編集の禁止) |
| やらせたくない操作 | コマンド名で書けるなら層1、引数(コマンドの後ろに付ける言葉)の組み合わせで決まるなら層2 | — |
何を書き出せばよいか迷うなら、Claude Code に質問してもらうのが早いです。 設定には触らせず、聞き取りだけにします。
貼る場所:Claude Code の入力欄(ターミナルには貼りません)
私の Mac で、AI に「読ませたくないもの」「書き換えさせたくない場所」
「やらせたくない操作」を整理したい。
私に質問して、3つに分けて箇条書きにまとめて。
設定ファイルには書き込まず、まとめを見せるだけにして。
パスワードや鍵の中身そのものは、私に聞かないで(置き場所の名前だけでよい)。
「値は聞かない」と付ける理由:守りたいのは中身なのに、整理のために中身を会話へ書いては本末転倒だからです。 壁に書くのは置き場所の名前(パス)だけで、値は書きません。
✓ ここまでの確認: 守りたいものを3種類に分けて、合計5つ前後を書き出した。 それぞれに「層1/層2/層3」のどれで止めるか、目星を付けた(間違えていても、次から直せます)。
STEP 2 — 目安 15分
いちばん単純な壁です。Claude Code の設定ファイルに、「これは読ませない」「これは実行させない」を書いておきます。
場所は ~/.claude/settings.json
(どのフォルダで使っても効く、自分用の設定。~ は自分のホームフォルダ)です。
書き方は2種類あります。 deny は「拒否」(常に止める)、 ask は「たずねる」(その都度、人に確認する)です。
下は書き方の見本です。そのまま貼らないでください。 すでに settings.json がある場合、この全文を丸ごと貼ると今の設定が消えます(消えた設定は戻せません)。 必ず、この後の 2-1 の手順で足してください。
{
"permissions": {
"deny": [
"Read(**/.env*)",
"Edit(**/.env*)",
"Write(**/.env*)",
"Read(~/.ssh/**)",
"Bash(sudo *)",
"Bash(git push --force*)",
"Bash(git push -f*)"
],
"ask": [
"Bash(git push*)"
]
}
}
Read(…)・Edit(…)・Write(…) は、AI の「読む・直す・書く」道具のこと。かっこの中が対象の場所で、** は「どのフォルダの中でも」の意味ですBash(…) は、ターミナルのコマンドを実行する道具のこと。Bash(sudo *) は「sudo で始まるコマンド」を指します.env と ~/.ssh(接続用の鍵の置き場)は触らせず、管理者権限と強制 push(--force と短い形の -f)は拒否、普通の push は毎回たずねる、という意味ですgit push origin main --force のように途中にオプション(--force のように、コマンドの動きを変える付け足し)が来る書き方は、
文字の並び順が違うだけで「git push --force で始まる」に当たらず、この2行をすり抜けます。
層1だけで強制 push を完全に止められるとは考えず、困るものは層2・3 でも受け止めますpush を deny でなく ask にする理由:普段の push(手元の変更をネット上の保存先へ送る操作)は必要な作業なので、全部拒否すると困ります。 一方、強制 push は履歴を書き換える取り返しのつかない操作なので、たずねるまでもなく拒否にします。 「毎回あるが外へ出る操作」は ask、「やる理由がほぼ無い危険な操作」は denyが分けやすい目安です。
すでに設定ファイルがあるなら、中身を消さずに足します。
Claude Code に案だけ作ってもらい、貼るのは自分でやると確実です。
.claude のように名前が「.」で始まるフォルダは Finder(Mac のファイル管理アプリ)では隠れているので、
まず設定ファイルをターミナルから開きます。
① 貼る場所:ターミナル
mkdir -p ~/.claude
touch ~/.claude/settings.json
cp -n ~/.claude/settings.json ~/.claude/settings.json.bak
open -e ~/.claude/settings.json
mkdir -p はフォルダを作る命令、touch は空のファイルを作る命令です。
どちらも、すでにあれば中身はそのままなので、ファイルがある人もまだ無い人も同じ手順で進められます。
cp -n は、元の設定を settings.json.bak という名前で控えとして残す命令です(すでに控えがあれば上書きしません)。
貼り替えて設定が消えても、この控えから戻せます。
最後の行で、テキストエディット(Mac 付属のメモ帳のようなアプリ)に設定ファイルが開きます。
② 貼る場所:Claude Code の入力欄(ターミナルには貼りません)
~/.claude/settings.json に、次の禁止を足したい。
・.env を含む名前のファイルは、読む・直す・書くを拒否
・~/.ssh の中は読めないようにする
・sudo と、git push の強制(--force)は拒否
・git push は毎回たずねる
今のファイルの中身を読んで、既存の設定は消さずに、足したあとの全文を見せて。
ファイルへの書き込みはしないで。
③ Claude Code が出した全文を、①で開いたテキストエディットに貼ります。 ファイルがまだ空だった人は、そのまま全文を貼ります。すでに中身があった人は、全部を選んで(⌘A)全文に置き換えます。 最後に ⌘S で保存します。
保存したら、ファイルが壊れていないかを機械に確かめさせます。
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 は起動時に設定を読むので、開いているあいだの画面には効きません。いったん終了して、起動し直してください。
層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 で試します)。
STEP 3 — 目安 15分 / この記事の山場
フックとは、「ある瞬間に、自動で走る小さなプログラム」のことです。 Claude Code では、AI が道具を使う直前に走らせられます。 そこでコマンドの中身を検査して、止めるか通すかを決めます。
決め方は、LAB 010 でも使った終了コードです。 フックが 2 で終われば「止める」、0 で終われば「通す」。 止めるときに標準エラー出力(エラー用の出力先)へ書いた文章は、理由として AI に返ります。
例として、層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
command がそのコマンドの文章ですsys.exit(2) が「止める」の合図で、直前の sys.stderr.write(…) の文章が理由として AI に届きます
次に、フックを呼び出す設定を、STEP 2 の settings.json に足します。
ここも 2-1 と同じく、Claude Code に足したあとの全文を出してもらい、自分で貼り直すと確実です。
2-1 の①の最後の行(open -e)で、テキストエディットを開いておいてください。
貼る場所:Claude Code の入力欄(ターミナルには貼りません)
~/.claude/settings.json に、Bash を実行する直前に ~/.claude/hooks/guard.py を走らせるフックを足したい。
今のファイルの中身を読んで、既存の設定(permissions など)は消さずに、足したあとの全文を見せて。
ファイルへの書き込みはしないで。
出てきた全文を、2-1 の③と同じ手順で貼って保存します。足したあとは、2-1 の検査コマンドを必ずもう一度走らせてください。
下は、足される部分の見本です。
"permissions" と同じ階層(いちばん外側の { } の中)で、
"permissions": { … } の閉じ括弧の後ろにカンマ(,)を付けて並びます。
カンマを忘れると全体が読めなくなります。貼る場所の確認用で、これだけを貼るものではありません。
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "/usr/bin/python3 ~/.claude/hooks/guard.py" }
]
}
]
}
読み方:PreToolUse は「道具を使う直前」、
matcher: "Bash" は「ターミナルのコマンドのときだけ」の意味です。
フックはただのプログラムなので、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行目は、「 " 」で囲んだ文章は、まとめて1つの言葉として扱われます。 「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枚とも本当に止まるかをダミーで確かめます。
LAB 013
層3:サンドボックスと、壁を試して配る
箱の中だけでコマンドを動かすサンドボックスで、すり抜けを止めます。 そのあと3枚の壁にダミーを当て、2台目の Mac へも配ります。 →