AIセキュリティエージェント
2つのClaude Codeコマンドで8つのセキュリティサブエージェントを起動。フェーズ1はSaaSロジックのRLSの欠陥と認証バグをスキャンし、フェーズ2は実際の攻撃を試みて本物の脆弱性を確認します。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。
あなたのアプリにはセキュリティの穴があります。どんなアプリにも必ずあります。問題は、ユーザーより先にあなたがそれを見つけられるかどうかです。
セキュリティの穴が実際にどういうものか、例を挙げましょう。誰かがあなたのアプリにサインアップします。あちこちクリックして探索します。URLバーの数字を変えてみます。すると突然、別のユーザーのプライベートデータが見えてしまいます。請求情報、保存されたドキュメント、個人メッセージ。ハッカーだからではありません。あなたのアプリに「自分のデータしか見られない」というルールがないからです。
これが多くのSaaSアプリが抱えている種類のバグです。映画のようなハッキングではありません。「自分のものだけ見られる」というルールが単に欠けているだけです。
この記事では、こうしたバグを自動的に検出するシステムについて説明します。2つのコマンド、8つのAIエージェント、フェーズ1は問題がありそうなものをすべて見つけ、フェーズ2は実際に侵入を試みてどの問題が本物かを証明します。確認されたバグだけが最終レポートに掲載されます。
ほとんどのSaaSアプリが抱える5つの穴
これらは、ソロ創業者、インディハッカー、感覚だけでコーディングする開発者が作ったアプリでよく見られるセキュリティ問題です。どれも悪用するのにハッキングのスキルは不要です。ブラウザの開発者ツールを使う好奇心旺盛なユーザーが、ほとんどを発見できます。
1. ユーザーが互いのデータを見られる
ユーザーデータを保存するデータベーステーブルを作ります。プロフィール、ドキュメント、設定など。デフォルトでは、ほとんどのデータベースは誰がデータを要求しているかを気にしません。誰かが要求すれば、データベースはそれを返します。
修正方法は「リクエストした人に属する行だけを返す」というルールを設けることです。データベース用語では、これを行レベルセキュリティと呼びます。「WHERE user_id = ログインしている人」をすべてのクエリに自動的に追加するフィルタだと考えてください。これがなければ、ログインしている任意のユーザーが他の誰かのデータをリクエストできます。
これはSaaSアプリで最も一般的なセキュリティホールです。ユーザーがURLのIDを変更するか、ブラウザの開発者ツールを開けば、見てはいけないものが見えてしまいます。
2. ログインをスキップしてバックエンドに直接アクセスする
あなたのアプリにはログインページがあります。ログインの背後には、データを取得・変更するAPIエンドポイントがあります。フロントエンドは常にすべてのリクエストにログイントークンを送信します。だからすべてのリクエストに有効なトークンがあると思い込んでいます。
しかし、誰かがフロントエンドをまったく使わずにAPIエンドポイントを直接呼び出せます。curlやPostmanなどのツールを使えばいいのです。バックエンドがすべてのリクエストで有効なログイントークンを確認しなければ、侵入できてしまいます。ログイン不要で。
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 # フェーズ1: 5エージェントがコードをスキャン
pentest.md # フェーズ2: 3エージェントが侵入を試みる
agents/
security-auditor.md # フェーズ1の全エージェントが従うルール
dev/
reports/
security/ # フェーズ1のレポート
pentest/ # フェーズ2のレポートフェーズ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キー(ボット保護のサイトキーなど)
- サードパーティサービスが管理するテーブル(決済プロバイダーが独自のテーブルにデータを同期する)
各例外は具体的です。そのパターンが現れる状況と、それが正しい理由。エージェントは発見事項を生成する前に例外リストを確認します。パターンが一致すればスキップします。誤検知なし。
このリストがあなたが書ける最も効果的なものです。 最初は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回の監査後にリストは安定します。
コードだけでなく実際のデータベースへのアクセスをエージェントに与える。 コードは真であるべきことを示します。ライブデータベースは実際に真であることを示します。それらが一致しない場合、コードが教えてくれない問題があります。
発見と証明を分ける。 フェーズ1のエージェントは疑わしいものを報告します。フェーズ2のエージェントはそれを悪用しようとします。両方の仕事を1つのエージェントに入れると矛盾が生じます。多すぎる報告(ノイズが多い)か、少なすぎる報告(見落とす)になります。異なる仕事を持つ2つのフェーズの方が良い結果を出します。
意見ではなく証拠を要求する。 エージェントに「これは安全か?」と聞かないでください。攻撃が成功したことを証明するリクエストとレスポンスを見せるよう求めてください。証拠は実際の検証を強制します。意見は近道を招きます。
すべての発見事項には修正が必要。 「セキュリティを改善することを検討する」ではありません。変更する具体的な行と変更内容。修正なしの発見事項は永遠にフォルダに置かれたままです。
成功したことも含める。 フェーズ2のレポートには失敗した攻撃もリストすべきです。インジェクションがブロックされた。スクリプト実行がブロックされた。クロスサイトリクエストがブロックされた。アプリが防御していることを知ることは、防御できていないことを知ることと同様に価値があります。
セキュリティを超えて
2フェーズパターン(すべてを見つけ、次に本物を証明する)はセキュリティ以外でも機能します。
コードレビュー。 フェーズ1のエージェントが潜在的な問題をフラグ立てします。フェーズ2のエージェントが問題が本物であることを証明するための失敗するテストを書きます。誤検知は同じ方法で排除されます。
パフォーマンス。 フェーズ1のエージェントが遅いデータベースクエリと大きなファイルバンドルを特定します。フェーズ2のエージェントが実際のベンチマークを実行します。実際のデータで2ミリ秒かかる「遅いクエリ」は本物の問題ではありません。
コンプライアンス。 フェーズ1のエージェントがデータ処理パターンをフラグ立てします。フェーズ2のエージェントがデータが実際にどこに流れるかを追跡してフラグが重要かどうかを確認します。匿名の分析データを処理する関数は、パターンが類似したものに見えても、プライバシーの同意処理を必要としません。
毎回同じ核心的なアイデア。コードがなぜ存在するかを理解するのに十分なコンテキストをエージェントに与える。発見と証明を分ける。ノイズがあなたに届く前にフィルタリングする。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。