Safeguard
AppSec

XSS Code Examples: How Cross-Site Scripting Looks in Practice

An XSS code example makes the abstract concrete: here is what vulnerable code looks like for each type of cross-site scripting, and the small change that fixes each one.

Aisha Rahman
Security Analyst
5 min read

An XSS code example is worth more than a paragraph of theory, because cross-site scripting always comes down to one visible mistake: application code places untrusted data into a page without encoding it, so the browser parses that data as markup or script instead of text. The examples below show that mistake for each type of XSS alongside the corrected version. They are illustrative snippets meant to teach detection and remediation, not payloads to point at a live target. Once you can spot the vulnerable shape in code, you can find it in your own codebase, which is the whole point.

Cross-site scripting sits inside A03:2021 Injection in the current OWASP Top 10, and every example here is a special case of the same rule: keep data out of executable contexts.

The core vulnerable shape

Before the specific types, internalize the pattern. XSS happens at a sink, a place where a string becomes part of the page. The most common dangerous sink in browser code is assigning to innerHTML:

// VULNERABLE: input is parsed as HTML
const name = new URLSearchParams(location.search).get("name");
document.getElementById("greeting").innerHTML = "Welcome, " + name;

If name contains an HTML tag, the browser builds it into the DOM. The fix is almost always the same one word swap, from a markup sink to a text sink:

// SAFE: input is inserted as text, never parsed as markup
document.getElementById("greeting").textContent = "Welcome, " + name;

That contrast, innerHTML versus textContent, is the single most useful thing to memorize about XSS.

Reflected XSS example

Reflected XSS echoes request data straight back into the response. A server that builds an HTML page by concatenating a query parameter is the archetype:

// VULNERABLE (server): search term echoed into HTML unescaped
app.get("/search", (req, res) => {
  const q = req.query.q;
  res.send(`<h1>Results for ${q}</h1>`);
});

The value of q lands directly in the HTML. The corrected version escapes the value for the HTML context, or better, uses a templating engine that auto-escapes by default:

// SAFE: escape untrusted data for the HTML context
const escapeHtml = (s) =>
  s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
res.send(`<h1>Results for ${escapeHtml(q)}</h1>`);

In practice you should lean on your framework's escaping rather than hand-rolling escapeHtml, but seeing it spelled out shows what auto-escaping does under the hood: it turns the characters that start a tag into their harmless entity forms.

Stored XSS example

Stored XSS is the most damaging type because the payload is saved and served to every later viewer. A comment feature that stores raw input and renders it as HTML is the classic case:

// VULNERABLE: stored comment rendered as raw HTML for every viewer
function renderComment(comment) {
  container.innerHTML += "<p>" + comment.body + "</p>";
}

Whatever an attacker saved as a comment runs in the browser of everyone who loads the page. The fix is again to render as text, and if the feature genuinely needs rich formatting, to sanitize with a vetted library rather than trusting the stored value:

// SAFE: build the node and set its text content
const p = document.createElement("p");
p.textContent = comment.body;
container.appendChild(p);

DOM-based XSS example

DOM-based XSS lives entirely in client-side JavaScript; the dangerous data flow may never reach the server. A script that reads from the URL fragment and writes it into the DOM is a common form:

// VULNERABLE: URL fragment written into the DOM as HTML
const tab = location.hash.slice(1);
document.querySelector("#panel").innerHTML = tabTemplates[tab];

If tab is attacker-controlled and used to build markup, the browser executes it. Because this happens at runtime in the browser, DOM XSS often slips past source review and needs the running app to surface, which is where dynamic testing through our DAST product tends to catch it. The fix is to constrain the input to a known set and never use it to build markup:

// SAFE: allowlist the value and render via a safe sink
const allowed = ["overview", "details", "settings"];
if (allowed.includes(tab)) {
  document.querySelector("#panel").textContent = labels[tab];
}

Reading these examples in your own code

Notice what every safe version has in common: untrusted data reaches the page as text, through textContent, framework auto-escaping, or an allowlist, never through a markup sink. When you audit code for XSS, search for the sinks first (innerHTML, document.write, dangerouslySetInnerHTML, and template constructs that emit raw HTML), then trace whether untrusted data can reach them. A static scanner automates that trace, and vulnerable versions of front-end libraries can introduce XSS of their own, so keeping dependencies current matters as much as writing careful code. The Academy has a fuller secure-coding path if you want to go deeper.

FAQ

What is the simplest XSS code example to understand?

Assigning untrusted input to innerHTML. The browser parses the assigned string as HTML, so any markup in the input becomes part of the page. Switching the assignment to textContent inserts the value as plain text and fixes it.

What is the difference between reflected, stored, and DOM-based XSS in code?

Reflected XSS echoes request data straight back into the response; stored XSS saves the payload server-side and serves it to later viewers; DOM-based XSS occurs entirely in client-side JavaScript that writes untrusted data into the DOM, often without the server ever seeing the payload.

How do I fix an XSS vulnerability in my code?

Render untrusted data as text rather than markup by using textContent, framework auto-escaping, or context-aware encoding. Avoid sinks like innerHTML, and if you must render user-authored HTML, sanitize it with a vetted library rather than a custom regex.

Are these XSS examples safe to run?

They are illustrative teaching snippets that show the vulnerable shape and its fix, not exploit payloads aimed at a live system. Use them to recognize and remediate the pattern in your own codebase, ideally alongside a static scanner that traces input to dangerous sinks.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.