我们给AI Agent配了个钱包:安全花钱前遇到的六个问题

ancilartech 发布于 2026-07-14 阅读 23

本文分享了给AI Agent配备钱包后遇到的六个关键问题及解决方案:1. 原始私钥赋予无限权限,改用ERC-4337智能账户限制权限;2. 代理持有USDC但无法支付Gas,通过Paymaster赞助或支持USDC支付Gas解决;3. 网络丢包导致重复支付,引入幂等性机制防止重复扣款;4. 恶意内容诱导代理转账,设置确定性支付门禁(白名单、限额)保护资金;5. 重试循环耗尽预算,添加断路器监控支出速度并报警;6. 无法追溯代理行为,通过身份标识和审计日志实现可问责性。

给 AI Agent(智能体)一个钱包,大概前五分钟感觉很神奇。你给它一把密钥,连接一个支付客户端,看着它自己买下一份数据。没有结账、没有人工,只是软件支付给软件。每次演示都会成功。

然后你试图让它安全地使用真实资金运行,神奇感就变成了一长串出问题的事项。不是什么稀奇古怪的问题。是每个团队在自主 Agent 从“能够支付”转向“应该被允许支付”时都会遇到的、枯燥的结构性问题。

这是一份构建日志。按我们遇到问题的顺序列出六次故障,每次包括失败原因和有效的修复方案。如果你正准备给一个 Agent 钱包,请先阅读本文。它能帮你避免痛苦的试错过程。

我们实际构建了什么,以及为什么演示会骗人?

我们构建了一个 Agent,它读取任务,决定需要付费资源,并通过 HTTP 使用 x402 用稳定币结算支付,从自己的钱包签名。演示立即成功,因为演示中没有对手方、没有重试、没有不可信输入、也没有支出限额。生产环境全部都有。下面的每个故障都是演示所证明的顺利路径与现实世界所保证的敌对路径之间的差距。

初始设置刻意简单。一个 Agent 循环,一个能支付的工具,以及一个用来支付的货币钱包。在网络干净、端点友好且只有单个请求的情况下,一切正常。

这就是陷阱。支付演示证明了顺利路径,而这恰恰是你不需要担心的一条路径。真正重要的东西都隐藏在演示跳过的路径中:当请求重试时、当输入具有敌意时、当循环失控时、或者当密钥泄露时会发生什么。我们全遇到了。

1. 原始密钥赋予了 Agent 无限权限

我们的第一个钱包是一个普通的 EOA(外部拥有账户),即原始私钥。那把密钥拥有无限的支出权限,且没有可编程控制,因此一个错误的决策或一次泄露就意味着彻底损失。修复方法是切换到 ERC-4337 智能账户,让钱包本身成为一个合约,在任何交易执行前强制执行规则。权限从“谁持有密钥”转变为“账户策略允许什么”。

原始密钥不适合作为 Agent 的基础。它无法说“不”。Agent 签署什么,它就执行什么,最高可达全部余额。

账户抽象在钱包层面解决了这个问题。使用 ERC-4337 智能账户,钱包是一个合约,首先根据自身逻辑验证每个操作。你将一个作用域限定的会话密钥(而非主密钥)交给 Agent,账户在链上强制执行限制。

// Agent 获得一个有边界的会话密钥,而非全部权限
const sessionKey = await smartAccount.grantPermissions({
  signer: agentSigner,
  permissions: [\
    { type: "erc20-spend", token: USDC, maxAmount: parseUnits("100", 6) },\
    { type: "contract-call", target: ALLOWED_VENUE },\
  ],
  expiry: nowPlusHours(24),
});

现在,被攻破的会话密钥只会造成有限损失,而非灾难。这一改变就是实验与可防御系统之间的区别。

2. Agent 有钱但付不起 Gas

我们的 Agent 持有 USDC 但没有原生 Gas 代币,因此它的第一笔真实交易直接失败了。它持有资金却无法移动。修复方案包含两部分:使用 Paymaster(支付主),这样 Gas 由赞助方支付或直接用 USDC 支付而非原生代币;同时针对 x402 支付使用无 Gas 的稳定币转账。接入了 Paymaster 后,Agent 仅使用它实际持有的资产即可进行交易。

这个问题几乎有点好笑,直到它阻碍了你。Agent 有余额,选择一个操作,签名,然后网络拒绝,因为账户中没有 Gas 代币。

人类会不假思索地充值 Gas。Agent 不会,你也不希望它为了支付手续费而管理第二种余额。更简洁的答案是 Paymaster——ERC-4337 的组件,代账户处理 Gas。

// 通过 Paymaster 路由操作,使 Gas 被赞助或用 USDC 支付
const smartAccountClient = createSmartAccountClient({
  account,
  bundlerTransport: http(PIMLICO_BUNDLER_URL),
  paymaster: pimlicoPaymaster,            // 赞助 Gas,或接受 USDC 作为 Gas
  chain: base,
});

在 x402 一侧,USDC 支付使用无 Gas 转账标准,因此 Agent 在授权支付时完全不需要 Gas。两者结合,Agent 只需要最初注入的那一种资产。

3. 连接断开导致 Agent 支付了两次

x402 没有协议层面的退款机制,因此当请求已结算但响应丢失时,Agent 会重试并再次为同一资源支付。我们在不稳定的网络上出现了重复支付。修复方法是幂等性:为每笔逻辑购买附加一个稳定键,在重试时复用相同的签名支付,并根据交易哈希记录每次结算,以便识别出重复支付并跳过,而不是再次支付。

这是一个会悄悄烧钱的问题。支付已在链上结算,但连接在 Agent 获取资源前断开,Agent 假定失败,于是再次支付。

由于没有退款机制,事后无法清理。你必须预防它。模式与任何非幂等支付相同:一个标识逻辑购买的键,以及一个记录已结算情况的账本。

async function payOnce(requestId, doPayment) {
  if (await ledger.has(requestId)) return ledger.get(requestId); // 已支付,复用结果
  const receipt = await doPayment();          // 结算一次
  await ledger.record(requestId, receipt);    // 持久化 tx 哈希和金额
  return receipt;
}

让每次付费操作都通过这样的守卫。重试变得安全,Agent 不再为同一件事支付两次。

4. 被污染的网页试图重定向 Agent 的资金

最可怕的故障:我们的 Agent 读取了作为任务一部分的不可信内容,而该内容包含一条指令,要求将付款发送到攻击者的地址。由于 Agent 可以支付,一次提示注入就足以让它执行。修复方法是隔离决策与资金。不可信输入永远不会触及授权支付的代码,一个确定性的门控(包含收款方白名单和硬性上限)位于模型与钱包之间。

这是那种让你夜不能寐的失败。一旦读取开放网络的 Agent 也能转移资金,每个输入都可能是潜在的花费指令。

错误在于让模型的输出直接流入支付流程。模型是可说服的。隐藏在工具结果或页面中的精心构造的一行文字,可以诱骗它向错误方付款。因此,你不应该信任模型对金钱的判断。要用门控来约束它。

// 模型提议。这个确定性门控决定。它无法被争辩。
function authorizePayment({ payTo, amountUsd }, dailySpent) {
  if (!ALLOWED_PAYEES.has(payTo)) throw new Error("收款方不在白名单中");
  if (amountUsd > MAX_PER_CALL) throw new Error("超出单次调用限额");
  if (dailySpent + amountUsd > DAILY_CAP) throw new Error("超出每日上限");
  return true; // 只有白名单内的、符合预算的支付才会到达钱包
}

原则简单且不容商量:将任何具有支付能力的 Agent 视为特权行为者,让不可信输入远离支付时刻,绝不允许模型覆盖门控逻辑。

5. 重试循环悄悄耗尽了预算

一个陷入规划或重试循环的 Agent 反复调用付费端点,每次调用金额极小且单独看起来没问题,因此没有触发任何警报。但累积起来几分钟内就烧光了预算。修复方法是在限额之上增加一个断路器:在滚动窗口内跟踪支出,当支出速度激增时暂停并发出警报,需要人工干预才能恢复。当问题是调用次数时,单次调用限额是不够的。

单笔支付的限额无法捕捉这种情况。每笔单独收费都在限额内。问题在于次数。

Agent 会循环。它们重试、重新规划、因为上次结果不够理想而再次调用同一工具。没有速度检查,一个良性循环就会变成昂贵的循环。因此我们添加了一个断路器,它监控支出速率,而不仅仅是每笔支付的大小。

当短时间窗口内的支出超过阈值时,Agent 停止支付并发出警报。人工查看、决定并恢复。这与信用卡网络在你卡上突然在一分钟内出现上百笔小额消费时使用的逻辑相同。

6. 没有人能证明哪个 Agent 做了什么

同时存在两个问题:对手方无法区分我们的 Agent 和爬虫,有时会屏蔽它;而当出现问题时,我们没有清晰的记录来证明谁授权了什么。修复方法是身份和溯源。为 Agent 赋予可验证的身份,使其能被识别和信任;并保留不可篡改的审计日志,将每笔支付链接到它所依赖的精确授权。没有这样的记录,Agent 的支出就无法问责。

匿名的 Agent 在两端都是问题。对外界来说它看起来像机器人,因此会被屏蔽。对你来说,它的支出是个黑盒;当一笔支付被质疑时,你无法重建它发生的原因。

解决办法是让 Agent 可归属。新兴的 Agent 身份标准让 Agent 能够证明自己是谁、代表谁,从而避免被误认为恶意流量。而在你这边,每笔支付都应记录在签署授权对应的日志中。

auditLog.record({
  agentId, mandateId,           // 谁执行的行为,在什么授权下
  payTo, amountUsd, txHash,     // 转移了什么,以及链上证明
  ts: Date.now(),
});

那条记录不是官僚主义。它决定了是“Agent 支出了这笔钱,这里有授权和链上收据”,还是只能无奈耸肩。

什么才能真正保证 Agent 钱包的安全?

安全是分层的,而不是单一的开关。使用智能账户,使权限在链上被限定;使用 Paymaster,使 Agent 只需要一种资产;幂等性确保重试不会重复支付;确定性门控确保不可信输入无法重定向资金;断路器确保循环无法耗尽预算;身份和审计追踪确保每笔支付可归属。上面的每个故障都对应其中一个控制措施。跳过其中任何一个,你就会在那里出问题。

将六个故障一起阅读,会看到一个模式。没有一个是关于模型是否足够聪明。全部都是关于在自主决策与不可逆支付之间放置确定性控制。

这就是全部工作。Agent 在上层可以像你希望的那样聪明。但它越接近钱包,它的自由就越需要让位于每次行为相同且无法被说服打破的规则。

演示是容易的部分。防护措施才是产品。

给 Agent 一个钱包确实只需几分钟的工作。让它安全地花费资金才是真正的工程,也是大部分价值所在。我们遇到的六个问题并非边缘情况。它们是“能工作一次的支付”与“可以留用真实资金运行的 Agent”之间的标准差距。

如果你只能从本文带走一件事,那就是顺序。先构建智能账户、Paymaster、幂等性守卫、支付门控、断路器以及身份和审计追踪,然后再将 Agent 指向任何重要的东西。安全交付 Agent 支付的团队,并非那些拥有最聪明 Agent 的团队。而是那些将防护措施视为产品的团队。

钱包是容易的部分。所有让钱包不再成为负债的东西,才是值得精心构建的部分。

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

相关文章

0 条评论