The three types
| Type | How the script arrives |
|---|---|
| Stored | The site saves it, for example in a comment or a profile, and shows it to every visitor who opens that page |
| Reflected | It is sent in a link or a form and echoed straight back in the response |
| DOM-based | It is created in the browser, when page scripts write untrusted data into the page unsafely |
Why it matters
Browsers trust scripts that come from your site. If an attacker's script runs there, it can read the page, send requests as the signed-in user, show a fake sign-in form or steal data. It is one of the classic web flaws, and it counts as a form of injection in the OWASP Top 10.
An example
Imagine a forum that shows each member's display name without encoding it. A member sets their name to a snippet of script. Every visitor who opens a thread that member posted in then runs that script. This example is hypothetical.
How to prevent it
- Encode output for its context. Text inside HTML, an attribute, a script and a URL each need different encoding. Modern frameworks such as React do this for you by default.
- Avoid the escape hatches. Raw HTML insertion, such as innerHTML, skips that protection.
- Sanitise rich text with a well-maintained sanitising library when users must be able to post formatting.
- Add a [content security policy](/glossary/content-security-policy) as a second layer that limits what injected script can do.
- Mark session cookies HttpOnly, so a script cannot read them directly.
Common mistakes
- Filtering out the word script. Attackers have many other ways to run code.
- Encoding for the wrong context, such as HTML encoding inside a script block.
- Trusting data that comes from your own database or an internal API. Stored XSS starts exactly there.
- Relying on a content security policy alone.
Related terms
SQL injection is the server-side relative. A secure code review is good at finding unsafe output handling.