
はじめに
どーも、bubobuboです。
バイブコーディングが急速に広まっています。私自身もプライベートでClaude CodeやCodexを使っていると、ちょっとしたツールや試作アプリを作る効率が大きく変わったことを実感しています。
生成AIに対しては、期待だけでなく拒否反応や否定的な意見もあります。とはいえ、技術者である以上、現在進行形で変化し続けているAI技術を無視するわけにはいきません。
生成AIで必須となるグラフィックボードといえば、本来は『Cyberpunk 2077』のような高精細なゲーム画面を描くために、大量の三角形とピクセルを高速に処理するためのものでした。ところが、仮想通貨ブームではハッシュを掘らされ、生成AIブームでは行列とテンソルをひたすら掛け続けています。ピクセル職人として就職したつもりが、採掘場と線形代数工場に配属されたようなもので、その処遇には涙を禁じ得ません。
バイブコーディングの利便性と検証責任
自分用のちょっとしたアプリを作らせるのであれば、バイブコーディングは非常に有用です。例えば、ちょっとしたChrome拡張が欲しい場合、既存の拡張機能を探すよりも、自分用に作ってしまった方が安心できることがあります。ブラウザ拡張は権限が強く、配布後のアップデートによって挙動が変わる可能性もあるためです。
ただし、バイブコーディングで作ったアプリが「動く」ことと「安全に使える」の間には、現時点では大きな隔たりがあります。したがって、バイブコーディングで作成したアプリを検証することなく、他人に使わせたり、外部に公開することについては非常に大きなリスクがあります。AIが作成したアプリであっても、そのアプリの検証責任は人間にあるからです。
公開したアプリで何かトラブルが起こっても「AIが書いたコードだから」という言い訳は通用しません。オープンソースソフトウェアやフリーソフトであれば、"at your own risk"(利用は自己責任で)と言えばいいような気もしますが、悪意のあるコードが混入するかもしれないし、残存していた致命的なバグを誰かが踏んでしまった場合、法的には問題は無かったとしても、最低限の品質保証もできない作者であるという誹りは免れないでしょう。
そのことを考えると、個人情報や決済情報を扱うような高いセキュリティが必要な処理を、バイブコーディングだけで完結させることは、まだ推奨できません。
バイブコーディングは行間を読んでくれるが…
実際にバイブコーディングを行うとわかることですが、自然言語でプロンプトを書いて指示を出すとして、それが要件定義ですらないようないい加減な指示であっても、数回壁打ちしていれば行間を勝手に推測してもっともらしい動作に仕上げてくれます。
しかしながら、正常系の動作で目立たない処理は、明示的に指示しないと抜け落ちることがあります。例えば、異常系の入力チェック、権限の確認、状態遷移の検証、二重送信対策、監査ログなどです。
自分以外が使う前提のアプリであれば、厳密な検証やセキュリティ機能の実装は必須です。アプリの使い方が良くわからない利用者がおかしな入力値を入れてしまうかもしれないし、悪意のある利用者が工夫した入力値を入れてしまうこともあります。
細かすぎる仕様書の理解は、人間にとっては易しくないが、AIなら余裕
ここで要件定義や基本設計を書ける人であれば、厳密な検証やセキュリティ機能の実装の必要性を理解しているはずなので、その指示をプロンプトとして記述できるはずです。
AIに限ったことではないですが、仕様書に明文化されていない制約は実装されにくいし、チーム開発だと伝言ゲームの結果として実装漏れも起こります。バイブコーディングでは、その傾向が表面化しやすいと言えます。
一方で、人間が読む仕様書に、厳密な検証やセキュリティ機能の実装の必要性を一から十まで明文化しなければならないのか、という問いもあります。例えば「電話番号の入力欄には数字以外を指定することはできない」なら、自明とはいえ重要な記載ではあります。ただ、これと同じ調子で「ショッピングカートに投入できる商品の数に"-1個"のような負数を指定できない」とか「代金の支払いに当てることができるポイント数に小数を指定することはできない」といった記載が続くと、開発者にとっては当たり前過ぎるので、そんな仕様書は読みたいか? といった問いも生まれてきます。
バイブコーディングに話を戻すと、AIなら仕様書(≒プロンプト)が分厚くなっても嫌な顔はされません。しかしながら、前述したように異常系の処理は明示的に指示しないと抜け落ちることがあるため、人間にとっては自明なことであっても、うんざりするほど明文化して行間をなくしてしまうほうが良いでしょう。
入力値エラー、ロジックエラー、リクエスト正当性エラーの違い
ここで、今回の記事で扱うエラーと、それに対する検証をあらかじめ整理します。
入力値エラー
入力値エラーに対する検証は、送られてきた値そのものが妥当かを確認するものです。
たとえば、数量が整数であるか、指定可能な範囲に収まっているか、商品IDの形式が正しいか、必須項目が入力されているか、文字長は指定文字数以内に収まっているか、といった確認です。
エラー処理が適切に実装されていないと、プログラムは例外を出したり、画面上で不自然な挙動を見せるなど、以降の処理が間違った結果になることがあります。それゆえに、単体テストや手動での確認でも比較的発見しやすい部類に入ります。
ロジックエラー
ロジックエラーに対する検証は、その値を使った操作が業務ロジックとして妥当かを確認するものです。
たとえば、ポイント交換機能を考えてみます。利用者は交換に必要なポイントを所持しているか、在庫は足りているか、その商品は引き換え対象なのか、といった確認です。
入力値としては正しく見えても、業務ロジックとしては許可してはいけない操作があります。ここでは「値が正しいか」ではなく、「その処理結果が業務ロジックとして正しいか」を確認する必要があります。そのためには、アプリが提供する機能や業務ルールの理解が必須となります。
リクエスト正当性エラー
リクエスト正当性エラーに対する検証は、そのリクエストが利用者本人の意図によって送信されたものか、正しい画面遷移を経て送信されたものか、現在の状態で受け付けてよいものかを確認するものです。
たとえば、CSRF(クロスサイトリクエストフォージェリ)対策がない場合、利用者が意図しない形で状態変更リクエストを送信させられる可能性があります。また、画面遷移チェックがない場合、本来は確認画面を経由してから実行すべき処理を、URLの直接指定によって実行できることがあります。
この問題は、入力値検証、ロジック検証では防げません。商品IDも数量も正しく、残高も在庫も足りている場合でも、そのリクエスト自体を受け付けてよいかは別の問題だからです。この問題に対処するには、アプリが提供する各種機能と業務ルールの幅広い理解、特に、利用者の合意が必要な操作は何であるかの理解が必須となります。
バイブコーディングでポイント交換機能を作ってみる
ここで、ショッピングサイトでよく見かけるような、所有ポイントから商品を引き換える機能を模したものを作ってみます。
プロンプトを書いてみる
「プロンプトの執筆者は、作文能力はそれなりにあるが、コーディングの知識は限定的である」という設定で、以下のプロンプトを作文して GPT-5.5 x Codex に投げてみました。
※以下のプロンプトの作成に際し、生成AIなどのアシストは一切受けておらず、筆者の地の作文です。
(以下、プロンプト全文)
ショッピングサイトでよく見かける、所有するポイント(単位の表記はpt)から商品を引き換える機能を模したものを作成してください。
- サーバーサイドはPythonで記述
- 所有ポイントや商品情報、商品の在庫数、商品の注文情報はサーバー側のデータベースで管理
- 利用者は1人だけで、テスト用に作成するものなので、アカウント管理の機能は不要
- 初回起動時には初期ポイントとして10000ポイントを付与- 画面構成
1.「商品一覧ページ」
トップページ。取り扱っている商品ID、商品名と引き換えに必要なポイントを一覧表示する。商品名はリンクになっており、クリックすると「商品詳細ページ」に遷移する。画面右上には、現在所持しているポイントを記載し、その右隣に「カート内容表示ページ」のリンクを表示し、その右隣に「引き換え履歴確認ページ」のリンクを表示する。2.「商品詳細ページ」
選択した商品ID、商品名と引き換えに必要なポイントを表示する。プルダウンメニューで引き換える商品の数を1-5の範囲で指定できるようにする。「カートに入れる」ボタンをクリックすると、カートに選択した商品と、指定した商品の数が一時的に格納され、成功した場合「カートに入りました!」というメッセージがポップアップする。他のページには遷移しない。ポイントが足りない場合は、カートに選択した商品と、指定した商品の数が一時的に格納されることはなく「ポイントが不足しています」というメッセージがポップアップする。他のページには遷移しない。
画面右上には、現在所持しているポイントを記載し、その右隣に「カート内容表示ページ」のリンクを表示し、その右隣に「引き換え履歴確認ページ」のリンクを表示する。3.「カート内容表示ページ」
カートに入れた商品ID、商品名と数、引き換えに必要なポイント合計を表示する。数は1-5までの範囲で変更できるようにする。商品ごとに「削除」ボタンを表示し、クリックするとその商品をカートから削除できるようにする。「レジに進む」ボタンをクリックすると、「引き換え商品確認ページ」に遷移する。画面右上には、現在所持しているポイントを記載し、その右隣に「カート内容表示ページ」のリンクを用意するが、disabled属性にしてクリックを無効にする。その右隣に「引き換え履歴確認ページ」のリンクを表示するが、disabled属性にしてクリックを無効にする。4.「引き換え商品確認ページ」
カートに入れた商品ID、商品名と数を表示する。「カートに戻る」ボタンをクリックすると「カート内容表示ページ」に遷移する。「引き換えを確定する」ボタンをクリックすると、引き換え処理が発生する。現在所持しているポイントから商品名と数に応じたポイントを減算して、データベースには、注文ID、引き換え実行日時、カートに入れた商品ID、商品名と数、減算されたポイントを引き換え履歴として書き込む。引き換えを行った商品名と数に応じて、データベースに格納されている商品の在庫を減算する。5.「引き換え履歴確認ページ」
引き換え履歴として、データベースから取得した注文ID、引き換え実行日時、カートに入れた商品ID、商品名と数、商品名と数に応じたポイント、それにより変化した所持ポイントを残高として表示する。画面右上には、現在所持しているポイントを記載し、その右隣に「カート内容表示ページ」のリンクを表示し、その右隣に「引き換え履歴確認ページ」のリンクを表示するが、disabled属性にしてクリックを無効にする。
- データベースに格納する商品情報は、以下の情報を参考にする
商品ID,商品名,引き換えポイント数,在庫
ITEM001,ガーネット,1000,10
ITEM002,アメシスト,1000,10
ITEM003,アクアマリン,1000,10
ITEM004,ダイヤモンド,4000,10
ITEM005,エメラルド,2000,10
ITEM006,パール,1000,10
ITEM007,ルビー,2000,10
ITEM008,ペリドット,1000,10
ITEM009,サファイア,2000,10
ITEM010,オパール,1000,10
ITEM011,トパーズ,1000,10
ITEM012,ターコイズ,1000,10
このプロンプトだけで期待するアプリの生成に成功しました。このプロンプト以降の壁打ちは行っていません。
このような画面構成になりました。





生成されたサーバーアプリを診断した結果
生成されたサーバーアプリに対して、典型的なデバッグおよび通信内容の書き換えによるWeb診断を行ってみました。
入力値検証やトランザクションは実装されていた
当初は、数量に範囲外の値を指定できる、必要ポイントを超えて引き換えることができる、といった問題が見つかることを想定していました。
しかし、実際には「入力値検証」「残高チェック」「在庫チェック」の観点では、明確な脆弱性を発見することはできませんでした。
生成されたソースコードを読んでみると、ポイント減算、在庫減算、引き換え履歴の作成についても、最低限のロック処理、トランザクション処理とロールバック処理が実装されていました。これらはWebアプリケーション開発における基本的な考慮事項です。そのためか、今回のプロンプトでは明示しなくても、AIがある程度補って実装してくれました。
これは少し意外な結果でした。今回のプロンプトには、数量の範囲、ポイント不足時の扱い、在庫数、データベースでの管理といった条件をある程度具体的に記載していたため、AIが実装すべき制約を拾ってくれたのだと考えることができます。
一般的に、アプリの機能が多いほど攻撃の入口も増え、脆弱性が入り込む余地は増えます。今回は記事の都合上、機能を大幅に削ったのがよくなかった(?)のかもしれません。少なくとも、今回の条件では「AIが生成したコードだから、単純な入力値検証で簡単に壊れてしまう」という展開にはなりませんでした。
CSRF対策と画面遷移チェック、二重送信対策がなかった
一方で、ソースコードをさらに読み込んでいくと、別の問題が見つかりました。
確定処理がGETリクエストで呼び出されている
まず、状態変更を伴う引き換え確定処理が、POSTではなくGETで呼び出せる実装になっていました。GETは読み取り操作で使うべきであり、ポイント減算や在庫減算のような状態変更を行う処理には適していません。
CSRF対策が実装されていない
また、CSRF対策が実装されていませんでした。ポイント引き換えを確定する処理では「利用者のポイントを減算し、商品の在庫を減らし、引き換え履歴を作成する」というデータベースの変更を伴う処理なので、利用者の操作に基づく正当なリクエストであることを確認する必要があります。しかし、生成されたコードにはCSRFトークンなどによる確認処理がありませんでした。
今回はアカウント管理を省略した簡易アプリのため、ログインセッションを悪用する典型的なCSRFとは条件が異なります。しかし、状態変更を伴うリクエストに対して、利用者の意図を確認する仕組みがないという設計上の問題は残ります。
画面遷移チェックがない
また、画面遷移のチェックも実装されていませんでした。通常の画面操作では「カート内容表示ページ」から「引き換え商品確認ページ」に進み、「引き換えを確定する」ボタンを押す流れになります。しかし、URLを直接指定する強制ブラウジングにより、確認画面を経由せずにポイント引き換えを完了させることができました。
注意点としては、確認画面を経由せずに引き換えを確定できることについては、必ずしも脆弱性とは限りません。しかしながら、確認画面に「利用者が最終的な内容に同意したことを確認する」という意味を持たせるなら、その状態をサーバー側で管理する必要があります。銀行系・信販系アプリで金銭が動く処理においては、確認画面を必ず経由する必要がありますし、無関係な画面遷移が挟まるとエラーになり、最初からやり直すように促されるのは、そのためです。
二重送信対策が不完全
ポイント引き換えを確定するボタンについては、画面上の二重送信対策がありませんでした。
一般的なWebアプリケーションでは、注文確定のような重要なボタンを押すと、処理が完了するまで、誤操作を防ぐ目的で他の操作はできないように、ユーザーインターフェース上で対策することがあります。
ただし、UI上でボタンを無効化するだけでは十分とは言えません。ブラウザのリロードやリクエストの再送信などによって、同じ処理が複数回呼び出される可能性があるからです。
今回生成されたコードでは、サーバー側には最低限のロック処理が実装されているため、単純な競合による残高や在庫の破壊は起きにくい実装でした。ただし、同じ確定操作を複数回行っても、ロジックとしては一度だけとして扱う冪等性(べきとうせい)の保証とは別の問題です。
重要な状態変更処理では、UI上の二重送信対策に加えて、サーバー側でも同一リクエストの再処理を防ぐための、積極的な設計が必要になります。
診断結果のまとめ
今回の診断結果で重要なことは、商品IDや数量といった入力値そのものは正しかったという点です。残高や在庫のチェックも適切でした。値の正当性に対する検証はある程度正しくできていたと見てよさそうです。
しかし「そのリクエストを受け付けてよいか」という検証は成されていませんでした。値が正しいことと、その操作を許可してよいかは別の問題です。
Webアプリケーションでは、入力値の妥当性だけでなく、そのリクエストは正しい手順で送られたものなのか、利用者本人の意図した操作なのか、現在の状態で受け付けてよい操作なのかを確認する必要があります。
以下に、それぞれの観点での診断結果をまとめた表を記します。
| 観点 | 見ているもの | 例 | 今回の結果 |
|---|---|---|---|
| 入力値検証 | データ型や書式、文字長、範囲 | 数量が整数か、商品IDが存在するか | 実装されていた |
| 業務ロジック検証 | 業務ルール上の妥当性 | 残高不足、在庫不足 | ある程度実装されていた |
| トランザクション制御 | 複数更新の一貫性 | ポイント減算、在庫減算、引き換え履歴の作成 | 最低限実装されていた |
| リクエスト正当性 | 操作の意図・手順・状態 | CSRF、確認画面の迂回、二重送信 | 不足していた |
AIに指示するべきこと、生成後に確認するべきこと
今回の結果から考えると、バイブコーディングにおいて「AIはセキュリティについては何も考えてくれない」と単純に考えるのは正確ではありません。少なくとも、プロンプトに具体的な制約を書いておけば、入力値検証、残高チェック、在庫チェック、最低限のトランザクション処理までは実装されることがあります。
しかし、それでも人間が確認すべきことは残ります。
特に重要なのは、値の検証だけでなく、リクエストの正当性を確認することです。状態変更を伴う処理では、利用者本人の意図したリクエストであることを確認する必要があります。Webアプリケーションであれば、CSRFトークンなどの対策が必要になります。
また、確認画面や承認画面に業務上の意味がある場合は、その画面を経由したことをサーバー側で確認する必要があります。単に画面上で「確認ページ」を用意しているだけでは、URLの直接指定やリクエストの直接送信によって、その画面を迂回できる可能性があります。
問題は、その画面遷移のルールを文字や図表で記述することが難しいという点です。それでも、プロンプトには以下の条件を明示したほうが良いでしょう。
- 状態変更を伴う処理では、GETではなくPOSTを使用してください。
- POSTリクエストにはCSRF対策を実装してください。
- 確認画面を経由した場合のみ、確定処理を受け付けてください。
- 確認画面表示時にサーバー側でワンタイムトークンを発行し、確定処理時に検証してください。
- 確定処理後はそのトークンを無効化してください。
- 同じ確定処理が複数回来ても二重に処理しないようにしてください。
- 入力値検証、残高チェック、在庫チェック、CSRF対策、確認画面迂回、二重送信についてテストを作成してください。
まとめ
今日のバイブコーディングは便利で、しかも優秀です。今回の実験でも、AI生成コードは想像よりもしっかりとした実装でした。入力値検証、残高チェック、在庫チェック、最低限のトランザクション処理については、明示的に細かく指示しなくても、ある程度行間を読んで補ってくれました。
しかしながら、値の妥当性やデータベース処理は適切でも、リクエストの正当性までは保証されていませんでした。今回のアプリでは、状態変更処理がGETで呼び出せること、CSRF対策がないこと、確認画面を経由せずに確定処理を実行できること、二重送信に対する設計が不十分であることが確認できました。
これは「AIが悪い」と言うよりは、人間が記述するプロンプト、つまり仕様の詰めが甘かったと考えるべきでしょう。同じ仕様を人間に渡したとしても、同じような実装になる可能性はあります。
バイブコーディングによって実装の速度は大きく上がりますが、何を許可して何を拒否し、どの状態でどの操作を受け付けるのかを決める仕事は、仕様を決める人間に残ります。
AIがあれば、専門外の人でも短時間でそれらしい成果物を作れる場面は増えるでしょう。しかしそれは、100点満点の成果物というよりは、60点から70点程度の及第点と考えたほうが良さそうです。そこから最後の詰めを行って100点に近づけるには、正しい業務知識とセキュリティ知識が必要です。言い方を変えると、60~70点の完成度のアプリで十分であれば、バイブコーディングは超有用であるということになります。
AI生成コードを評価するうえで重要なのは、単に「動くか」ではなく「何を保証していて、何を保証していないのか」を読み解くことではないかと思います。