AI セキュリティエージェント
Claude Code の 2 つのコマンドで 8 つのセキュリティサブエージェントを起動。フェーズ 1 で SaaS の RLS ギャップと認証バグをスキャンし、フェーズ 2 で本物の脆弱性を攻撃して確認します。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。
あなたのアプリにはセキュリティの穴があります。すべてのアプリにあります。問題はユーザーより先に自分が見つけられるかどうかです。
セキュリティの穴とは実際にこういうものです。誰かがサインアップします。あちこち触ります。URL バーの数字を変えます。すると別のユーザーのプライベートデータが見えます。請求情報、保存したドキュメント、個人メッセージ。その人がハッカーだからではありません。「自分のものしか見られない」というルールがアプリに存在しなかっただけです。
ほとんどの SaaS アプリがこの種のバグを抱えてリリースしています。映画のようなハックではありません。ただ「あなたは自分のものだけ見られる」というルールが抜けているだけです。
この記事では、こうしたバグを自動で検出するシステムを紹介します。2 つのコマンド。8 つの AI エージェント。フェーズ 1 で問題に見えるものをすべて洗い出し、フェーズ 2 で実際に侵入を試みて本物の問題だけを証明します。確認された脆弱性だけが最終レポートに残ります。
ほとんどの SaaS アプリが抱える 5 つの穴
ソロファウンダー、インディーハッカー、バイブコーダーが作ったアプリで最もよくあるセキュリティ問題です。どれもハッキングのスキルは不要です。ブラウザの開発者ツールを持つ好奇心旺盛なユーザーがほとんどを見つけられます。
1. ユーザー同士のデータが見える
ユーザーデータを保存するデータベーステーブルを作ります。プロフィール、ドキュメント、設定など。デフォルトでは、多くのデータベースは誰がデータを要求しているかを気にしません。誰かがリクエストすれば、そのまま返します。
修正は「リクエストしている人が所有する行だけを返す」というルールを設けることです。データベース用語では Row-Level Security と呼ばれます。すべてのクエリに「WHERE user_id = ログイン中のユーザー」を自動で追加するフィルターだと考えてください。これがなければ、ログイン中のユーザーは誰でも他のユーザーのデータをリクエストできます。
SaaS アプリで最もよくあるセキュリティホールです。URL の ID を変えるか、ブラウザの開発者ツールを開けば、本来見られないものが見えます。
2. ログインを飛ばしてバックエンドに直接アクセス
アプリにはログインページがあります。その先にはデータを取得・変更する API エンドポイントがあります。フロントエンドは常にリクエストにログイントークンを付けます。だからすべてのリクエストに有効なトークンがあると思い込みます。
でも curl や Postman を使えば、フロントエンドを通わずに API エンドポイントを直接呼べます。バックエンドがすべてのリクエストで有効なトークンを確認していなければ、そのままアクセスできます。ログイン不要で。
3. シークレットキーがブラウザに露出している
アプリは外部サービスと連携します。決済、メール、AI API。それぞれのサービスがシークレットキーを提供します。ブラウザに露出しても安全なもの(Stripe の公開可能キーなど)と、そうでないもの(Stripe のシークレットキー、メール API キー、データベース管理者キーなど)があります。
シークレットキーがフロントエンドのコードに含まれると、誰でも開発者ツールを開いてそれを見つけ、使えます。あなたのアカウントからメールを送ったり、クレジットカードに課金したり、完全な管理者権限でデータベースにアクセスできます。
4. API が必要以上の情報を返している
ユーザープロフィールのエンドポイントが、フロントエンドで表示するためにユーザーのデータを返します。でもバックエンドはフロントエンドが必要とするものだけでなく、データベースの行のすべてを返します。メール、フルネーム、内部 ID、サブスクリプションのステータス、場合によってはパスワードハッシュまで。
ユーザーが API を呼んで自分のデータが返ってきます。問題ないように見えます。でもレスポンスにはフロントエンドが使わない 15 個の余分なフィールドが含まれています。これで内部データ構造がわかります。さらに穴 #1 も存在すれば、システム内のすべてのユーザーのこの詳細情報を取得できます。
エラーメッセージも同じです。何かが失敗したとき、アプリが生のデータベースエラーを返すことがあります。「テーブル 'users' に 'stripe_customer_id' カラムが存在しません」というメッセージは、攻撃者にデータベースの構造を正確に教えます。
5. ブラウザのセキュリティルールが設定されていない
ブラウザにはセキュリティ機能が組み込まれていますが、アプリが有効にしないと動きません。これはサーバーがすべてのレスポンスと一緒に送る HTTP ヘッダーです。ブラウザに次のようなことを伝えます:
- 「他のサイトが私のアプリをフレームに埋め込まないで」(クリックジャッキング防止)
- 「明示的に許可したスクリプトだけ動かして」(コードインジェクション防止)
- 「自分のドメインからのリクエストだけ受け付けて」(クロスサイト攻撃防止)
これらのヘッダーがなければ、ブラウザがブロックするよう設計された攻撃にさらされます。ほとんどのフレームワークはデフォルトで設定しません。
セキュリティスキャナーが役に立たない理由
コードのセキュリティ問題をスキャンするツールはあります。問題はこれです。見た目に問題があるものはすべてフラグを立てます、それが実際には正しくても。
例を挙げます。アプリに決済を処理するバックグラウンドジョブがあります。バックグラウンドジョブにはログインしているユーザーがいないため、動作するには管理者レベルのデータベースアクセスが必要です。スキャナーは「管理者データベースアクセス」を見て重大な脆弱性としてフラグを立てます。でもこれは正しい設計です。バックグラウンドジョブはそのアクセスが必要なのです。バグではなく、機能の動き方です。
これをフォールスポジティブと呼びます。危険に見えるが実際には問題ないものにフラグが立ちます。
実際のスキャンではこれが常に起きます。スキャナーは 87 件の問題を報告します。すべて読みます。82 件は設計通りに動いているものです。5 件の本物のバグは偽のアラームの山に埋まっていて、コードベースへの深い知識なしにはどれがどれかわかりません。
核心的な問題: セキュリティツールはビジネスロジックを理解しません。 バックグラウンドジョブに管理者アクセスが必要なことを知りません。「ideas」テーブルが意図的に公開されていることを知りません。オンボーディングフローが意図的に特定の認証パターンを使っていることを知りません。危険に見えるパターンを見てフラグを立てるだけです。
2 フェーズの解決策
パイプラインは 2 つの Claude Code スラッシュコマンドです。それぞれが同時に動く AI エージェントのチームを起動します。
.claude/
commands/
security.md # Phase 1: 5 agents scan your code
pentest.md # Phase 2: 3 agents try to break in
agents/
security-auditor.md # Rules all Phase 1 agents follow
dev/
reports/
security/ # Phase 1 reports
pentest/ # Phase 2 reportsフェーズ 1(レポーター): 5 つのエージェントがコードを読んで上の 5 つの穴を確認します。各エージェントは 1 つの領域に集中します。ライブデータベースにもアクセスできるため、コードが言っていることではなく、実際にデプロイされているものを確認できます。アウトプット: 問題に見えるものすべてを列挙したレポート。
フェーズ 2(エクスプロイター): 3 つのエージェントが動作中のアプリに実際に侵入しようとします。フェーズ 1 のレポートを読んで各発見事項を攻撃します。実際のリクエストを送り、実際の攻撃を試み、何が起きたかを記録します。実際に侵入できなければ、その発見事項はフォールスポジティブとしてマークされて削除されます。アウトプット: 残ったすべての発見事項に証拠が付いた検証済みレポート。
2 フェーズ間のフィルターがこれを機能させます。フェーズ 1 が疑わしいものをすべて捕捉します。フェーズ 2 が何が本物かを証明します。フォールスアラームはフェーズ 2 で死に、あなたの時間を無駄にしません。
フェーズ 1: 5 人のレポーター
各レポーターは 1 種類のセキュリティ問題に集中する AI エージェントです。すべてが同時に動きます。
データベースアクセス監査エージェント
ユーザー同士のデータが見えるかを確認します。ライブデータベースに接続して、コードだけでなく実際のアクセスルールを確認します。ユーザーデータが保存されているが「自分の行だけを返す」ルールが存在しないテーブルを特定します。必要以上の権限を持つデータベース関数も確認します。
入力バリデーション監査エージェント
アプリがユーザー入力を受け付けるすべての箇所を確認します。フォームフィールドにコードを入力して実行させられるか。データベースにコマンドを実行させる文字列を送れるか。../../etc/passwd のような名前のファイルをアップロードして本来読めないファイルを読めるか。このエージェントはそれらすべてをテストします。
ログインとセッション監査エージェント
ログインシステムが堅牢かを確認します。ログインが必要なのにないエンドポイントはあるか。偽または期限切れのトークンで API を呼べるか。通常ユーザーがトークンを調整して管理者機能にアクセスできるか。大量パスワード試行を防ぐ仕組みはあるか。
データ漏洩監査エージェント
アプリが何を露出しているかを確認します。API レスポンスにフロントエンドが使わない余分なフィールドは返っているか。エラーメッセージがデータベースの内部情報を表示しているか。フロントエンドの JavaScript に本来あるべきでないシークレットキーはあるか。ブラウザ履歴やサーバーログが取得できる URL に機密データが含まれていないか。
設定監査エージェント
アプリのセキュリティ設定を確認します。ブラウザのセキュリティヘッダーは有効か。アプリはあらゆるウェブサイトからのリクエストを受け入れるよう設定されているか(そうすべきでありません)。ログインクッキーは正しく設定されているか。依存パッケージに既知の脆弱性はあるか。
5 つすべてが同時に動きます。 それぞれがテキストで発見事項を返します。コードには手を加えません。1 つのオーケストレーターがすべてを読んで 1 つのレポートにまとめます。
フォールスポジティブを減らす鍵: ビジネスロジックの認識
これが通常のセキュリティスキャナーとの違いです。
どのコードベースにも問題に見えるが正しいものがあります。エージェントはそれを事前に知っておく必要があります。そうでなければ、他のスキャナーと同様に毎回フラグを立てます。
エージェント定義には「文書化された例外」というセクションがあります。エージェントが認識してスキップすべきパターンのリストです。例えば:
- 管理者データベースアクセスが必要なバックグラウンドジョブ(ログインユーザーがいないため、管理者アクセスが唯一の方法)
- 意図的に全員が読めるパブリックデータを保存するテーブル
- サインアップ時に追加のユーザー情報を取得する認証パターン(Google からユーザー名を取得するなど、必要な処理)
- ブラウザに置かれることが意図された公開 API キー(ボット対策のサイトキーなど)
- サードパーティサービスが管理するテーブル(決済プロバイダーがデータを自分のテーブルに同期)
各例外は具体的です。どんなパターンが現れたとき、なぜそれが正しいのか。エージェントは発見事項を生成する前に例外リストを確認します。パターンが一致すればスキップします。フォールスアラームなし。
このリストを書くことが最も効果的な 1 つのアクションです。 5〜10 項目から始めてください。監査のたびに通過してきたフォールスポジティブを追加します。3 回目の実行までに、エージェントはノイズの 90% 以上を事前にキャッチするようになります。
エージェントはライブデータベースにも接続して、実際にデプロイされているものを確認できます。コードはあるべき状態を言います。ライブデータベースクエリは実際の状態を示します。マイグレーションスクリプトがテーブルにアクセスコントロールを追加するはずだったのにサイレントに失敗していた場合、ライブクエリがそれを検出します。「コードは問題ないが、デプロイがコードと一致していない」というカテゴリのフォールスアラームをまるごと排除できます。
スコープ設定: 変更された部分だけを確認
毎回コードベース全体で 5 つのエージェントを動かすのはコストがかかります。ほとんどの場合、最後のレポート以降に変更されたものだけを確認すれば十分です。
コマンドはこれを自動で処理します。最後のセキュリティレポートの日時を確認し、それ以降に変更されたファイルを特定して、それらだけをエージェントに渡します。セキュリティ関連の変更がなければ早期終了します。
フルスキャンは最初の監査や大きなリファクタリング後のものです。それ以外は最近の変更だけにスコープを絞ります。
フェーズ 2: 3 人のエクスプロイター
フェーズ 2 はフェーズ 1 のレポートを読んで、動作中のアプリへの実際の侵入を試みます。開発サーバーが動いている必要があります。これらのエージェントは実際のリクエストを送り、実際の攻撃を試みます。
API エクスプロイター
バックエンドのエンドポイントを直接呼びます。穴 #1(他のユーザーのデータにアクセスするための ID の変更)、穴 #2(ログイントークンなしでのエンドポイント呼び出し)、穴 #3(データベースを騙す特殊文字の送信)の攻撃を試みます。すべてのリクエストとレスポンスを証拠として記録します。
ブラウザエクスプロイター
ブラウザでアプリを開き、穴 #4 と #5 の攻撃を試みます。フォームフィールドにコードを入力して実行されるか確認します。アプリが別サイトのページに埋め込めるかチェックします。ログイントークンをコピーしてログアウトし、古いトークンがまだ使えるか試みます。
ログインエクスプロイター
認証に完全に集中します。通常ユーザーとしてログインして管理者機能にアクセスしようとします。ユーザー ID や権限レベルを変えるためにトークンの変更を試みます。レート制限があるか確認するために 50 回の高速ログイン試行を送ります。パスワードリセットのフローでトークンの再利用が可能か確認します。
すべての発見事項には証拠が必要です。 送信した正確なリクエストと返ってきた正確なレスポンス。攻撃が成功したという証拠を出せなければ、その発見事項は削除されます。これがフェーズ 2 がフォールスアラームを排除する理由です。エージェントはバグが存在するかもしれないとただ言うのではなく、実際に攻撃しなければなりません。
フィルターの実際
ある本番 SaaS アプリの実際の監査では、数字はこうなりました:
| ステージ | 件数 |
|---|---|
| フェーズ 1 の報告された問題 | 87 |
| フェーズ 2 でキルされたフォールスアラーム | 82 |
| 確認された本物のバグ | 5 |
| 排除されたノイズ | 94% |
キルされた 82 件の例: バックグラウンドジョブでの管理者データベースアクセス(正しい設計)、ユーザーごとのアクセスルールがないパブリックテーブル(意図的なパブリック)、サインアップ時に使われる特定の認証パターン(その機能に必要)、開発モードでのみ表示される詳細なエラーメッセージ(プロダクションでは非表示)。
確認された 5 件は本物でした。1 件はプロフィールを更新することで誰でも管理者アクセスを自分に付与できるものでした。1 件は誰でも自分のアカウントに無制限のクレジットを追加できるものでした。3 件目は決済フローのオープンリダイレクトで、チェックアウト後にユーザーを偽サイトに送れるものでした。それぞれに具体的な修正箇所が付いていました。
フェーズ 2 なしでは 87 件を渡されてどれが重要かわかりません。フェーズ 2 があれば、攻撃証拠付きの 5 件の本物だけが残ります。
実行方法
コマンドは 2 つ:
/securityフェーズ 1。5 つのエージェントが同時にスキャンします。デフォルトは最後のレポート以降の変更のみ。レポートは dev/reports/security/ に保存されます。深刻な問題を見つけたらフェーズ 2 の実行を促します。
/pentestフェーズ 2。フェーズ 1 のレポートを読みます。開発サーバーが動いていなければ起動します。3 つのエージェントが同時に侵入を試みます。検証済みレポートは dev/reports/pentest/ に保存されます。
| フラグ | コマンド | 動作 |
|---|---|---|
--full | /security | 最近の変更だけでなく全体をスキャン |
--days N | /security | 直近 N 日間の変更を確認 |
--skip-security | /pentest | 再実行せず最新のフェーズ 1 レポートを使用 |
--api-only | /pentest | バックエンドのみテスト |
--browser-only | /pentest | フロントエンドのみテスト |
--auth-only | /pentest | ログインシステムのみテスト |
自分で構築するためのルール
このパターンはどのデータベース、フレームワーク、認証プロバイダーでも動作します。機能させるポイントはここです。
最初のスキャンの前に例外リストを書く。 どのアプリにも問題に見えるが正しいものがあります。エージェントはそのリストを事前に必要とします。正しいとわかっているパターンから始め、各実行から出てくるフォールスポジティブを追加します。2〜3 回の監査でリストが安定します。
コードだけでなく実際のデータベースにアクセスさせる。 コードはあるべき状態を言います。ライブデータベースは実際の状態を示します。その 2 つが一致しない場合、コードでは発見できない問題があります。
発見と証明を分ける。 フェーズ 1 のエージェントが疑わしいものを報告します。フェーズ 2 のエージェントが攻撃します。両方の仕事を 1 つのエージェントに入れると矛盾が生まれます。レポートが多すぎる(ノイジー)か少なすぎる(見落とし)のどちらかになります。仕事が違う 2 フェーズの方が良い結果を出します。
意見ではなく証拠を求める。 エージェントに「これはセキュアか?」と聞かないでください。攻撃が成功したことを証明するリクエストとレスポンスを見せるよう求めてください。証拠は本物の検証を強制します。意見は近道を招きます。
すべての発見事項に修正を付ける。 「セキュリティを改善することを検討してください」ではなく、変更する具体的な行と変更後の内容を。修正のない発見事項はフォルダーに永遠に残る発見事項です。
機能したものも含める。 フェーズ 2 のレポートには失敗した攻撃も列挙すべきです。インジェクションがブロックされた。スクリプト実行がブロックされた。クロスサイトリクエストがブロックされた。アプリが何を防いでいるかを知ることは、防いでいないものを知ることと同じくらい価値があります。
セキュリティを超えた応用
2 フェーズのパターン(まず全部を見つけ、次に本物を証明する)はセキュリティ以外にも使えます。
コードレビュー。 フェーズ 1 のエージェントが潜在的な問題をフラグ。フェーズ 2 のエージェントが問題が本物であることを証明する失敗テストを書く。フォールスポジティブは同じ方法でキル。
パフォーマンス。 フェーズ 1 のエージェントが遅いクエリと大きなファイルを特定。フェーズ 2 のエージェントが実際のベンチマークを実行。実データで 2 ミリ秒かかる「遅いクエリ」は本物の問題ではありません。
コンプライアンス。 フェーズ 1 のエージェントがデータ処理パターンにフラグ。フェーズ 2 のエージェントがデータが実際にどこに流れるかを追跡してフラグが重要かを検証。匿名アナリティクスを処理する関数は、パターンが似ていても、プライバシー同意の処理は必要ありません。
毎回同じ考え方です。コードがなぜ存在するかをエージェントに理解させる十分なコンテキストを与える。発見と証明を分ける。ノイズがあなたに届く前にフィルターする。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。