Safeguard
AppSec

OWASP Top 10 and XSS: Where Cross-Site Scripting Fits Now

In the OWASP Top 10, XSS is no longer its own category. As of the 2021 list it lives inside A03: Injection. Here is what changed, why, and how to defend against it.

Safeguard Research Team
Research
5 min read

In the current OWASP Top 10, XSS is not a standalone entry. As of the 2021 edition, cross-site scripting was folded into A03:2021 Injection (CWE-79), where it sits alongside SQL injection and command injection. If you learned the list from the 2017 version, this is the change to internalize: XSS was previously its own category, A07:2017. The OWASP team merged it into Injection because the underlying mechanism is identical to every other injection flaw, namely untrusted data reaching a sink that interprets it as code. For XSS the interpreter is the browser; for SQL injection it is the database. The data-becomes-code transition is the same defect wearing different clothes.

This reclassification is not academic. It tells you how to think about the defense, because the fix for XSS is the same discipline that fixes injection generally.

Why XSS moved into Injection

The 2021 rework of the Top 10 was data-driven and consolidated related weaknesses under shared root causes. Injection is one of the most-tested categories in the underlying dataset, and the analysis found injection patterns extremely widespread across tested applications. Cross-site scripting shares the exact taint-to-sink shape, so grouping it under A03 reflects how the flaw actually behaves rather than treating it as a separate species.

The practical upshot: when someone says an app has "an A03 issue," that could be SQL injection or it could be XSS. Both trace back to failing to separate untrusted data from executable context.

The three types of XSS

XSS comes in three well-established forms, distinguished by how the malicious payload reaches the victim's browser.

  • Reflected XSS: the payload rides in a request (a query parameter, form field) and is echoed straight back into the response. It requires tricking a user into clicking a crafted link, so it is per-request and non-persistent.
  • Stored XSS: the payload is saved server-side (a comment, profile field, support ticket) and served to everyone who views that content later. This is the most damaging kind because it needs no per-victim interaction.
  • DOM-based XSS: the vulnerability lives entirely in client-side JavaScript that writes untrusted data into the DOM in a dangerous way, so the malicious change may never touch the server at all.

Each type reaches a "sink" where data becomes markup or script. The classic dangerous sinks are writing to innerHTML, document.write, and passing strings to constructs that evaluate them.

What an XSS flaw looks like

Conceptually, XSS happens when application output places attacker-controlled data into an HTML context without encoding it for that context. A template that interpolates a raw value into the page is the archetype:

// Vulnerable: untrusted input written directly as HTML
element.innerHTML = "Hello, " + userProvidedName;

If userProvidedName contains markup, the browser parses it as part of the page. The corrected version treats the value as text, never as markup:

// Safe: the value is inserted as text, not parsed as HTML
element.textContent = "Hello, " + userProvidedName;

That single distinction, text versus markup, is the heart of XSS defense.

How to defend against XSS

The controls follow directly from the injection framing: keep untrusted data out of executable contexts, and where it must appear, encode it for that specific context.

  1. Context-aware output encoding. HTML, HTML-attribute, JavaScript, CSS, and URL contexts each need different escaping. Modern template engines and frameworks (React, Angular, Vue) auto-escape by default, which is why sticking to framework rendering rather than manual string concatenation eliminates most XSS.
  2. Avoid dangerous sinks. Prefer textContent over innerHTML. When a framework offers an escape hatch like React's dangerouslySetInnerHTML, treat every use as a review checkpoint.
  3. Sanitize when you must render user HTML. If a feature genuinely needs to display user-authored rich text, run it through a vetted sanitizer library rather than a homegrown regex, which attackers routinely bypass.
  4. Content Security Policy (CSP). A strong CSP is defense in depth, restricting where scripts may load from and blocking inline script execution, so that even a missed encoding is less likely to run.
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

Finding XSS before attackers do

Detection uses both halves of application testing. Static analysis traces untrusted input to a rendering sink in source code, which is effective for reflected and stored XSS where the flow is visible. DOM-based XSS often needs the running application to surface, since the dangerous flow happens in the browser at runtime; dynamic testing through our DAST product is where that class tends to appear.

Do not overlook the dependency angle. Vulnerable versions of front-end libraries and sanitizers have shipped XSS bypasses of their own, so a component you trust to prevent XSS can become the source of one. Keeping those current is dependency hygiene, and OWASP's own guidance and our Academy both cover the pairing of secure coding with dependency management.

FAQ

Is XSS still in the OWASP Top 10?

Yes, but not as its own entry. Since the 2021 edition, cross-site scripting is part of A03:2021 Injection (CWE-79), grouped with SQL and command injection. It was previously the standalone A07:2017 category.

Why was XSS merged into the Injection category?

Because the root cause is identical: untrusted data reaching a sink that interprets it as code. In XSS the interpreter is the browser rather than a database, but the data-becomes-code defect is the same, so OWASP grouped it under A03.

What are the three types of XSS?

Reflected XSS echoes a payload from the request straight back into the response; stored XSS persists 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.

What is the single most effective XSS defense?

Context-aware output encoding, which modern frameworks apply automatically. Rendering values as text rather than markup, avoiding sinks like innerHTML, and adding a strong Content Security Policy as defense in depth eliminate the large majority of XSS.

Never miss an update

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