0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

公開中のAI製Webアプリ200件の91%に脆弱性。細かく指示したら再発率は45.7%に上がった

0
Posted at

fig_eyecatch.png

公開中のバイブコーディング製Webアプリ200件を調べた研究で、91%から少なくとも1つの脆弱性が見つかりました。
意外だったのは、AIに細かい技術指示を書いた条件では、同じ脆弱性の再発率が45.7%まで上がったことです。調査の条件と数字を冷静に見ながら、自分のアプリを公開する前に確認できる5項目まで落とし込みます。

Who am I?

BPR Prot. は、AIで作ったアプリ・Webサービスのローンチ前の最終確認から、公開後の見守りまでを、ひとつのツールで伴走するサービスです。個人開発に励む皆さんが安心してサービスを公開できるように、日々セキュリティまわりの情報を追いかけています。

X(@BPR_Prot)では、セキュリティの最新情報やリリース前に見落としやすいポイント、Claude Code などのAI活用Tipsを日々発信しています。よければご覧ください!

9041件から、公開稼働中の200件を調べた

ITmedia NEWSの報道によると、2026年10月7日、米Microsoftやシンガポール国立大学などに所属する研究者らの論文「Understanding the (In)Security of Vibe-Coded Applications」が報じられました。
https://www.itmedia.co.jp/news/article/2610/07/2000002055/

対象は、公開されているリポジトリを適当に200件選んだものではありません。9041件からWebアプリを選び、実際に公開稼働しているものに絞ったうえで、200件を無作為に抽出しています。

fig_funnel.png

段階 件数
オープンソースとして公開されているバイブコーディング製アプリ 9041件
そのうちWebアプリ 8695件
公開稼働中のWebアプリ 984件
無作為に抽出して調査 200件

調査した200件のうち、91%から少なくとも1つの脆弱性が見つかりました。件数に直すと182件です。

見つかった欠陥は合計1186件で、その65.77%が深刻度「致命的」または「高」でした。元記事では、不正アクセスや乗っ取り、データ漏えいにつながる可能性がある欠陥と説明されています。

91%は「1件以上見つかったアプリ」の割合なので、軽微な問題が1件あったアプリと、深刻な問題が複数あったアプリを区別できません。

ただ、この調査に「人間が書いたアプリ」との比較調査はありません。
冷静に考えると、人力でも脆弱性ゼロのアプリはほとんどないはずです。
この結果だけを見て「AI製は人間製より危険」とまでは言えません。

それでも、公開稼働中の200件から1186件の欠陥が見つかり、その多くが「致命的」または「高」だった事実は残ります。AIを使うかどうかの議論で終えるより、自分が公開するアプリに同じ抜けがないかを確認する材料として使うのがよさそうです。

脆弱性が生まれた原因は3つに分けられている

研究では、脆弱性が生まれる原因を「知識」「目的」「記憶」の3つに分類しています。

fig_causes.png

知識の欠陥 63.4%

もっとも多かったのが知識の欠陥です。
明示されていなくても守るべき暗黙のルールを見落としたり、ユーザーから危険な指示を受けても忠実に実行したりするケースが含まれます。

たとえば「この管理画面を作って」と頼めば、画面そのものは作れても、誰がその操作をしてよいかという認可まで十分に入るとは限りません。ログインの有無と、操作する権限の有無は、コード上では別々に確かめる必要があります。

目的の欠陥 23.1%

目的の欠陥は、安全性よりも手っ取り早く動かすことを優先し、不適切な処理で済ませるものです。

開発中には、エラーを先へ進めるために権限を一時的に緩めたり、入力チェックをあと回しにしたりすることがあります。そこで画面が動くようになると、仮の処理だったことが見えにくくなります。

記憶の欠陥 13.5%

記憶の欠陥は、以前決めた安全対策が途中で抜け落ちるものです。
一部の処理だけ適用を忘れたり、「後で実装する」としたまま公開したりするケースが含まれます。

会話が長くなり、複数のファイルを順番に直していると、最初に決めたルールが後半の実装に反映されないことがあります。似たAPIが5本あるのに、4本だけ認可チェックが入っている、といった状態です。

3分類を見ると、AIにセキュリティ知識を足すだけでは拾い切れない理由が分かります。知識があっても、動作を優先した仮実装や、長い作業の途中で抜けたルールは残りえます。

細かい技術指示では、再発率が45.7%に上がった

特に意外だったのは、条件を変えて同じ作業をやり直させた実験です。

fig_recur.png

条件 同じ脆弱性の再発率
工夫なし 25.7%
「本番運用に耐えるものを」と書き添える 11.0%
セキュリティ強化用のスキルを追加 11.9%
細かい技術指示を書く 45.7%

何も工夫しない条件では25.7%だった再発率が、細かい技術指示を書くと45.7%まで上がりました。「詳しく指示すれば、そのぶん安全になる」とは限らない結果です。

指定された実装に意識が寄り、アプリ全体で守るべき条件の確認が薄くなった可能性はありますが、論文が理由を示したわけではありません。

より上位のAIモデルに変更しても、結果はほとんど改善せず、悪化したケースもあったとITmedia NEWSは報じています。モデルを替えることや技術指示を細かく足すことだけでは、公開前の確認を置き換えられません。

一方で、「本番運用に耐えるものを」と添えた条件では11.0%、セキュリティ用スキルを追加した条件では11.9%でした。
この数字は、研究内の特定条件で同じ脆弱性が再発した割合です。その一文やスキルだけで安全性が決まるわけではありません。

使い方としては、実装を頼むときに目的を短く伝える方法があります。

この機能は公開後の本番運用に耐えるものとして実装してください。

作り終えたあと、別の会話で実装を渡し、作成時とは違う観点から確認させる方法もあります。

この実装の認可の抜けを指摘してください。
修正はまだせず、未ログイン・一般ユーザー・管理者の3つの立場で確認してください。

ただし、AIの自己点検だけで安全性を保証することはできません。クラウドの管理画面、本番環境の権限、実際に配信されたファイルなど、会話の外にある状態は別に確かめます。

原因3分類を、自分への質問に変える

原因の名前を覚えるより、公開前に自分のアプリへ質問する形にすると使いやすくなります。

知らないまま任せた場所はないか

  • ログインしているかだけでなく、誰がどのデータを読めるか、変更できるかまで決めましたか。
  • 秘密情報の置き場所やユーザー入力の扱いを、理由が分からないままAIへ任せていませんか。
  • 管理画面や決済など、影響の大きい機能を優先して見直しましたか。

分からない部分が見つかったら、すぐに修正を頼む前に「今の実装は何を信用しているか」を説明させると、確認する場所を絞りやすくなります。

このAPIが信用している値を列挙してください。
ブラウザから変更できる値と、サーバー側で検証している値を分けて説明してください。

回答は手がかりとして使い、実際のコードと管理画面でも確かめてください。

動かすために入れた仮実装は残っていないか

  • エラーを避けるため、一時的に権限や入力チェックを緩めた箇所はありませんか。
  • 固定のユーザーIDや仮の管理者判定が残っていないか、後回しにした作業とあわせて回収しましたか。

コメントに残したTODOやFIXMEは、次のコマンドでまとめて探せます。

rg -n 'TODO|FIXME|HACK|仮実装|後で実装' . \
  -g '!node_modules' -g '!.next' -g '!dist'

見つかったものがすべて脆弱性というわけではありません。公開を止めるものか、公開後の改善でよいものかを1件ずつ分けます。

途中で決めたルールが全体に残っているか

  • 認可や入力チェックのルールが、同種の画面とAPIすべてに適用されていますか。
  • 古い実装をコピーしたファイルに以前のルールが残っていないか、同じ処理名やAPI名で横断検索しましたか。

指摘がなかった場合も、次の5項目は自分の目と実環境で確かめてください。

公開前に確認したい5項目

すべてのセキュリティ項目を一度に確認するのは大変です。
以下の5項目は研究の内訳ではなく、私たちが公開前に優先したいものとして選びました。事故の影響が大きく、AI製アプリでも抜けを見つけやすい項目から始めてください。

fig_check5.png

1. 認証・認可

認証は「誰がログインしているか」、認可は「その人が何をしてよいか」の確認です。
ログイン機能が動いていても、一般ユーザーが管理者用APIを呼べるなら認可は足りていません。

確認用に、未ログイン・一般ユーザー・管理者の3つの状態を用意します。保護されたページをURLから直接開くだけでなく、ブラウザの開発者ツールにあるNetworkタブでAPIのURLを確認し、それぞれの状態で同じ操作を試してください。

一般ユーザーで次を試してください。

  • /adminのような管理画面を直接開けず、ボタンを隠しただけの管理APIも呼べないか
  • URLやリクエスト中のuserIdを別の値に変えても、他人のデータが返らないか

画面が見えないことだけで判断せず、サーバーの応答も確かめてください。許可していない操作では、データを返さず、処理も実行しない状態にします。

Next.jsなら、ページ側のリダイレクトだけでなく、Route Handler、Server Action、API Routesなど、データを読み書きする入口ごとに認可があるかを検索します。ミドルウェアだけに任せている場合も、個々の処理で権限を確認できているか見てください。

2. 秘密情報

.envを.gitignoreに追加しただけでは、すでにコミットした値や、ビルド時にブラウザ用JavaScriptに入った値までは消えません。

まず、追跡中のファイルに鍵らしいものがないか探してください。

git ls-files | grep -iE '(^|/)\.env|\.pem$|\.p12$|service-account.*\.json$'

次に、gitの履歴も検索します。差分に含まれる一般的な名前を拾うなら、次のコマンドが使えます。

git log -p --all | grep -iE "api[_-]?key|secret"

この検索は候補を探すもので、すべての鍵を検出できるわけではありません。サービス固有の変数名や鍵の接頭辞が分かる場合は、それも検索語に加えます。ripgrep(rg)が無ければgrep -rnで代用できます。

Next.jsでは、NEXT_PUBLIC_で始まる環境変数がブラウザ向けのコードへ埋め込まれます。プロジェクト内の利用箇所を一覧にします。

rg -n 'NEXT_PUBLIC_[A-Z0-9_]+' . \
  -g '!node_modules' -g '!.next'

rgは通常、.gitignoreで除外されたファイルや隠しファイルを検索しません。値は.env.localなどを開いて確かめ、公開される前提のものかを見直してください。クライアント側で使えないというエラーを直すため、秘密の変数にNEXT_PUBLIC_を付けていないかも点検してください。

ビルド後に、秘密として扱う値の「実際の文字列」を成果物から検索する方法もあります。値をコマンド履歴や共有ログへ残さないよう注意し、ローカル環境で対象ディレクトリだけを検索してください。

rg -l --fixed-strings '検索したい実際の値' .next/static

Viteなら通常はdist/、ほかの構成では実際の出力先を対象にします。ファイル名だけを表示する-lを付けておくと、値そのものを端末へ繰り返し出さずに確認できます。

一度でも公開した秘密が見つかったら、コードや履歴から消すだけでは足りません。発行元の管理画面で古い値を無効にし、新しい値に差し替えてください。

3. 入力

ブラウザの入力欄にmaxlengthを付けても、APIに直接送るリクエストには効きません。文字列の長さや形式をサーバー側でも確かめてください。

フォームごとに、少なくとも次を試してください。

  • 空文字や空白だけの値
  • 想定より長い値
  • 数字を期待する場所への文字列
  • 選択肢にない値
  • URLパラメータやJSONから直接送った値

ブラウザの開発者ツールにあるNetworkタブで対象リクエストを確認すると、画面から何が送られているか分かります。APIテスト用のクライアントを使っているなら、同じリクエストを複製して値を変えても構いません。自分が管理する開発環境かテスト環境で行ってください。

Reactを使っている場合は、ユーザー入力をHTMLとして差し込むdangerouslySetInnerHTMLも検索します。

rg -n 'dangerouslySetInnerHTML' . \
  -g '!node_modules' -g '!.next' -g '!dist'

SQLを直接書いているなら、入力値を文字列連結で埋め込まず、利用しているライブラリのプレースホルダーやパラメータ化されたAPIを使います。

サーバーが不正な値を拒否し、データベースに保存せず、内部のエラー内容も伏せているところまで確かめてください。

4. データベースの権限

データベースの権限は、コードだけでなく管理画面にも設定があります。AIとの会話に管理画面の現在値が渡っていなければ、コードレビューだけでは判断できません。

Supabaseを使っている場合は、ダッシュボードで各テーブルのRLSの状態とポリシーを確認してください(メニュー名は変わることがあります)。

  • RLSが有効で、該当するポリシーがない操作は拒否されます
  • RLSが無効なら、APIへ公開されたテーブルが意図せず露出するおそれがあります
  • service_roleはRLSを迂回するため、ブラウザへ渡しません

「RLSを有効にしたらデータが出なくなった」というときに、RLSを無効に戻して解決しないようにしてください。必要なユーザーと操作だけを許可するポリシーを作り、一般ユーザーのアカウントで実際に読み書きを試してください。

ポリシーでは、リクエストから送られたuser_idをそのまま信用していないかを見ます。ログイン中のユーザーIDと、対象行の所有者が一致するときだけ許可する形になっているか確認してください。

Firebaseを使っている場合は、Firebase ConsoleでFirestore DatabaseのRulesとStorageのRulesを開いてください。開発中のallow read, write: if true;や、期限付きのテスト用ルールが残っていないかを調べます。

ルールを直したら、Firebase Emulator SuiteやRules Playgroundなど、使える検証手段で未ログイン・本人・他人の条件を試します。Admin SDKはSecurity Rulesを通らないため、サーバー側では呼び出したユーザーと操作権限をコードで確かめる必要があります。

最後に、新しく追加したテーブルやStorageの保存先も見てください。以前からある場所は守られていても、追加した場所だけ初期設定のまま残ることがあります。

5. 依存パッケージ

AIが提案したパッケージをそのまま追加していると、使わなくなったものや、名前を取り違えたものが残ることがあります。まずpackage.jsonを開き、今も使っている依存だけになっているかを確かめてください。

npmを使っているプロジェクトでは、既知の脆弱性を次のコマンドで確認できます。

npm audit

本番に含まれる依存に絞って確認するなら、次のコマンドも使えます。

npm audit --omit=dev

結果が出たら、件数だけで判断せず、どのパッケージから入っているか、実際に使う経路か、更新で互換性が変わらないかを確認します。npm audit fix --forceは大きなバージョン変更を伴うことがあるため、内容を読まずに実行しないでください。

依存関係の経路はnpm explainで確認できます。

npm explain パッケージ名

更新後は、そのアプリで重要な動作をもう一度試してください。監査結果の件数だけで判断せず、修正による不具合も確かめます。

コピーして使える公開前チェックリスト

公開前のタスクにそのまま貼れる形にしました。使っていないサービスの項目は飛ばして大丈夫です。

認証・認可

  • 未ログインで保護された画面とAPIに入れない
  • 一般ユーザーで管理画面と管理APIを使えない
  • userIdやorderIdを変えても他人のデータを読めない・変更できない
  • ボタンを隠すだけでなく、サーバー側で権限を確認している

秘密情報

  • .envや鍵ファイルがgitの追跡対象に入っていない
  • gitの過去の差分にAPIキーや秘密鍵が残っていない
  • NEXT_PUBLIC_の付いた変数をすべて見直した
  • ビルド後のファイルに秘密の値が含まれていない
  • 一度でも公開した鍵は、発行元で無効化して再発行した

入力

  • 空文字・長すぎる値・形式違いをサーバー側で拒否できる
  • URLパラメータやAPIへ直接送った値も検証している
  • ユーザー入力をHTMLとして無条件に差し込んでいない
  • SQLに入力値を文字列連結で埋め込んでいない

データベースの権限

  • Supabaseの各テーブルでRLSの有効・無効を確認した
  • Supabaseのポリシーを操作ごとに読み、本人以外を拒否できる
  • service_roleをブラウザへ渡していない
  • FirebaseのFirestoreとStorageに全開放ルールが残っていない
  • 新しく追加したテーブルや保存先の権限も確認した

依存パッケージ

  • 不要な依存パッケージが残っていない
  • npm auditなど、利用中の管理ツールで監査した
  • 指摘された依存経路と影響を確認した
  • 更新後にアプリの重要な動作を再確認した

全部に一度でチェックを付けなくても大丈夫です。
まずは認証・認可と秘密情報、データベースの権限から見るだけでも、公開判断の材料はかなり増えます。

今回の調査では、細かい技術指示で同じ脆弱性の再発率が45.7%に上がり、上位モデルへの変更でもほとんど改善しませんでした。

AIへの頼み方を工夫しつつ、最後はできあがったコードと実環境を別々に確かめてください。公開ボタンを押す前に、この5項目を一度通してみてください。



わたしたちについて

最後に少しだけ自己紹介です。

ここまでの項目を一つずつ手で確かめるのは、正直なかなか大変ですよね。
チェック項目があっても、目に見えにくい部分はブラックボックスになりやすいので「いつか何か問題が浮上するのでは」という不安がつきまといます。

BPR Prot. は、AIで作ったWebアプリを公開前に点検するためのサービスで、なかでも手間のかかるところをまとめて診断できるようにしています。

  • サイトのURLを入れるだけで、セキュリティをA〜Fで採点
  • コードに脆弱性がないか診断
  • ファイルにマルウェアが紛れていないか検査
  • 開発中に直接診断を呼び出すMCP連携(Proプラン)

無料でも診断できるので、公開前の最終チェックにぜひお役立てください。

https://bpr-prot.com/?utm_source=qiita&utm_medium=article&utm_campaign=vibe91


Xで話しかけてもらえるとめちゃ嬉しいです!
フォローよろしくお願いします。 @BPR_Prot

最後まで見てくださりありがとうございました。
それでは良い開発ライフを!ノシ

fig_banner.png

出典

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?