← すべての投稿に戻る
公開日 · 著者 Renaud Deraison

消したはずの鍵は履歴に残っていた

10 月 8 日、GhostAction と呼ばれるキャンペーンが乗っ取った 2 つのメンテナーアカウントを使い、security-audit.yml という workflow を 346 のリポジトリにコミットした。Uber の athenadriver や、スター 1 万 8,000 の pyxel も含まれる。10 月 9 日には、Socket が数えたアカウントは 500 超、リポジトリは数万に達していた。この workflow は git log -p --all を実行し、これまでのすべてのコミットから Anthropic、OpenAI、OpenRouter、GitHub、AWS の鍵を grep する。1 コミット後に消された鍵も対象だ。Bromure Agentic Coding では、エージェントが持つのは常に代用値だけなので、エージェントがリポジトリの履歴に書き込んだ鍵は、見つけた者にとって何の価値もない。

ファイルから鍵を消すのはコミット 1 つで済む。その直前のコミットは、 リポジトリが存在する限り鍵を持ち続ける。

10 月 8 日木曜日の 13:40 UTC、ゲームエンジン pyxel の作者である Takashi Kitao 氏の GitHub アカウントが、.github/workflows/security-audit.yml という ファイルのコミットを始めた。4 分後、そのファイルは同氏の 27 のリポジトリに 入っていた。21:10 UTC には、Uber の athenadriver の原作者であるアカウント henrywoo が同じ一斉コミットを始めた。21:26 までに 318 のリポジトリに達し、 その中には 279 のフォークと、このアカウントが書き込み権限を持つ uber/athenadriver そのものも含まれていた。

Socket は 10 月 9 日に分析を 公開し、これらの コミットを GhostAction によるものとした。2025 年 9 月に初めて確認された 認証情報窃取キャンペーンだ。同日の終わりまでに、Socket の続報は、10 月 7 日 以降にこの workflow を「数万のリポジトリ」へコミットした GitHub アカウントを 500 以上数えていた。The Hacker News と StepSecurity も同じ波を報じている。Socket によれば、workflow は影響を 受けたリポジトリですでに実行されており、記事の公開時点でも kitao/pyxel と uber/athenadriver のデフォルトブランチに残っていた。

security-audit.yml がすること

workflow の名前は「Security Audit」。workflow_dispatch と、あらゆる ブランチやタグへのすべての push で、フィルターなしに動く。checkout ステップは fetch-depth: 0 を使い、すべてのブランチ、すべてのタグ、完全な履歴を取得する。 そのうえで、1 つのステップが 2 つの仕事をこなす。

1 つ目は、リポジトリの GitHub Actions のシークレットを狙う。攻撃者は workflow をコミットする前にリポジトリの既存の workflow を読み、そこで使われて いるシークレットの名前をペイロードに書き込んでいた。pyxel の場合は CARGO_REGISTRY_TOKEN、PERSONAL_ACCESS_TOKEN、PYPI_PASSWORD、 PYPI_USERNAME だった。

2 つ目は今回の波で新たに加わったものだ。このステップは作業ツリーを 13 の 認証情報パターンで grep し、続いて git log -p --all を実行して、最大 20 万行の履歴を同じパターンで grep する。

  • AI サービス: Anthropic (sk-ant-)、OpenAI のプロジェクトキー (sk-proj-)、OpenRouter (sk-or-)
  • ソース管理: GitHub のクラシックトークン (ghp_)、Fine-grained トークン (github_pat_)、GitLab のトークン (glpat-)
  • AWS: アクセスキー ID、一時キー ID、シークレットキー、セッション トークン。各キー ID の前後 2 行も残すので、隣にコミットされた シークレットキーも一緒に持ち出される。
  • SaaS とクラウド: Google の API キー、Slack のトークン、SendGrid の キー

すべては 1 回の curl POST で、暗号化されていない HTTP で 193.32.204.199 に送られる。何か見つかったかどうかにかかわらず、実行の たびにリポジトリ名が報告される。

Socket は標的が誰かを指摘している。henrywoo のリポジトリのうち 2 つは pyllama と chatllama で、機械学習プロジェクトだ。そのコントリビューターや フォークした人は「こうした種類の鍵を持っている可能性が高い」。Anthropic や OpenAI の鍵は課金される推論として転売でき、その鍵を通るすべてのプロンプトは 買い手の手を経由する。

履歴はすべてを残す

Socket の対処ガイダンスには、漏れた鍵をファイルの編集で片付けたことのある人 なら不安になるはずの一文がある。「現在のツリーに残っているかどうかに かかわらず、いずれかのコミットで見つかった認証情報はすべてローテーション すること」。次のコミットで消した鍵も、その前のコミットには残っている。 git log -p --all はその両方を読む。

コーディングエージェントはこの履歴を速いペースで増やしている。GitGuardian の State of Secrets Sprawl 2026 によると、 Claude Code が共著者となった公開コミットがシークレットを漏らした割合は 3.2% で、公開 GitHub 全体の基準値 1.5% を上回った。同じレポートは、2025 年に 漏洩した AI サービス関連のシークレットを 1,275,105 件と数えている。前年比 81% 増だ。

エージェントが失敗していた統合テストを通すところを見たことがある人なら、 このパターンはおなじみだろう。エージェントは環境変数に鍵を持っている。テスト が通るからと、それを設定ファイルやテストのフィクスチャ、まだ ignore されて いない .env に書き込む。コミットする。あとで開発者かエージェントが鍵を 環境変数に移し、もう一度コミットする。ツリーはきれいに見える。履歴には鍵が あり、すべてのフォークがその履歴を持ち、fetch-depth: 0 の workflow が それを読む。

workflow を書き込んだトークン

Socket は 2 つのアカウントがどう乗っ取られたかを特定していない。The Hacker News は最も可能性の高い経路として、インフォスティーラーのログや認証情報 ダンプから得た personal access token を挙げている。インフォスティーラーの ログは開発者のマシンから来る。ノートパソコンでは、GitHub のトークンは ~/.config/gh/hosts.yml や ~/.git-credentials、多くの場合 GH_TOKEN にも 置かれ、開発者として動くあらゆるものから読める。コーディングエージェントが インストールしたり実行したりするものも例外ではない。

トークンは 1 つあれば足りる。それがあれば、攻撃者はアカウントが書き込める リポジトリを一覧し、workflow を読んでシークレットの名前を拾い、それぞれに コミットできる。henrywoo の一斉コミットは 16 分で 318 のリポジトリに及んだ。

盗まれたトークン 1 つ、workflow 1 つ、全コミットメンテナーのノート PC~/.config/gh/hosts.yml~/.git-credentialsGH_TOKEN=ghp_… (本物)スティーラーのログ→ トークン売買THN による推定経路security-audit.yml を 16 分で 318 個on: push (any branch), workflow_dispatchfetch-depth: 0git log -p --all | grep 13 patternsconfig/test.py の履歴a1f3 + KEY="sk-ant-api03-…" ← 発見c92e - KEY="sk-ant-api03-…" + KEY=os.environ["…"]ツリーはきれい、鍵は履歴に残る。193.32.204.199POST (平文 HTTP)REPO=…PYPI_PASSWORD=…sk-ant-api03-…AKIA_CTX_START…現在の鍵も消した鍵も同じどのフォークも同じ履歴を持つ。henrywoo だけで 279 フォーク (Socket 調べ)。
GhostAction のチェーン。開発者のマシンから盗まれたトークンが workflow をコミットし、workflow がこれまでのすべてのコミットを読み、その履歴にある鍵は現在のものも消されたものも、1 回の POST で送り出される。

同じリポジトリを Bromure マシンから書いたら

Bromure Agentic Coding では、エージェントは Mac 上の Linux VM の中で動き、 VM が出す HTTP リクエストはすべてホスト側のプロキシを通る。Anthropic の鍵、 GitHub のトークン、AWS の鍵は、暗号化されたまま Mac に残る。では、Bromure マシンが書いたリポジトリと、それを書いた開発者に GhostAction を放ってみよう。

履歴にあるのは代用値

VM の中では、どの認証情報も本物と同じ形をした代用値だ。 ANTHROPIC_API_KEY は sk-ant-api03-brm-…、OpenRouter の鍵は sk-or-v1-brm-…、GH_TOKEN と ~/.git-credentials には ghp_ に続く 36 文字が入っている。それぞれ本物の値とインストールごとのソルトから HKDF-SHA256 で導出され、本物に戻すことはできない。プロキシは通信路上で 本物の値に差し替えるが、それは認証情報が属するホスト宛てのリクエストに 限られる。

だから、エージェントがテストを通すために鍵を config/test.py に書き込む とき、書き込むのは手元にあるものだ。コミットすれば、コミットに入るのは sk-ant-api03-brm-… だ。あとで片付けのコミットが来ても、履歴に残るのは その同じ代用値だ。

GhostAction の grep はそれを見つける。sk-ant- は sk-ant-api03-brm- に マッチし、ghp_ は GitHub トークンの代用値にマッチする。 193.32.204.199 への POST がそれを運び、買い手が試す。Anthropic は鍵を、 GitHub はトークンを拒否する。それらを有効な認証情報に変えられるのは、 あなたの VM から出るリクエストに対する、あなたの Mac 上のプロキシだけだから だ。いずれかのコミットで見つかった鍵はすべてローテーションせよという Socket の助言には、このリポジトリについては短い答えがある。どの鍵も あなたの Mac の外で機能したことは一度もなく、AI プロバイダーの代用値は名前に -brm- を含むので、履歴をスキャンすれば見分けられる。

ノート PC のトークンも代用値

キャンペーンのもう半分はアカウントそのものだ。エージェントを Bromure マシンで動かせば、エージェントが使う GitHub トークンは Mac に置かれる。 パッケージのインストールスクリプトであれ、スティーラーであれ、プロンプト インジェクションであれ、VM の中で動くものが ~/.git-credentials と GH_TOKEN に見つけるのは ghp_ の代用値だ。攻撃者が自分のマシンからそれを 使っても、GitHub は 401 を返す。

スティーラーは盗んだものをどこかへ送る必要もあり、そのリクエストは プロキシを通る。代用値の正当な送り先は 1 つだけ、発行されたときのホストだ。 プロキシは送信されるすべてのリクエストのヘッダーとボディを調べ、別の ホストへ向かう代用値がないかを探す。スティーラーのサーバーへ向かう ghp_ トークンには HTTP 451 が返り、何も転送されず、VM は一時停止し、Mac に This environment may have been compromised が開く。そこにはトークンの プレビュー、認証情報の名前、それが属するホスト、送られようとした先の ホストが表示される。

workflow の push はクリックを待つ

この系統のワームは、いつも買い手を待つわけではない。先週の tensorlake のペイロードは、 開発者のマシンから直接、被害者のリポジトリにファイルをコミットしていた。 新しい Bromure マシンは、すべての git フォージに Prompt before write を 使う。push は書き込みなので、VM からリポジトリに security-audit.yml を コミットしようとすると macOS のダイアログ Allow write on "github.com" from workspace "…"? で止まり、通信路上に現れたとおりの POST /owner/repo.git/git-receive-pack が表示される。henrywoo の一斉 コミットは 16 分でこの書き込みを 318 回行った。Bromure マシンからなら、 最初の 1 回があなたを待つ。そしてあなたは、318 のリポジトリにセキュリティ 監査を追加してくれとエージェントに頼んではいない。

トークンの使用はすべて記録される

Security Timeline は、認証情報の差し替えをすべて Credential brokering の行として記録する。どのトークンが、どのホストに、どの マシンから、いつ使われたか。10 月 8 日の 21:10 から 21:26 のあいだに あなたの GitHub トークンが何かを push したかを知りたければ、答えはあなたの Mac の、エージェントには編集できない記録の中にある。トレースを有効に していれば、bromure-cli trace leaks がトークンが誤ったホストに現れた リクエストをすべて一覧する。

Bromure では:履歴には代用値、本物の鍵は Mac から出ないマシンの VM (Ubuntu)GH_TOKEN=ghp_… (代用値)ANTHROPIC_API_KEY=sk-ant-api03-brm-…エージェントのコミット+ KEY="sk-ant-api03-brm-…"スティーラーが ghp_ を送信git push security-audit.ymlVM に盗める本物の秘密はないプロキシ (Mac 上)本物への差し替えは認証情報自身のホストだけ代用値が別ホストへ → 451、VM 停止、Mac に警告git-receive-pack → Prompt before writeSecurity Timeline:全差し替えトークン、ホスト、マシン、時刻GitHub の履歴sk-ant-api03-brm-…GhostAction の grepが sk-ant- に一致、外へ送るAnthropic: 401GitHub: 401買い手の手には使えない文字列だけVM の外、Mac に保管Anthropic・OpenAI・OpenRouter の鍵 · GitHub トークン · AWS の鍵 · SSH 秘密鍵 · トレース · Security Timeline
同じチェーンを Bromure マシンに向けると。workflow が読む履歴にあるのは代用値、スティーラーが VM で見つけるトークンも代用値で、それを誤ったホストへ運ぶリクエストは Mac で止まる。

鍵が漏れたあとの後始末

鍵が漏れたときの定番の助言は、ローテーションし、git filter-repo で履歴を 書き換え、force-push して、フォークやミラーが古いコミットを先にコピーして いないことを祈る、というものだ。GhostAction は、その最後の部分がなぜ失敗 するかを示している。Socket は 1 つの名前空間だけで 279 のフォークを数えた。 どれも履歴を持ち、今では workflow も持っている。公開コミットに届いた鍵は、 現在のツリーがどう見えようと、もう失われている。

Bromure マシンは、そもそもコミットに何が届くかを変える。エージェントは、 GitGuardian の数字が示すとおりに鍵の扱いが雑でもかまわない。フィクスチャに 鍵を貼り付けても、.env をコミットして忘れてもいい。エージェントが有効な鍵を 持ったことは一度もないので、その履歴のどれもあなたを傷つけられない。

コミットに入るのは代用値

VM 内のエージェントに見えるのは、本物の鍵と同じ形をした代用値だけだ。 ファイルに書き込まれてコミットされた鍵は、あなたの Mac のプロキシを 通したときにしか機能しない -brm- の文字列だ。

収穫は 401 ばかり

GhostAction のパターンは代用値にマッチして送り出す。Anthropic、 OpenAI、GitHub は、あなたの Mac の外ではどこでもそれを拒否する。

盗まれたトークンはマシンを止める

VM 内の GitHub トークンも代用値だ。GitHub 以外へ送られれば 451 が返り、 VM は一時停止し、警告がトークンとホストを示す。

push はクリックを待つ

Prompt before write は VM からのすべての push を、リクエストを示す macOS のダイアログで保留し、Security Timeline はトークンの使用を すべて記録する。

次の grep

GhostAction の波は 13 か月で 3 回目になる。最初の波は 2025 年 9 月で、CI の シークレット 3,325 件を奪った。2 回目は今年の 8 月 31 日から 9 月 30 日に かけてで、772 のリポジトリにある 2,577 件のシークレットを狙った。今回は完全な 履歴と、AI プロバイダーの 3 つのパターンが加わった。次の波はさらにパターンを 増やすだろうし、それが読む履歴は増える一方だ。

エージェントが触れる鍵が代用値である場所でエージェントを動かせば、 エージェントが残す履歴には、買い手が使えるものは何も残らない。代用値の 仕組みと、それが漏れたときに何が起きるかについては、エージェントは自らの鍵を 漏らした と キーは一度も失効 しなかった を読んでほしい。