フォームのhidden inputにAPIキーを入れる構成は、静的サイトとサーバーレス系のSaaSでよく見ます (Web3Forms 等)。
<form action="https://api.web3forms.com/submit" method="POST">
<input type="hidden" name="access_key" value="abc-123-xyz" />
<!-- 入力欄 -->
</form>
「access_keyが公開されている、秘密鍵漏洩リスク」。AIレビューがこう指摘してくることがあります。半分正しく、半分誤解。これは秘密鍵漏洩リスクではなく、abuse (乱用) 制御の話。対策の軸が違います。
秘密鍵と公開識別子は別物
API認証で使われる「キー」には大きく2種類あります。
| 種類 | 例 | 公開可否 | 漏れた時のリスク |
|---|---|---|---|
| 秘密鍵 (secret) | AWS Secret Access Key、Stripe secret_key、GitHub Personal Access Token | 絶対に公開不可 | 直接の不正利用、データ流出、課金被害 |
| 公開識別子 (public identifier) | Web3Forms access_key、Stripe publishable_key、Google Maps API key (Web用) | 公開前提 | abuse (スパム送信、不正リクエスト) |
両者は名前が似ていますが、設計思想が違います。秘密鍵は「持っていれば全権限が使える鍵」、公開識別子は「サービスのどのエンドポイントか識別するためのラベル」。
公開識別子は仕様上「サーバーには出ない、クライアントに出る」のが前提。フォームのhidden inputに書くのは仕様通り。これを「秘密鍵が漏れている」と判断するのは混同です。
なお Google Maps API key のような「Web用に公開される鍵」も同様で、HTML から見えるのは想定通り。ただしHTTP referrer 制限、利用 API の制限、課金上限を SaaS 側で必ず設定する前提です。「公開前提」と「無制限に使ってよい」は別の話。
公開識別子のリスクは abuse
公開識別子は誰でも見られる前提。悪意ある第三者がそれを使って不正リクエストを送るリスクがあります。スパムフォーム送信を大量に飛ばす。別ドメインから自サイト経由のフリをして送る。レート制限を超えるリクエストで通知先 (運用者のメール) を埋める。課金型なら見えない請求を発生させる。
これらは abuse (乱用) と呼ばれる類で、秘密鍵漏洩とは対処法が違います。
abuse制御の対策
公開識別子を使うSaaSが abuse 対策として提供するのは、主にドメイン制限、CAPTCHA、レート制限の組み合わせです。
Domain allowlist (origin、refererの制限)
「このキーは https://example.com からのリクエストのみ受け付ける」とSaaS側で設定します。第三者が自サイトで同 access_key を使ったフォームを置いても、SaaSが referer や origin を見て弾きます。設定箇所はSaaS管理画面の Allowed Origins か Allowed Domains。
ただしブラウザフォーム経由の乱用は抑えられても、curl 等の非ブラウザクライアントは HTTP ヘッダーを偽装できる。暗号学的な認証ではないことを理解しておきます。あくまで「通常のフォーム経由の流用を抑える」ための仕組みです。
CAPTCHA、Bot検証
reCAPTCHA、Cloudflare Turnstile、hCaptcha 等をフォームに組み込み、自動化されたbot送信を弾きます。ハニーポット欄 (<input type="text" name="botcheck" style="display:none">) は最低限の対策で、bot が誤って入力するダミー欄として機能します。Turnstile (Cloudflare無料) は現代の標準、UXへの影響が最小。設定はSaaSの管理画面、もしくはフォーム側で組み込みます。
レートリミット
「同IPから1分間にX回まで」「同 access_key で1日Y回まで」のような制限をSaaS側で設定します。乱用が始まっても影響範囲を限定できる。設定箇所はSaaS管理画面の Rate Limits か Quota。上限近づき時に email や Slack 通知を設定すると気づきやすいです。
実装側で隠す、は対策にならない
「access_keyを直書きするのが心配なら、ビルド時に環境変数から読む」という提案もよく見ます。けれど abuse 対策にはなりません。
<!-- ビルド時 -->
<input type="hidden" name="access_key" value={import.meta.env.PUBLIC_KEY} />
結局HTMLとして配信されるので、ブラウザの「ソース表示」で誰でも見られます。環境変数を経由しても、最終配信物に書かれている事実は変わらない。
公開識別子は「ソースを見られる」前提で設計されたもの。隠すのではなく abuse を制御するのが正解です。
AIレビューが「鍵漏洩」と指摘してきたら
実例として「WEB3FORMS_ACCESS_KEY がhidden inputとして公開される。キー流用によるスパム送信、フォーム濫用のリスク」という指摘がありました。
これも半分正しく半分誤解。スパム送信リスクは存在するので、abuse 対策は必要です。けれど「秘密鍵としての漏洩」ではなく、設計上公開される識別子。反論の正しい軸は「秘密鍵ではないので隠しようがない、abuse 対策でSaaS側設定を確認する」。AIレビューの優先度ラベル (例: P1 Security) は誇張気味で、Domain allowlist、CAPTCHA、レートリミットの運用設定確認に変えます。
「key」と名のつくものが全部秘密ではありません。公開識別子と秘密鍵を区別し、公開識別子は「ソースに見える前提」で abuse 制御を組む。ドメイン制限、CAPTCHA、レートリミットをSaaS側で設定すれば、フォームのhidden inputに access_key を書いていても乱用リスクを下げられます。