なぜ危ないのか
AIは「ログインしていないユーザーをログインページにリダイレクトし、ログイン後に元のページへ戻す」という一般的なUIパターンを実装する際、`?next=`パラメータをそのままlocation.hrefに渡すコードを生成しやすい。これはフロントエンドのサンプルとして教材に多いパターンで、AIの学習データにも多く含まれているためだ。
公開前スキャンなしにこのHTMLを共有すると、受け取った担当者が別のサーバーに転用するケースがある。その転用先でも同じオープンリダイレクトが動作するため、問題が別の場所で再現する。AIが生成したHTMLは「動く」ことが確認されているだけで「安全」とは限らない。
スキャンツールが指摘するのはコードパターンの一致であり、動的な入力値を追った意味解析はできない。そのためスキャンをパスしたからといって完全に安全とは言えない点も理解しておく必要がある。スキャンは「怪しいパターンがないか」を素早く確認する手段として使い、最終判断はコードの意味を読んで行う。
- 相手がログインなしで開ける状態か確認する
- PCとスマホで最低1回ずつ表示を確認する
- 内部情報・個人情報・不要な外部送信が残っていないか見る
- レビュー期限と修正時の差し替え方を決めておく
ソースで見る場所
生成されたHTMLのscriptブロックで`URLSearchParams`の使用箇所を探す。`new URLSearchParams(window.location.search)`で取得した値を使っている場合、それが`location.href`・`location.replace`・`location.assign`に代入されているかどうかを確認する。この二つが近い行に並んでいればオープンリダイレクトのパターンだ。
`decodeURIComponent`や`atob`を使っている箇所も要注意だ。URLエンコードまたはBase64でエンコードされたURLを復元してからリダイレクト先に使う手法があり、単純な文字列検索では見落とすことがある。これらの関数の出力が直接または間接的にlocation系プロパティに渡されていないかを確認する。
アンカータグの`href`属性がJavaScriptで動的に設定されている場合も確認対象だ。`document.getElementById('link').href = someUrl`のような行があり、`someUrl`がURLパラメータ由来であれば、クリックされた際に外部へ誘導されるリスクがある。静的HTMLでも遅延バインディングでhrefを書き換えているケースがある。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
安全に共有する設定
AIへの指示にオープンリダイレクト対策の制約を組み込む場合は「リダイレクト先のURLは許可リストで検証し、許可リストにないURLへの遷移は`/`(トップページ)にフォールバックさせること」と書くのが明確だ。この表現はAIが理解しやすく、許可リスト方式の実装を出力する確率が高い。
スキャンをCIに組み込む場合は`grep -E 'location\.href\s*=|location\.replace\(|location\.assign\(' *.html`をshellスクリプトとして保存し、GitHub Actionsのstep内で実行する。ヒットが1件以上あれば`exit 1`でビルドを止めることで、マージ前に担当者が確認するゲートを作れる。
スキャンをパスしたHTMLは、ギガサイト便のような会社ドメイン認証機能を使って共有する。受け取る側が特定の会社のメールアドレスでアクセスしないと開けない設定にすることで、URLが外部に流出しても被害の範囲を限定できる。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
再発防止ルール
オープンリダイレクトが一度発見されたHTMLは、その生成に使ったプロンプトを記録しておく。次回同じ用途でHTMLを生成するときにそのプロンプトを改善版と比較することで、どの表現が問題のあるコードを生む傾向があるかを蓄積できる。チームのプロンプト集に修正前後のペアを残すことが長期的な品質向上につながる。
新しいAIモデルにアップデートした際は、既存のプロンプトテンプレートをそのまま使って生成したHTMLに同じ問題が再現しないかを検証する。モデルのバージョンによって安全なコードパターンへの傾向が異なるため、アップデート時の動作確認をルーティン化しておく。
Semgrepのカスタムルールをプロジェクトのリポジトリに`.semgrep/`ディレクトリを作成して追加する。`pattern: location.href = $VAR`というルールを記述してSemgrepで実行すると、変数代入のパターンだけを抽出できる。チームメンバーが増えてもスキャンコマンドを共有するだけで同じ基準を適用できる。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
よくある質問
許可リストに含まれるURLを正規表現で記述してもよいですか?
正規表現による比較はバイパスされやすく推奨しない。`https://example.com/dashboard`のように完全な文字列を配列に列挙し、`===`で完全一致比較するほうが安全だ。正規表現を使う場合はアンカー`^`と`
を両端に必ず付ける。CIのgrepスキャンがfalse positiveを大量に出す場合、どう対処すればよいですか?
ヒットした行を見て右辺が文字列リテラル(`'`または`"`で囲まれた固定値)の場合は安全として扱えるため、awkでフィルタリングしてfalse positiveを除外するshellスクリプトを追加する。Semgrepに切り替えるとパターン精度が上がりfalse positive率が下がる。
オープンリダイレクトとXSSは同時に対策する必要がありますか?
両者は独立した脆弱性で、オープンリダイレクトを塞いでもXSSは別途存在する可能性がある。AI生成HTMLの公開前チェックとして、二つのスキャンをセットで実施するのが効率的だ。Semgrepのp/javascriptルールセットは両方を含んでいるため一括検出できる。