ClaudeCode を利用し始めてもうすぐ1年ほどが経ちそうですが、最近悩みがあります。複数セッションを並行で走らせていると、どのセッションが止まっているか分からないこと。そして、作業許諾を求められるたびに内容を確認せずエンターを押す癖がついてしまったことです。
だったら自分を信用しないほうがいいと思い、セキュリティリスクのある挙動は AI 側に監視させるツールを作ろうと考えました。こうして生まれたのが CC Pipeline(仮称) です。
ClaudeCode の Hooks を使った、自分用の監視ダッシュボード。ローカル完結、外部サービス不要、Python 標準ライブラリだけで動く個人ツールです。最終ゴールはリスク検知時の自動停止ですが、この記事では可視化・検知・致命的操作のブロックまでを扱います。
- 第1部: フック(hook.py)でイベントを拾い、基板風SVGダッシュボードでリアルタイム表示
- 第2部: 致命的操作のブロック(security_guard.py)と疑わしい操作の警告表示を追加
完成形
まず完成形をお見せします。
CC Pipeline は、ClaudeCode が今どの処理フェーズにいるかを「基板(circuit board)」風の SVG 図で可視化します。このデザインにしたのは、エージェントのループが電子回路の信号フローっぽいな、と思ったからです。データが各ステージを流れていく様子を、基板上の信号が配線を走るように表現しました。
SESSION -> PROMPT -> THINK -+- EXPLORE EDIT VERIFY RENDER SUBAGENT
|
+- GATE / COMPACT
-> DONE
表示は3色だけです。色選びは地味にこだわったところです。暗い背景に映えつつ、色覚多様性にも配慮して、明度と彩度が異なる3色を選びました。
- 琥珀(amber): 実行中 -- 温かみのある橙で「動いてる感」を出したかった
- 珊瑚(coral): ユーザー待ち -- 離れた席からでも「止まってる」と分かる警告色
- ミント(mint): 完了 -- 落ち着いた色で「もう見なくていい」を表現
なかでも承認待ち(GATE)は離れた席からでも分かる強さで表示されます。これが作った一番の動機でした。複数セッションを併走させているとき、どれが止まっているか一目で分かります。
ダッシュボードには NOW パネルと TRACE テーブルもあります:
- NOW: 現在実行中のツール名、引数の要約、経過秒数
- TRACE: ツール呼び出し履歴(直近14件)、各ステージの色でマーク
アーキテクチャ
ClaudeCode ──(hook: stdin JSON)──> hook.py ──> state.json
|
Browser <──(HTTP / no-store)── serve.py <─────────────┘
設計判断のポイントは「常駐プロセスをフック側に持たせない」ことです。
-
hook.py: フックイベントを1件受けて
state.jsonを更新して終了する短命プロセス。ツール呼び出しのたびに起動し、数十ミリ秒で終わる -
state.json: 全セッションの現在状態を持つ単一ファイル。原子的に差し替え(
tempfile+os.replace) -
serve.py:
index.htmlとstate.jsonを 127.0.0.1 限定で配信。Cache-Control: no-store - index.html: 単一ファイルのダッシュボード。ビルド不要。500ms ポーリング
なぜ常駐させないか。最初は WebSocket で常駐サーバにイベントを流す方式も考えました。でもフックは async: true でも重い処理はツール実行に影響しますし、常駐プロセスがクラッシュしたときに「ダッシュボードが壊れたからClaudeCodeも動かない」は本末転倒です。1回の呼び出しで完結するほうがシンプルで壊れにくい。「監視ツールが監視対象の邪魔をしない」が最優先でした。
hooks の仕組み
ClaudeCode の hooks 機能を使ったことがない方向けに、簡単に説明します。
hooks は ClaudeCode の各イベントで外部コマンドを実行できる機能です。~/.claude/settings.json に登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python",
"args": ["C:\\Users\\<user>\\.claude\\pipeline\\hook.py"],
"async": true,
"timeout": 5
}
]
}
]
}
}
フックに渡されるのは標準入力経由の JSON です。hook_event_name, session_id, tool_name, tool_input などが含まれます。
登録するイベントは以下です:
-
SessionStart/SessionEnd: セッションの開始・終了 -
UserPromptSubmit: ユーザーがプロンプトを送信 -
PreToolUse/PostToolUse/PostToolUseFailure: ツール呼び出しの前後 -
Notification: 承認待ち・アイドル待ちの検出 -
SubagentStart/SubagentStop: サブエージェントの出入り -
PreCompact/PostCompact: コンテキスト圧縮 -
Stop: ターン終了
hook.py の絶対条件は3つです。これを1つでも破ると ClaudeCode の動作がおかしくなります。自分は全部踏みました。これが大失敗でした。
-
必ず exit 0 で終わる。
hooks の仕様上、exit 2 はそのツール呼び出しを拒否する意味になります。監視用フックで exit 2 を返すと、正常な操作まで全部ブロックしてしまいます -
stdout に何も出力しない。
hooks の stdout はモデルへのフィードバックとして扱われます。監視スクリプトのログや進捗が意図せずモデルのコンテキストに混入し、応答がおかしくなります -
async: trueで登録する。
sync(async:false)はフックの完了を待ってからツールを実行します。state.json への書き込みを含む監視処理を sync で動かすと、毎回のツール実行が遅延します
開発で踏んだ落とし穴ベスト5
先行試作で実際に踏んだ落とし穴から、同じように hooks を触る方が踏みそうなものを5つ紹介します。ここが今回一番しんどかったところです。hooks は async: true で動くため、失敗しても表に出ません。「あれ、なんか動いてない気がする...」と思ってから原因を突き止めるまでが長い。
なお、原因の特定と修正は全て ClaudeCode がやってくれました。自分がやったのは「動かない」「おかしい」と伝えることだけです。ただ、問題の存在に気付くまでが長かった。
1. Python の stdin が Windows で CP932 になる
症状: 日本語のプロンプトやファイルパスが 繧・縺 の並びに化ける。
原因: Windows の Python は sys.stdin をロケール(CP932)で開く。ClaudeCode が送る JSON は UTF-8。
対策: sys.stdin.buffer.read().decode("utf-8", "replace") で読む。
# NG
raw = sys.stdin.read()
# OK
raw = sys.stdin.buffer.read().decode("utf-8", "replace")
sys.stdin.read() は使わないでください。出力側も encoding="utf-8" を明示します。文字化けした JSON を json.loads に渡してもエラーメッセージ自体が文字化けしていて、何が起きてるか全く分からなかったです。「動かない」と伝えたら ClaudeCode が原因を特定して buffer.read() に書き換えてくれました。
2. python3 が Microsoft Store のエイリアス
症状: フックが無言で失敗する。async 実行なのでエラーが表に出ない。
対策: コマンドは python3 ではなく python を使う。ClaudeCode がインストーラに検査処理を入れてくれました:
$py = Get-Command python -ErrorAction SilentlyContinue
if ($py.Source -like "*\WindowsApps\*") {
throw "python points to Microsoft Store app alias."
}
3. ~ が exec form で展開されない
症状: フックは登録されているのに state.json が作られない。
原因: exec form ではシェルを介さないため ~ は文字列のまま渡る。PowerShell 経由でも引数として渡した ~ は展開されない。
対策: 絶対パスを使う。ClaudeCode がパスの JSON エスケープも json.dumps() で処理するように修正してくれました。
{
"args": ["C:\\Users\\ntaka\\.claude\\pipeline\\hook.py"]
}
4. Windows に fcntl が無い
症状: 並列ツール呼び出しや複数セッションで state.json の更新が落ちる。
原因: ClaudeCode は複数ツールを並列で呼ぶことがある。ロックなしでは書き込みが競合する。
対策: ClaudeCode が fcntl がある環境は flock、無ければ os.open(..., O_CREAT|O_EXCL) の排他生成をロックとして使うクロスプラットフォーム実装を書いてくれました。
try:
import fcntl
except ImportError:
fcntl = None
def _lock_exclusive_create(fn):
fd = None
deadline = time.time() + 3.0
while True:
try:
fd = os.open(LOCK, os.O_CREAT | os.O_EXCL | os.O_RDWR)
break
except FileExistsError:
if time.time() - os.path.getmtime(LOCK) > 5:
os.unlink(LOCK) # 古いロックは奪い返す
continue
if time.time() > deadline:
break
time.sleep(0.01)
except OSError:
break
try:
fn()
finally:
if fd is not None:
os.close(fd)
os.unlink(LOCK)
5. ExitPlanMode の分類ミス
症状: プラン承認待ちで停止しているのに THINK 表示になる。
原因: ExitPlanMode を思考系ツールに分類していた。実際は呼んだ時点でユーザーの応答待ちに入り、PostToolUse はユーザーが答えるまで発火しない。
対策: 表示がおかしいと伝えたら、ClaudeCode が ExitPlanMode と AskUserQuestion を gate(ユーザー待ち)に再分類してくれました。
TOOL_STAGE = {
# ...
"AskUserQuestion": "gate",
"ExitPlanMode": "gate",
}
ClaudeCode と一緒に作る開発フロー
今回の開発は ClaudeCode 自身とペアプロで進めました。ClaudeCode を監視するツールを ClaudeCode に作らせるというメタな構図です。ClaudeCode がダッシュボードの hook.py を修正すると、その修正作業自体がダッシュボードに表示される。自分のツールが自分を監視してる、変な感じでした。
実際にうまくいった進め方を紹介します。
SPEC.md を単一の情報源として先に書く
実装を始める前に、要件・制約・既知の落とし穴・受け入れ基準をすべて SPEC.md にまとめました。ClaudeCode のセッションが長くなると、会話の先頭にある制約が薄れがちです。ファイルにしておけば、いつでも読み直せます。
DEV_PROMPT.md でキックオフ
セッション開始時に貼るプロンプトを DEV_PROMPT.md として用意しました。
まず SPEC.md を読み込んでほしい。特にセクション9「既知の落とし穴」は、
先行の試作で実際に踏んで原因を特定済みのものだけを並べてある。
ここに書かれた対策はすでに検証されているので、別の方法を思いついても
理由なく置き換えないでほしい。
進める前に、以下の3点について案と根拠を出してほしい。まだコードは書かないで。
1. 実装言語
2. 配布形態
3. ダッシュボードの実装方式
最初にコードを書かせないのがポイントです。実装言語・配布形態・ビルド有無は後から変えると全体に波及します。ここで「Node.js + React で」と言われてから「やっぱり Python 標準ライブラリだけで」に戻すと、全部やり直しです。先に方針を擦り合わせておきます。
落とし穴は「検証済み」と明示する
SPEC.md のセクション9には、先行試作で踏んだ落とし穴を症状・原因・対策の形式で書きました。重要なのは「対策は検証済み」と明示することです。
これを書かないと、ClaudeCode が「もっと良い方法がありますね」と判断して、より一般的な実装に置き換えることがあります。結果、同じ問題を踏み直す。fcntl の代替ロック実装で2回やられました。さすがに焦りました。「反証があるなら根拠を先に出せ」という条件付きにしておくと防げます。
フェーズ順に進め、先回り実装をしない
SPEC.md には開発フェーズを定義しました:
| # | フェーズ | 完了条件 |
|---|---|---|
| 1 | hook.py とデータモデル | 受け入れ基準 1〜11 が通る |
| 2 | serve.py と最小ダッシュボード | 状態がテキストで正しく表示される |
| 3 | 基板の描画と点灯 | 受け入れ基準 12〜17 が通る |
| 4 | インストーラ | 受け入れ基準 18〜20 が通る |
| 5 | ドキュメント | 手順どおりに未設定環境から動く |
各フェーズの完了条件を満たしてから次へ進みます。フェーズをまたいだ先回り実装はしません。ClaudeCode は放っておくと「ついでにUIも整えておきますね」と先回りしがちなので、明確に区切っておくのが大事です。
テストハーネスを最初に作る
フェーズ1の最初にテストハーネスを作りました。JSON を hook.py の stdin に投入して state.json を検証するスクリプトです。
フックは async 実行で stdout も封じているため、失敗が表に出ません。「動いているはず」と思い込んで次に進み、後から「ずっと壊れてた」と気付くパターンが何度かありました。テストがなかったら詰んでいました。
まとめ
所要時間
- Phase 1(hook.py + test): テスト22件作成、全パス
- Phase 2(serve.py + minimal dashboard): テキスト版動作確認
- Phase 3(circuit board UI): SVG基板・ノード点灯・デモモード・760pxフォールバック
- Phase 4(installer): install.ps1/install.sh、テスト5件全パス
- Phase 5(README.md): セットアップ手順・トラブルシューティング
成果物
-
hook.py: フックハンドラ(Python 標準ライブラリのみ、120行) -
serve.py: ダッシュボードサーバ(50行) -
index.html: 単一ファイルダッシュボード(ビルド不要、CSS/JS インライン) -
install.ps1/install.sh: インストーラ
技術スタック
- Python 3.13(標準ライブラリのみ)
- 単一 HTML + インライン CSS/JS(外部依存なし)
- ClaudeCode hooks(exec form, async)
今後の展望
- ステージ別の滞在時間グラフ
- セッション履歴の簡易保存
- 複数マシンからの監視(現在は localhost 限定)
ClaudeCode と一緒に何かを作るとき、「SPEC を先に書く」「落とし穴を検証済みと明示する」「フェーズを区切る」「テストを最初に作る」の4点は、どのプロジェクトでもだいたい効くと思います。同じように悩んでいる方の足しになれば。
第2部: セキュリティ監視機能の追加
ダッシュボードで「見える」ようになりました。次は「防げる」ようにします。
第1部を実際に使っていて、見えるのは安心だけど見えたときにはもう手遅れなケースがあると気付きました。rm -rf / を実行しようとしている瞬間をダッシュボードで目撃しても、止められなければ意味がない。これは光と影の「影」の部分です。hooks の sync モードを使えば、致命的な操作をブロックし、危険な操作を警告としてダッシュボードに表示できます。
2本立てアーキテクチャ
セキュリティ監視は2つのスクリプトに分けました。
| スクリプト | async | 役割 |
|---|---|---|
security_guard.py |
false | 致命的操作をブロック(exit 2 で阻止) |
hook.py |
true | 警告レベルのアラートを記録(ダッシュボードに表示) |
なぜ2本に分けたかというと、sync と async で求められる特性が全く異なるからです。
- sync(async:false)は速くないとツール実行を待たせる。security_guard.py はファイルI/Oを一切行わず、純粋なパターンマッチだけで判定。数ミリ秒で終わる
- async は止められないが詳細を記録できる。hook.py は state.json への書き込みがあるので、sync だとオーバーヘッドが大きい。代わりに警告を記録してダッシュボードに表示する
最初は1本にまとめようとしましたが、「速さ」と「記録の充実さ」を1本で両立するのは無理だと気付きました。割り切って分けた結果、それぞれがシンプルになりました。
settings.json への登録例:
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python",
"args": ["C:\\Users\\<user>\\.claude\\pipeline\\security_guard.py"],
"async": false,
"timeout": 3
}
]
},
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python",
"args": ["C:\\Users\\<user>\\.claude\\pipeline\\hook.py"],
"async": true,
"timeout": 5
}
]
}
]
}
}
ルール一覧
全ルールの一覧です。BLOCK は実行を阻止、WARN はダッシュボードに記録のみです。
| レベル | カテゴリ | 検出対象 | 対象ツール |
|---|---|---|---|
| BLOCK | ファイルシステム破壊 |
rm -rf /、rm -rf ~、rm -rf $HOME
|
Bash, PowerShell |
| BLOCK | ファイルシステム破壊 |
del /s /q C:\、FORMAT
|
Bash, PowerShell |
| BLOCK | ファイルシステム破壊 |
mkfs、dd if=/dev/zero of=/dev/sd*
|
Bash |
| BLOCK | データベース破壊 |
DROP DATABASE、DROP SCHEMA
|
Bash, PowerShell |
| BLOCK | 秘密情報の外部送信 |
curl POST + $AWS_SECRET / $API_KEY / .ssh/id_* 等 |
Bash, PowerShell |
| BLOCK | システムクラッシュ | fork bomb (:()|:&) |
Bash |
| BLOCK | 権限破壊 |
chmod -R 000 /、chmod -R 777 /
|
Bash |
| WARN | 環境変数ファイル |
.env、.env.local 等の読み書き |
Read, Edit, Write |
| WARN | Git 危険操作 |
git push --force、git push -f
|
Bash, PowerShell |
| WARN | Git 危険操作 | git reset --hard |
Bash, PowerShell |
| WARN | 権限変更 | chmod 777 |
Bash |
| WARN | 特権実行 |
sudo コマンド |
Bash |
| WARN | システムディレクトリ |
/etc/ や C:\Windows\ への書き込み |
Write, Edit |
| WARN | 一括削除 |
find -delete、絶対パスへの rm -rf
|
Bash, PowerShell |
| WARN | 外部送信 |
curl -X POST、wget --post
|
Bash, PowerShell |
BLOCK の chmod -R 777 / はルートへの再帰適用だけをブロックします。WARN の chmod 777 はパスに関係なく警告を出します。同じパターンでも影響範囲でレベルが変わるということです。
BLOCK ルールの設計
ここが今回一番悩んだところです。どこからがブロック対象かの線引き。厳しくしすぎると日常の開発が止まり、緩すぎると意味がない。最終的に、実行したら取り返しがつかないものだけに絞りました。
ブロック時は exit 2 を返し、stdout に理由を出力します。
def check(tool, tool_input):
for rule_fn in BLOCK_RULES:
result = rule_fn(tool, tool_input)
if result is not None:
return result
return None
# ブロック時の出力
print(f"BLOCKED: {reason}. Try: {alternative}")
sys.exit(2)
stdout メッセージの効果には驚きました。ClaudeCode はなぜブロックされたか、代わりに何をすべきかを読んで、代替手段を試みてくれます。単に exit 2 で落とすだけだとなぜか失敗したとしか認識せず、同じことを繰り返すことがある。理由と代替案を添えると、ちゃんと方向転換してくれる。hooks 経由でモデルと「対話」できるのは良い発見でした。
ルール関数の例(rm -rf /):
@_rule
def destructive_rm(tool, tool_input):
if tool not in BASH_TOOLS:
return None
cmd = _get_command(tool_input)
if not cmd:
return None
if re.search(r"\brm\s+.*-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+/\s*$", cmd) or \
re.search(r"\brm\s+.*-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+~/?(\s|$|[;&|])", cmd):
return ("rm -rf on root/home would destroy the filesystem",
"rm -rf ./specific-directory to delete only what you need")
return None
WARN ルールの追加
hook.py には警告ルールを追加しました。ブロックするほどではないけれど、「やってるな」と知っておきたい操作です。自分的には .env を読んだときの警告が一番役立っています。ClaudeCode が環境変数ファイルに触ったことを知らないまま過ごすのは、地味に怖いので。
| ルールID | 検出対象 |
|---|---|
env-file-access |
.env、.env.local 等の読み書き |
git-force-push |
git push --force、git push -f
|
git-reset-hard |
git reset --hard |
chmod-permissive |
chmod 777 |
sudo-usage |
sudo コマンド |
system-dir-write |
/etc/ や C:\Windows\ への書き込み |
bulk-delete |
find -delete、絶対パスへの rm -rf
|
external-post |
curl -X POST、wget --post
|
警告ルールの実装パターン:
WARN_RULES = []
def _warn(rule_id):
def decorator(fn):
WARN_RULES.append((rule_id, fn))
return fn
return decorator
@_warn("env-file-access")
def _warn_env(tool, tool_input, cwd):
if tool not in ("Read", "Edit", "Write", "MultiEdit"):
return None
if not isinstance(tool_input, dict):
return None
for key in ("file_path", "path"):
p = str(tool_input.get(key, ""))
if re.search(r"[/\\]\.env(\.[a-zA-Z]+)?$", p) or p == ".env":
return p
return None
PreToolUse で各ルールを検査し、マッチしたら alerts 配列に記録します:
for rule_id, check_fn in WARN_RULES:
alert_detail = check_fn(tool, data.get("tool_input"), cwd)
if alert_detail:
push_alert(s, {
"t": now, "level": "warn", "rule": rule_id,
"tool": tool, "detail": clip(str(alert_detail), 90),
"blocked": False,
})
パターンの重複について。security_guard.py と hook.py の両方に似たパターンがあります(例: chmod 777)。security_guard.py は / へのものだけブロックし、hook.py は 777 全般を警告します。2つのスクリプトは独立プロセスで通信手段がないため、それぞれで完結させています。「DRY じゃない」と気になるかもしれませんが、ここは意図的です。
ダッシュボードの4色目
index.html に赤(--danger)を追加しました。3色で収まっていたのに4色目を足すのは少し悩みましたが、セキュリティアラートを既存の3色で表現すると意味が曖昧になるので、専用の色を用意しました。
:root {
--live: #F2A93B; /* 琥珀: 実行中 */
--done: #6FD3B4; /* ミント: 完了 */
--halt: #EC6A54; /* 珊瑚: ユーザー待ち */
--danger: #E5484D; /* 赤: アラート */
}
SVG ノードにアラート時の赤点灯を追加しました:
.node.alert rect.body { stroke: var(--danger); stroke-width: 2; fill: #2B1A1A; }
.node.alert circle.led { fill: var(--danger); }
.node.alert circle.halo { stroke: var(--danger); animation: ping 1s ease-out infinite; }
.node.alert text.code { fill: var(--danger); }
TRACE テーブルの下に ALERTS パネルを追加しました。直近10件のアラートを表示します。アラートがないときは非表示にして、普段の画面をすっきり保っています。
設計上のこだわりとして、赤は稀であるべきだと考えています。常に赤いダッシュボードは「オオカミ少年」になってしまいます。BLOCK は本当に致命的なものだけ、WARN も「念のため見ておきたい」レベルに絞っています。赤が光ったら本当に注意が必要、という信頼感を維持したかったからです。
(今見ると見にくいなって思っています。。。)
テスト
security_guard.py のテスト(test_security_guard.py): 31件
- ブロック対象:
rm -rf /、rm -rf ~、DROP DATABASE、秘密鍵 POST、fork bomb など - 許可対象:
rm -rf ./node_modules、DROP TABLE、通常の curl GET - 壊れた入力: 空、不正 JSON、配列
- stdout フォーマット:
BLOCKED:プレフィックス、Try:代替案
テスト手法は subprocess で exit code と stdout を検証します:
def run_guard(tool_name, tool_input=None, extra_fields=None):
data = {"hook_event_name": "PreToolUse", "tool_name": tool_name}
if tool_input is not None:
data["tool_input"] = tool_input
result = subprocess.run(
[PYTHON, GUARD_PATH],
input=json.dumps(data).encode("utf-8"),
capture_output=True,
timeout=5,
)
return result
def test_rm_rf_root(self):
r = run_guard("Bash", {"command": "rm -rf /"})
self.assertEqual(r.returncode, 2)
self.assertIn(b"BLOCKED", r.stdout)
hook.py のテスト(test_hook.py): 30件(既存22件 + アラート8件)
アラート関連の追加テスト:
-
.env読み取り →env-file-accessアラート -
git push --force→git-force-pushアラート - 通常ファイル読み取り → アラートなし
- アラートの必須フィールド検証(t, level, rule, tool, detail, blocked)
- アラート上限30件のキャップ
まとめ
hooks の sync/async を使い分ければ「監視」と「防御」を両立できます。
- sync(async:false): 致命的操作をブロック。ファイルI/Oなしで高速に判定
- async(async:true): 警告を記録してダッシュボードに表示。詳細なログを残せる
ルールは Python のハードコードで十分でした。YAML や JSON で設定ファイルを分けることも考えましたが、ルールの追加・変更頻度は低いですし、正規表現のデバッグは Python 内で完結したほうが楽です。設定ファイルでカスタマイズ可能にするのは聞こえが良いですが、個人ツールにそこまでの柔軟性は不要でした。
今後やりたいこと:
- MCP ツール呼び出しへのルール適用
- ブロック履歴の永続化
- ルールのホットリロード(現状はプロセス起動ごとに読み込み)
その先に考えていること(AIDR構想)
自分の中での最終形態は、このツールを AIDR(AI Detection and Response)に発展させることです。
そうとう難しいと思いますが作成に踏み出すだけでも得られる経験があると思うので頑張ってみようと思います。
いま作ったものはエージェントの自己申告(hooks 経由のツール呼び出し情報)だけを見ています。でも本当に怖いのは、エージェントが申告していない副作用です。ツール呼び出しイベントには出てこないプロセス生成、ファイル改変、外部通信。こういったホスト OS 上の実イベントとエージェントのテレメトリを突き合わせて、両者の乖離を検知シグナルにする。それが AIDR の中核です。
具体的には:
| 段階 | 内容 | 状態 |
|---|---|---|
| Phase 1 | 進行状況の可視化 + セキュリティ監視 | 本記事の範囲 |
| Phase 2 | ホストイベント収集・相関エンジン・検知ルール | 構想段階 |
| Phase 3 | 対応・封じ込め(自動停止) | 将来検討 |
Phase 2 で想定している検知シナリオ:
- 作業ディレクトリ外への書き込み(宣言スコープと実パスの照合)
- 宣言なき副作用(ツールイベントに対応しないホストイベント)
- hooks の無効化・改ざん検知
- 間接プロンプトインジェクションの兆候(外部コンテンツ取得直後の挙動急変)
ホストイベント収集(Windows なら ETW / Sysmon、Linux なら auditd / eBPF)、子プロセス追跡、相関エンジン。どれも重い。ただ、ロードマップとしては頭の中にあるので、別記事で設計メモとして書くかもしれません。
第1部・第2部を通して、ClaudeCode の hooks は可視化にも防御にも使い回せる仕組みでした。まだ手探りな部分も多いですが、これからの運用でブラッシュアップしていこうとおもいます。


