Cross-Site Scripting (XSS)
Also known as: XSS, Cross-site scripting, Script injection
A flaw in which untrusted input is placed into a web page without contextual output encoding, so the browser treats data as active content; it is prevented by encoding output for its context, framework auto-escaping and a strict Content-Security-Policy.
How it works
Cross-site scripting is a flaw in how a web application builds the pages it sends. When the code places a value that came from outside, such as a search term, a comment or a profile field, straight into the page, the browser receives one document and cannot tell which parts the developer wrote and which parts were supplied by a user. Input that should have been shown as text can be read as part of the page itself.
The root cause is the same shape as other injection flaws: data and instructions share one channel. It comes in three common forms. Reflected cases echo a request value back in the immediate response. Stored cases keep the value in a database and show it to later visitors. DOM-based cases happen entirely in the browser, when client code writes a value into the page through an unsafe interface. The impact depends on what the page can do for the signed-in user, which is why session cookie flags and the browser's content policy matter.
The fix is to encode on output, for the exact place the value lands. Text in an element body, an attribute value, a URL and a script context each need different encoding, which is why a single generic filter is fragile. Modern frameworks encode by default when you bind values as text, and they become unsafe only when a developer opts out with a raw-HTML feature. Where rich content must be allowed, pass it through a maintained sanitizer with an allow-list.
Defence is layered. Contextual output encoding is the primary control. A strict Content-Security-Policy limits which scripts the browser will run even if a flaw slips through. HttpOnly and SameSite cookie flags keep session cookies out of reach of page scripts. Validation of type, length and format reduces exposure, and CSP violation reports plus WAF and log monitoring reveal attempts. Blocklists of characters or keywords alone should never be the main defence.
Walk through it
- 1Read the vulnerable page code
- 2Identify the sink
- 3Read the signals, pick the fix
- Scope every unsafe sink
- Encode, add CSP and verify
A pre-release review flags the help-centre search page. It shows the visitor's search term back to them in the results heading. Read the handler as a reviewer and note how the page text is produced.
1// GET /search?q=...2router.get("/search", async (req, res) => {3 const q = req.query.q;4 const html = "<h1>Results for " + q + "</h1>" + renderResults(await find(q));5 res.send(html);6});Spot it
- Content-Security-Policy violation reports for script directives from pages that normally produce none.
- WAF matches for script-like input in query parameters, form fields or headers that do not usually contain markup.
- Responses that echo a request parameter back, combined with unusually long or oddly structured parameter values from one source.
- A spike of inbound links to a single reflecting page from unrelated external sites or referrers.
- Browser-side error reports or unexpected outbound requests from pages that should only talk to your own origin.
CSP report endpoint
ts=2026-10-11T10:02:11Z document-uri=https://support.fabrikam.example/search violated-directive=script-src-elem blocked-uri=inline disposition=report
ts=2026-10-11T10:02:19Z document-uri=https://support.fabrikam.example/search violated-directive=script-src-elem blocked-uri=inline disposition=reportWAF
ts=2026-10-11T10:02:10Z rule=script-like-input action=log src=203.0.113.77 host=support.fabrikam.example path=/search param=q
ts=2026-10-11T10:02:18Z rule=script-like-input action=log src=203.0.113.77 host=support.fabrikam.example path=/search param=qsplSplunk: pages with a burst of CSP script violations
index=csp sourcetype=csp_report violated_directive="script-src*"
| stats count by document_uri
| where count > 20Tune the threshold to the page's baseline. Browser extensions cause some noise, so correlate with WAF matches before escalating.
kqlKQL: repeated WAF hits for cross-site scripting rules
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog" and ruleGroup_s has "XSS"
| summarize hits=count() by clientIp_s, requestUri_s
| where hits > 20Stop it
Encode output for the context it lands in
Encode every untrusted value at the point it is written to the page, using the encoding that matches the context: element text, attribute, URL or script data. Rely on framework auto-escaping, and treat any raw-HTML feature as a reviewed exception.
Deploy a strict Content-Security-Policy
Allow scripts only from your own origin or by nonce, and forbid inline script by default. If a flaw slips through, the browser refuses to run content you did not authorise. Use report-only mode first to find breakage.
Protect sessions, validate input and sanitize rich content
Set HttpOnly, Secure and SameSite on session cookies. Validate type, length and format with allow-lists. Where users must submit rich text, run it through a maintained sanitizer with an allow-list of elements.
Encode the value instead of joining it into markup
Vulnerable
const html = "<h1>Results for " + q + "</h1>" + renderResults(rows);
res.send(html);Hardened
const html = "<h1>Results for " + escapeHtml(String(q ?? "")) + "</h1>" + renderResults(rows);
res.send(html);Better still, render through a template engine or framework that encodes by default, and keep escapeHtml as a single reviewed helper.
Baseline security headers
Hardened
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'");
res.cookie("sid", token, { httpOnly: true, secure: true, sameSite: "lax" });Start in report-only mode to learn what the policy would block, then enforce.
- Use a framework or template engine that encodes output by default, and ban raw-HTML features except in reviewed, sanitized cases.
- Encode for the correct context on output; never rely on input filtering alone.
- Deploy a strict Content-Security-Policy without unsafe inline allowances, with a report endpoint.
- Set HttpOnly, Secure and SameSite on session cookies.
- Sanitize any user-supplied rich content with a maintained allow-list sanitizer.
- Enforce static-analysis rules for tainted values reaching HTML sinks in CI, and monitor CSP and WAF reports.
If it already happened
Tighten WAF rules on the affected page, enable or tighten the Content-Security-Policy, and if exposure is likely disable the reflecting feature while the code fix ships.
Encode output on every affected sink, remove stored values that render unsafely, invalidate active sessions for potentially exposed users, and review logs to scope who viewed the affected content.
Run regression tests that confirm values are displayed as text, restore any altered content, and notify affected users if regulated data or sessions were exposed.
Add a static-analysis rule and review checklist item against unencoded output, keep the CSP enforced with reporting, and retain the log detections above permanently.
Check yourself
1. What is the root cause of cross-site scripting?
2. Which control is the primary fix for XSS?
3. What does a strict Content-Security-Policy add to an application that already encodes output?