Security, Abuse & Privacy

Cross-Site Scripting

Definition

Cross-site scripting, abbreviated XSS to avoid a collision with CSS, comes in three shapes. Stored XSS means the hostile text is saved somewhere and served to later visitors. Reflected XSS means it arrives in a URL — usually a query string — and is echoed straight back into the page. DOM-based XSS never involves the server at all: the page reads something from location.hash or location.search and writes it into the document with innerHTML or document.write. All three end the same way, with attacker-chosen JavaScript running under your origin, which under the same-origin policy is trusted to read the page, its storage and its cookies. The root cause is always the same: data was placed into HTML without being escaped first.

Why It Matters

Most people publishing a generated page assume they have no XSS surface because there is no server and no database. DOM-based XSS does not need either. A single-file page that reads a name from the query string and drops it into innerHTML to say hello is vulnerable the moment someone crafts a link — and that link looks like your address, arrives from you, and is trusted accordingly. What runs next can rewrite the page, harvest whatever is typed into it, or read every key in localStorage. The fix is usually a one-line change from innerHTML to textContent, which is a smaller job than the risk suggests.

How It Works

The browser makes no distinction between markup the author wrote and markup assembled at runtime; if a string containing a script tag or an onerror attribute reaches the HTML parser, it runs. Safe handling means escaping the five HTML-significant characters, or better, avoiding HTML assembly entirely: set textContent, use createElement and setAttribute, and keep URLs out of href values unless the scheme is checked. Never pass untrusted text to eval, to setTimeout as a string, or to a framework's raw-HTML escape hatch. A Content Security Policy without unsafe-inline is the backstop, blocking injected inline scripts even when escaping was missed, and the sandbox attribute on an iframe limits what embedded content can do. None of these replace escaping; they reduce what a miss costs.

Real-World Example

A marketer publishes a personalised proposal page at nova-offer.99helpers.site that reads a client name from the query string and writes it into the heading with innerHTML. A link with markup instead of a name in that parameter turns the page into whatever the sender wants, at an address the recipient has every reason to trust. Switching the assignment to textContent, and adding a policy of script-src 'self' 'unsafe-inline', removes the injection while leaving the personalisation working.

Common Mistakes

  • Assuming a static page cannot have XSS — DOM-based injection lives entirely in the browser and needs no server
  • Escaping on the way in but not on the way out, so text stored safely is still written raw into the page later
  • Using innerHTML for plain text because it was quicker to type than textContent, which is the single most common source of the flaw
  • Treating a Content Security Policy as a substitute for escaping rather than a second layer behind it

Related Terms

Put a file online in seconds

Drop in a document, an image, a page or a whole static website and share the link — free, with no build step and no server to set up.

Host a file free →