我们如何通过赋予零凭证来保护Vercel eve Agent

infisical 发布于 2026-07-10 阅读 17

本文介绍了使用Vercel的eve框架构建AI Agent的过程,eve通过文件夹结构定义Agent的指令、工具、子代理等,简化了开发。但Agent需要真实凭证(如API密钥)才能工作,这带来了凭证泄漏风险。作者通过Infisical的Agent Vault实现零凭证方案:Agent的.env文件仅包含占位符,真实凭证存储在Vault中,由Vault在请求离开Agent进程后动态注入,从而防止提示注入导致的凭证窃取。文章还探讨了凭证作用域、认证方式等问题,强调零凭证是Agent安全的更优解。

Blog image

Vercel 刚刚发布了 eve,一个用于构建 AI Agent 的开源框架。它承诺为 Agent 带来与 Next.js 为 Web 应用相同的变革:将架构压缩成一个文件夹和文件结构,而不是一堆胶水代码。

使用 eve,整个 Agent 就是一个文件目录:它遵循的指令、它可以调用的工具、它可以委托给的专业模块、以及人们可以联系它的渠道。

我们构建了一个 Agent 来验证这一承诺的可信度,同时也测试了另一件事。我们为 Infisical 开源社区 原型设计了一个支持 Agent:它需要阅读我们的代码库,并回答关于 CLI 或平台的问题,同时引用实际源代码。这个 Agent 需要真正的凭据才能完成工作——一个模型 API 密钥和一个 GitHub Token——而我们不希望这两者中的任何一个出现在它自己的进程中。因此,我们通过 Agent Vault(一个开源的凭据代理和保管库)路由所有请求,并观察 Agent 在未持有任何使其工作成为可能的密钥的情况下完成实际工作。

文件夹即架构

打开一个 eve 项目,目录结构几乎能告诉你关于 Agent 的一切:

agent/
  agent.ts          model + config
  instructions.md   who the agent is
  tools/            custom capabilities
  skills/           playbooks the agent loads on demand
  connections/      external tools and MCP servers
  subagents/        specialists it can delegate to
  channels/         where people reach it (Slack, web, HTTP)
  schedules/        cron-triggered runs

这就是整个心智模型。文件名及其在树中的位置定义了它的功能。相比之下,手动在原始模型 API 上搭建 Agent 时,“架构” 分散在十几个文件中,还有一个没人想碰的系统提示词,其优势显而易见。

有几个部分值得详细讲解,因为它们让我们无需编写太多额外代码就构建了凭据设置。

模型选择只需一行代码。 agent.ts 导出配置,模型是一个单独的字段:model: "anthropic/claude-sonnet-5"。默认情况下,eve 会通过 Vercel 的 AI Gateway 路由该字符串,但它也可以直接调用任何其他 LLM API,并支持回退。我们的主 Agent 在常规问题上运行 Claude Sonnet。而代码研究人员子 Agent——在较难问题上深入挖掘代码的专家——在其自己的 agent.ts 中指向上 Opus,只需命名一个不同的字符串。

工具是类型化的函数,内置功能已经为你准备好。 网络搜索、获取 URL、读写文件、运行 shell 命令——这些开箱即用。当我们需要特定功能时——例如一个函数,它返回我们的官方文档和 GitHub 链接,以便 Agent 将用户指向真实页面而不是猜测 URL——我们将其编写为 tools/ 中的类型化函数:defineTool 包裹输入数据的 Zod schema 和一个执行查找的 execute 函数。模型只能看到 execute 返回的内容,而永远不会看到其背后的代码或凭据。

连接为我们节省了最多的时间,而且也是我们凭据方案的核心部分。 在典型的 Agent SDK 中手动连接 GitHub 意味着要自己编写每个工具调用:列出 issues、获取文件、搜索代码——每个端点一个函数。在 eve 中,一个连接就是一个指向服务器的文件,Agent 按需发现整个工具集。我们的连接是 connections/github.tsdefineMcpClientConnection 指向 GitHub 自己的托管 MCP(Model Context Protocol)服务器,并有一个读取 process.env.GITHUB_TOKENgetToken 回调。一旦这个文件存在,Agent 就可以搜索 issues、读取文件和打开 PR,而无需我们编写任何 GitHub 特定的函数。而这个 GITHUB_TOKEN 正是我们即将挖空的值。

子 Agent 让主 Agent 保持精简。 我们的主 Agent 将代码问题委托给一个代码研究人员子 Agent,该子 Agent 有自己的提示词、自己的沙箱和自己的模型,定义方式与主 Agent 相同,但位于 subagents/code-researcher/agent.ts。它会克隆我们的仓库,在真实源代码中 grep,然后返回一个文件和行号,而不是猜测——这正是构建一个基于实际代码库(而非模型记忆)回答问题的支持 Agent 的全部意义。

渠道是通向同一个 Agent 的不同前门。 有一个纯 HTTP 端点,你可以用 curl 访问;一个可以嵌入 Next.js 站点的网页聊天组件;以及用于 Slack 和 Discord 的平台渠道,可以回答团队已在使用平台上的提及和私信。我们不需要为 HTTP 端点编写任何东西,因为 eve 默认启用它。但我们可以单独配置 slack.tsdiscord.ts 或其他任何东西。这样就避免了创建并行的 Agent,并且一次更新会在所有渠道上生效。

沙箱是 Agent 真正动手的地方。 克隆仓库、在代码中 grep、运行命令——这些都不会发生在你的本地机器或应用进程中。eve 会启动一个隔离的沙箱并在其中运行。

提供商是分层的:在 Vercel 上是一个硬件隔离的微型虚拟机;在本地,它会依次尝试:Docker 优先,然后是更轻量的微型沙箱,如果两者都不可用,则回退到纯 JS 的 just-bash 模式。这个回退是静默的,不会报错。如果 eve 启动时 Docker 未运行,你会得到一个更慢、能力更弱的沙箱,且没有警告你已不在容器中——在假设本地运行与生产环境一致之前,值得检查一下。

这个沙箱有一个很好的成本特性值得指出。我们的代码研究人员子 Agent 第一次运行时,它会克隆相关仓库,并将其打包成一个缓存模板镜像——大约 1GB,包含我们的整个 monorepo。之后的每个会话都会从该模板通过写时复制技术启动一个新沙箱,因此新容器只有几 KB,而不是几 GB。你只克隆一次,后续的每个会话几乎免费。

框架的其余部分完善了作为生产级产品应有的功能:

  • 任何工具都可以要求在执行前经过人工审批,因此风险高或成本高的操作会暂停并等待人工批准。
  • 定时任务是由 cron 触发的目录,因此每日报告或夜间同步无需任何人提示即可自行运行。
  • 评估是在每次部署时运行的有评分的测试套件,因此一个悄悄破坏某些功能的提示词调整在发布前就会被捕获。
  • 一个等待人工审批或 OAuth 登录的轮次会暂停,并在收到回复时恢复,即使回复来得较晚。

当需要发布时,eve Agent 就是一个普通的 Vercel 项目。执行 vercel deploy,你就会免费获得沙箱、持久会话和定时任务。eve 不强制要求 Vercel(因为它是开源的),但确实为与 Vercel 兼容而进行了优化。

eve 没有解决的问题:谁持有密钥

这一切都无法改变每个 Agent 最终都会遇到的一个事实。要执行任何实际操作,Agent 都需要凭据:模型 API 密钥、GitHub Token,或者任务所需的任何东西。默认模式是 Agent 进程直接读取一个 .env 文件,这意味着 Agent 持有一个实时密钥。这正是 近期 AI 编码 Agent 泄露其 .env 文件的事件 背后的原因。如果恶意指令进入其上下文——隐藏在 GitHub issue 中的提示注入、读取的恶意文件、获取的网页——Agent 的自身环境中就隐藏着一个值得窃取的目标。

这不是 eve 特有的问题,而是每个 Agent 框架都面临的 凭据代理 问题,从本地编码助手到数据库前的 MCP 服务器 都是如此。任何 Agent 需要凭据才能行动的地方,同时也是凭据可能外泄的地方。

因此,在连接演示之前,我们搭建了 Agent Vault,一个专为此目的构建的开源凭据代理和保管库。设置只需一条命令:

agent-vault vault run --vault workshop -- eve dev

这会启动 Agent,并将其出站流量路由到保管库。Agent 的 .env 只包含占位符字符串,或者完全为空。没有真正的 Anthropic 密钥,没有真正的 GitHub Token。实际凭据存储在保管库中,而 Agent Vault 会在实际请求发出时——即请求已经离开 Agent 自身进程之后——才将其注入到线路上。

我们要求 Agent 打印其自身的 Anthropic API 密钥并 cat.env 文件。它拒绝了,这是意料之中的:模型经过训练不会应要求转储凭据。但这种拒绝实际上不是重点,因为本来就没有真正的东西可以拒绝。我们事后检查了 .env 文件:里面的每个值都是虚拟字符串,因此即使是巧妙的提示注入,对攻击者也毫无价值。

更有趣的部分是 GitHub 连接。我们将其指向我们的一个仓库,真正的个人访问 Token 只存储在保管库中,而 Agent 的工具调用返回干净的结果:它列出了 issues 并读取了文件,没有出现错误,尽管其自身 .env 中的凭据只是一个无法自行认证任何内容的占位符。沿着同样的路径,这种模式可以扩展到 Agent 自身环境没有有效凭据的私有仓库。

请求带着虚拟值离开 Agent,而 Agent Vault 会在请求离开 Agent 进程之后、到达 GitHub 之前,在线路上替换虚拟 Token 或附加一个 Token。LLM API 密钥也以同样的方式代理。Agent 的进程中没有任何东西是提示注入能够真正窃取的。

这是行业在 Agent 凭据方面正在趋同的模式,而且随着 Agent 的自主性越来越强,它变得更重要,而不是相反。人类开发者可以审视可疑指令并停止。而自主完成长任务的 Agent 不会有同样的暂停,因此最安全的设计是它一开始就没有真正的秘密可以泄露——无论是粗心、被攻破,还是仅仅按照攻击者注入的指令去做。

在将凭据连接到 Agent 之前值得提出的问题

这在现实世界中是如何实际发生的? 提示注入是常见路径:隐藏在 GitHub issue、网页或 Slack 消息中的指令告诉 Agent 读出或转发其持有的凭据,Agent 会遵循它,因为它无法区分注入指令和真实指令。这并非理论。对技能市场上销售的恶意技能的研究发现,大约八分之一包含某种形式的凭据外泄或凭据收割。

为什么不直接对 Token 进行范围限制,而是要用代理? 范围限制有帮助,你也应该这样做。只读、单一仓库的 GitHub Token 可以限制泄露后的爆炸半径。但范围限制并不能阻止泄露本身,它只是缩小了泄露的价值。而代理则完全将凭据从 Agent 的可触及范围内移除,因此无论范围如何,都没有东西可以泄露。

Agent 不仍然需要某种认证吗? 是的,但不需要取回秘密。Agent 向 Agent Vault 认证时使用的 Token 只允许它通过代理访问预定义的上游服务集合,它并不是 secret zero,也不能用来拉取代理持有的任何其他凭据。真正在目标 API 处打开某些东西的凭据是由代理本身在请求离开 Agent 后附加的,Agent 永远不会看到或持有它。

如果 Agent 无论如何都试图泄露这个虚拟值呢? 对攻击者来说不会有什么有用的结果。Agent 的 .env 中的值是一个真实字符串,但它不是一个有效的凭据。打印它、通过邮件发送它或将其发布到某个地方,交出的不是能打开任何大门的钥匙。

持久化功能在本地 eve dev 会话中是否有效,还是只在 Vercel 上有效? 目前只在 Vercel 上有效。本地开发运行相同的代码,但检查点和崩溃恢复是部署运行时的一部分。在构建依赖会话在本地开发重启后仍能存活的工作流之前,值得了解这一点。

这给我们带来了什么

eve 为 Agent 提供了一个干净、可检查的形状——一个文件夹中的多个文件,而不是一堆胶水代码——并且为你处理了沙箱、子 Agent、持久性和部署。这些都没有触及凭据问题,也本就不该去触及。那是一个独立的层,它决定了被攻破或过于热情的 Agent 是否能用手中持有的东西造成损害。

我们在 eve 上构建了我们的支持 Agent,并用 Agent Vault 保护了它。当它准备好回答 Slack 中的问题时,它的 .env 文件中只包含两个占位符字符串,两者都无法认证任何东西。它用来思考的模型密钥和它用来搜索代码的 GitHub Token 都存放在 Agent 本身永远无法触及的地方。

Finn avatar

Finn

Infisical 技术内容营销人员

  • 原文链接: infisical.com/blog/eve-s...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论