Date: 2026-06-11
Class: SEAMS / cross-chain message trust (consumer-side receive-handler)
Source case: Venus Protocol OmnichainGovernanceExecutor (LayerZero v1), Arbitrum 0xc1858cCE6c28295Efd3eE742795bDa316D7c7526. WALK-CLEAN.
When auditing a _lzReceive/_handle/receiveWormholeMessages/_ccipReceive/nonblockingLzReceive consumer, verify ALL five. A gap in any one = potential takeover. Venus passed all five:
keccak256(_srcAddress)==keccak256(trustedRemote) in LzApp.lzReceive + the app's own srcChainId_==srcChainId.require(proposals[pId].id == 0)). Belt-and-suspenders.cast read it - do not assume. LZ v1 packs abi.encodePacked(remoteAddr(20), localAddr(20)); the local half should equal the consumer itself.msg.sender == endpoint/router/mailbox; the inner non-blocking re-entry requires msg.sender == address(this).retryMessage STRICTER (onlyOwner + trustedRemote recheck) than the permissionless LZ base.LzApp/NonblockingLzApp) is mandatory. The consumer's own .sol often shows only srcChainId_==srcChainId and looks under-validated; the real source/caller gate lives in the LZ base. Do not call "MISSING SOURCE VALIDATION" until you've read the parent.cast reads of trustedRemoteLookup + srcChainId + endpoint turn a code-only WALK into a config-validated WALK. The seam is often a MISCONFIG (c), not a code bug - and only on-chain reads catch that.vm.expectRevert on direct/forged/replay/retry calls gives REAL empirical proof of the defense (5 PASS = 5 reverts). Cheap, deterministic, no archive node needed.LzApp/OAppReceiver correctly are NOT exploitable via subclasses (a)/(d) - the base enforces them. Spending time forge-testing a textbook OFT/executor that inherits cleanly = low yield. Confirm inheritance is clean, cast-read the config, and MOVE ON.lzReceive (custom integrations) - check (a)/(d) by hand.cast-read every satellite the day it deploys.lzCompose) and CCIP retry - the second-class paths are where a check the main path has gets dropped.LayerZero's Immunefi program EXCLUDES "impacts to OApps as a result of their own misconfiguration." A consumer-side trustedRemote/srcChain bug is therefore NOT payable under LayerZero - it must be routed to the CONSUMER protocol's own bounty (here, Venus). Always map a consumer-side finding to the consumer's program, not the bridge's.