Protos-AgentConsole v3.0

Znanje · Memorija i procesi

Svo znanje

JSON-LD safe serializer

json-ld-xss-safe-serializer·Protos-Web

JSON-LD safe serializer

Problem

Next.js apps often embed schema.org data with:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>

JSON.stringify does not escape </script>, <!--, or line separators (U+2028, U+2029). If any user-controlled field (blog title, description, copy-pasted quote) contains </script>, the browser closes the tag and executes whatever follows. Classic HTML-context XSS.

Fix

Route every JSON-LD <script> through a single helper:

export function serializeJsonLd(data: unknown): string {
  return JSON.stringify(data)
    .replace(/</g, '\\u003c')
    .replace(/>/g, '\\u003e')
    .replace(/&/g, '\\u0026')
    .replace(/\u2028/g, '\\u2028')
    .replace(/\u2029/g, '\\u2029')
}

Then:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}
/>

\uXXXX escapes are still valid JSON, so the payload parses identically — they just can't break out of the <script> tag.

Locations to check (grep before shipping)

rg "dangerouslySetInnerHTML" src | rg "JSON.stringify"

Should return zero matches — everything must go through serializeJsonLd.

Related

  • OWASP DOM-based XSS cheat sheet, section "JSON in <script> tags"
  • serialize-javascript npm package (heavier, escapes < too)
  • React <script> intrinsic in React 19+ still passes strings verbatim.