给 Agent 链上支付加一道签名前检查
文章用开源 Python 库 sra-riskgate 检查 Agent 即将签署的交易,识别 USDC 无限额度授权等风险。检查针对实际签名内容,可在本地运行,无需 GPU、API 密钥或网络调用。
AI 深度解读
AI Agent 自己付钱,最危险的时刻可能发生在付款之前:它以为在确认一笔账单,实际签下的却是允许别人长期动用钱包的授权。原文作者给出的办法很明确:把检查放到签名之前,直接解析待签数据,再由代码执行付款规则。
作者用自己开发的 sra-riskgate 演示了交易检查、付款策略和 x402 签名检查。对准备接入自动付费 API 的程序员,这篇值得读的地方是检查点怎么放;需要保留的疑问,则是这些规则经过了多大范围的验证。
Agent 说要付款,实际签的是什么?
x402 的交互可以概括为:API 返回 402 Payment Required,Agent 签署 USDC 授权,付款在链上结算,不需要人点击确认。作者强调,稳定币付款结算后无法像信用卡交易那样申请拒付,因此检查必须发生在签名前。
问题是,「付款」和「授权别人花钱」可能被页面文案包装成同一件事。
普通转账是把确定金额交给收款人;approve(spender, 2**256 - 1) 则允许指定地址以后继续动用钱包里的代币。这里的 2**256 - 1 是一个极大的额度,通常被视作无限授权。页面写一句「授权服务扣费」,模型可能就把它理解成正常流程。
check_transaction 检查实际交易里的目标合约、调用数据、金额、发送方和链,而不是接受 Agent 对操作的描述。原文示例使用 Base 的 chainId: 8453,无限授权对应的函数选择器是 0x095ea7b3,结果返回 hold,并标记 TOKEN_APPROVAL、UNLIMITED_APPROVAL。
作者采用了保守边界:只有普通 USDC transfer 才继续进入付款策略检查。approve、permit、transferFrom、其他合约和原生 ETH 都会被暂停,交给人工处理。
检查对象必须是即将签署的数据,业务文案只能作为不可信输入。
转账格式正常,也不能直接放行
安装示例要求 pip install "sra-riskgate>=0.11.0"。作者称,这些检查不需要 GPU、API key 或网络调用,但调用方仍要提供自己的付款策略。
原文拿一笔 25 USDC 转账演示:USDC 有 6 位小数,因此链上整数是 25_000_000,transfer 的函数选择器是 0xa9059cbb。同一笔转账,传入 max_amount=100 会得到 approve;改成 max_amount=10,则返回 hold 和 HIGH_VALUE_TRANSFER。
收款对象也要检查。如果 known_recipients 里没有该地址,会触发 NEW_COUNTERPARTY。金额没超限,不代表收款人可信。
更贴近 Agent 风险的是 untrusted_request。作者建议把账单、付款备注和 x402 扩展字段中的原始文字传进去。如果里面自行宣称「已预批准」「已离线验证」「风险低」,规则就会暂停付款。原文示例触发了 self_asserted_verification_in_payload、self_asserted_risk、self_asserted_check、verified_offline 四个标记。
这些文字只是请求方的自我声明,不能替代付款方的验证。
库还会校验地址与金额,并对照 OFAC 已发布的受制裁加密地址做离线精确匹配。这里使用的是带日期的快照,不是实时筛查服务。USDC 脱锚检查也依赖调用方传入价格读数;示例里的 peg_deviation_pct=0.0 是输入参数,不能理解成库已经联网确认价格正常。
x402 没有发送交易,也可能已经交出了付款能力
在 EVM 链的 x402 exact 方案中,Agent 不一定自己发送交易。它签的是 EIP-712 格式的 TransferWithAuthorization,对应 EIP-3009。持有签名的人,可以在授权有效期内提交它来转走指定 USDC。
所以只检查金额不够,还得检查这份签名能用多久。
下面的示例检查一笔 0.25 USDC 授权:先设置 7 天有效期,再缩短为 5 分钟。
import time
from sra_riskgate import check_authorization
MY_WALLET = "0x9649FbfB8121d8980F19AB8C028Eb2eC35916A86"
PAYEE = "0x2ffb88f73980bc6069c598487396e1e472deb7ee"
now = int(time.time())
typed_data = {
"primaryType": "TransferWithAuthorization",
"domain": {"name": "USD Coin", "version": "2", "chainId": 8453,
"verifyingContract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},
"message": {"from": MY_WALLET, "to": PAYEE, "value": "250000", # 0.25 USDC
"validAfter": str(now - 600), "validBefore": str(now + 7 * 24 * 3600),
"nonce": "0x" + "11" * 32},
}
result = check_authorization(typed_data, sender=MY_WALLET, peg_deviation_pct=0.0)
print(result.decision.value, result.flags)
print(result.explanation)
typed_data["message"]["validBefore"] = str(now + 300) # 常规的 5 分钟 x402 窗口
print(check_authorization(typed_data, sender=MY_WALLET, peg_deviation_pct=0.0).decision.value)
重点看 validBefore:第一份授权持续 168.0 小时,超过默认 1 小时上限,触发 LONG_LIVED_AUTHORIZATION;改为 now + 300 后,示例得到 approve。
check_authorization 还会暂停过期授权、尚未生效的授权、Permit、不属于该链 USDC 的 domain,以及 from 与实际钱包不一致的请求。类型定义也会与 USDC 的 EIP-3009 typehash 对应定义核对,避免检查的是一种结构,签出来却不是有效 USDC 授权。
真正接入钱包时,作者要求所有签名都经过这一道判断:
if result.decision.value != "approve":
raise RuntimeError(f"Payment not signed: {result.decision.value} {result.flags}")
signature = wallet.sign_typed_data(typed_data)
关键是第一行:只有明确得到 approve 才签名。坏数据、未知链或检查异常都应落到 hold。
Claude Desktop、Cursor 等 MCP 客户端还可以通过 uvx 启动 sra-riskgate-mcp。它提供六个只读工具,包括 check_transaction 和 check_x402_authorization。服务端的时钟与有效期上限来自配置,Agent 不能自行放宽。不过,提供检查工具与强制执行签名门禁是两件事,后者仍需要接入方保证。
我的判断
我认同作者把解码、金额计算、有效期判断和名单查询交给确定性代码。这些问题有明确答案,让模型再解释一次,只会多出被付款文案影响的机会。
作者也给了模型实验数据:微调后的 4B 模型 SRA-RiskGate-4B,在留出测试集的 847 笔应拦截付款中,错误放行 4 笔,比例为 0.47%,基础模型对应比例是 34.6%。这 4 笔全部利用了付款文本里的自我背书。
后来补上的声明检查拦住了全部 4 笔,并且没有误拦 3,358 笔合法付款。但作者明确承认,规则是在看到失败案例后编写的,基准也是合成数据。这能说明它修补了已知漏洞,不能证明它能识别新的攻击措辞。
相比让模型独自判断,这套做法的边界更清楚;相比逐笔人工确认,它保留了一部分自动化空间,代价是遇到复杂调用就暂停。原文没有提供生产环境误拦率、延迟或与其他方案的对照数据,不能替它断言综合效果更好。
还要防止把 approve 读成全面安全保证。离线名单可能过时,精确匹配无法覆盖所有关联风险,「地址不在名单」更不等于合规放行。它是一组风险门禁,覆盖范围由规则、输入和签名入口共同决定。
能拿来做什么
- 给自动付费 Agent 接入钱包时,把检查放进实际签名入口,并确认所有签名调用都经过它;只在提示词里要求 Agent「付款前检查」还不够。
- 接入 x402 API 时,同时检查金额、收款人、链、USDC 合约和有效期,尤其不要因为金额只有 0.25 USDC,就忽略一份能持续 7 天的授权。
- 把账单和 x402 扩展字段原文作为
untrusted_request传入,把额度、可信收款人和有效期上限放在付款方配置里,避免请求内容自行充当审批依据。 - 评估这类库时,用实际业务会遇到的授权结构和失败输入做验证;将原文的 4 个攻击案例作为回归样本,并继续补充新措辞与规则未覆盖的情况。