Cross-site scripting (XSS) is one of the most common web security problems. It happens when a website puts untrusted data (text from a user, a URL parameter, an API) into a page as if it were code, so a malicious visitor can make the page run their script in other people’s browsers.
This lesson is about defending against it.
Why it is serious
A script running on your page has the same powers as your own code. It can read what the user sees, change the page, send requests as the user, and read anything stored in the page’s localStorage. The user sees your site and trusts it, but the page is doing what someone else told it to.
The root cause
Data and code get mixed up:
<!-- Your code builds HTML from a comment typed by a visitor -->
commentBox.innerHTML = "<p>" + comment + "</p>";
If comment is plain text, everything is fine. If it contains HTML, the browser parses it as HTML, and that can include executable parts. The page cannot tell your markup from the visitor’s.
The three types (in short)
| Type | Where the bad data comes from |
|---|---|
| Stored XSS | saved on the server (a comment, a profile) and shown to other users |
| Reflected XSS | part of the request (a search term in the URL) echoed back in the response |
| DOM-based XSS | JavaScript on the page takes data from the URL or elsewhere and puts it into the DOM unsafely |
This course focuses on the part JavaScript controls: how you put data into the page.
The defences
- Treat all external data as untrusted: user input, URL parts, API responses,
postMessagedata, local storage, third-party content. - Output text as text. Use
textContent, notinnerHTML: textContent vs innerHTML. - Build elements with
createElementrather than gluing HTML strings. - Validate and encode on the server, which is the real security boundary.
- If you must allow HTML (a rich text comment), use a well-tested sanitizer library that removes dangerous parts. Do not write your own with regular expressions.
- Avoid dangerous APIs:
eval(),new Function(), string arguments tosetTimeout,document.write(), inline event handler attributes,javascript:URLs (DOM-based XSS). - Add a Content Security Policy (CSP) as a second layer, so the browser refuses inline and unexpected scripts.
- Keep cookies safe:
HttpOnly,Secure,SameSite, so injected script cannot read session cookies. - Keep libraries updated.
A safe version
<ul id="comments"></ul>
<script>
function addComment(text) {
const li = document.createElement("li");
li.textContent = text; // always treated as text
document.querySelector("#comments").append(li);
}
addComment("Great article!");
addComment("<b>Not bold</b>, just text with angle brackets");
</script>
The second comment appears with the angle brackets visible. Nothing is interpreted as HTML.
Defence in depth
No single rule is enough: use safe output and server-side validation and a CSP and safe cookies. If one fails, the others limit the damage.
Do not rely on client-side checks
Filtering on the front end is a courtesy. Attackers do not use your form. They send requests directly. The server must validate and encode too.