なぜ危ないのか
Slack IncomingなどのWebhook URLは認証なしにPOSTリクエストを受け付ける。URLを知っている人なら誰でも任意のメッセージをそのチャンネルに投稿できるため、HTML内にWebhook URLが書かれていると、それを見た第三者が社内Slackチャンネルにスパムや誤情報を送り込める状態になる。
AIはフォームの送信結果をSlackに通知するデモを作る際に、`fetch('https://hooks.slack.com/services/T.../B.../...', { method: 'POST', body: JSON.stringify({text: message})})`のようなコードをHTMLに直書きすることが多い。このURL全体がシークレットとして扱われるべき情報だが、AIはそれをコードの一部として自然に出力する。
Webhook URLが流出した後の対処は、Slack管理画面でそのWebhook Appを削除して再作成するしかない。URLを変更するとそれを参照しているすべてのコードを更新する必要があり、対応コストが大きい。公開前に一度確認して除去する方が、事後対応よりはるかに低コストだ。
- 相手がログインなしで開ける状態か確認する
- PCとスマホで最低1回ずつ表示を確認する
- 内部情報・個人情報・不要な外部送信が残っていないか見る
- レビュー期限と修正時の差し替え方を決めておく
ソースで見る場所
HTMLファイルで`hooks.slack.com`・`discord.com/api/webhooks`・`outlook.office.com/webhook`という文字列を検索する。`grep -in 'webhook\|hooks\.slack\|discord.*webhook' output.html`でヒットした行を確認し、URLの全体が含まれているかをチェックする。URLが変数に格納されている場合はその変数の定義箇所まで追う。
fetch・XMLHttpRequestを使ってPOSTリクエストを送っているコードを探す。`grep -n 'fetch\|XMLHttpRequest' output.html`でヒットした行のURL引数を確認し、hooks.slack.comやdiscord.comなど通知系サービスのドメインが含まれていないかをチェックする。
環境変数または設定ファイルからWebhook URLを取得する処理が正しく機能しているかも確認する。`process.env.WEBHOOK_URL`のような記述があれば本来の使い方だが、その隣に実際のURLがフォールバック値としてハードコードされているケースがある。`|| 'https://hooks.slack.com/...'`という記述がある場合は要削除だ。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
安全に共有する設定
Webhook URLを除去した後のHTMLを共有するには、URL部分を`SLACK_WEBHOOK_URL_PLACEHOLDER`のような識別しやすい文字列に置換してから共有する。受け取り側がこのHTMLを動かすには自前のWebhook URLに置き換える必要があることを共有メモに記載する。
Webhook通知の動作を実際に見せたい場合は、テスト専用のSlackワークスペースまたはプライベートチャンネル専用のWebhook URLを作成する。本番のWebhook URLは使わず、テスト用URLの通知先を外部に見られても問題のないチャンネルに限定することでリスクを抑える。
ギガサイト便のパスワード保護機能でHTMLを共有し、Webhook URLの代わりにプレースホルダーが入ったソースをレビュアーに確認してもらう。Webhookの実際の動作を確認するデモはクローズドな環境(社内Slackのテストチャンネル)で行い、外部共有のHTMLには含めないという運用を定める。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
再発防止ルール
AIにSlack通知を含むHTMLを生成させるプロンプトに「Webhook URLはHTMLに直書きせず、`const WEBHOOK_URL = 'YOUR_WEBHOOK_URL_HERE';`のようにプレースホルダーを使うこと」という制約を必ず追加する。この一行を入れるだけで、AIが実際のURLを生成する確率が大幅に下がる。
`.gitignore`にHTMLファイルを追加せずリポジトリに含めている場合は、pre-commitフックで`grep -rn 'hooks\.slack\.com\|discord\.com/api/webhooks' *.html`を実行する設定を追加する。ヒットした場合はコミットを止めて担当者に確認を促す。husky(Node.js)またはpre-commit(Python)でhookを簡単に管理できる。
すでに公開されているHTMLにWebhook URLが含まれていることが後から発覚した場合、まずSlack管理画面でそのIncoming Webhook Appを削除して無効化する。次に新しいWebhook URLを作成し、それを参照しているすべてのコードを更新する。公開期間中にそのURLが悪用されていないかをSlackの監査ログで確認することも忘れずに行う。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
よくある質問
WebhookのURLはランダムな文字列を含むため、推測されにくいのでは?
推測は困難だが、HTMLのソースコードに書かれていれば推測不要で直接取得される。URLが長くてランダムでも、ソースに書いてある時点でHTMLにアクセスできる人全員に公開されている状態と同じだ。
Webhook URLをBase64エンコードしてHTMLに埋め込めば安全ですか?
安全ではない。Base64はエンコードであって暗号化ではなく、`atob()`をコンソールで実行すれば一瞬で元に戻せる。隠蔽にはなるが、ソースを見た開発者ならすぐに気づいて解読できる。
Webhook URLが漏えいした後、そのURLをSlack側で無効化するにはどうすればよいですか?
Slackの管理画面で「App管理」→該当のIncoming Webhook Appを選択→「アプリの削除」または「Webhookの再生成」を行う。削除すると同じURLは永久に使えなくなり、新しいURLが発行される。