别再把 .env 交给 agent:真 key 留在代理,agent 只拿一张会过期的会话票

2026-10-29 Skills安全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 第一次跑会让你输密码,因为要把证书加进钥匙串。

五条验收标准

  1. 在 agent 会话里让它调一次被 bundle 覆盖的接口,请求成功;去 Sessions 页面能看到这次请求被记录为 brokered。
  2. env | grep -i key 在 agent 进程里搜不到任何真 key,只有占位符。
  3. 把 session 撤销,重跑同一条命令,代理返回 403——不是 401、不是超时,说明流量确实走了代理。
  4. 让 agent 访问一个 bundle 里没登记的主机(比如 curl https://example.com),在 Access bundle hosts only 策略下被拒。
  5. 把 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 一把、能单独撤销”,就已经避开了最坏的那类事故。

原文:https://infisical.com/blog/dont-give-ai-agents-your-env