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

許可ルールがパスワードを抱えていた

11月6日、Apiiro は、Claude Code の「Yes, and don't ask again」ボタンが承認したコマンドをシークレットごと .claude/settings.local.json に保存すること、そして公開 GitHub リポジトリ 1,122 件が、本物らしいパスワード、トークン、鍵の入ったこのファイルをコミットしていたことを報告した。広く使われているシークレットスキャナー 2 種は、そのうち 215 件を見逃した。Bromure Agentic Coding では、エージェントが打てるシークレットはすべて代用値だ。保存されたルールにはどこでも使えない値しか入らず、push はすべて Mac 上であなたの返事を待つ。

同じ確認をもう出さないために「Yes, and don't ask again」をクリックする。Claude Code は次回それと見分けられるよう、コマンドをまるごと、パスワードも含めて、プロジェクト内のファイルに保存する。

11月6日、Apiiro はコーディングエージェントが「don't ask again」を通じてシークレットを漏らしているという調査を公開した。翌朝には The Hacker News が報じた。

仕組みはありふれている。Claude Code がシェルコマンドの実行を求め、あなたが Yes, and don't ask again を選ぶと、プロジェクト内の .claude/settings.local.json に許可ルールが書き込まれる。ルールが npm test * のようなパターンになることもある。正確なコマンドそのものになることもあり、そのコマンドの PGPASSWORD=、sshpass -p、mysql -p の後ろにパスワードがあれば、パスワードはコードの隣の JSON ファイルに入ることになる。

Apiiro が公開 GitHub でこのファイルを探したところ、次のものが見つかった。

  • 103,617 リポジトリに 104,185 個のコピー
  • シークレットを 1 つ以上含むコピーが 1,122 個、異なるシークレットは計 1,379 件
  • データベースのパスワード 379 件、公開サーバーの SSH ログイン 43 件、GitHub・GitLab・npm のトークン 42 件、AWS キー 23 件
  • 63 の組織のシークレット。その中にはデータベースベンダーの管理者権限のクラウドキーや、医療記録データベースのオーナーパスワードもあった

Apiiro が確認した時点で、どのファイルもまだデフォルトブランチにあった。半数のリポジトリでは、少なくとも 217 日間そこにあった。古いコミットには 509 リポジトリにわたってさらに 734 件のシークレットがあり、74 リポジトリの 211 個のフォークがコピーを持っていた。共有される .claude/settings.json、履歴、保存されたエージェントのチャットログまで数えると、Apiiro の集計は 1,994 リポジトリで 2,539 件になった。

1 クリック、公開シークレットまで 4 段階承認プロンプトPGPASSWORD=s3cr3t psql-h db.example.com …"Yes, and don't ask again"許可ルールを保存.claude/settings.local.jsonコマンド全体を平文でコミット539 / 1,105追跡したリポジトリでClaude 共作のコミットが変更GitHub で公開1,122シークレット入り。半数は217 日以上そのままファイルを消してもシークレットは消えない古いコミットに 509 リポジトリ分のシークレット 734 件。74 リポジトリの 211 フォークにも同じ値
パスワードが承認プロンプトから公開リポジトリにたどり着くまで。許可ルールは正確なコマンドを保存する。エージェントがプロジェクトフォルダーをコミットするとファイルも一緒に出ていき、デフォルトブランチを掃除した後も、フォークと古いコミットにはコピーが残る。

なぜどこでも止まらなかったのか

Claude Code もファイルを git から外そうとはしている。初めて settings.local.json を書くとき、このファイルを git のグローバル除外設定に加える。だがこのルールは 1 台のコンピューターにしかない。同僚のノート PC、CI ランナー、新しいクローンにはないし、すでにコミット済みのファイルには何の効果もない。Claude Code がプロジェクト自身の .gitignore にこのファイルを加えることはない。Apiiro が比較した 16 のコーディングエージェントのうち、承認したコマンド全体をデフォルトでプロジェクト内のファイルに保存すると確認されたのは Claude Code だけだった。

シークレットスキャナーも同じだった。Apiiro はすべてのコピーに gitleaks と TruffleHog をかけた。1,122 ファイルのうち gitleaks が検出したのは 564、TruffleHog は 643 で、215 は両方をすり抜けた。その 215 の大半はコマンドに打ち込まれたパスワードだった。スキャナーは AKIA… や ghp_… のような既知の鍵形式を探すが、sshpass -p hunter2 はどれにも当てはまらない。

ユーザーは 1 年以上前にこの問題を報告していた。2025 年 8 月の issue anthropics/claude-code#5544 は、Claude Code が API キー入りの承認済みコマンドをこのファイルにそのまま保存していると指摘した。60 日間動きがなかったため、ボットが「not planned」として閉じた。関連する 4 件の報告も同じボットに閉じられ、最新のものは The Hacker News の記事の前日だった。

シークレットがコマンドにあったのは、エージェントの手にあったから

1,379 件のシークレットはどれも、エージェントが打てる値だった。開発者がチャットに貼ったか、.env ファイルに置いたままにしたか、エージェントが ~/.aws/credentials や ~/.pgpass から読み、実行したいコマンドに直接埋め込んだかだ。許可ルールは、エージェントがすでに持っていたものを書き留めたにすぎない。

今後に向けた Apiiro の助言も同じ方向を指している。シークレットをコマンドに入れず、シークレットマネージャーの変数を参照して、保存されるルールには $DB_URL しか入らないようにする、というものだ。すべての開発者とすべてのエージェントが毎回それを守るなら効果はある。だが失敗したコマンドを動かそうとするエージェントは、変数を展開してしまう。Bromure は別の道を取り、本物の値をエージェントの環境からまるごと外しておく。

同じクリックを Bromure Agentic Coding で

Bromure Agentic Coding は Claude Code、Codex、Grok、Kimi、omp を Mac 上の Ubuntu VM で動かす。エージェントは、私たちがワイヤ境界と呼ぶものを通して認証情報を受け取る。VM の中ではすべてのシークレットが代用値で、本物の値は Mac 上で暗号化され、そのマスターキーは macOS のキーチェーンにある。Mac 上のプロキシが通信経路上で代用値を本物の値に差し替える。それも、その認証情報が属するホストへのリクエストに限ってだ。

だから、エージェントがトークン入りのコマンドの実行を求め、あなたが Yes, and don't ask again を選んでも、Claude Code はこれまでどおりコマンドを保存する。その中のトークンが代用値なのだ。

エージェントが打てるシークレットはすべて代用値

代用値はツールが期待する形を保つので、プロジェクトでは何も変わらない。

Apiiro が見つけた漏洩Bromure マシンでエージェントが持つもの
GitHub と GitLab のトークンghp_ + 36 文字、glpat- + 20 文字。GH_TOKEN、GITLAB_TOKEN、~/.git-credentials に入る
npm トークンNPM_TOKEN 内の brm_… 値。registry.npmjs.org に限定した Other API key エントリーから
AWS キー本物のアクセスキー ID と偽のシークレットキー。Mac が各リクエストを本物で署名し直す
データベースのパスワード指定した変数に入る brm-db-… 値。ClickHouse、Mongo Data API、Elasticsearch 向け
SSH ログインパスワードは一切なし。Mac から出ない鍵による署名だけ

エージェントが ClickHouse のエンドポイントに対して実行しそうなコマンドを例に取ろう。

curl -u default:brm-db-7Qk2…Xw9 \
  'https://analytics.example.com:8443/?query=SELECT+count()+FROM+events'

これを永久に承認すると、許可ルールには brm-db-7Qk2…Xw9 が入る。プロキシは analytics.example.com へのリクエストで、Basic 認証ヘッダーとリクエスト本文の両方で代用値を差し替える。データベースのシークレットは SQL や JSON の中に乗ることが多いからだ。それ以外の場所では、この文字列は何の役にも立たない。

AWS の場合、シークレットは VM に届きもしない。エージェントの ~/.aws/config はヘルパーを指しており、ヘルパーは本物のアクセスキー ID と 40 文字の偽シークレットを渡す。AWS CLI、boto3、terraform は偽物で署名し、Mac はリクエストが出ていく前にその署名を外して本物のシークレットで署名し直す。本物のシークレットキーは VM の中に一度も存在しないので、エージェントが書くどんなコマンドにも入りようがない。

Apiiro の集計にあるサーバーログイン 43 件は SSH の話だ。Bromure マシンの SSH_AUTH_SOCK は Mac への橋渡しで、VM は署名を求めることはできても、秘密鍵のバイトが渡ってくることはない。YubiKey、Secretive、1Password に守られた鍵は 1 本ずつ提供でき、タッチの確認は Mac に出る。エージェントが sshpass -p を必要とすることはないので、許可ルールが保存するパスワードもない。

マシンの VM承認済みコマンドGH_TOKEN=ghp_Xk3…a7Q gh api …"Yes, and don't ask again"settings.local.json代用値を保存。使えるのはこの Mac のプロキシ経由だけgit pushコミットにファイルも入るあなたの Macプロキシが経路上で差し替え代用値 → 本物のトークンgithub.com 宛てのみ本物はキーチェーンで封印トークン、AWS、SSH 鍵:どんな形でも VM に入らないPrompt before writepush はあなたの返事を待つインターネットapi.github.com本物のトークンを受け取り、コマンドは普段どおり動く誰かがファイルをコピーリポジトリ、フォーク、古いコミットから取って試すと:401 Bad credentials
Bromure マシンでの同じクリック。許可ルールはやはりコマンドを保存するが、その中のトークンは代用値だ。それを公開してしまう push は Mac のダイアログを待ち、GitHub からコピーされた代用値はどこでも失敗する。本物の値に変えられるのは、あなたの Mac のプロキシだけだからだ。

GitHub 上の代用値では何も開かない

仮にファイルが公開リポジトリ、フォーク、十数個の古いコミットに届いたとしよう。それを拾った人が手にするのは、ghp_ と、GitHub が一度も発行していない 36 文字だ。その人のノート PC から送っても認証に失敗する。どの本物のトークンに対応するかを知っているのはあなたの Mac のプロキシだけで、そのプロキシはあなたのマシンの VM にしか応えない。

マシンの内側から代用値が間違ったホストに向かえば、プロキシはそれを持ち出しの試みとして扱う。HTTP 451 でリクエストを止め、VM を一時停止し、This environment may have been compromised という警告を開く。警告には、その認証情報、それが属するホスト、送られた先のホストが示される。代用値はただの置き換えではなく、仕掛け線にもなる。

ローテーションは一か所で済む

Apiiro の最初の指示は、何かを掃除する前に、ファイルにかつて入っていたパスワード、トークン、鍵をすべてローテーションすることだ。本物のシークレットが .env ファイル、シェル履歴、エージェントの設定に散らばっていれば、それは宝探しになる。Bromure では本物の値は一か所にある。Mac 上のマシンの Credentials ペインだ。そこに新しいトークンを貼っても VM 内の代用値は変わらないので、マシン内の許可ルール、スクリプト、設定はそのまま動き続ける。

Other API key には op://Engineering/npm/token のような 1Password の参照も使える。Bromure はそれを Mac 上で解決し、2 分ごとに解決し直すので、1Password でローテーションした鍵は数分以内に有効になる。再起動も、VM 内での編集もいらない。

シークレットをこのペインに移すのは一手で済む。Import env file… が既存の .env や ~/.bashrc を読み、値を伏せた状態で変数を表示し、それを認証情報に変える。プロジェクトフォルダーには、エージェントがパスワードを読んでいた .env ファイルがもう要らない。

push はあなたを待つ

Apiiro がたどったリポジトリのうち 539 件では、Claude を共同作成者として記したコミットがこのファイルを変更していた。エージェントはタスクを仕上げる一環としてコミットし、push する。

新しい Bromure マシンでは、Guardrails ペインですべてのフォージサービスが Prompt before write から始まる。読み取りは通る。HTTPS での git push は、操作内容を示す Mac のダイアログで止まり、あなたが答えるまで何も出ていかない。ポリシーはプロキシで動くので、VM の中の何物もそれをオフにできない。

その行を読むレビュアー

どのスキャナーも見つけられなかった 215 件は、コマンドの中のパスワードだった。コミットされた JSON ファイルで PGPASSWORD=… psql -h db.example.com を読むモデルなら、それが何かはわかる。

Bromure のコードレビューは、あなたのマシンの 1 台でエージェントを使って GitHub リポジトリを見守る。タイミングは Every new commit、Every new pull request、または週次のスケジュールで、Bromure はスキャンをまたいで指摘の重複を取り除く。Apiiro の「履歴も含めて組織内を検索する」という手順には、Run Baseline Now でリポジトリ全体の単発レビューを始められる。GitHub へのポーリングは Mac から行われ、レビューエージェントもほかと同じ代用値で作業する。

トランスクリプトに残るのも代用値

Apiiro の集計には、保存されたエージェントのチャットログも含まれていた。VM 内の Claude Code 自身のセッションファイルはエージェントが見たものを記録するが、Bromure マシンではそれは代用値だ。Mac 上の Bromure のトレースは、トークンの差し替えを sk-a…f9q3 のような短いプレビューとして残し、本物のシークレットのバイトがトレース記録に入ることはない。それでも誰かが VM に手で貼り付けたせいで本物らしいシークレットが通信に現れれば、トレースはそれを漏洩の可能性として示し、bromure-cli trace leaks がすべてを一覧にする。Guardrails で Only allow credentials Bromure injects を有効にすれば、Bromure が入れたものではない認証情報を運ぶリクエストはブロックされ、侵害の警告が出る。

すべてのコマンドに代用値

VM 内のトークン、データベースのシークレット、AWS キーは代用値だ。許可ルールが保存するのは、あなたの Mac を通したときにしか使えない値になる。

SSH にパスワードは不要

VM は Mac に署名を求める。秘密鍵が渡ることはなく、sshpass -p も必要ない。

push は Mac で待つ

新しいマシンでは Prompt before write が既定。push はあなたの返事を待ち、それを強制するのはプロキシだ。

ローテーションは一度

本物の値は 1 つのペインか 1Password にある。そこで変えれば、どの代用値もそのまま動く。

今週やること

Apiiro のリストから、コーディングエージェントを使うすべてのチーム向けに。

  • GitHub の組織内で、履歴も含めて .claude/settings.local.json と .claude/settings.json を検索する。
  • それらのファイルにかつて入っていたシークレットをすべてローテーションし、それから掃除する。
  • git rm --cached .claude/settings.local.json を実行し、このファイルをプロジェクトの .gitignore と組織のテンプレートに加える。
  • pre-commit と CI のスキャンで .claude/、.cursor/、.gemini/、.specstory/ を .env ファイルと同じように扱い、スキャナーがコマンド内のパスワードを検出できることを確かめる。

そのうえで、本物のシークレットをエージェントの手の届かないところに移そう。Bromure マシンなら、その日の開発者が Apiiro のルールを覚えていたかどうかに関係なく、次の「don't ask again」が保存するのは代用値だ。

エージェントがシークレットを履歴やログに書き写すという、より広いパターンについてはトークンはトランスクリプトに残ったを参照してほしい。差し替えの仕組みの詳細は、マニュアルの Credentials の章で読める。