Intermediate·11 min read·Updated 2026-10-15

Cross-Site Scripting (XSS): Stored, Reflected & DOM-Based

Original WeSecureX guide for authorized testing of sites you own. Linked Sources are citations for verification - not copied articles. Ads are not served on this page.

Cross-Site Scripting (XSS / CWE-79) remains one of the most widespread client-side vulnerabilities in web applications. Because browsers operate under the Same-Origin Policy (SOP), any JavaScript executing within an application's origin has unrestricted access to that origin's DOM, local storage, session cookies (unless protected with `HttpOnly`), and authenticated APIs. This guide details the mechanics of Stored, Reflected, and DOM-based XSS, examines framework vulnerabilities in React and Vue, and outlines modern multi-layered defense strategies.

Cross-Site Scripting (XSS): Stored, Reflected & DOM-Based
cross-site scripting · WeSecureX Research Lab
Quick Answer

Cross-Site Scripting (XSS) is a client-side code injection vulnerability where an attacker executes arbitrary JavaScript in a victim's browser within the security context of the vulnerable origin. This allows session token theft, keystroke logging, CSRF execution, and site defacement. Defenses require context-aware output encoding, strict Content Security Policy (CSP), and avoiding unsafe DOM sinks.

Key Takeaways
  • XSS executes arbitrary JavaScript in the victim's browser under the application's origin authority.
  • Stored XSS is persistent and highest impact; Reflected XSS requires tricking a user into clicking a malicious link.
  • DOM XSS occurs purely client-side when untrusted sources flow into dangerous sinks like `innerHTML`.
  • Modern frameworks protect standard JSX expressions, but `dangerouslySetInnerHTML` and `href` attributes remain vulnerable.
  • HttpOnly cookies protect session tokens from theft even if an XSS vulnerability occurs.
Visual Diagram · Cross-Site Scripting (XSS) Vector & CSP Defense
1

1. Malicious Script Payload Injection

Attacker submits inline script tags into comment fields, profile inputs, or URL parameters.

2

2. Browser Executes Untrusted Script

Victim browser executes injected JavaScript, accessing session tokens and sensitive DOM elements.

3

3. Context-Aware Encoding & CSP Shield

Server encodes HTML output and enforces strict CSP headers (script-src 'self'), blocking unauthorized scripts.

4

4. Automated XSS Security Audit

Application endpoints undergo dynamic XSS fuzzing scans on [WeSecureX Security Console](/scanner).

ShareShare on XLinkedIn

The Execution Model: How Browsers Execute Injected Scripts

Cross-Site Scripting (XSS) occurs when malicious JavaScript is injected into trusted web applications and executed by victims' browsers.

XSS Vulnerability Classes & Execution Contexts Matrix

XSS ClassificationInjection Point & PersistencePrimary JavaScript SinksDefensive Countermeasure
Stored XSS (Persistent)Saved in database (comments, profiles, forums)innerHTML, outerHTML, unescaped server templatesStrict contextual output encoding, DOMPurify sanitization
Reflected XSS (Non-Persistent)Embedded in search query or URL parameterReflected in immediate HTTP HTML responseFramework auto-escaping, Content Security Policy (CSP)
DOM-based XSSClient-side JavaScript DOM manipulationlocation.search, eval(), document.write()W3C Trusted Types API, textContent instead of innerHTML
Mutation XSS (mXSS)Browser HTML parser mutating seemingly safe markupSVG foreignObjects, MathML namespace switchesAudited HTML sanitizers (DOMPurify with mXSS protection)

The browser's HTML parser interprets text streams sequentially, building the Document Object Model (DOM). When the parser encounters characters like <, >, ", or ' in an unescaped context, it transitions into an execution state, treating incoming strings as HTML elements or JavaScript expressions.

Once an attacker successfully executes JavaScript in the victim's browser, they gain the full privileges of the authenticated user. Typical exploit payloads accomplish:

  • Session Hijacking: Stealing session tokens stored in localStorage, sessionStorage, or accessible cookies (document.cookie).
  • Credential Harvesting: Injecting fake login forms directly into the active webpage to capture passwords or MFA codes.
  • Silent API Manipulation: Making background fetch() requests on behalf of the user to change email addresses, transfer funds, or generate API keys.
  • Keylogging: Attaching event listeners (document.addEventListener('keydown')) to transmit keystrokes to an external server.

The Three Types of XSS Compared

XSS vulnerabilities are classified into three primary types based on where the payload is stored and how it reaches the browser:

TypePersistenceDelivery MechanismCommon LocationsPrimary Defense
Stored (Persistent)Permanent (Database, file)Loaded automatically from database on page visitComments, user profiles, forum posts, chat logsContext-aware output encoding & sanitization
Reflected (Non-Persistent)NoneEmbedded in link/URL and reflected immediately in HTTP responseSearch results, error messages, login redirect paramsOutput encoding of request parameters
DOM-BasedClient-side memory/DOMExecuted purely client-side via JavaScript without server reflectionlocation.hash, document.referrer, window.nameSafe DOM APIs & Trusted Types

DOM XSS: Dangerous Sinks vs. Safe Primitives

DOM-based XSS occurs entirely in the client-side JavaScript execution environment. An untrusted input (Source) flows into an execution function (Sink) without adequate sanitization.

Dangerous DOM Sources (Untrusted Input)

  • location.search (URL query parameters)
  • location.hash (URL fragments)
  • document.referrer
  • window.name
  • postMessage() events without origin verification

Dangerous Execution Sinks

  • element.innerHTML = untrustedData;
  • element.outerHTML = untrustedData;
  • document.write(untrustedData);
  • eval(untrustedData);
  • setTimeout(untrustedString, 100);
  • location.href = untrustedUrl; (allows javascript:... pseudo-protocols!)

Safe DOM Alternatives

  • Use element.textContent instead of element.innerHTML
  • Use document.createElement() and element.setAttribute() instead of concatenating HTML strings
typescript
// VULNERABLE DOM XSS Pattern
const params = new URLSearchParams(window.location.search);
const name = params.get('name');
// If name contains <img src=x onerror=alert(1)>, script executes immediately!
document.getElementById('greeting')!.innerHTML = `Hello, ${name}`;

// SAFE DOM Pattern
const safeElement = document.getElementById('greeting')!;
safeElement.textContent = `Hello, ${name}`; // Browser treats string strictly as text

Modern Frontend Vulnerabilities: React, Next.js & Vue

Modern frameworks like React automatically encode values rendered inside JSX curly braces ({userContent}), converting < to &lt; and > to &gt;. However, developers frequently bypass these protections:

  1. `dangerouslySetInnerHTML` in React:
tsx
   // VULNERABLE: Bypasses React's automatic encoding
   <div dangerouslySetInnerHTML={{ __html: post.content }} />
  1. URL Attribute Injection (`javascript:` URIs):

React does NOT validate URL protocols in anchor tags. If user-supplied input flows into href, attackers can submit javascript:alert(document.domain):

tsx
   // VULNERABLE if websiteUrl starts with 'javascript:'
   <a href={user.websiteUrl}>Visit Website</a>
  1. Vue.js `v-html` Directive: Similar to React's dangerous HTML, v-html executes raw scripts if user content is passed un-sanitized.
typescript
// Safe URL validation helper for React anchor tags
export function sanitizeUrl(url: string): string {
  const trimmed = url.trim().toLowerCase();
  // Disallow dangerous pseudo-protocols
  if (
    trimmed.startsWith('javascript:') ||
    trimmed.startsWith('data:') ||
    trimmed.startsWith('vbscript:')
  ) {
    return '#'; // Fallback to safe destination
  }
  return url;
}

Context-Aware Output Encoding Rules

Output encoding must match the exact context where data is inserted. Standard HTML entity encoding (&lt;) is insufficient if data is placed inside an attribute or script tag:

  • HTML Body Context: Convert & to &amp;, < to &lt;, > to &gt;, " to &quot;, ' to &#x27;.
  • HTML Attribute Context: Inside <input value="...">, attributes must be attribute-encoded to prevent breaking out of quotes.
  • JavaScript Variable Context: Inside <script>var x = '...';</script>, use JSON serialization with Unicode escaping (\u003C for <) to prevent script tag termination (</script>).
  • URL Parameter Context: Use standard percent-encoding via encodeURIComponent().

Industrial-Strength Sanitization with DOMPurify

When applications must accept rich text (e.g., Markdown or WYSIWYG HTML), sanitization must strip executable elements while preserving benign formatting tags (<b>, <i>, <p>). Use a battle-tested library like DOMPurify:

typescript
import DOMPurify from 'isomorphic-dompurify';

export function renderUserHtml(untrustedHtml: string) {
  // Configure strict allowlist of allowed tags and attributes
  const cleanHtml = DOMPurify.sanitize(untrustedHtml, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'code'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
  });

  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}

Content Security Policy (CSP Level 3) as Defense-in-Depth

Content Security Policy (CSP) is an HTTP response header that restricts the resources (scripts, images, stylesheets, frames) the browser is allowed to load and execute. Even if an attacker injects an <script> tag into the DOM, a strict CSP prevents its execution.

Modern Nonce-Based CSP Policy

Modern CSP architectures use cryptographic random nonces generated per HTTP request:

http
# Modern Strict CSP Header (Nginx or Next.js middleware)
Content-Security-Policy: \
  default-src 'self'; \
  script-src 'self' 'nonce-rAnd0m12345' 'strict-dynamic'; \
  object-src 'none'; \
  base-uri 'none'; \
  frame-ancestors 'none';

Modern Defenses: W3C Trusted Types API & Next.js Nuances

The W3C Trusted Types API

Trusted Types locks down dangerous DOM sinks (like element.innerHTML and script.src) at the browser engine level. When enforced via CSP (Content-Security-Policy: require-trusted-types-for 'script'), the browser throws a runtime TypeError if plain strings are assigned to DOM sinks:

typescript
// Enforcing Trusted Types in Modern JavaScript
if (window.trustedTypes && trustedTypes.createPolicy) {
  const sanitizePolicy = trustedTypes.createPolicy('dompurify-policy', {
    createHTML: (input: string) => DOMPurify.sanitize(input),
  });

  // Browser accepts only TrustedHTML instances; rejects plain strings!
  const safeHTML = sanitizePolicy.createHTML(userComment);
  document.getElementById('content')!.innerHTML = safeHTML as unknown as string;
}

Next.js & React Auto-Escaping Gotchas

While React escapes strings rendered inside JSX expressions (<div>{userText}</div>), three dangerous escape hatches remain:

  1. `dangerouslySetInnerHTML`: Bypasses React escaping entirely.
  2. URL Attribute Protocol Injection: <a href={userLink}> executes JavaScript if the user submits javascript:alert(1).
  3. SVG File Uploads: SVGs containing <script> tags execute arbitrary JavaScript when viewed directly in the browser.

Automating Cross-Site Scripting (XSS) Audits with WeSecureX Headless DAST

Cross-site scripting (XSS) remains one of the most pervasive threats in modern web applications. Because client-side frameworks render dynamic DOM structures and bind to user state, static regex crawlers fail to catch complex DOM-based or stored XSS flaws.

How WeSecureX Detects Reflected, Stored, and DOM XSS

The WeSecureX Vulnerability Scanner executes automated dynamic audits using a headless browser sandbox:

  1. Headless DOM Execution: WeSecureX renders your web pages in an instrumented Chromium environment. It hooks critical JavaScript sinks (innerHTML, document.write, eval, location.href) and monitors for payload execution in real time.
  2. Context-Aware Payload Fuzzing: The engine injects safe, non-destructive test vectors specifically tailored to the injection context (HTML body, attribute strings, script blocks, or URI parameters) to confirm whether malicious tags like <svg/onload> execute.
  3. Stored XSS Propagation Auditing: WeSecureX injects unique tracking markers into input fields (such as user profile names or comments) and crawls downstream views to verify whether the unescaped script tag executes when viewed by other users.
  4. Content Security Policy (CSP) Efficacy Testing: The scanner evaluates whether your server's Content Security Policy successfully neutralizes inline script execution or if dangerous directives ('unsafe-inline', 'unsafe-eval') leave the application exposed.

Triage & Remediation in the Platform

When an XSS vulnerability is identified, WeSecureX registers the finding in your Vulnerabilities Dashboard with:

  • Exact DOM stack trace showing the injection source and vulnerable sink.
  • Step-by-step remediation guide recommending context-aware output encoding or DOMPurify integration.
  • Automated re-testing capability to ensure your patch completely closes the exploit vector.

Launch a test using our Free Online XSS Scanner, verify headers with the Security Headers Checker, or read our guide on How to Triage False Positives in Vulnerability Scanning.

bash
# WeSecureX Automated XSS Detection Terminal Output
$ wesecurex audit --url https://app.example.com/search?q=test --vector xss

[CRAWL] Hydrating Single Page Application via Headless Chromium
[PROBE] Injected test probe: q=%22%3E%3Csvg%2Fonload%3Dconsole.log(9812)%3E
[INTERCEPT] Console event captured: 9812 in window execution context
[DOM_SINK] Location: document.querySelector('#search-results').innerHTML
[SEVERITY] High (CVSS v4.0: 8.2) - Reflected Cross-Site Scripting
[REMEDY] Use textContent instead of innerHTML or sanitize with DOMPurify.sanitize().
[REPORT] Report archived in WeSecureX Vulnerabilities Manager.

Apply it with WeSecureX

Add a hostname you own or are allowed to test, run a scan, and open the findings for that target. A confirmed result includes the request WeSecureX sent and the response the site returned, so you can reproduce it, fix it in your own application, and rescan the same host to confirm the issue is gone. WeSecureX is automated testing, not a manual penetration test, and a clean report is not a promise that every weakness is gone.

Checks that belong with this topic

  • Store sensitive session tokens in `HttpOnly; Secure; SameSite` cookies, never in localStorage.
  • Audit all uses of `dangerouslySetInnerHTML` (React) and `v-html` (Vue).
  • Sanitize rich HTML user inputs using DOMPurify with strict tag allowlists.
  • Validate protocols on user-supplied links (`href`) to prevent `javascript:` execution.
  • Replace dangerous DOM sinks (`innerHTML`, `eval`, `document.write`) with safe primitives (`textContent`).
  • Deploy a strict Content Security Policy (CSP Level 3) with request nonces.

Common Mistakes to Avoid

❌Relying solely on blacklisting specific script tags like `<script>`

Why it happens: Developers write regex to strip `<script>` and `</script>` tags from input.

Why it matters: JavaScript can execute via hundreds of HTML tags and event handlers: `<img src=x onerror=alert(1)>`, `<svg onload=alert(1)>`, `<body onpageshow=...>`.

Correct approach: Use context-aware output encoding or DOMPurify rather than custom regex tag-stripping.

❌Storing sensitive authentication tokens in localStorage

Why it happens: Developers find `localStorage.setItem('token', jwt)` convenient for SPA authentication.

Why it matters: `localStorage` is accessible to ANY JavaScript executing on the origin. A minor XSS vulnerability immediately leads to full account takeover.

Correct approach: Store session tokens in `HttpOnly; Secure; SameSite=Strict` cookies that cannot be read by JavaScript.

❌Passing untrusted user input directly to `href` in anchor tags

Why it happens: Developers assume React automatically sanitizes URLs.

Why it matters: Attackers supply `javascript:alert(document.domain)`. When the user clicks the link, the script executes in the user's session.

Correct approach: Validate user URLs against an allowlist of permitted protocols (`http:`, `https:`, `mailto:`).

❌Using `unsafe-inline` in Content Security Policy without nonces

Why it happens: Teams enable `script-src 'unsafe-inline'` to make legacy third-party scripts work.

Why it matters: `unsafe-inline` completely disables CSP protection against inline XSS script injections.

Correct approach: Migrate to cryptographic request nonces (`'nonce-...'`) and `'strict-dynamic'`.

❌Sanitizing input upon insertion into the database instead of encoding at output

Why it happens: Developers attempt to clean input data once at API entry points.

Why it matters: Input may be used in multiple contexts (HTML, PDF generation, email, JSON API, mobile app). Sanitizing for one context breaks or under-protects others.

Correct approach: Store raw data in the database and apply context-aware encoding or sanitization at the exact point of rendering.

Troubleshooting Guide

Problem: DOMPurify is stripping legitimate links or styles from rich-text content

Possible Causes:

  • Default DOMPurify configuration strips custom attributes or inline CSS.

How to verify: Inspect the output of `DOMPurify.sanitize(input)` in console.

How to fix: Configure explicit `ALLOWED_TAGS` and `ALLOWED_ATTR` options in DOMPurify settings.

Problem: Browser console logs 'Refused to execute inline script because it violates Content Security Policy'

Possible Causes:

  • An inline `<script>` tag was loaded without the matching cryptographic nonce.
  • An inline event handler (`onclick=...`) was used in HTML.

How to verify: Inspect the CSP header in network tab and verify if the nonce matches the script's `nonce` attribute.

How to fix: Attach event listeners via `addEventListener` in external scripts and supply matching `nonce` attributes.

Problem: React hydration error occurs when using DOMPurify on server and client

Possible Causes:

  • Different DOM parsing environments between Node.js (JSDOM) and browser.

How to verify: Check Next.js console for 'Text content did not match server-rendered HTML'.

How to fix: Use `isomorphic-dompurify` or sanitize rich text inside a `useEffect` hook on client-side mount.

Actionable Checklist

WeSecureX Platform · Scanner Engine

Automated Vulnerability Scanner

Launch targeted or full-domain security audits to detect OWASP Top 10 vulnerabilities automatically.

Includes: OWASP Top 10 automated test suites · Real-time severity scoring (CVSS v3.1) · Instant remediation code snippets

Frequently Asked Questions

The Same-Origin Policy prevents a script loaded on `evil.com` from reading data on `example.com`. However, XSS injects malicious code directly into `example.com`. Because the script runs *within* `example.com`, SOP treats it as trusted.

No. HttpOnly cookies prevent an attacker from *stealing* the cookie via `document.cookie`. However, an attacker executing XSS can still make authenticated background requests (`fetch('/api/transfer')`), log keystrokes, and deface the page.

Trusted Types is a W3C specification supported in Chromium browsers that enforces type-safety on dangerous DOM sinks. It blocks direct string assignment to `innerHTML`, requiring values to pass through a registered sanitization policy.

No. Input validation ensures data adheres to expected business formats (e.g., verifying an email structure). However, legitimate names like `O'Connor` or mathematical equations contain characters (`'`, `<`, `>`) that can trigger XSS if not encoded at output.

A CSP nonce is a cryptographically strong random token generated by the server for each HTTP response. The server places the nonce in the CSP header (`script-src 'nonce-XYZ'`) and in trusted inline `<script nonce="XYZ">` tags. The browser only executes scripts with matching nonces.

Authoritative Sources & References

  • OWASP Cross Site Scripting (XSS) Prevention Cheat SheetOWASP Foundation (official)
    View Source
  • W3C Content Security Policy Level 3 SpecificationW3C (official)
    View Source
  • CWE-79: Improper Neutralization of Input During Web Page GenerationMITRE (official)
    View Source
  • W3C: Trusted Types Specification for DOM XSS PreventionW3C (official)
    View Source

Continue exploring related technical architecture and defensive guides

Security Research & Engineering Articles

All articles