結論(秘密情報らしき文字列がない)
秘密情報らしき文字列がないとは、公開ページのHTMLに、クラウドや外部APIを操作できるAPIキー・アクセスキー・トークン・秘密鍵がそのまま書き込まれていない状態のことです。HTMLは誰でも「ページのソースを表示」で読めるため、画面に出していない変数の中でも漏えいと同じです。セキュリティAIチェッカーはOpenAI形式・AWS・GitHub・Stripeの秘密キーと秘密鍵ブロックを検出すると、重み5(最大)の×と判定します。
この記事のポイント
- HTMLやJavaScriptに書いた値は、minifyやBase64をしても、ログイン後のページでも、利用者全員の手元に届きます。
- ブラウザに置いてよいのは公開用キー(Stripeの pk_、リファラー制限済みのGoogle Mapsキー等)だけで、秘密用キーは絶対に置きません。
- 検出対象は sk-(OpenAI形式)、AKIA/ASIA(AWS)、ghp_ 等(GitHub)、sk_live_/rk_live_(Stripe)、BEGIN PRIVATE KEY の5パターンです。
- 1つでも一致すれば × 要改善(重み5が得点0)。根拠欄にはパターン名だけを表示し、キーの値は表示しません。
- 実在するキーだった場合は、HTMLから消すだけでなく、直ちに無効化・再発行(ローテーション)して利用ログを確認します。
- 恒久対策は、ブラウザから自組織のサーバーを呼び、サーバーが .env のキーで外部APIを呼ぶプロキシ構成です。
| 診断項目名 | 秘密情報らしき文字列がない |
|---|---|
| 項目ID | html.secret-pattern |
| 分類 | 公開ファイル・情報露出 |
| 重大度・配点の重み | 最重要(critical) 重み 5 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V13.3.1(秘密情報は鍵保管庫等の管理基盤で扱う)、OWASP WSTG-INFO-05(Webページ内容からの情報漏えいレビュー)、CWE-798(ハードコードされた認証情報の使用)、CWE-540(ソースコード内への機密情報の混入) |
秘密情報らしき文字列がないとは(HTMLへのAPIキー直書きの確認)
秘密情報らしき文字列がないとは、公開ページのHTMLに、クラウドサービスや外部APIを操作できる「秘密の鍵」(APIキー、アクセスキー、トークン、秘密鍵)がそのまま書き込まれていない状態のことです。セキュリティAIチェッカーはこの項目(html.secret-pattern)で、トップページのHTMLに代表的な秘密キーの形式が含まれていないかを確認します。
HTMLは、ブラウザが受け取る時点で誰でも読めるデータです。「画面に表示していない」「JavaScriptの変数に入れただけ」という状態でも、右クリックの「ページのソースを表示」や開発者ツールで容易に取り出せます。
秘密鍵やAPIキーが漏れると、第三者がその権限で外部サービスを呼び出せるため、不正利用による高額請求、保存データの窃取、サービス停止などに直結します。この項目の重大度は critical、重みは本診断の最大値である5に設定されています。
| 項目 | 内容 |
|---|---|
| 項目ID | html.secret-pattern |
| 診断カテゴリ | 情報露出 |
| 重大度 | critical |
| 重み | 5(本診断の最大値) |
| 関連標準 | OWASP ASVS 5.0.0 V13.3.1、OWASP WSTG-INFO-05、CWE-798、CWE-540 |
「秘密情報」と「公開してよい識別子」の違い
外部サービスのキーには、ブラウザに置く前提で設計された「公開用」と、サーバー内だけで使う「秘密用」があります。この区別が、APIキーの扱いを判断する出発点です。
| 種類 | 例 | ブラウザに置いてよいか |
|---|---|---|
| 公開用キー | Stripeの公開可能キー(pk_live_ / pk_test_)、Google Maps JavaScript APIのキー | 置く前提。ただしGoogle側の「ウェブサイト制限(HTTPリファラー制限)」とAPI制限は必須 |
| 秘密用キー | Stripeのシークレットキー(sk_live_)・制限付きキー(rk_live_)、AWSアクセスキー(AKIA / ASIA で始まるIDと対になるシークレット)、OpenAI等の生成AI APIキー、GitHubのトークン、TLS/SSHの秘密鍵 | 絶対に置かない |
各サービスの公式な位置づけ
- Stripe:公式ドキュメントは、バックエンド外部へ公開しても安全なのは公開可能キーだけで、制限付きキーを含むその他のキーは保護が必要だと明記しています。
- AWS:IAMの一意ID接頭辞として、長期アクセスキーに
AKIA、STSの一時アクセスキーにASIAを使うと公開しています。 - GitHub:2021年に、トークンへ
ghp_(個人アクセストークン)、gho_、ghu_、ghs_、ghr_の接頭辞とCRC32チェックサムを付ける形式を導入し、誤検知の少ない機械検出を可能にしました。
このように接頭辞が決まっているキーは、HTMLの中から機械的に見つけられます。攻撃者も同じ方法で公開ページを探しているため、サイト管理者が先に見つけて取り除く必要があります。
なぜHTMLにAPIキーを書いてはいけないのか(よくある誤解)
HTMLやJavaScriptに書いた値は、どんな加工をしてもブラウザに届いた時点で利用者の手元にあります。次のような思い込みが、秘密情報の直書きを生みます。
- 「難読化・minifyしたから大丈夫」:変数名が短くなるだけで、文字列リテラルはそのまま残ります。
- 「Base64にしたから読めない」:Base64は暗号ではなく、誰でも復元できます。
- 「ログインしないと見えないページに書いた」:ログイン後のHTMLやJavaScriptも、利用者全員の手元に届きます。
- 「Google Mapsのキーは漏れても無害」:公開前提のキーですが、OWASP WSTG-INFO-05は、利用できるAPIの種類だけを制限したGoogle APIキーは、攻撃者が同じキーでGoogle Maps系APIを呼び出すことができ、その料金をサイト所有者が負担することになると警告しています。公開キーでも「リファラー制限+API制限」を設定して初めて安全になります。
生成AI時代の典型パターン
生成AIのAPIをブラウザの fetch から直接呼ぶ実装では、Authorization: Bearer sk-... をJavaScriptに書いてしまう例が後を絶ちません。正しくは、ブラウザからは自組織のサーバー(PHP、Next.jsのRoute Handler等)を呼び、そのサーバーが .env 等から読み込んだキーで外部APIを呼ぶ「プロキシ構成」にします(設定例は後述)。
自治体サイトでは、AIチャットボットなどの機能を外部業者が埋め込むケースが増えています。納品物のスクリプトにキーが直書きされていないかは、発注者側でも確認すべきポイントです。
秘密情報らしき文字列がないの診断基準(セキュリティAIチェッカー)
取得対象
- 入力URLへGETを行い、リダイレクト(最大4回)を追跡した最終ページのHTML本文(最大2,000,000バイト)を対象とします。
Content-Typeがtext/*またはHTML系、もしくはContent-Type未指定の場合のみ本文として解析します。- 外部JavaScriptファイルや下層ページの本文は、この項目では読みません(インラインで書かれた値のみが対象)。
検出パターン
| パターン名(根拠欄に表示) | 判定条件 |
|---|---|
| OpenAI形式のAPIキー(sk-...) | sk-(任意で proj- / svcacct- / admin-)に続く32文字以上の英数字・_・- で、かつ大文字と数字を両方含む。sk-fading-circle のようなCSSクラス名を除外するための条件。sk-ant-... など同じ書式の他社キーもこの名前で検出される |
| AWSアクセスキーID(AKIA/ASIA...) | AKIA または ASIA +大文字英数字16文字 |
| GitHubトークン(ghp_等) | ghp_ gho_ ghu_ ghs_ ghr_ +英数字30文字以上、またはfine-grained個人アクセストークンの github_pat_ +英数字・_ 36文字以上 |
| Stripeシークレット/制限付きキー(sk_live_/rk_live_) | sk_live_ または rk_live_ +英数字20文字以上(公開可能キー pk_、テスト用 sk_test_ は対象外) |
| 秘密鍵ブロック(BEGIN PRIVATE KEY) | -----BEGIN PRIVATE KEY-----、または PRIVATE KEY の前に RSA / EC / OPENSSH / DSA / ENCRYPTED / PGP のいずれかが付くPEM・PGPヘッダー(-----BEGIN PGP PRIVATE KEY BLOCK----- を含む) |
いずれも大文字小文字を区別して照合します。
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| ○ 問題なし(pass) | 上記のいずれにも一致しない | 重み5を満点で加算 |
| × 要改善(fail) | 上記のいずれか1つ以上に一致 | 重み5が分母に入り、得点0 |
| (判定なし) | HTML本文を取得できない(非テキスト応答など) | 本項目は結果に出力されず、得点の分母にも入らない(代わりに「HTMLを解析できる」(html.available)が na として記録される) |
- 根拠欄には一致したパターン名だけを表示し、キーの値そのものは表示しません。診断結果がさらに漏えい経路になるのを防ぐためです。
- あわせて「ダミー値や無関係な文字列の可能性もあるため目視確認を」と併記します。
- この項目には △ 要確認(warn)の判定はありません。一致すれば必ず ×、一致しなければ ○ です。
意図的に対象外としているもの
- Google APIキー(
AIza...):自治体サイトの地図埋め込みで正規に使われ、リファラー制限で保護する設計のため、critical判定にすると誤検知が多発します。 - Slack等その他サービスのトークン:現行の検出パターンには含まれません。
- Stripe公開可能キー(
pk_)、各種の公開用サイトキー(reCAPTCHA等)。 - JWT形式の文字列:公開トークンや期限切れのサンプルも多く、HTMLだけでは秘密性を判断できません。
自治体サイトでの判定例
| 状況 | 判定 |
|---|---|
| CMSで生成された通常の公式サイト | ○(ほとんどがこれ) |
「AIチャットボット」を外部業者が埋め込み、トップページのスクリプトに sk-proj-... が直書き | × |
地図ページに AIzaSy... のMaps APIキー | ○(別途、Google Cloud側の制限設定を確認すべき) |
この診断の限界
外部 .js ファイル、下層ページ、APIレスポンス内のキーは検出できません。○ は「トップページのインラインHTMLに代表的な形式が見つからなかった」ことを意味し、秘密情報が一切ないことの証明ではありません。
公開ファイルの露出は「代表的な管理ファイルが公開されていない」、ソースマップ経由の原文公開は「ソースマップ参照を公開していない」と「ソースマップの原文公開」の各項目で別に確認します。
秘密情報が漏れるとどんなリスクがあるか
- クラウド課金の不正利用:漏れたAWSキーで仮想マシンを大量起動され、暗号資産の採掘に使われる被害は広く知られています。生成AIのAPIキーも、第三者に大量利用されると従量課金が膨らみます。
- データの窃取・改ざん:ストレージやデータベースの鍵であれば、住民からの申請データや添付ファイルが読み出される可能性があります。
- なりすまし:決済サービスのシークレットキーで返金・送金操作、GitHubトークンでソースコードの改ざんやCI/CDの乗っ取りが起こり得ます。
実例:コード内のアクセスキーが長期間公開されていた事例
トヨタ自動車は2022年10月、T-Connectのユーザーサイトのソースコードの一部が2017年12月から2022年9月15日までGitHub上で公開状態となっており、そこに含まれていたデータサーバーへのアクセスキーにより、メールアドレスと顧客管理番号296,019件が漏えいした可能性があると公表しました。
HTMLではなくソースコード公開の事例ですが、「コード内のアクセスキー」が数年間見過ごされる危険を示しています。公開されたコードに秘密情報が混入すると、気づくまでの全期間が悪用可能な期間になります。
APIキーが見つかったときの対処と対策(プロキシ構成・Next.js・再発防止)
1. 直ちに行うこと(実在するキーだった場合)
- キーを無効化・再発行(ローテーション)する。HTMLから消すだけでは不十分です。検索エンジンのキャッシュ、Web魚拓、CDN、利用者のブラウザに残っています。
- 発行元の管理画面で、公開期間中の利用ログ(APIリクエスト履歴、課金明細)を確認する。
- 不審な利用があれば、組織のインシデント対応手順に従い報告する。
2. 恒久対策:サーバー側プロキシへ移す
PHPの例です。キーは .env に置き、.env は配信禁止にします。
<?php
// api/chat.php
$env = parse_ini_file(__DIR__ . '/.env');
$apiKey = $env['OPENAI_API_KEY'] ?? '';
$ch = curl_init('https://api.openai.com/v1/responses');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],
CURLOPT_POSTFIELDS => file_get_contents('php://input'),
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
# api/.htaccess
<Files ".env">
Require all denied
</Files>
このようなプロキシは、そのまま公開すると第三者に無制限に使われ課金が膨らみます。入力長の上限、レート制限、必要に応じて利用者認証を必ず組み合わせてください。
3. Next.js(Vercel)での注意
Next.js(Vercel)では、NEXT_PUBLIC_ 接頭辞を付けた環境変数はビルド時にクライアントのJavaScriptへ埋め込まれます。秘密キーには絶対に付けず、Route Handler(app/api/.../route.ts)内で process.env.OPENAI_API_KEY を参照してください。
4. 公開前提キーの制限
Google Maps等のキーは、Google Cloudコンソールで「アプリケーションの制限:ウェブサイト」に自組織のドメインを https://www.city.example.lg.jp/* のように完全な形式で登録し、「APIの制限」で必要なAPIだけに絞ります。
5. 再発防止
- リポジトリに秘密情報スキャン(GitHub Secret Scanning、gitleaks等)を導入し、コミット前に検出する。
- 委託契約・仕様書に「APIキー等の秘密情報をクライアント側コードに含めない」「納品前に秘密情報スキャン結果を提出する」を明記する。
確認方法(curl)
# トップページに代表的なキー形式が含まれていないか確認
curl -s https://www.city.example.lg.jp/ | grep -Eo 'sk-(proj-)?[A-Za-z0-9_-]{32,}|A(KI|SI)A[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{30,}|(sk|rk)_live_[0-9A-Za-z]{20,}|BEGIN [A-Z ]*PRIVATE KEY' | sed 's/.\{8\}$/********/'
出力された値を報告書やチャットへ貼り付けないよう注意してください(上記は末尾8文字を伏せています)。修正後はセキュリティAIチェッカーで再診断し、○ になったことを確認します。
サイトや業務を止めないための注意点(ガードレール)
- キーをローテーションすると、旧キーを使っている全システムが停止します。新キーの配布先を洗い出してから旧キーを失効させてください(Stripeのように猶予期間を設けられるサービスもあります)。
- ダミー値(
sk-xxxxxxxx...等のサンプル)で × になった場合は、紛らわしい文字列自体を削除するのが最も確実です。
よくある質問(FAQ)
JavaScriptをminifyや難読化すればAPIキーを置いても大丈夫ですか?
大丈夫ではありません。minifyや難読化で短くなるのは変数名だけで、文字列リテラルはそのまま残ります。Base64も暗号ではなく誰でも復元できます。秘密用キーはブラウザに届くコードには一切置かないでください。
Google MapsのAPIキー(AIza...)がHTMLにあるのは問題ですか?
Google Maps JavaScript APIのキーはブラウザに置く前提のキーで、本項目では検出対象外です。ただし、ウェブサイト制限(HTTPリファラー制限)とAPI制限を設定しないと、第三者が同じキーで地図系APIを呼び出し、料金をサイト所有者が負担することになります。Google Cloudコンソールで両方の制限を設定してください。
秘密情報らしき文字列が診断で × になりました。HTMLからキーを削除すれば解決しますか?
実在するキーなら削除だけでは不十分です。検索エンジンのキャッシュ、Web魚拓、CDN、利用者のブラウザに値が残っているため、発行元の管理画面でキーを無効化・再発行し、公開期間中の利用ログや課金明細を確認してください。
サンプルのダミー値でも × になりますか?
検出パターンの形式に一致すれば、ダミー値でも × になります。根拠欄にも目視確認を促す注意を出しています。紛らわしいサンプル文字列は公開ページから削除するのが最も確実です。
生成AIのAPIをブラウザから安全に使うにはどうすればよいですか?
ブラウザからは自組織のサーバー(PHPやNext.jsのRoute Handler等)を呼び、そのサーバーが .env 等から読み込んだキーで外部APIを呼ぶプロキシ構成にします。プロキシには入力長の上限、レート制限、必要に応じて利用者認証を組み合わせ、第三者に無制限に使われないようにします。
Next.jsで NEXT_PUBLIC_ を付けた環境変数に秘密キーを入れてもよいですか?
いけません。NEXT_PUBLIC_ 接頭辞の環境変数はビルド時にクライアントのJavaScriptへ埋め込まれます。秘密キーには付けず、Route Handler(app/api/.../route.ts)の中で process.env から参照してください。
秘密情報らしき文字列の診断が ○ なら、サイトに秘密情報は一切ないと言えますか?
言えません。この項目が読むのはトップページ(入力URLの最終ページ)のインラインHTMLだけで、外部JavaScriptファイル、下層ページ、APIレスポンス内のキーは検出できません。○は「代表的な形式が見つからなかった」という意味です。
この項目を含む127項目を、URLを入れるだけで無料診断します。
参考資料
- OWASP ASVS 5.0.0 V13 Configuration
- OWASP WSTG-INFO-05 Review Web Page Content for Information Leakage
- AWS IAM identifiers(一意ID接頭辞 AKIA/ASIA)
- GitHub Blog「Behind GitHub's new authentication token formats」
- Stripe APIキー
- Google Maps Platform API security best practices
- トヨタ自動車「お客様のメールアドレス等の漏洩可能性に関するお詫びとお知らせについて」(2022年10月7日)
- ScanNetSecurity「トヨタ『T-Connect』登録情報が漏えいの可能性、GitHub上でデータサーバーへのアクセスキーを公開」