#441 复审时由两侧独立指出(R1b 与我自己的推导一致),Codex 判定利用门槛高。记录在案,说明为何暂不修,以及闭合它需要什么。
窗口
#441 给 executeRecovery 加的校验是:
读 activeRecovery() ← check
↓ ← 窗口
writeContract executeRecovery() ← use
两次 RPC 调用之间,链上 proposal 可以被覆盖。此时后端校验通过、但实际执行的是另一个 proposal —— 正是校验想防的场景。
为什么现在闭不上
合约的 executeRecovery() 无参数:
function executeRecovery() external;
没有任何办法把「我期望执行的是 newOwner=X 这个 proposal」表达进调用里。后端侧无论怎么写都消不掉这个窗口 —— 这是接口能力的缺失,不是实现疏忽。
为什么风险可接受(暂时)
- 覆盖 proposal 需要已经是该账户的 guardian(
proposeRecovery 有 guardian 门禁,proposeRecoveryWithSig 需要有效的 passkey guardian 签名)—— 攻击者本来就得先攻破 guardian 集合
- 窗口是两次 RPC 往返,非常窄
- 相比修复前(完全不校验)已是严格改进:随机的、非蓄意的分歧现在会被挡住
真正的闭合方式(需要合约侧)
给合约加一个带期望值的重载,例如:
function executeRecovery(address expectedNewOwner) external; // 不匹配则 revert
或者让 executeRecovery() 接受 proposal nonce / hash。这样检查与执行就是原子的,后端只需把读到的值透传。
需要在 airaccount-contract 侧提 issue 并纳入接口版本管理(参照 Brood/orgs/aastar/INTERFACES.md 的跨仓库接口契约约定)。
关联
#441 复审时由两侧独立指出(R1b 与我自己的推导一致),Codex 判定利用门槛高。记录在案,说明为何暂不修,以及闭合它需要什么。
窗口
#441 给
executeRecovery加的校验是:两次 RPC 调用之间,链上 proposal 可以被覆盖。此时后端校验通过、但实际执行的是另一个 proposal —— 正是校验想防的场景。
为什么现在闭不上
合约的
executeRecovery()无参数:没有任何办法把「我期望执行的是 newOwner=X 这个 proposal」表达进调用里。后端侧无论怎么写都消不掉这个窗口 —— 这是接口能力的缺失,不是实现疏忽。
为什么风险可接受(暂时)
proposeRecovery有 guardian 门禁,proposeRecoveryWithSig需要有效的 passkey guardian 签名)—— 攻击者本来就得先攻破 guardian 集合真正的闭合方式(需要合约侧)
给合约加一个带期望值的重载,例如:
或者让
executeRecovery()接受 proposal nonce / hash。这样检查与执行就是原子的,后端只需把读到的值透传。需要在
airaccount-contract侧提 issue 并纳入接口版本管理(参照Brood/orgs/aastar/INTERFACES.md的跨仓库接口契约约定)。关联