A decentralised exchange asked us to test the web layer around its contracts. Their concern was precise: the smart contracts were audited, but the interface users actually trust to build a transaction was not.
Client
Decentralized exchange
Industry
Web3
Service
Web & API penetration test
Severity
CRITICAL

01
The front-end displayed community-contributed token metadata, and it rendered part of that metadata as trusted markup so it could carry basic formatting. Metadata is attacker-controllable input. Rendering it as markup means a token’s description can carry code that runs in the browser of anyone who views it.
An audited contract does not help here, because the attack never touches the contract’s logic. It sits between the user’s intent and the transaction the wallet is asked to sign. The interface is the bridge where a human decision becomes an on-chain instruction, and if that bridge can be made to run untrusted code, the instruction the user approves is no longer the one they intended. A perfectly correct contract will faithfully execute a request the user was tricked into authorising.
This is a Web2 flaw with Web3 consequences, and it is missed precisely because the security attention went to the contract, where the assets appear to live, rather than to the interface, where the decisions are actually made.
02
We tested the front-end as the untrusted boundary it is, focusing on every place external data reached the page as something other than plain text.
Trace community-supplied metadata from its source to the point it was rendered
Confirm whether that data was treated as markup or as text, and what a description could therefore carry
Establish whether injected code could observe or alter a transaction before it reached the wallet
Check whether a content policy constrained what injected code could do even if it ran
Delivered against a controlled test token we created, so the finding was reproducible without exposing any real user or asset.
03
01
Traced community-supplied token metadata
02
Found it rendered as trusted markup
03
Showed a wallet transaction could be altered
04
Sanitized the rendering, retested

04
Untrusted metadata is now rendered as text through a sanitising renderer rather than as trusted markup, a content security policy constrains what any injected code could do, and a transaction-preview step shows the user the permissions a signature actually grants before they approve it. The first change removes the injection; the second and third limit the damage if a new path is ever found.
We retested the rendering path and the signing flow after the fix. Reported as CRITICAL in 2025. Retest included in the engagement.
Case Studies


