Hermes AgentのDockerからAPIキーを追い出す|egress proxyの導入前検査

hermes egress statusを実行すると、Enabled no、Binary (missing)、Docker enforce yesと表示されました。私の検証機はまだ導入前です。だからこそ、秘密を動かす前に境界と失敗条件を確認できます。
AI エージェントを Docker へ閉じ込めても、実 API キーを環境変数で渡せば、sandbox 内のプロセスは値を読めます。ファイルを隔離したのに、外部サービスの鍵は同居したままです。
Hermes Agent v0.20.6 には、この穴を狭めるhermes egressがあります。sandbox には代理トークンだけを置き、host 側の iron-proxy が送信時に実 API キーへ差し替えます。
前回から今回への差分
前回は、危険コマンド承認、job の運用契約、GitHub 側の保護を分けました。今回は、sandbox が外へ接続するときの資格情報を分けます。
| 項目 | 前回の記事 | egress proxy導入後の仕様 |
|---|---|---|
| 主な対象 | shell commandとGit操作です | Dockerからproviderへの通信です |
| sandbox内の資格情報 | 実APIキーが入る可能性があります | 対応providerでは代理トークンだけを渡します |
| 実APIキーの場所 | host環境とsandboxの両方に残り得ます | host側のproxy daemonへ寄せます |
| 外向き通信 | SDKがproviderへ直接接続します | allowlist済みhostへiron-proxy経由で接続します |
| proxy停止時 | 通信経路とは無関係です | デフォルトではDocker backendをfail-closedで止めます |
Docker backend を使っていない場合は、「導入前の状態を読む」までで止められます。AWS Bedrock や Vertex AI だけを使う場合は、「守れない認証方式」を先に確認してください。
なぜredactだけでは足りないのか
security.redact_secretsは、tool output や log へ出た鍵らしい文字列を伏せます。デフォルトで有効です。ただし、これは表示経路の保護です。sandbox 内の悪意ある process が、環境変数を直接外部へ送る動きまでは止めません。
Docker 隔離も目的が違います。host の filesystem や process を sandbox から遠ざけますが、provider SDK を動かすために実 API キーを渡せば、その値は sandbox の内側にあります。
私は次の 3 段に分けて考えます。
- 保管庫は、host 上の鍵を一元管理します。
- egress proxy は、sandbox へ実 API キーを渡さないようにします。
- redact は、会話や log へ混ざった値を伏せます。
どれか 1 つで済ませず、秘密が置かれる場所ごとに壁を置きます。
用語を揃える
| 用語 | この手順での意味 |
|---|---|
| sandbox | Hermes Agentがterminal commandを隔離して動かすDocker containerです |
| egress | sandboxからproviderなど外部へ出る通信です |
| iron-proxy | hostでTLS通信を受け、代理トークンを実APIキーへ差し替えるdaemonです |
| proxy token | sandboxへ渡す不透明な代理文字列です。実APIキーではありません |
| upstream | OpenRouter、OpenAI、Anthropicなど、通信先のproviderです |
| allowlist | proxyが接続を許すupstream hostの一覧です |
| local CA | proxyがTLSを終端するためにhostで生成し、sandboxが信頼する証明書です |
| fail-closed | proxyを使えないとき、実APIキーへ戻さずDocker起動を止める動作です |
通信経路を先に見る
導入後の経路は次の形です。
[Hermes Agent on host]
│
├─ 実APIキー ───────────────┐
│ │
▼ ▼
[Docker sandbox] [iron-proxy]
代理トークンだけ保持 host側daemon
│ HTTPS_PROXY │ 実APIキーへ差し替え
└─────────────── TLS ───────┤
▼
[allowlist済みprovider]
OpenRouter / OpenAI / Anthropicほか
sandbox にはHTTPS_PROXY、HTTP_PROXY、各 provider の標準環境変数が入ります。ただし、OPENROUTER_API_KEYなどの値は実鍵ではなく代理トークンです。既存 SDK は変数名を変えずに使えます。
host 側には CA 秘密鍵、proxy 設定、token 対応表が残ります。公式仕様では、これらを~/.hermes/proxy/へ置きます。CA 秘密鍵と token 対応表は mode 0600です。
この方式でも host が無傷になるわけではありません。同じ uid で daemon memory を読める攻撃者や、CA 秘密鍵を奪った攻撃者は別の脅威です。守る範囲は Docker sandbox からの持ち出しに限定されます。
導入前の状態を読む
2026 年 9 月 2 日、Hermes Agent v0.20.6 の実機で version を確認しました。
hermes --version
先頭行は次の通りです。
Hermes Agent v0.20.6 (2026.8.27) · upstream 71c823bb
次に、egress 機能の状態を読みます。この command は設定を変更しません。
hermes egress status
実測結果です。
Enabled no
Binary (missing)
Binary version (unknown)
Config (not generated)
CA cert (not generated)
Tunnel port 9090
Process (stopped)
Listening no
Credential src env
Docker enforce yes
Enabled noなので、まだ代理トークンは作られていません。Docker enforce yesは、機能を有効にした後、proxy が止まっていれば Docker backend を拒否する設定です。
Hermes 側の設定値も確認しました。
hermes config get terminal.backend
hermes config get proxy.enabled
hermes config get proxy.enforce_on_docker
実測は次の 3 行です。
local
false
true
この機体の terminal backend はlocalです。さらにdockercommand 自体が見つかりませんでした。
docker version --format 'client={{.Client.Version}} server={{.Server.Version}}'
bash: docker: command not found
ここで setup を強行しても、sandbox 経由の疎通を検証できません。私は実 API キーに関わる設定を、利用経路がない機体へ先回りで入れません。
この機体では導入へ進まない
ここまでに実行したのは、version、egress の状態、設定値、Docker command の有無を読む操作だけです。CA、代理トークン、proxy 設定は生成していません。
terminal.backendがlocalで Docker もないため、この先へ進むと設定ファイルだけを作り、肝心の sandbox 通信を試せない状態になります。導入手順は Docker backend を使える別の機体で、疎通と拒否の両方を実測してから扱います。この稿では公式 command の転載で穴を埋めません。
allowlistとSSRF拒否を分ける
デフォルト allowlist には、OpenRouter、OpenAI、Anthropic、Google、xAI、Mistral、Groq、Together、DeepSeek、Nous Research が含まれます。自己 host 型 provider や独自 MCP server は、自動では追加されません。
接続先を増やすときは、必要な host だけをproxy.extra_allowed_hostsへ足します。広い wildcard は避けます。
一方、SSRF deny list は接続を拒否する IP 範囲です。デフォルトでは loopback、link-local、RFC 1918、IPv6 ULA、CGNAT などを止めます。169.254.169.254の cloud metadata も対象です。
allowlist 済みの名前が DNS rebinding で内部 IP を返しても、IP 境界で拒否します。host 名の許可と IP 範囲の拒否は、別々に残す必要があります。
守れる認証方式、守れない認証方式
静的な header や query parameter へ鍵を置く provider は対応しています。
| provider | host側で差し替える場所 |
|---|---|
| OpenRouter、OpenAI、xAIなど | Authorization headerです |
| Anthropic | x-api-keyまたはAuthorizationです |
| Azure OpenAI | api-keyまたはAuthorizationです |
| Google AI Studio | x-goog-api-keyまたは?key=です |
AWS SigV4 と GCP service account OAuth は対象外です。
| 環境変数 | 未対応の理由 |
|---|---|
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | requestごとにSigV4署名を作るため、静的header置換では扱えません |
GOOGLE_APPLICATION_CREDENTIALS | service account fileからOAuth tokenを作るため、鍵1個の置換では扱えません |
これらの変数が host にある場合、hermes egress statusは警告しますが proxy 起動は止めません。該当 provider を sandbox で使わないなら、sandbox へ渡さない設定を別に確認します。
実機で止まった地点
今回遭遇した表示だけを残します。proxy daemon と sandbox の障害例は、実測できていないため載せません。
| 実測した表示 | 読み取れること | 今回の判断 |
|---|---|---|
docker: command not found、終了code 127 | Docker clientを呼び出せません | Docker経由の検証へ進みません |
Enabled no | egress proxyは無効です | 代理トークンを生成したとは扱いません |
Binary (missing) | 管理対象のiron-proxy binaryがありません | daemonを起動・検証したとは扱いません |
私の検証機はlocal backend で、Docker も未導入でした。今回確認できたのは導入前の状態までです。秘密を扱う設定なので、使い回せる公式手順より、この機体でどこまで実行したかを優先して記録しました。
一次情報
| 出典 | 確認した内容 |
|---|---|
| Hermes Agent Egress credential-injection proxy | proxy token、Docker連携、allowlist、SSRF拒否、Bitwarden、認証方式、失敗条件、v0.39のlog仕様 |
| Hermes Agent Security | Docker隔離、環境変数の扱い、redact、defense-in-depthの境界 |
| Hermes Agent Secrets | Bitwardenなど外部保管庫、複数source、profileでの秘密管理 |
| iron-proxy | host側で資格情報を注入するproxy本体とApache-2.0 license |
※直近 28 日間の GSC では、関心テーマに直接一致する egress proxy の検索語は確認できませんでした。題材は AI エージェントのシークレット境界を狭める関心から選んでいます。
