Cross-site scripting (XSS): types, impact and prevention

XSS lets an attacker run their JavaScript in your users' browsers. Learn the three types and the layered defences that stop it.

A script tag injected into a web page

Cross-site scripting (XSS) happens when a site puts untrusted input into a page without proper handling, so an attacker's JavaScript runs in a victim's browser with the site's trust.

What an attacker can do

Run in the victim's session, which can mean: read data shown on the page, perform actions as the user, steal tokens that are readable by JavaScript, show fake login forms, or redirect to harmful sites.

The three types

Reflected XSS. Input in the request (such as a search term in the URL) is echoed back in the page. The attacker tricks a victim into opening a crafted link.

Stored XSS. The malicious input is saved (a comment, profile, product review) and shown to every visitor who views it. Usually the most dangerous.

DOM-based XSS. The bug is in client-side JavaScript that writes untrusted data into the page, for example using innerHTML with a value from the URL.

A harmless demonstration

If a comment box shows what you type without encoding, entering <script>alert(1)</script> pops an alert box. That proves script runs. A real attacker would do something harmful instead of an alert. Try this only on your own test app.

Prevention

1. Encode output for its context. Turn special characters into safe forms depending on where data lands: HTML text, an HTML attribute, JavaScript, CSS or a URL. Modern frameworks (React, Vue, Angular, Astro templates) escape by default. Most XSS today comes from bypassing that.

2. Avoid dangerous APIs. innerHTML, document.write, dangerouslySetInnerHTML, v-html, eval. Use textContent and safe template bindings.

3. Sanitize HTML when you must allow it. If users can submit rich text, run it through an allow-list sanitizer such as DOMPurify (with a maintained version) on trusted code paths. Do not write your own regex filter.

4. Content Security Policy (CSP). A response header that restricts where scripts can load from and blocks inline scripts. A strict CSP is a strong second line of defence even if a bug slips through.

5. Cookie flags. HttpOnly keeps JavaScript from reading session cookies. Secure and SameSite add more protection.

6. Validate input where sensible, but do not rely on it. Encoding output is the key.

Common mistakes

  • Encoding for the wrong context (HTML-encoding data placed inside a JavaScript block).
  • Trusting data because it came from your own database (it may have been stored by an attacker).
  • Allowing user-controlled URLs in href, where javascript: links can run code. Allow only http and https.
  • Third-party scripts with their own bugs or compromises.

Takeaway: encode output by context, avoid raw HTML APIs, sanitize any HTML you allow, and add a Content Security Policy and HttpOnly cookies as backups.


Web Security series, Part 1 (injection and access control): 2 of 4. Previous: SQL injection explained: how it works and how to stop it.

Keep reading