Welcome back to Sherlock’s Vulnerability Spotlight, where we highlight an impactful vulnerability uncovered during a Sherlock audit.
This week, we examine a signature malleability vulnerability found by
@0xSimao in the
@crestalnetwork Contest. This vulnerability has already been fixed following the contest, far before the launch of mainnet contracts.
Summary of the Vulnerability
Several “WithSig” entrypoints recover the signer from an EIP-712 digest that only commits to:
- projectId
- base64RecParam / base64Proposal
- serverURL
The typed data does not include other calldata inputs that materially affect execution, such as:
- tokenAddress (determines what token is charged and the cost mapping used)
- tokenId (which NFT gets consumed / marked as used)
- privateWorkerAddress (affects which worker the request is privately routed to)
Attack Steps
1) User produces a valid signature for a deployment request
The user signs getRequestDeploymentDigest(projectId, base64Proposal, serverURL) (only these 3 fields)
2) Signature becomes observable (ERC-4337 relayer/bundler mempool)
When routed via an AA service (e.g., Biconomy), user operations are commonly broadcast to a bundler mempool, making the signature available to third parties monitoring it.
3) Attacker re-submits onchain with malicious unsigned parameters
Because the signature does not commit to additional calldata:
Token route:
attacker calls createAgentWithTokenWithSig(...) with:
- same projectId/base64Proposal/serverURL/signature
- but a different tokenAddress (still “enabled” and possibly more expensive)
- and/or a different privateWorkerAddress (invalid or attacker-chosen)
NFT route:
attacker calls createAgentWithSigWithNFT(...) with:
- same signed fields
- but a different tokenId (any NFT the user owns), consuming/locking it via nftTokenIdMap[tokenId] = Pickup
- and/or a different privateWorkerAddress, forcing a wrong/censoring worker path
Root Cause
Signed message is incomplete:
the EIP-712 deployment request struct only includes (projectId, base64RecParam, serverURL) and omits execution-critical inputs like tokenAddress, tokenId, and privateWorkerAddress.
Digest reuse across multiple entrypoints:
multiple “WithSig” functions rely on getRequestDeploymentDigest() while taking additional calldata, enabling parameter substitution and even cross-function replay (same digest, different semantics).
What’s the Impact?
Forced/incorrect payments:
in createAgentWithTokenWithSig, attacker can select a different tokenAddress (among enabled tokens) so the user pays a cost they did not agree to.
Persistent DoS / censorship:
- attacker can lock an unintended NFT tokenId as “used” (Status.Pickup), preventing legitimate future use
- attacker can supply malicious privateWorkerAddress to misroute or break private deployments, harming liveness and enabling worker censorship incentives
User intent violation:
users can be forced to create agents with parameters they never authorized, breaking trust assumptions around signature-based flows.
Mitigation
Sign all parameters that affect execution, at minimum:
- privateWorkerAddress
- tokenId
- tokenAddress
We are proud to have helped secure
@crestalnetwork through this discovery.
When it absolutely needs to be secure, Sherlock is the right choice.