SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-22

如何防止 AI 代理泄露 API 密钥和机密

AI 代理持有 API 密钥时,一次工具调用就可能将其泄露。本文介绍凭据网关、限定作用域的短期令牌,以及为何拒绝规则无法阻止 shell 命令窃取机密。

让机密信息不进入 AI 代理意味着什么

AI 代理本质上是一个执行命令的普通 Linux 进程。该进程持有的所有环境变量都可被它运行的代码读取。因此,代理环境中的 API key 也可以被代理发送到它能够访问的任意主机。让机密信息不进入代理,意味着只向它提供句柄,而不是密钥:例如短期、限定作用域的令牌,或在网络边界由其他组件替换为真实值的占位符。

这不是模型变得恶意的故事。实际机制更简单。代理读取包含指令的网页、README 或 issue 评论,并执行这些指令,因为对语言模型而言,你编写的文本与它获取的文本没有区别。这就是提示注入。一旦发生提示注入,损害范围只取决于一个因素:该进程可以读取什么。如果你还没有设置隔离边界,请参阅在服务器上安全运行编码代理,其中介绍了本指南所依赖的隔离层级。

用通俗语言说明威胁模型

以 agent 运行所使用的用户身份执行此命令。

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

它输出的每一行,都可能让陌生服务器通过一次 HTTP 请求获取数据。现在查看 agent 附近磁盘上的内容。

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

拥有 shell 访问权限的 agent 无需使用复杂的漏洞利用手段,就能将这些数据传出。以下 4 种常见路径即可完成,而且在日志中看起来都像正常工作:

  • 向任意主机发起出站 curlfetch,并将值放入查询字符串。
  • 向 agent 具有写入权限的仓库执行 git commitgit push
  • 执行软件包安装脚本;该脚本会以 agent 用户身份运行任意代码。
  • 查询包含该值的主机名对应的 DNS;即使 HTTP 出站流量被阻止,数据仍会通过 DNS 离开。

仅靠审核无法解决问题。正确的做法是确保 agent 无法访问任何有价值的数据。

工作树中的秘密会进入上下文窗口

代理会读取文件。代理工作所在的仓库中,只要存在 .env 文件,代理就会读取它。文件一旦被读取,就会进入上下文窗口,也就是说,它会出现在会话记录、您保留的任何日志,以及代理接下来生成的内容中。

之前,密钥位于代理工作的工作树中:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

之后,将文件移出代理可访问的范围:

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

由于工作树中不再包含该文件,代理用户无法打开它。代理自身配置中的拒绝规则是第二层防护,而不是第一层防护。Claude Code 会从项目中的 .claude/settings.json 读取权限规则:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

这可以防止代理在探索文件时误打开文件。但它无法阻止注入的指令运行 base64 .env,因为这是 shell 命令,而不是文件读取操作。运行该命令前是否会请求确认,取决于会话的权限模式;而 auto mode 将在 August 2026 成为 Claude Code 的默认模式,因此无人监控的服务器会在没有提示的情况下运行更多此类命令。相同的限制也适用于影响代理行为习惯而非权限的机制:要求代理只执行能够解决问题的最小改动的技能可以防止一次运行过程访问不必要的文件,但这仍然只是模型可能被说服放弃的建议。应将配置视为护栏,将文件系统权限视为墙。容器内部也存在相同的层次划分:Docker Compose 中的环境文件和秘密介绍了这一问题在下一层中的对应情况。

为每个代理使用独立的非特权用户

如果代理以您的身份运行,它会继承您的 SSH 密钥、云凭据和 shell 历史记录。使用独立用户只需执行一条命令,即可隔离这些内容。

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

最后一行必须以 cat: /home/you/.ssh/id_ed25519: Permission denied 失败。如果它输出了密钥,说明您的主目录对组用户或所有用户可读,使用 chmod 700 ~ 修复。不要将代理用户添加到 sudo,也不要为它配置范围大于实际所需单条命令的 NOPASSWD 规则。VPS 上的最小权限用户详细介绍了组和 sudoers 配置。运行多个会话后,请继续保持这种隔离,因为一个 Claude Code 会话可以直接向另一个会话发送文本,第一个会话持有的任何内容都可能通过该通道在一条消息中传出。

云 VPS 上还应增加一道边界。实例元数据服务会在固定的链路本地地址上响应,并且通常会向任何发起请求的对象提供角色凭据。

sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

从代理所在的环境执行检查。sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ 应不输出任何内容并以非零状态退出,因为数据包会在离开服务器前被拒绝。

在边界注入凭据

真正能解决这个问题的方案是凭据注入。代理不会持有真实密钥。它通过本地网关发送请求,网关在请求发出前将占位符替换为真实密钥。密钥存储在网关的存储中,属于另一个进程,并由另一个用户拥有。

OneCLI 是这一方案的一个开源实现,采用 Apache-2.0 许可证,并作为与代理并列的容器运行。截至 July 2026,项目文档说明了以下配置:

git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --wait

仪表板监听端口 10254,网关监听端口 10255。您只需存储一次真实凭据,然后为每个代理提供一个占位值来代替密钥,并提供其专属的受限访问令牌。代理会将该令牌放在 Proxy-Authorization 标头中发送。网关根据主机和路径匹配出站请求,解密匹配的凭据并替换占位值。代理的环境中不包含任何值得窃取的内容。

这里的价值不在于加密本身,而在于“这个代理使用了什么凭据、何时使用”变成了一个日志查询。您只需读取一条审计记录,而不必猜测 6 个环境中的哪一个保存了密钥副本。

将机密交给进程,而不是环境

如果您在 systemd 下运行 agent,则完全不需要环境变量。LoadCredential= 会将机密放在只有该服务能够读取的私有目录中;在 unit 文件中,该机密以 %d 的形式提供,在进程内则以 $CREDENTIALS_DIRECTORY 的形式提供。该值不会出现在 /proc/<pid>/environ 中,因此 ps eww 无法显示它;服务停止后,该目录也会消失。

先将凭据加密到本机。这些命令来自 systemd 文档,适用于 systemd 250 或更高版本,Ubuntu 24.04 和 Debian 13 均包含此版本:

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

最后一个命令会输出 sk-example-value。这证明加密文件可以在此主机上解密。然后在 unit 中引用该文件:

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

agent 代码需要该值时,会在 $AGENT_KEY_FILE 处打开文件。读取文件只在读取期间存在。环境变量则会在进程的整个生命周期内存在,并会传递给它启动的每个子进程。

优先使用短期令牌,而不是长期密钥

永不过期的密钥在数月后出现在日志或会话记录中时,仍然有效。服务提供会话令牌时,应使用会话令牌,并将有效期设置为工作允许的最短时间。

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

AWS STS(安全令牌服务)接受的最短有效期是 15 分钟,通常足以完成一个代理任务。对于 GitHub,应为代理用户创建独立的 gh 登录,并使用限定范围的令牌,将其限制在代理操作的单个仓库内。这样,在该会话中执行 gh auth token 时,返回的内容就无法访问其他资源。先按资源限制范围,再按时间限制有效期。

验证,然后持续验证

每次修改 agent 配置后,都应运行以下 3 项检查。请使用 agent 的用户身份运行,而不是使用您自己的身份。

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

第一项检查不应输出任何内容。第二项应输出 ls: cannot open directory '/home/you/': Permission denied。第三项会显示 agent 的网络路径对外呈现的身份。这正是网关模式要解决的问题:如果显示 401,表示 agent 未携带自己的 GitHub 凭据;如果显示 200,表示它携带了凭据,因此您应确认具体使用的是哪个令牌。如果您以无人值守方式运行 agent,请参阅控制 VPS 上的 AI agent 成本,了解与这些访问限制配套的预算限制。

FAQ

我可以直接相信模型不会泄露我的密钥吗?

不可以,因为在这个威胁模型中,模型不是攻击者。代理会读取网页、代码仓库和 issue 跟踪器中的文本,而这些文本可能包含指令。模型没有可靠的方法区分您的指令和它获取的文本。任何依赖模型做出正确选择的控制措施,都会在某次注入指令具有足够说服力时失效。因此,控制措施必须放在操作系统或网络层,而不能依赖模型。

环境变量真的不适合存放代理密钥吗?

它们有一个具体问题:会被继承。代理启动的每个子进程都会获得一份副本,包括构建脚本、测试运行器和任何软件包安装钩子。同一用户还可以通过 /proc/<pid>/environ 读取这些变量,因此代理运行的任何程序都能读取它们,而无需代理主动传递。通过 LoadCredential= 或网关在使用时读取文件,可以将暴露范围限制在使用时刻。

将密钥放入保管库后,问题就能自动解决吗?

只能解决一部分。保管库解决的是存储问题。如果您自行托管该保管库,还需要单独进行加固,因为 Vaultwarden 服务器通常是通过其管理令牌或备份文件被攻破的,而不是通过其中保存的加密条目被攻破。它无法解决最后一步:某个程序从保管库取出密钥,再将其作为环境变量交给代理。这会让您回到原点。关键在于由谁执行替换。如果代理获取密钥,代理就持有密钥。如果网关或 init 系统在代理进程之外执行替换,代理就不会持有密钥。

如何判断代理是否已经泄露了某些内容?

通常事后无法确定,这正是使用网关的理由。没有网关时,证据会分散在 shell 历史记录、代理的对话记录,以及您很可能没有保留的出站连接日志中。使用凭据网关后,每次使用凭据都会记录为一行,其中包含代理身份和时间戳。如果怀疑发生泄露,应先轮换密钥,再进行调查。轮换成本低,而确定性并不容易获得。

我今天至少应该做什么?

将所有 .env 文件移出代理工作目录,并为每个代理创建一个无特权用户。这两项更改大约需要十分钟,可以阻断最常见的路径:代理读取本不该与代码放在一起的凭据文件。网关和短期令牌是下一步措施,不是第一步。相同的起点适用于任何代理运行时,包括 在 VPS 上安全运行自主代理