别再把 .env 交给 agent:真 key 留在代理,agent 只拿一张会过期的会话票
先看一个具体场景:你给 coding agent 一个 GitHub token,让它在私有仓库里提 issue。这个 token 现在存在于三个地方——.env 文件、agent 的上下文、以及它可能打印出来的任何日志。它能读、能写、能推代码,有效期一年。
真正该给它的权限是”对某个仓库的 issues 只读”。但 token 是粗粒度的,你很难只给这一件事。
换一个思路:让 agent 连 key 都看不见。
agent 发请求到一个前置代理,请求里带一个占位符。代理认出目标主机、查规则、把真凭据贴上去再转发。agent 从头到尾只持有一张代理的会话票——不是 GitHub 的 key,也没法把它变成 GitHub 的 key。
Infisical 的 Agent Vault 就是这么做的,四个概念:
| 概念 | 是什么 |
|---|---|
| service | 一个外部 API,绑一把真凭据;可限定主机、HTTP 方法、路径 |
| access bundle | 一组 service,决定”这次 agent 能碰哪些 API” |
| session | 绑定某个 bundle、有期限的授权;到期或撤销后代理停止贴凭据 |
| proxy | 出口代理,默认监听 17323 端口 |
一、建 bundle 和 service
Infisical 里打开 Agent Vault → Access Bundles → Create Access Bundle,起名比如 agent-demo,进去 Add Service。
选模板(GitHub 之类)会自动填好主机和凭据类型;没有模板就选 Custom HTTP API 自己填 host。凭据那一步把真 key 贴进去。
这里有一件事必须做:service 的详情里能限定 HTTP 方法和路径。留空等于这个主机上的所有方法、所有路径都放行——那就退回到”给了一把大 key”。填上你实际要用的,比如只允许 GET /repos/{owner}/{repo}/issues。
如果 API 把 key 放在 URL 或请求体里而不是 header,用 substitution:填一个占位符,代理会把它替换成真值。记住这个占位符,第三步要用。
二、起代理
Proxies → Create Proxy → 起名 → Create。弹出的 Proxy Token 只显示一次,复制那行命令。
Docker 版:
docker run -d --name agent-vault-proxy \
-p 17323:17323 \
-v agent-vault-proxy:/etc/infisical/agent-vault \
-e INFISICAL_AGENT_VAULT_PROXY_TOKEN=<你的 proxy token> \
infisical/cli agent-vault proxy --domain https://<你的 Infisical 域名>
镜像里带 CLI,容器监听 17323。挂的那个 volume 存代理的缓存和证书,重启后即使连不上 Infisical 也能起来。用 systemd 的话把 token 写进 /etc/infisical/agent-vault-proxy.env 并 chmod 600。
建 proxy 的时候有个 Traffic Policy,默认是 Any host——意思是代理会把请求转发到它能到达的任何主机,包括你内网的系统。要收紧就选 Access bundle hosts only,代理只放行 bundle 里登记过的主机,其他一律拒绝;真有别的要放(比如包仓库),在 Exceptions 里补。
默认轮询间隔 60 秒,意味着你改规则、撤会话,最慢一分钟生效。
验证代理活着:
curl -s http://localhost:17323/_agent-vault/ca | jq -r .certificate | head -2
第一行应该是 -----BEGIN CERTIFICATE-----。
三、建 session
Sessions → Create Session → 选 bundle → 设 Expires → Create。返回的 --session-token 后面那串就是会话票,同样只显示一次。
这张票就是 agent 全部的凭据。
四、agent 那边做三件事
1)信任代理的证书。 代理要解密 HTTPS 才能贴凭据,所以 agent 必须信它的 CA。
curl -fsS http://<代理地址>:17323/_agent-vault/ca | jq -er .certificate > ca.pem
openssl x509 -in ca.pem -noout -fingerprint -sha256
证书是走明文 HTTP 拿的,所以必须核对指纹:拿输出的冒号分隔值和 Infisical 里 Proxies 页面显示的指纹比,对不上就别用这个文件。
2)把真 key 换成占位符。 SDK 通常不给 key 就不启动,随便填个值就行,比如 agent-vault。Bearer / Basic 类型的凭据,代理会整条 header 替换掉,占位符写什么都行;substitution 类型必须用第一步那个精确的占位符。
3)把流量指到代理。 会话票放在代理 URL 的 userinfo 里:
export HTTPS_PROXY="http://x-agent-vault:<session-token>@<代理地址>:17323"
export NODE_EXTRA_CA_CERTS=./ca.pem
Python 用 REQUESTS_CA_BUNDLE / SSL_CERT_FILE,Node 用 NODE_EXTRA_CA_CERTS。macOS 第一次跑会让你输密码,因为要把证书加进钥匙串。
五条验收标准
- 在 agent 会话里让它调一次被 bundle 覆盖的接口,请求成功;去 Sessions 页面能看到这次请求被记录为 brokered。
env | grep -i key在 agent 进程里搜不到任何真 key,只有占位符。- 把 session 撤销,重跑同一条命令,代理返回 403——不是 401、不是超时,说明流量确实走了代理。
- 让 agent 访问一个 bundle 里没登记的主机(比如
curl https://example.com),在Access bundle hosts only策略下被拒。 - 把 CA 指纹改成错的(或用另一台代理的 ca.pem),agent 报证书错误、请求发不出去。
五个坑
- 代理和 agent 跑在同一台机器上,等于没做。 agent 能读到代理的令牌和 CA 私钥。至少用容器隔离,最好是另一台机器;两台放同一内网,不然每个请求都要多绕一圈。
- 17323 绝对不能暴露到公网。 会话票是明文放在 URL 里的,谁能访问代理谁就能用你的 bundle。
- 别跳过指纹核对。 明文 HTTP 下载的证书,中间人可以换掉;指纹对不上就重新下。
- 407 和 403 含义不同。 407 是请求里没带会话票(
HTTPS_PROXY写错、或者忘了--session-token);403 是会话过期/被撤、主机没被 bundle 覆盖、或者方法路径不在允许范围内。按这个顺序查,比瞎改配置快。 - CI 里每次跑新建一个 session。 别把长期 session 塞进 secrets——那就又变回一把永久 key 了。用 API 在流水线开头建、结尾撤。
泼冷水
- Agent Vault 目前是预览状态。 上面的配置项、端口、命令都可能变,别把它当成不会再动的基础设施。
- 它解决的是”凭据被读走”,不是”agent 干坏事”。 有权限的 agent 照样能删你的数据、发错邮件。权限边界还是得你自己划。
- 多一层代理就多一个故障点。 代理挂了、证书过期了、轮询没生效,都会表现为”agent 突然调不通 API”。先在本地跑顺,再上生产。
- 个人项目未必值得。 你只有一把模型 key、跑在自己电脑上,把 key 写进环境变量一周换一次,比搭一套代理省事。这套东西的价值从”agent 能碰到第二个系统”开始——尤其是支付、云账号、客户数据库这些。
最后一句
今天能做的:把你 agent 环境里最贵的那把 key 找出来,换成短期凭据。哪怕不上代理,先做到”每个 agent 一把、能单独撤销”,就已经避开了最坏的那类事故。