AI Agent 处理数字资产时的权限矩阵
权限矩阵逐项写明 AI Agent 可以准备什么、由人批准什么,以及由哪个渠道执行。
本文仅用于教育目的,不能替代正式的智能合约审计、法律建议、金融建议或针对具体协议的安全审查。
处理数字资产的 AI Agent 需要的不只是一份工具清单。它需要一份权限矩阵,逐项写明 Agent 可以读取和准备什么、由人批准什么,以及由哪个授权渠道执行。

有用的单位是能力,而不是工具。签名、访问密钥、启用模块、更改 fallback handler 或批准一笔 DELEGATECALL 操作,每一项都会打开对账户的控制权,所以每一项都需要自己的一行、自己的审批和自己的负责人。
本文把这一思路落实为一份矩阵,面向运行基于 Safe 的运维流程的小团队,内容包括:各行的设置、每项权限背后的资源、托管服务商的策略引擎覆盖哪些内容、哪些仍由团队负责,以及在 Agent 接触生产环境之前用于检验矩阵的反例。
关于生产环境中 Agent 权限的一般原则,请看下面链接的文章。本矩阵聚焦于密钥、签名、模块、guard 以及绕过它们的路径。
从打开控制权的能力入手
工具名称很少能说明风险。一个叫 propose 的工具可能只是起草 payload,也可能持有为它签名的密钥。因此,矩阵先列出能力,再把工具对应到能力上。
在 Safe 智能账户中,以下能力会改变谁能转移资金或账户如何运作:
- 签名或提交交易,或者持有执行这些操作的密钥。
- 添加或移除 owner,以及更改阈值。
- 启用或禁用模块。根据 Safe 文档,模块可以执行任意交易,恶意模块可以接管一个 Safe。
- 设置 transaction guard,或者从 Safe 1.5.0 起设置 module guard。根据文档,有缺陷的 guard 可能导致 Safe 拒绝服务。
- 更改 fallback handler。文档提醒,它允许在账户内执行任意外部函数。
- 批准 operation 字段为 DELEGATECALL 的交易。Safe 支持 CALL 和 DELEGATECALL;使用 DELEGATECALL 时,目标代码在 Safe 的上下文中运行,使用 Safe 的存储和余额。
- 升级、暂停协议自身的合约,或更改其参数。
有一处版本差异会影响矩阵。从引入 guard 的 Safe 1.3.0 到 1.4.1,transaction guard 只检查通过 execTransaction 执行的交易,不覆盖由模块执行的交易。Safe 1.5.0 为这条路径新增了独立的 module guard,但只有在设置之后才会生效:仅升级到 1.5.0,或仅设置 transaction guard,模块执行的交易依然不受检查。我们在本地分叉上验证了这两点:在设置了拒绝一切调用的 guard 的 Safe 1.4.1 上,owner 签名的交易被回滚,而模块的交易照常执行;在 Safe 1.5.0 上,直到设置了 module guard,模块的交易才被回滚。
- Safe: Smart account concepts
- Safe: Modules
- Safe: Guards
- Safe: Fallback handler
- Solidity: Delegatecall and libraries
逐行看矩阵
简而言之:Agent 读取、解码、模拟并起草;人批准确切的 payload 或具体的变更;Agent 之外的授权渠道负责签名和执行。下面每一行分三部分:Agent 可以做什么、谁来批准、由什么执行。
- 查询余额和配置。Agent:限定在清单所列网络和 Safe 内的类型化读取。审批:事先商定的读取策略。执行:只读工具。
- 解码和模拟。Agent:准备固定在某个区块上的证据。审批:由人审查覆盖范围和结果。执行:经授权的模拟器。
- 准备一笔转账。Agent:未签名的草稿。审批:签名者审查确切的 payload。执行:Agent 之外的授权渠道。
- 签名或提交。Agent:不在其范围内。审批:人工签名流程。执行:钱包或授权渠道。
- 轮换密钥、更改 owner 或更改阈值。Agent:有记录的提案。审批:该变更的负责人和签名者。执行:管理渠道。
- 更改模块、guard、fallback handler 或进行升级,或签署任何 DELEGATECALL。Agent:分析和草稿。审批:针对该变更的专项审查。执行:管理渠道。
- 暂停合约或更改参数。Agent:诊断和提案。审批:流程中指定的负责人。执行:受限的外部工具。
有两行看起来相似,其实不同。准备一笔转账和批准一笔 DELEGATECALL 最后都可能是一次 execTransaction 调用,但后者可能改变账户本身。所以矩阵把它和模块或 guard 变更放进同一类审查。
写明每项权限背后的资源
只有写明了资源,一行才能真正执行。对每项权限,团队记下网络、Safe、代币合约、已批准的收款地址、Agent 可以起草的函数,以及适用的限额。
例外情况也一样。如果某个流程需要 DELEGATECALL,例如用于批量调用,矩阵应写明经过审查的目标合约及其版本,其他任何 DELEGATECALL 目标都不进入 Agent 的草稿。如果某个集成需要模块,矩阵要写明模块地址、负责人和计划移除的日期。
托管服务商的策略引擎覆盖什么
有些团队把 Agent 连接到由托管或钱包基础设施服务商持有的密钥上,这些服务商有自己的策略引擎,例如 Turnkey 或 Fireblocks。这些引擎对到达它们的请求执行规则。矩阵需要记录每个引擎执行了哪些行,哪些行依赖其他组件。
根据 Turnkey 的文档,组织内的每条策略都会在每次请求时被评估:任何匹配的 deny 优先;否则看是否有匹配的 allow;其余情况一律隐式拒绝。以太坊规则可以读取已解析的交易字段,例如收款地址、金额和 calldata;只有上传了合约接口,函数名和参数才可用;也可以评估类型化数据。同一份文档说明,由 root quorum 批准的操作不经过策略引擎,任何策略都无法限制它们。
根据 Fireblocks 的文档,其 Policy Engine 会把策略应用于从 Fireblocks Console 或其 API 创建的所有交易。规则按首次匹配原则自上而下应用,因此顺序很重要。合约调用规则可以按方法过滤。只有 workspace 的 Owner 或 Admin 级用户可以管理策略,提议的变更必须经审批组批准。
Safe 还带来了由合约定义、而非由服务商引擎定义的控制路径:
- 受某个引擎管理的密钥可能只是多个 owner 之一。其他 owner 的密钥在别处、按别的规则签名。
- 已启用的模块通过 execTransactionFromModule 执行交易,在这条路径上 Safe 不会校验 owner 签名;如果有校验,也由模块自身完成。只有当服务商的引擎管理着调用该模块或为其授权的密钥时,它才能看到这次执行;即便如此,引擎收到的也是发给模块的请求,而不是由 owner 签名的 Safe 交易,因此针对 Safe 交易编写的规则对它并不适用。
- guard 和 fallback handler 都是在 Safe 合约本身中配置的。设置了 transaction guard 后,无论由哪把密钥签名,它都会检查每一次 execTransaction,但不检查模块执行的内容;在 Safe 1.5.0 上,这条路径需要单独的 module guard。fallback handler 负责响应对 Safe 未实现函数的调用。此外,已启用的模块无需 owner 签名即可更改上述全部配置,因为这些都是 Safe 对自身的调用。
- DELEGATECALL 体现在 operation 字段中:这个字段既出现在 owner 签名的 SafeTx 里,通常是 EIP-712 类型化数据,也出现在提交该交易的 execTransaction 调用中。只有当规则按引擎实际收到的格式读取这个字段时,才能拦截它;如果密钥只对 SafeTx 的哈希签名,引擎就没有任何字段可读。
服务商的引擎是矩阵中的一个执行点,而不是矩阵本身。对 Safe 的 owner 来说,引擎读到的收款地址通常就是 Safe 本身;真正的目标地址、金额和 gas 退款字段都在 calldata 或类型化数据内部,只有规则对其解码时才会被检查。团队仍需决定写哪些规则、谁可以修改它们、哪些路径会绕过它们,例如 root quorum、模块或其他 owner,以及如何用反例测试这些路径。
- Turnkey: Policy overview
- Turnkey: Policy language
- Turnkey: Root quorum
- Fireblocks Help Center: About Policies
- Fireblocks Developer Portal: Set transaction authorization policy
需要单独决策的新模块
示例场景。一个支付集成请团队启用一个模块,用于执行定期转账。Agent 可以阅读模块的代码和文档,列出它在 Safe 上调用的函数,并起草 enableModule 交易。它不能决定是否启用。
启用和禁用模块都是 Safe 对自身的调用:要么经过达到阈值签名的交易,要么经过一个已启用的模块。模块一旦启用,就可以一直操作,直到有人执行 disableModule;把集成从团队的工具里移除,并不会把它从 Safe 中移除。到 Safe 1.4.1 为止,没有任何 guard 检查模块执行的内容;在 1.5.0 上,只有 module guard 会检查,而且前提是已经设置。因此,矩阵应把启用、之后的移除和 guard 问题当作三个独立的决定,各有指定的负责人。
用反例检验矩阵
只有 Agent 周围的工具真正执行矩阵,它才有用。第一项检查在任何人签名之前,把每份草稿归入某一行。这个示例使用 bash 和 Foundry 的 cast;规则刻意写得很短,应按你自己的矩阵调整。该函数接收 Safe 交易的全部九个字段,而不只是目标地址和 calldata,因为 gas 退款字段同样可能转移资金。
# 在任何人签名之前,把 Agent 准备的草稿归入矩阵中的某一行。
# 需要 bash 和 Foundry 的 cast。规则只是示例:请按你自己的矩阵调整。
SAFE=0x... # Agent 为其准备草稿的 Safe
TOKEN=0x... # 此流程唯一可以转出的代币合约
RECIPIENT=0x... # 此流程唯一批准的收款地址
TRANSFER=$(cast sig "transfer(address,uint256)")
ZERO=0x0000000000000000000000000000000000000000
classify() { # 参数:SafeTx 的九个字段,按顺序
[ "$#" -eq 9 ] || { echo "STOP: pass all nine SafeTx fields"; return; }
local to value=$2 data=${3,,} op=$4 selector=${3:0:10}
to=$(cast to-check-sum-address "$1")
if [ "$op" != "0" ]; then
echo "ADMIN REVIEW: operation $op is not CALL; with 1, DELEGATECALL, the target code runs with the Safe's storage"
elif [ "$to" = "$(cast to-check-sum-address "$SAFE")" ]; then
echo "ADMIN REVIEW: call to the Safe itself, which can change owners, threshold, modules, guard or fallback handler"
elif [ "$5 $6 $7" != "0 0 0" ] || [ "$(cast to-check-sum-address "$8")" != "$ZERO" ] || [ "$(cast to-check-sum-address "$9")" != "$ZERO" ]; then
echo "STOP: gas refund fields are set; the Safe would pay a refund to refundReceiver"
elif [ "$value" = "0" ] && [ "$to" = "$(cast to-check-sum-address "$TOKEN")" ] && [ "${selector,,}" = "$TRANSFER" ] && [ "${#data}" -eq 138 ] \
&& [ "$(cast decode-calldata "transfer(address,uint256)" "$data" | head -n 1)" = "$(cast to-check-sum-address "$RECIPIENT")" ]; then
echo "SIGNERS: token transfer to the approved recipient; review the exact payload and amount"
else
echo "STOP: target, function, recipient or value outside the matrix"
fi
}最重要的是那些不能被当作常规转账放行的情况:以 DELEGATECALL 发送的同一 payload、一次启用模块或更改 fallback handler 的 Safe 自调用、用 approve 代替转账、指向清单之外的合约或收款地址的转账,以及设置了 gas 退款字段的草稿,这会让 Safe 在转账之外再向第三方付款。
MODULE=0x... # 正在审查的模块地址
NOREFUND="0 0 0 $ZERO $ZERO" # safeTxGas baseGas gasPrice gasToken refundReceiver
PAY=$(cast calldata "transfer(address,uint256)" $RECIPIENT 5000000000)
classify $TOKEN 0 $PAY 0 $NOREFUND # SIGNERS
classify $TOKEN 0 $PAY 1 $NOREFUND # ADMIN REVIEW
classify $SAFE 0 $(cast calldata "enableModule(address)" $MODULE) 0 $NOREFUND # ADMIN REVIEW
classify $SAFE 0 $(cast calldata "setFallbackHandler(address)" $MODULE) 0 $NOREFUND # ADMIN REVIEW
classify $TOKEN 0 $(cast calldata "approve(address,uint256)" $RECIPIENT 5000000000) 0 $NOREFUND # STOP
classify $MODULE 0 $PAY 0 $NOREFUND # STOP
classify $TOKEN 0 $(cast calldata "transfer(address,uint256)" $MODULE 5000000000) 0 $NOREFUND # STOP
classify $TOKEN 0 $PAY 0 0 100000 5000 $TOKEN $MODULE # STOP第二项检查看链上状态。集成被移除后,它的模块不应再处于启用状态,也不应出现矩阵未列明的模块、guard 或 fallback handler。下面的读取先列出 Safe 当前的配置,然后从被移除模块的地址模拟一次调用,不发送任何交易。
export ETH_RPC_URL=... # 该 Safe 所在网络的 RPC
cast call $SAFE "VERSION()(string)"
# 所有已启用的模块,而不只是你预期的那个 (第一页最多 10 个;如果第二个值不是 0x...01,继续查询)。
cast call $SAFE "getModulesPaginated(address,uint256)(address[],address)" 0x0000000000000000000000000000000000000001 10
# 从存储槽读取 transaction guard 和 fallback handler (为零表示未设置)。
cast storage $SAFE 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8
cast storage $SAFE 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5
# 仅限 Safe 1.5.0:module guard (为零表示没有 guard 检查模块执行的内容)。
cast storage $SAFE 0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947
cast call $SAFE "isModuleEnabled(address)(bool)" $MODULE
# 反例:不是已启用模块的地址无法通过该 Safe 执行。
# 模块处于禁用状态时的预期结果:以 GS104 回滚。如果返回了结果,说明它可以执行。
cast call $SAFE "execTransactionFromModule(address,uint256,bytes,uint8)(bool)" \
$SAFE 0 0x 0 --from $MODULE我们按原样运行了这三段代码,对象是两个公开的主网 Safe,版本分别为 1.3.0 和 1.4.1,另外还在一个本地分叉上先启用、再禁用了该模块。分类结果与注释一致,模拟调用只在模块启用期间返回 true。在 Safe 1.4.1 上,模块列表显示了一个已启用的模块,而只检查单个地址的方法没有发现它。在分叉上,设置了 gas 退款字段的草稿被归为 STOP;我们仍然执行了这份草稿,结果 Safe 除完成转账外,还用自身余额向退款接收方付了款。这些检查覆盖的是你写下的规则;它们不能替代对模块代码或服务商策略的审查。
每项变更都有负责人
每一行都需要一名负责人,矩阵的每次变更都应像账户变更一样对待。有三类事件应触发复审:Safe 新版本,因为 guard 对模块的覆盖范围在 1.4.1 和 1.5.0 之间不同,而且 module guard 需要明确设置;服务商策略的变更,或可编辑策略的人员变更;以及任何新的模块、guard、fallback handler 或 DELEGATECALL 目标。
记录应显示谁批准了每项变更、运行了哪些反例以及结果如何。有了这份记录,团队之后才能解释 Agent 为什么能做某件事,或者为什么不能。
常见问题
在这份权限矩阵中,AI Agent 永远不做什么?AI Agent 不签名、不提交交易、不持有密钥,也不对 Safe 执行管理类变更。它只读取、解码、模拟并起草。
谁来为 Safe 启用模块?Safe 的 owner,通过达到阈值签名的交易,经由管理渠道完成。已启用的模块也可以启用模块,部署该 Safe 时的 setup 调用同样可以,这也是在给 Agent 授予访问权限之前列出所有已启用模块的又一个理由。Agent 可以分析模块并起草交易。
把矩阵变成交付物
矩阵是一份工作文档:每项能力一行,写明资源、负责人、每条规则的执行点,以及检验它的反例。在 AI Agent 接触 Safe 之前,团队应能说明每项操作需要哪种审批,以及 Agent 试图越界时是哪项测试失败了。
如果你想讨论自己 Safe 的权限设置,可以通过联系页面与我们联系;留言时请注明本文以及你考虑的 Safe 或流程,不要附上密钥或机密的运营资料。
参考来源
资料查阅于 2026 年 10 月 1 日。这些来源描述的是机制和配置;矩阵、模块场景和分类规则是本文提出的方案。已执行并保存输出:三段代码,使用 Foundry 的 cast 1.8.3,在两个公开主网 Safe 上和一个本地分叉上运行,包括 gas 退款的情况;在一个 Safe 1.4.1 本地分叉上进行的 guard 与模块测试;以及在一个新的 Safe 1.5.0 本地分叉上进行的 module guard 测试。已对照文档核实但未执行:对 Turnkey 和 Fireblocks 策略引擎的描述。
- Safe: Smart account concepts
- Safe: Modules
- Safe: Guards
- Safe: Fallback handler
- Safe contracts 1.4.1: ModuleManager
- Safe contracts 1.5.0: Safe
- Solidity: Delegatecall and libraries
- Turnkey: Policy overview
- Turnkey: Policy language
- Turnkey: Root quorum
- Fireblocks Help Center: About Policies
- Fireblocks Developer Portal: Set transaction authorization policy
- Foundry: cast reference


