Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Cross-Site Scripting Vulnerabilities in JSON Web Applications

JSON does not execute by itself, but JSON-driven interfaces can still be highly vulnerable to Cross-Site Scripting (XSS). The bug usually appears when an application takes untrusted values from an API response, server-rendered state blob, search result, comment, or CMS record and inserts those values into an unsafe browser context.

For most modern web apps, the dangerous step is not fetching JSON. It is turning JSON values into live HTML, JavaScript, CSS, or navigable URLs. If a field from a JSON payload reaches a sink such as innerHTML, insertAdjacentHTML(), document.write(), or an unvalidatedhref, an attacker can often run code in another user's browser.

Where XSS Actually Happens in JSON Apps

  • Unsafe DOM rendering: API fields are inserted with innerHTML or a framework escape hatch instead of being rendered as text.
  • Embedded bootstrapping data: Server-rendered pages place raw JSON.stringify()output into a <script> block without escaping <, allowing a payload with</script> to break out.
  • Rich text handling: Comments, bios, CMS blocks, or markdown previews are treated as safe HTML without robust sanitization.
  • Unsafe parsing: Developers still occasionally use eval() or new Function() to parse response text instead of JSON.parse().
  • URL injection: JSON fields like website, redirectUrl, or avatarUrl are trusted without protocol validation, which can enable javascript: or unexpected data: payloads.

In practice, JSON-related XSS often shows up as stored XSS through API-backed content, DOM-based XSS in the frontend, or a server-side rendering mistake that turns serialized state into executable markup.

Example 1: Safe JSON Response, Unsafe Rendering

A perfectly valid JSON API can still feed an XSS bug if the frontend renders its values into HTML instead of text.

Vulnerable pattern:

const profile = await fetch("/api/profile").then((r) => r.json());

bio.innerHTML = profile.bio;
websiteLink.href = profile.website;

If profile.bio contains HTML, script gadgets, or event handlers, the page may execute them. Ifprofile.website is javascript:alert(1), clicking the link can execute code.

Safer pattern:

const profile = await fetch("/api/profile").then((r) => r.json());

bio.textContent = profile.bio;

try {
  const website = new URL(profile.website, window.location.origin);
  if (website.protocol === "http:" || website.protocol === "https:") {
    websiteLink.href = website.toString();
  }
} catch {
  websiteLink.removeAttribute("href");
}

Render text as text, and validate URLs before turning them into clickable navigation.

Framework default escaping helps, until you bypass it

React, Vue, Angular, and similar frameworks escape normal text interpolation by default. In React, <div>{profile.bio}</div> is normally safe because the value is rendered as text, not parsed as markup.

Problems start when you opt into raw HTML rendering, such as dangerouslySetInnerHTML in React,v-html in Vue, or other bypass APIs. Those escape hatches are only appropriate when the HTML is trusted or has been sanitized with a well-maintained sanitizer.

Example 2: XSS in Embedded JSON State

A common server-rendering pattern is to place initial page state into <script type="application/json"> so the client can read it during hydration. The browser does not execute that script block, but HTML parsing still ends the element when it sees </script>.

Vulnerable pattern:

const state = {
  bio: "</script><script>alert('xss')</script>",
};

const html = `<script id="boot" type="application/json">${JSON.stringify(state)}</script>`;

If a value contains </script>, the attacker can terminate the JSON container and inject a new executable script tag into the page.

Safer pattern:

const safeState = JSON.stringify(state).replace(/</g, "\u003c");

const html = `<script id="boot" type="application/json">${safeState}</script>`;

Escaping < prevents </script> from being interpreted as a closing HTML tag. When possible, use your framework's built-in serializer or a battle-tested helper instead of rolling your own HTML serialization.

Serve APIs as JSON, not HTML

JSON API responses should be returned with a JSON content type such as application/json. That does not eliminate XSS on its own, but it reduces browser confusion and keeps API payloads out of HTML parsing paths where they do not belong.

Example 3: Parsing JSON with Code Execution APIs

Parsing response text with executable JavaScript APIs is both outdated and dangerous. JSON should be treated as data, not as code.

// Avoid this
const data = eval("(" + responseText + ")");

// Use this
const data = JSON.parse(responseText);

Defenses That Still Matter

  • Prefer safe sinks: Use textContent, DOM text nodes, and framework text interpolation for untrusted values.
  • Sanitize only when HTML is required: If you must render user-controlled HTML, use a maintained sanitizer such as DOMPurify and keep it patched. Do not sanitize once and then later append more unsanitized fragments into the same container.
  • Validate URLs and protocols: Treat href, src, redirect targets, and custom deep links as untrusted input too.
  • Escape embedded JSON in HTML: When placing serialized state into a page, escape HTML-significant characters before writing it into a <script> element.
  • Use CSP as defense in depth: A strong Content Security Policy can limit the blast radius, but it is not a substitute for correct output handling.
  • Consider Trusted Types where supported: Trusted Types can help enforce safer handling around dangerous DOM sinks and reduce accidental innerHTML usage in large frontends.

Quick review checklist

  • Search the codebase for raw HTML sinks such as innerHTML and framework-specific bypass APIs.
  • Trace every field from API response to DOM sink, not just the ones obviously named html.
  • Check server-rendered pages for inline JSON blobs created from JSON.stringify().
  • Verify that rich text, markdown, and CMS content are sanitized before rendering and not mutated afterward.
  • Validate outbound URLs from JSON before assigning them to href, src, or redirects.
  • Confirm API responses use application/json and are not accidentally rendered as HTML pages.

Conclusion

Cross-site scripting in JSON web applications is rarely about JSON syntax. It is about what the application does with untrusted data after parsing it. If you render untrusted values as text by default, sanitize the rare cases that need HTML, safely embed server state, and treat CSP as backup rather than the main fix, you remove the most common XSS paths in modern JSON-heavy frontends.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool