証明できる指摘だけを出す仮説駆動セキュリティアセスメントを Claude Code Skill にした
はじめに
以前、Security Audit Skill を作った話 を書きました。あれはレイヤーごとの検出パターン集で、「どこを見ればいいか」を教えてくれるスキルです。
しばらく運用して、足りないものに気づきました。「どこを見るか」と「どう結論づけるか」は別の技術 だということです。
パターン検出は eval() があれば「あった」と言えます。でも AI にセキュリティレビューをやらせると、だいたい 2 つの壊れ方をします。
- 偽陽性: 到達性も悪用可能性も確かめずに、パターンだけで「脆弱性です」と報告する
- サイレントな見落とし: 見ていないコンポーネントを、なんとなく「大丈夫そう」で済ませる
どちらも「何も見つけない」より悪い。前者はレビュー全体の信頼を壊し、後者は嘘の安心を与えます。
そこで、「どう結論づけるか」を司る もう 1 つのスキルを作りました。それが security-assessment です。
スキャンとアセスメントの違い
一言でいうと、問いが違います。
- スキャンが問うのは「このパターンは出現するか?」
- アセスメントが問うのは「このシステムが実際にどう動くかを踏まえて、攻撃者は何を達成でき、それを証明できるか?」
顧客に渡す価値があるのは後者です。長い「かもしれない」リストではなく、証拠に裏付けられた少数の指摘 に落とし込む。スキャナは顧客自身でも回せるのだから、AI がやるべきは「ツールが吐いた候補を、証明できる指摘まで昇格させる(か、正直に落とす)」ことです。
security-audit が「どこを見るか」のライブラリ、security-assessment が「どう結論づけるか」のエンジン。2 つは補完関係で、後者は前者を optional dependency として参照します。
設計の中心にある 3 つの仕組み
1. 仮説駆動 ── falsifier を先に書く
チェックリストは反証できません。「eval() があったから報告」で終わる。だから偽陽性を量産します。
このスキルでは、攻撃者の能力についての 反証可能な 1 文 を仮説として立てます。そして証拠を探す前に falsifier(それを否定する条件)を書く。これが「パターンマッチで結論に飛びつく」のを止める最大のレバーです。
H-007
statement: テナントAのユーザーが、IDを差し替えてテナントBの請求書を取得できる
falsifier: このハンドラに到達する全経路で、クエリ層にテナント述語がかかっている
status: OPEN | REFUTED | SUPPORTED | INCONCLUSIVE
そして 確証より先に反証を探す。「このコントロールはどこに置かれ得るか」(ミドルウェア、ガード、ORM のデフォルトスコープ、DB の行レベルポリシー…)を全部列挙してから、1 つずつ確認する。見つかれば REFUTED(安く、正しく、レポートのノイズにならない)。見つからなければ「意図的に不在を確認した」という、はるかに強い主張になります。
これは実際に効きました。あるアセスメントで、いかにも危険な公開エンドポイント(本番データを書き換えるように見える)を見つけたのですが、反証を先に探したら「本番以外の環境でしか動かないガード」が入っていた。素朴なスキャンなら Critical で誤報していたところです。署名検証がちゃんと実装された Webhook も同様に、先に反証を探すことで「実は守られている」と気づけました。
2. UNKNOWN レジストリ ── 「安全」と「未確認」を分ける
静的解析では確定できないことがあります。「デプロイ済みの IAM 設定で、この公開関数は本当に誰でも叩けるのか」「git 履歴に残った鍵は、今も有効なのか」。
こういうものを 勝手に safe/unsafe と決めない。UNKNOWN として登録します。UNKNOWN が悪用経路上にある限り、その経路の指摘は CONFIRMED に昇格できない ── これはツールが機械的に強制します。
結果として、レポートには必ず「評価できなかった領域」のセクションが付きます。「そこが安全だ」という主張ではなく、「そこは見ていない/確認できなかった、確認するにはこのコマンドを叩けばいい」と正直に書く。正直な『未評価』は、自信満々の推測に勝る、というのがこのスキルの一番の思想です。
3. 証拠グレードと偽陽性トリアージ
すべての候補(ツールの出力でも、サブエージェントの指摘でも、自分の読みでも)は、この順で通過しないと指摘になれません。
候補 → 到達性 → 既存コントロール → 悪用可能性 → 影響 → 分類
証拠には E1〜E6 のグレードがあります。
- E1/E2: 認可環境での挙動の直接観測 / エントリポイントからシンクまで全経路をトレース
- E4: 設定を権威ある情報源から直接読んだ(公開バケット等、設定そのものが脆弱性)
- E5/E6: ツールの出力(未検証)/ フレームワークの慣習・思い込み
E5・E6 単独では絶対に指摘にできない。Semgrep が「Critical」と言っても、それは重大度の根拠になりません(sa validate が弾く)。「ツールがそう言った」を指摘に昇格させることこそ、顧客が金を払って避けてほしいことだからです。
重大度も 計算 します。CVSS 単体は却下。そのエンドポイントがインターネット公開か、背後のデータが給与テーブルかマーケブログか、を CVSS は知らない。T(技術影響)× E(悪用可能性)× B(ビジネス影響)× X(露出) を 1〜4 で採点して、重み付き和でバンドを出します。
マルチエージェントで回す
実際のアセスメントは、こういう構成で回しました。
[メイン] オーケストレーター(設計・トリアージ・昇格)
├── [サブA] バックエンド(認可・インジェクション)
├── [サブB] フロントエンド(XSS・トークン保管)
├── [サブC] シークレット・サプライチェーン・CI/CD
└── [サブD] プライバシー・コンプライアンス
サブエージェントが生むのは 仮説・証拠・候補 まで。指摘への昇格はオーケストレーターだけ が行います。理由があって、あるレイヤーのコントロールが別レイヤーの弱点を打ち消していることがあり、そこがまさに素朴なレイヤー別レビューが偽陽性を出す場所だからです。クロスレイヤーの文脈は、統合する側にしか見えません。
そして オーケストレーターはサブエージェントの報告を鵜呑みにしない。ここが地味に効きました。
あるサブエージェントが「ソースのコメントに個人情報らしき記述が残っている(High 懸念)」と挙げてきました。オーケストレーター(=メイン)が現物を開いて確認したら、値は akdjhjf... みたいなキーボードガチャ文字列と ZZZ... の テストデータ で、実データではなかった。これを FALSE-POSITIVE として記録し、レポートから落としました。
LLM の一番の盲点は「もっともらしいが実在しないコードを、自信たっぷりに語る」ことです。だからスキルには「すべての主張の前に実ファイルを読み直せ」が明文化されています。サブエージェントの要約ではなく、成果物(git diff、ファイルの中身、実際の挙動)を確認する。
なぜ「長いプロンプト」ではなくスキル + CLI なのか
このスキルは、状態を持つ CLI(sa)を同梱しています。プロンプトだけにしなかった理由はいくつかあります。
-
アンチドリフト: 長いアセスメントは、面白いスレッドを追いかけて他の 9 個を忘れることで失敗します。調査フレームを明示的なスタックに積む(
sa stack dumpで今どこにいるかが見える)ことで、これを防ぐ。 - 再開可能・委譲可能: 状態がディスク上の 1 ディレクトリにあるので、セッションをまたいでも、サブエージェントに渡しても続きから回せる。
-
公開ゲート:
sa validateが、証拠のない指摘・ツールを重大度根拠にした指摘・UNKNOWN が残ったままの CONFIRMED・E1/E2 なしの Critical を、機械的に弾く。人間の規律に頼らず、仕組みで品質を強制する。
顧客データはスキルのディレクトリに一切入りません。エンゲージメントごとの作業ディレクトリに隔離され、一般化した学びだけが(サニタイザを通って)スキルに還元されます。
回してみて変わったこと
いくつか、体感が変わりました。
- 指摘が減って、質が上がった。「30 個のうち 20 個がノイズ」ではなく、「6 個の本物」。顧客は 6 個なら直す。
- 『未評価』を堂々と書けるようになった。PASSIVE(静的解析のみ、対象システムへは無通信)で回すと、デプロイ済み IAM や漏洩鍵の現在の有効性は確定できない。それを UNKNOWN として「確認コマンド付きで」渡すのは、失敗ではなく有用な成果物です。
- 指摘が予防に変わった。確定した指摘クラスごとに「次の同種インスタンスを止める CI/lint ガードレール」への写像を出す。レポートは「何が壊れているか」だけでなく「次の 1 件を何が止めるか」まで書いて初めて完成、という思想です。
- 偽陽性がカタログになる。落とした候補は理由付きで記録され、次のアセスメントが同じパターンを再審議しないで済む。
まとめ
- スキャンは「パターンが出現するか」を、アセスメントは「攻撃者が何を達成でき、証明できるか」を問う
- falsifier を先に書く(反証優先)のが、偽陽性を減らす最大のレバー
- 確定できないことは UNKNOWN にして、safe/unsafe を勝手に決めない
- E5/E6(ツール出力・思い込み)単独では指摘にしない。E1/E2 まで昇格させる
- マルチエージェントで集めて、オーケストレーターだけが現物確認して昇格 する
- 状態を持つ CLI +
validateゲートで、品質を人間の規律でなく仕組みで強制する
「AI にセキュリティレビューをやらせる」で終わらせず、「証明できる指摘だけを出させる」ところまで設計に落とすと、実務で使える成果物になります。
姉妹スキル(どこを見るか)はこちら:
Discussion