Escrow meta-authorizations underbind their payload and never expire
Escrow meta-transaction digests omit part of the payload they authorise and never expire. A holder of an unused signature can attach arbitrary evidence or dispute reasons, and signatures stay valid indefinitely.
Description
Escrow meta-transaction digests omit part of the payload they authorise and carry no freshness
binding. raiseDisputeFor hashes ("RAISE_DISPUTE", address(this), episode, signatureNonce, chainid) without reason; markDeliveredFor omits evidenceHash. A holder of a valid unused
signature can therefore attach arbitrary narrative or evidence and make it permanent for that
episode.
Separately, every meta-action is a bearer digest valid until its first compatible execution —
no validUntil, no per-action nonce, no signer cancellation. The sharpest is
COMMENCE_MILESTONE, which binds only (address(this), milestoneIndex, chainid): a retained or
leaked signature lets any caller start milestone i's delivery clock at a time of their choosing,
after which the permissionless autoReleaseMilestone pays the seller the full milestone amount
without the buyer ever greenlighting the work.
Payee and amount are fixed at creation, so this is stale first use rather than repeated drain.
Recommendation
Move every Escrow meta-action to EIP-712 typed data binding the exact payload hash, the action,
the episode, a per-action nonce and validUntil, over a domain carrying chainId and
verifyingContract. Give COMMENCE_MILESTONE a short expiry specifically, given its
autoReleaseMilestone coupling.
Resolution
Fixed. Every meta-authorization is now an EIP-712 typed struct over the clone's own domain, binding the full payload — including reasonHash, evidenceHash and milestoneIndex — plus a validUntil capped at 3 days.
Affected files
Escrow.sol#L456-L481, #L484-L504, #L506-L525, #L530-L539, #L557-L566, #L621-L628, #L833-L842