数週間前、私たちは Project Glasswing の最初の知見を公開しました。これは、frontier security models を企業のコードベースに向けたときに何が起きるかを調べたものです。また、defensive structures が frontier AI による脅威からインフラと顧客を保護するためにどのように適応するかも検証しました。その後も AI エコシステムは急速に変化し続けており、単一モデルに密着して構築した開発者は、そのモデルが使えなくなったり、より高性能なモデルに取って代わられたときに何が起きるかを既に体験しています。こうした市場の変化は、私たちの核心的主張を補強するだけです:その日その日の先頭にいるモデルがどれであっても、エージェント的ワークフローの未来は単独のモデルやプロンプト、単一のエージェントセッションでは見つかりません。ローカライズされたセキュリティ「スキル」から継続的なフリート全体のスキャンパイプラインへ移行するには、モデルを交換可能なコンポーネントとして扱うアーキテクチャが必要です。単一モデルに依存すると、防御のカバレッジが制限されます。同じシステムが同一のレンズでコードパスを見がちだからです。これを打ち消すために、モデルは頻繁に入れ替え、相互にテストされるべきです。パイプライン全体でモデルを変えることで(例えば、初期発見にモデルAを使い、検証にまったく別のモデルBを使うなど)、脆弱性が異なるロジック集合でクロスチェックされることを確保できます。さらに、真のエンタープライズ規模のハーネスは孤立したリポジトリを超えて、クロスリポジトリ依存関係を辿り、数千の生候補を信頼できるトリアージ済みの修正キューにまで絞り込む必要があります。本記事はそのモデル非依存レイヤーを構築する実践的な方法に焦点を当て、状態管理、誤検知の排除、スケールでのエンドツーエンドのトリアージをどのように管理するかを説明します。
先に挙げておきたい2つの反論
最初の記事では、汎用のコーディングエージェントがなぜこの仕事をこなせないかを説明しました。主な問題は、エージェントが同時に一つの仮説しか持たないこと、実際のリポジトリの一部を扱った後にコンテキストウィンドウが埋まり、コンテキストの圧縮中に情報を失ってしまうことです。詳細はあの記事を参照してください。
ここで想定される2つの質問に答えておきます。
-
「サブエージェントを使えばハーネスは不要では?」
サブエージェントは有用で良い出発点です。しかしセキュリティ分析は、複数回の実行にわたって存続し、コンテキストウィンドウを共有せず、後で再スコープや相互参照ができる何百もの個別調査を必要とします。永続性、重複排除、再開可能性、そして最終的にはフリート全体の依存関係トレースが必要です。これはオーケストレーションの問題であり、プロンプトだけでは到達できません。
-
「この記事は frontier models の広告では?」
いいえ。私たちのアプローチはハーネスを中心に据えており、モデルではありません。脆弱性発見については、その時点で目的に最も適した frontier model を用いて実行します。異なるモデルを同じターゲットに向けると、それぞれが異なる割合のバグを発見します。ハーネスが持続する要素です。自分でシステムを構築するなら、初日からモデル非依存に設計してください。そうすれば任意のモデルを制約なく使う自由が得られます。
すべてはスキルから始まる
私たちは最初に約450行の security-audit スキルを単一リポジトリで実行し、実際のバグが浮かび上がるまでプロンプトを調整しました。後に、そのオーケストレーションを追加してシステム全体の配管となる部分を作りました。真の価値はプロンプトそのものにあり、我々のプロンプトは最初のスキルが持っていた攻撃者シナリオ、バグクラス、アンチパターン検出をほぼそのまま保持しています。
そのスキルは1セッションで7フェーズの監査を実行するように書かれていました:
- 3つの並列リサーチエージェントが再con(recon)を行い、architecture.md を作成する。
- 各攻撃クラスごとに1つの Hunter エージェントがコードを破ることを試みる(レビューではなく破壊的検査)。
- 逆向き(adversarial)なバリデータが各検出を否定しようとする。
- 生き残ったものは人間が読める脆弱性レポートとしてまとめられる。
- 同時に findings.json としてスキーマに従い_emit_され、機械的チェックでそのファイルが検証される。
- 最後に、新しいエージェントが独立してすべての発見をソースに対して再検証する。
生き残って再検証された発見は ingest API に提出されます。
この最初のスキルは後のハーネスにほぼそのまま対応しています:
| Skill phase | Harness stage |
|---|
| Recon agents write architecture.md | Recon |
| Hunter agents run per class attack | Hunt |
| Adversarial validators disprove each finding | Validate |
| Surviving findings become a report | Report |
| findings.json is checked mechanically for schema adherence | Mechanical validation of line numbers and functions in findings |
| Fresh agent re-verifies findings | Independent validation |
スキルは機能しましたが、すぐに限界が明らかになりました。カバレッジ指標を見ると、単一実行では複数回実行したときに捕捉できるバグの約半分しか見つかりません。私たちの経験では、見つかったものはより単純で目立つものに偏っていました。プロセスが基本的に「10回実行して手で差分を取る」になっているなら、本格的なハーネスを検討する必要があります。
スキルを実行・微調整しているときに、私たちは3つの壁にぶつかりました:
- コンテキストの枯渇:1時間ほどでコンテキストウィンドウが埋まり、モデルが自分のメモリを食い合い、午前中に追跡したバグを即座に忘れてしまう。
- これを打破するため、状態を完全に外部化し、LLM をステートレスな計算エンジンとして扱いました。
- 永続性:実行中にクラッシュすると最初からやり直し。AI のレート制限エラーや接続不安定で数時間の作業を失うのは高くつきます。
- クロスリポジトリ推論:単一リポジトリのセッションはそれを消費するアプリケーションとの関係に完全に盲目です。コンポーネント間のインターフェースを検査すると見つかるバグの数は、予想より多いことが多い。
ADVICE: 実用的で最小限のハーネスは、データベースに保持される Recon、Hunt、Validate のステージと、独自に検出をファイル化できない別個の Validator から成ります。重要なリポジトリが1つ以上あるまではクロスリポジトリトレースは飛ばして構いません。ノイズに溺れるようになるまでは専用の Deduplication エージェントも不要です。まずは開発環境でスキルを作り、プロンプトを良好に動作させ、必要になったら次のアーキテクチャ段階を構築してください。ないことがボトルネックになっている場合にのみ追加する、という姿勢です。
スキルをパイプラインへ形式化する
この分野の多くの AI セキュリティ記事は単一リポジトリやキュレートされたベンチマークについてです。フリート全体をこの方法で、かつクロスリポジトリトレース付きで走らせる話はほとんど書かれていません。我々のコードベースは Rust、Go、C、Lua、TypeScript、Python といった多数の言語に加え、様々な構成管理システムや静的設定、追加コンテキストで構成されています。そこで我々は自分たちに合った新しい方法を考案しました。
最初のスラッシュコマンド実行から、128 の異なるリポジトリをカバーし、自動的に関連依存関係を発見して問い詰めるフリートスキャナへ移行するのに約6週間かかりました。形式化はほぼ機械的でした:スキルの各フェーズをそれぞれのエージェントに持ち上げ、背後にデータベースを入れ、前にオーケストレータを置きました。対応はほぼ1対1です。フリート全体は1つの統一ハーネスで動き、言語ごとの調整は不要で、リポジトリ間の依存をトレースします。
構文をモデルに委譲することでシステムは言語非依存になりますが、差別化要因はリポジトリ間の依存を辿る能力です。ハーネス自体は C のポインタを見ているか TypeScript ファイルを見ているかは気にせず、セキュリティオーケストレーションという上位レベルのロジックに集中します。これにより、多数の異なるコードベースに対してカスタム言語解析を書かずにスケールできます。
2段階の脆弱性調査ワークフロー
我々の脆弱性調査ワークフローは、Vulnerability Discovery Harness (VDH) と Vulnerability Validation System (VVS) の2段階の運用フレームワークに基づいて構築されています。
- VDH はディスカバリエンジンとして機能し、コードベースを能動的にスキャンして潜在的なセキュリティ問題を表面化させます。
- バグが VVS に入ると(複数のハーネスがここにフィードできる)、Deduplication、Judgment、そして最終的な Fixing の各段階を通ります。
我々は VDH にあるモデルを使い、VVS にはまったく異なるモデルを使います。つまりモデル同士が互いにダブルチェックする形になります。これは明白なセキュリティ上の利点をもたらします:Model A(VDH)の出力を Model B(VVS)に判断させることで、発見が全く異なる論理的重みや訓練データセットを持つ評価にかけられ、Model A の仮定を厳しくストレステストする偏りのない敵対的な第三者の役割を果たします。
運用面では、モデル提供者を交換可能なコモディティとして扱える利点もあります。モデル提供者は時間経過や同一モデルバージョン内でも温度(temperature)、キャッシュ、推論リソース予算を変更する可能性があります。モデルが時間的に予測可能に振る舞うことに依存するシステムを作る代わりに、私たちのハーネスは下流の変動を吸収して壊れないように作られています。
ステージ1:Vulnerability Discovery Harness (VDH)
最初の記事では各エージェント/ステージの役割を説明しました。ここでは主に、ステージ間の接着剤(glue)と、その可否を左右するいくつかの重要な詳細について話します。
| エージェント/ステージ | 主な役割 | サブエージェント / ツーリング |
|---|
| Recon | ターゲットのアーキテクチャをマップし、潜在的な脅威ベクトルを洗い出す | 3並列の Recon サブエージェントが architecture.md を書く |
| Hunt | クラス毎の攻撃を実行し、断片をまとめ、バイナリをプローブする | 攻撃クラスごとに兄弟プロセスを生成(モデルによってフリート全体タスクの9%〜20%を処理)。Wishlist ツールにアクセスし書き込む。 |
| Validate | 機械的に検出をチェックし、その後敵対的に否定する | 2パスで動く:まずスキーマ/パスの初期チェックを行うコードパス、次に単一の孤立したエージェントが検出の否定を試みる |
| Gapfill | 空のカバレッジセルに対して新しい Hunt タスクを生成する | 未検証(領域 × 攻撃クラス)セルを検出して新しい Hunt タスクをキューに入れる |
| Dedup | 重複する検出を特定・統合する | 決定論的コードとエージェントを組み合わせ、根本原因でクラスタ化してリアルタイムに折り畳む |
| Trace | 依存グラフを歩き、消費者リポジトリのタスクをスパーンする | グラフを辿り、特定された各消費者リポジトリ内に Hunt タスクを付加してクロスリポジトリバグを捕らえる |
| Feedback | 既存レポートから学び、将来の実行を最適化する | 検証失敗、浅い実行、繰り返し見逃しを取り込み、キュー内のプロンプトを即座に書き換えて次のタスクを鋭くする |
| Report | 人間向けレポートをレンダリングする | スクリプトだけ(モデル不要) |
Table 1: Vulnerability Discovery Harness (VDH)
ステージ4〜8は継続的なプロデューサー・コンシューマーループとして動きます。初期のハントが進むにつれて、Gapfill、Feedback、Trace エージェントが新しいタスクを生成し、Dedup がオーバーラップする検出を折り畳み、ループの残りがキューを消費し続けます。これにより、サイクルの後半で発見された脆弱性も検証され、報告され、他のコードに対して同じバグが含まれていないかチェックされ、同じ実行内で処理されることが保証されます。
このようにパイプラインを分割することで厳格なコンテキストコントロールが保証されます。コンテキストウィンドウが埋まるとモデルは幻覚を始めます。各エージェントの仕事を非常に絞り込み、コンテキスト使用量を総ウィンドウの25%未満に抑えています。単純な「read all files」アプローチはこの制限を必ず超えます。
我々がハマった一つの落とし穴は、並列化の前に永続性を組み込む必要があるという点でした。想定外のエラーで5時間の実行を捨てたくはありません。すべてのステージは ( run_id , repo , stage ) をキーとする1つの SQLite データベースに書き込みます。任意のステージは再開、リトライ、あるいは後の実行に引き込まれても作業をやり直す必要はありません。検出は発生した時点でストリーミングされ保存されます。
Findings are streamed and saved as they happen,
結果は発生時にストリーミングされ保存されます。