なぜ危ないのか
AIはOAuthのチュートリアル記事や公開されたサンプルコードを学習しており、その中にはclient_secretをフロントエンドに書いた不適切なサンプルも含まれている。AIがそのパターンを再現すると、生成されたHTMLに実際の認証情報が埋め込まれた形になり、ソースを見た人なら誰でも取得できる状態になる。
プロンプトに「OAuthを実装して」とだけ書くと、AIがPKCEフローを使わずにclient_secretを使う古いフローで実装することがある。このフローはサーバーサイドで行うべき処理であり、フロントエンドに書くこと自体が設計上の誤りだ。プロンプトで正しいフロー(PKCE)を指定することで問題を回避できる。
生成されたOAuthコードは動作するか否かの確認で終わることが多く、セキュリティの観点から精査されないまま共有されがちだ。OAuthの実装は見た目の複雑さとセキュリティの難しさが一致しないため、動いているように見えても重大な設計上の問題を含んでいることがある。
- 相手がログインなしで開ける状態か確認する
- PCとスマホで最低1回ずつ表示を確認する
- 内部情報・個人情報・不要な外部送信が残っていないか見る
- レビュー期限と修正時の差し替え方を決めておく
ソースで見る場所
OAuthのPKCEフローが使われているかを確認する。`grep -n 'code_verifier\|code_challenge\|PKCE' output.html`でヒットしない場合、client_secretを使った従来のフローで実装されている可能性がある。PKCEのフローではcode_verifierをlocalStorageに保存してcode_challengeを計算する処理が必要で、これらのキーワードがなければ確認が必要だ。
fetch・XHRのリクエストでトークンエンドポイントにPOSTしているコードを探す。`/token`または`/oauth/token`という文字列でgrepし、そのリクエストボディにclient_secretが含まれていないかを確認する。含まれている場合は、その処理をサーバー側のAPIとして分離する必要がある。
`window.location.hash`や`new URLSearchParams(window.location.search)`でアクセストークンを取り出している処理を確認する。Implicit Flowで生成されたコードに多いパターンで、Implicit FlowはOAuth 2.0のBest Current Practiceでは非推奨とされている。代わりにPKCEを使うよう修正するか、AIに再生成を依頼する。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
安全に共有する設定
AIへのプロンプトにOAuth実装の制約を加える場合は「OAuthはPKCEフローで実装し、client_secretはHTMLに含めないこと。トークンエンドポイントへのリクエストはサーバー側で行うことを前提とし、フロントエンドではコードチャレンジの生成とcallbackのコード受け取りまでを担当すること」と書く。この指示でAIはPKCE実装を選ぶ確率が高まる。
生成後スキャンをCIに組み込む場合は、`grep -rE 'client_secret\s*[:=]\s*["\x27][^"\x27]+["\x27]'`でHTMLファイルに実際の値が設定されたclient_secretがないかを検出する。プレースホルダー(`YOUR_CLIENT_SECRET`など)は除外するようgrepのinvert-matchを組み合わせる。
スキャンをパスしたHTMLをギガサイト便でパスワード認証付きに公開し、URLと一緒に「このHTMLにはOAuthのclient_idが含まれます。テスト用アプリのものです。共有禁止」という注記を送付先に伝える。URLの期限は最大でも1週間に設定し、レビュー後に手動で失効させる手順をカレンダーに登録する。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
再発防止ルール
OAuthを含むHTMLを生成するためのプロンプトテンプレートをチームのWikiまたはGitHubリポジトリのdocsディレクトリに保存する。テンプレートにはPKCEフロー使用・client_secret排除・callback URLはlocalhostのみ・Implicit Flow禁止という制約が含まれた状態で管理し、誰でもコピーして使える形にする。
OAuthのclient_idとclient_secretをGitHub Secretsで管理しているリポジトリでは、pre-commitフックに`grep -r 'client_secret' --include='*.html' .`を追加する。ヒットした場合はコミットを止め、担当者に確認を促すメッセージを表示する。
新しいAIモデルに切り替えた際は、OAuthを含むHTMLを生成させてセキュリティ品質を検証する受け入れテストを実施する。テスト項目はclient_secretの有無・PKCEの採用・Implicit Flowの不使用の三点で、いずれか一つでも問題があれば旧モデルのプロンプトテンプレートを更新してから本格運用する。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
よくある質問
AIにPKCEフローを指定しても、毎回正しく実装してくれるとは限らないのでは?
正しく実装されない場合がある。そのため生成後のスキャンとコードレビューがプロンプト制約と同等に重要だ。PKCEの確認ポイントはcode_verifierの生成とcode_challengeのSHA-256ハッシュ計算の二点で、この処理があればほぼ正しいPKCEフローと判断できる。
Semgrepにはclient_secretの検出ルールが既成のものとしてありますか?
`semgrep --config=p/secrets`を実行するとAWS・Google・GitHubなどのシークレットパターンが含まれるルールセットが適用される。OAuthのclient_secretはサービスにより形式が異なるため、カスタムルールを追加して自社が使うProviderの形式に合わせる方が精度が上がる。
HTMLをzip圧縮して送る場合もスキャンは必要ですか?
必要だ。zipに圧縮する前のHTMLファイルをスキャンすればよい。zipを解凍せずにスキャンするには`unzip -p archive.zip | grep -i client_secret`でできるが、複数ファイルがある場合は解凍後に一括スキャンするほうが漏れがない。