Gemini APIプロジェクト停止から学んだ、ログ公開前のマスク運用

Gemini APIキー流出でGoogle Cloudプロジェクトが停止されたときの対応メモ

Gemini APIを使っていたところ、Google Cloud Platform / API プロジェクトが停止されたという通知が届きました。

メールの内容としては、対象プロジェクトが「リソースの乗っ取りと思われる不正行為に関与していたため停止された」というものでした。

最初に見たときはかなり焦りましたが、原因を整理していくと、開発ログやエラーログの扱いがかなり危ないことに気づきました。

今回の記事では、実際に起きたこと、考えられる原因、異議申し立てで入力した内容、そして今後の再発防止策をまとめます。

なお、この記事ではプロジェクトID、チケットID、APIキーなどの識別情報はすべてダミー化しています。

起きたこと

Google Cloudから、Gemini API用のプロジェクトが停止されたというメールが届きました。

内容を要約すると、次のような通知です。

Google Cloud Platform または API プロジェクトが停止されています。

対象プロジェクトは、リソースの乗っ取りと思われる不正行為に関与していたため停止されました。

復旧を希望する場合は、プロジェクトオーナーとしてログインし、異議申し立てを送信してください。

つまり、Gemini APIのキーがどこかから漏れて、第三者に不正利用された可能性があるということです。

停止されたプロジェクトIDはメール内に記載されていましたが、ブログやSlackに共有する場合は、以下のように伏せた方が安全です。

Gemini API project: gen-lang-client-XXXXXXXXXX

考えられる原因

今回、APIキーが漏れた可能性として考えたのは、主に次の2つです。

  • ブログ記事に実行ログやエラーログを貼ったときに、APIキーをマスクし忘れた
  • ChatGPTやGeminiなどの生成AIにログ解析を依頼するときに、APIキー入りのログをそのまま貼った

どちらも、開発中にはかなりやりがちなミスです。

特に技術ブログを書くときは、エラー内容や実行結果をそのまま載せたくなります。しかし、ログの中にはAPIキー、アクセストークン、プロジェクトID、メールアドレス、ローカルパスなどが混ざることがあります。

GitHubだけでなく、技術ブログ、Qiita、Zenn、WordPressの記事なども検索エンジンやクローラーに拾われます。

そのため、一度公開してしまうと「あとで消せばいい」では済まない可能性があります。

危ないログの例

たとえば、次のようなログはそのまま公開してはいけません。

curl "https://example.googleapis.com/v1/models?key=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXX"

APIキーがURLのクエリ文字列に入っているため、コピーしてブログに貼った瞬間に漏洩します。

公開用には、必ず次のように置き換えます。

curl "https://example.googleapis.com/v1/models?key=YOUR_API_KEY"

また、環境変数の確認結果も注意が必要です。

echo $GEMINI_API_KEY
AIzaSyXXXXXXXXXXXXXXXXXXXXXXXX

このような出力は、記事には載せません。

載せるなら、次のようにします。

echo $GEMINI_API_KEY
YOUR_GEMINI_API_KEY

異議申し立てで入力した内容

Google Cloudの通知メールには、プロジェクト停止に対する異議申し立てのリンクがありました。

フォームでは、主に次の内容を入力する必要がありました。

  • このアクティビティを誘発したと考えられる原因
  • 問題解決のために実施する予定の対策
  • 第三者による不正利用が疑われる場合の説明

今回は、原因を隠さずに「APIキーが漏洩した可能性がある」として説明しました。

入力内容は、おおむね次のような形です。

アクティビティを誘発した原因:

技術ブログへの実行ログ掲載、または生成AIへのログ解析依頼の際に、
ログ内に含まれていたGemini APIキーを誤ってマスクせずに含めてしまい、
そこから第三者にキーが漏洩して不正利用された可能性が高いと考えています。


問題解決のために実施する予定の対策:

該当する可能性のあるAPIキーは直ちに削除または無効化しました。

今後は、ブログや生成AIにログを投入・掲載する前に、
APIキー、アクセストークン、プロジェクトID、チケットID、メールアドレスなどを
必ずマスクする運用に変更します。

また、APIキーには可能な範囲で利用制限を設定し、
不要になったキーは削除します。

ここで大事なのは、言い訳をするのではなく、原因仮説と再発防止策を具体的に書くことです。

異議申し立て後に届いたメール

異議申し立てを送信すると、Google Cloud側から受付メールが届きました。

内容としては、次のような意味でした。

異議申し立てを受け付けました。

担当チームが対象プロジェクトを確認し、多くの場合は2営業日以内に連絡します。

この時点では、こちらで追加操作をするというより、Google Cloud側の審査結果を待つ状態になります。

Slackなどで共有するときの注意点

今回のようなトラブルは、仲間内のSlackで共有するとかなり有益です。

ただし、共有する情報はかなり慎重に選ぶ必要があります。

共有してはいけない情報

  • 生のAPIキー
  • アクセストークン
  • Ticket Reference ID
  • 問い合わせ番号
  • プロジェクトIDの全文
  • Google Cloudのスクリーンショット内にある識別情報
  • 請求先アカウント情報
  • メールアドレス
  • ローカル環境の絶対パス

特にTicket Reference IDは、Googleとの問い合わせを特定するための情報です。

外部に公開する必要はありません。Slackが仲間内であっても、スクリーンショットを貼る場合は黒塗りした方が安全です。

プロジェクトIDはどう扱うか

プロジェクトIDは、APIキーほど直接的に危険な情報ではありません。

ただし、対象プロジェクトを特定できる情報なので、公開記事や広いSlackチャンネルでは伏せた方が無難です。

たとえば、次のようにします。

NG:
gen-lang-client-0102262870

OK:
gen-lang-client-XXXXXXXXXX

もしすでにSlackへ投稿してしまった場合でも、APIキーそのものを出していなければ、過度に焦る必要はないと思います。

ただし、今後の共有ではプロジェクトIDも伏せる運用にした方がよいです。

Slackで共有するなら、この形がよさそう

仲間内に共有するなら、次のような内容にすると、余計な情報を出さずに注意喚起できます。

Gemini APIキーが漏洩した可能性があり、
Google CloudのAPIプロジェクトが停止されました。

原因として考えられるのは、
・技術ブログに実行ログを貼るときにAPIキーをマスクし忘れた
・ChatGPT / Geminiにログ解析を依頼するときに、APIキー入りログをそのまま貼った
あたりです。

対応として、
・該当APIキーを削除 / 無効化
・Google Cloud側へ異議申し立て
・今後はログを外部に出す前にAPIキーやトークンをマスク
・APIキーに利用制限を設定
を行う予定です。

共有時の注意:
・APIキーは絶対に貼らない
・Ticket Reference IDも貼らない
・プロジェクトIDもできれば伏せる
・スクリーンショットを貼る場合は識別情報を黒塗りする

開発ログをブログやAIに貼る前に、
一度サニタイズする運用にした方がよさそうです。

今後の再発防止策

今回の件で、APIキーの扱いをかなり見直す必要があると感じました。

今後は、次のルールで運用します。

  • APIキーをコードに直書きしない
  • .env に入れたキーをGit管理しない
  • .gitignore.envtoken.json を必ず入れる
  • ブログにログを貼る前に、APIキーやトークンを検索する
  • AIにログを渡す前に、ローカルでマスク処理をする
  • 不要になったAPIキーは削除する
  • 可能なAPIキーには利用制限を設定する
  • 公開記事ではプロジェクトIDやチケットIDも伏せる

最低限チェックしたい文字列

ログを公開する前に、少なくとも次のような文字列を検索します。

AIza
api_key
API_KEY
token
access_token
refresh_token
client_secret
authorization
Bearer
credentials.json
token.json
project_id
ticket
reference

特に AIza で始まる文字列は、Google系APIキーでよく出てくるため要注意です。

簡易的なログマスク用スクリプト

毎回目視だけで確認するのは危ないので、最低限のマスク処理を挟むようにします。

たとえば、次のような簡易スクリプトを作っておくと、ブログやAIに貼る前のログを一度クレンジングできます。

import re
from pathlib import Path

INPUT_FILE = "raw_log.txt"
OUTPUT_FILE = "masked_log.txt"

patterns = [
    # Google API key っぽい文字列
    (r"AIza[0-9A-Za-z\\-_]{20,}", "YOUR_GOOGLE_API_KEY"),

    # Bearer token
    (r"Bearer\\s+[0-9A-Za-z\\._\\-]+", "Bearer YOUR_TOKEN"),

    # よくある key=value 形式
    (r"(api_key|API_KEY|access_token|refresh_token|client_secret)\\s*=\\s*[^\\s]+", r"\\1=YOUR_SECRET"),

    # JSON内のキー
    (r'"(api_key|access_token|refresh_token|client_secret)"\\s*:\\s*"[^"]+"', r'"\\1": "YOUR_SECRET"'),

    # メールアドレス
    (r"[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}", "your-email@example.com"),
]

text = Path(INPUT_FILE).read_text(encoding="utf-8")

for pattern, replacement in patterns:
    text = re.sub(pattern, replacement, text)

Path(OUTPUT_FILE).write_text(text, encoding="utf-8")

print(f"masked log saved: {OUTPUT_FILE}")

使い方は、マスクしたいログを raw_log.txt に保存してから実行します。

python mask_log.py

出力された masked_log.txt を確認し、問題なければブログやAIへの貼り付けに使います。

もちろん、このスクリプトだけで完全に安全になるわけではありません。

ただ、何もせずにログをそのまま貼るよりは、かなり事故を減らせます。

生成AIにログを渡す前にもマスクする

今回の件で特に反省したのは、ブログ公開前だけでなく、生成AIにログを渡す前にもマスクが必要という点です。

エラー解析を頼むときは、ついログをそのまま貼ってしまいます。

しかし、そのログの中にAPIキーやアクセストークンが含まれていると、セキュリティ的にはかなり危険です。

今後は、次の流れにします。

  1. ローカルでログを保存する
  2. マスク用スクリプトを通す
  3. マスク後のログを目視確認する
  4. 問題なければChatGPTやGeminiに貼る
  5. ブログ記事に使う場合も同じマスク済みログだけを使う

特に、APIキー、トークン、証券番号、住所、電話番号、メールアドレス、ローカル絶対パスなどは、外部に出す前に必ず消すようにします。

今回の教訓

今回の件で一番大きかった教訓は、APIキーはGitHubだけでなく、ブログやAIへの貼り付けからも漏れるということです。

開発ログをそのまま貼る文化は便利ですが、APIキーやトークンが混ざった瞬間に事故になります。

特に、自分のように開発ログをブログ記事化している場合、次の2つはセットで考えた方がよさそうです。

  • 公開前のログマスク
  • AIに渡す前のログマスク

また、Slackなどの仲間内共有でも、APIキーだけでなく、Ticket Reference IDやプロジェクトIDも必要以上に出さない方が安全です。

共有する価値があるのは、具体的なIDではなく、何が危なかったのか、どう対応したのか、次からどう防ぐのかです。

まとめ

Gemini APIキーが漏洩した可能性により、Google Cloudプロジェクトが停止されました。

原因としては、技術ブログへのログ掲載、または生成AIへのログ解析依頼時に、APIキー入りのログをそのまま外部に出してしまった可能性があります。

対応として、APIキーの削除・無効化、Google Cloudへの異議申し立て、ログのマスク運用を進めることにしました。

今後は、ブログに載せるログだけでなく、ChatGPTやGeminiに渡すログも、事前に必ずサニタイズします。

開発ログは便利ですが、そのまま公開すると危険です。

APIキーやトークンは、漏れてから対応するのではなく、外に出す前に消す運用にしておくのが大事だと感じました。

コメント

タイトルとURLをコピーしました