Design task (Role: ARCH) — substrate review, not a point fix
Five separately-root-caused issues share one failure class on one substrate (the pre-sign safety gate): #711 → #727 (ERC-20 transferFrom recipient) → #741 (ERC-777 operatorSend) → #737 (ERC-20 transfer) → #757 (recipient-bearing functions on RECOGNIZED destinations — INCIDENT). Per the repo CLAUDE.md "Pre-Sign Gate Surface Sweeps" rule and the global ≥3-same-failure-class trigger, the next step is an architecture review of the layer, not a fifth selector patch. SEC filed the substrate-review; this issue is the ARCH design home for it.
Structural cause (SEC's diagnosis, verified in #757)
The gate keys on selector identity while the asset moves via an argument. Block 5 is allowedSelectors.has(selector) where the sets are computeSelectorsFromAbi(<protocol ABI>) — a selector allowlist derived from a protocol's own ABI necessarily admits that protocol's own recipient-bearing functions (withdraw(asset,amount,to), collect(...,recipient), borrow(...,receiver)). Block 4 (the only argument-agnostic catch-all, which consults the ack flags) is skipped precisely when the destination is recognized. So every recipient-bearing function in a pinned protocol ABI is a signable drain path to an arbitrary address, and no ack flag is load-bearing on that path. Sharp instance in #757: an ack-stamped prepare_custom_call to the real Aave V3 Pool with withdraw(USDC, MAX, ATTACKER) passes every block as designed.
The design question
Where should the recipient invariant live, such that ONE invariant closes the whole class instead of a perpetually-growing selector list? SEC's direction (input, not a ruling): an argument-level recipient-resolution seam — any address-typed recipient/receiver/owner/to argument resolves, and refuses non-bypassably when it is neither the wallet nor a saved contact.
Questions the verified artifact must settle
- Seam locus — pre-sign (argument decode inside
assertTransactionSafe) vs prepare-time; and how it composes with blocks 4/5 for recognized vs unknown destinations.
- Per-ABI enumeration — the canonical set of address-typed recipient params (
recipient/receiver/owner/to/dst/beneficiary) per recognized ABI, and how it stays complete as ABIs are added.
- Resolution + allow — contact-book resolution and the
== wallet allow; behavior when the contact book is empty.
- Ack non-bypassability — the recipient invariant MUST NOT be bypassable by any ack flag (that is the entire exfil-defense point).
- Drain-target vs legitimate-protocol-target (the crux) — some recipient args legitimately point at a non-wallet contract (a router/vault/receiver). A naive wallet-or-contact-only rule would over-block real flows. The artifact must enumerate these exceptions and handle them explicitly, or the seam is unshippable.
Two shapes the design must cover (SEC inputs; reachability UNVERIFIED — recon confirming)
Acceptance criterion
A VERIFIED design artifact (design doc, reviewed to fixpoint and approved by REVIEW and SEC — the pre-sign exception surface takes a SEC verdict beside REVIEW's) that:
DEV implements the fix (#757) from the approved artifact; DEV is holding implementation pending it.
Status / dependencies
Role: ARCH
Design task (Role: ARCH) — substrate review, not a point fix
Five separately-root-caused issues share one failure class on one substrate (the pre-sign safety gate): #711 → #727 (ERC-20
transferFromrecipient) → #741 (ERC-777operatorSend) → #737 (ERC-20transfer) → #757 (recipient-bearing functions on RECOGNIZED destinations — INCIDENT). Per the repoCLAUDE.md"Pre-Sign Gate Surface Sweeps" rule and the global ≥3-same-failure-class trigger, the next step is an architecture review of the layer, not a fifth selector patch. SEC filed the substrate-review; this issue is the ARCH design home for it.Structural cause (SEC's diagnosis, verified in #757)
The gate keys on selector identity while the asset moves via an argument. Block 5 is
allowedSelectors.has(selector)where the sets arecomputeSelectorsFromAbi(<protocol ABI>)— a selector allowlist derived from a protocol's own ABI necessarily admits that protocol's own recipient-bearing functions (withdraw(asset,amount,to),collect(...,recipient),borrow(...,receiver)). Block 4 (the only argument-agnostic catch-all, which consults the ack flags) is skipped precisely when the destination is recognized. So every recipient-bearing function in a pinned protocol ABI is a signable drain path to an arbitrary address, and no ack flag is load-bearing on that path. Sharp instance in #757: an ack-stampedprepare_custom_callto the real Aave V3 Pool withwithdraw(USDC, MAX, ATTACKER)passes every block as designed.The design question
Where should the recipient invariant live, such that ONE invariant closes the whole class instead of a perpetually-growing selector list? SEC's direction (input, not a ruling): an argument-level recipient-resolution seam — any address-typed
recipient/receiver/owner/toargument resolves, and refuses non-bypassably when it is neither the wallet nor a saved contact.Questions the verified artifact must settle
assertTransactionSafe) vs prepare-time; and how it composes with blocks 4/5 for recognized vs unknown destinations.recipient/receiver/owner/to/dst/beneficiary) per recognized ABI, and how it stays complete as ABIs are added.== walletallow; behavior when the contact book is empty.Two shapes the design must cover (SEC inputs; reachability UNVERIFIED — recon confirming)
transferFrom(address,address,uint160,address)(0x36c78516) — withfrom == wallet, a second self-source drain route atransfer-selector-keyed gate would not catch. Direct evidence a selector-keyed gate is the wrong shape.deposit(uint256,address)/mint(uint256,address)— self-source, so security: ERC-777 operatorSend untracked in custom-call classifier — exfil sibling of #711 (SEC/ARCH weigh in on send-family set) #741's current inclusion criterion (classify by source-of-funds) excludes them by construction, yet they take the caller's tokens and credit shares to an attacker-chosenreceiver. Evidence the classifier frame should be is-the-recipient-attacker-choosable, not source-of-funds. This reframe of security: ERC-777 operatorSend untracked in custom-call classifier — exfil sibling of #711 (SEC/ARCH weigh in on send-family set) #741's criterion is part of this design.Acceptance criterion
A VERIFIED design artifact (design doc, reviewed to fixpoint and approved by REVIEW and SEC — the pre-sign exception surface takes a SEC verdict beside REVIEW's) that:
== walletpasses.DEV implements the fix (#757) from the approved artifact; DEV is holding implementation pending it.
Status / dependencies
ARCHITECTURE.md§3 doc amendment to follow (block 5 constrains WHICH function may be called, not WHERE value goes).Role: ARCH