# XSS and Content Security Policy

> Untrusted text rendered as HTML becomes the attacker’s script; escaping prevents it and CSP limits the damage.

Concept · Application & cloud security · https://levelcode.org/concepts/xss-and-csp

**Cross-site scripting (XSS)** is [injection](https://levelcode.org/concepts/sql-injection) into a web page. If text from a user, a URL or a third party is rendered as HTML, it can contain a `<script>`, or an `<img onerror=…>`, that runs **in your origin**. There it can read the page, act as the user and call your API. The [same-origin policy](https://levelcode.org/concepts/same-origin-policy-cors) doesn't help, because the script is already inside.

| Type | The payload comes from |
|---|---|
| Stored | Your database (a comment, a profile name), shown to other users |
| Reflected | The request (a search query in the URL), echoed into the response |
| DOM-based | Client-side JS writing URL fragments or other input into the DOM |

## Prevention: escape for the context

- **Let the framework escape.** React, Vue, Angular, Go's `html/template` and Jinja escape interpolated values by default. `{comment}` in JSX renders as text, not HTML.
- **Be careful with the escape hatches:** `dangerouslySetInnerHTML`, `v-html`, `innerHTML`, `insertAdjacentHTML`, `document.write` and `template.HTML`. If you must render user HTML, such as rich-text comments, sanitize it with a maintained library like DOMPurify.
- **Watch the context, not just the characters.** Values inside `href` need a scheme check (`javascript:` URLs), and values inside inline `<script>` or event-handler attributes need different encoding. Avoid putting untrusted data in those places at all.

```js
el.textContent = userInput      // ✓ text
el.innerHTML = userInput        // ✗ HTML
```

## Content Security Policy: limit the damage

CSP is a response header that tells the browser which scripts may run. With a strict policy, an injected inline script is blocked even when escaping failed somewhere:

```http
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'
```

- **Nonce-based policies:** each response gets a random nonce, and only `<script nonce="r4nd0m">` tags run. Attacker-injected scripts don't know the nonce.
- **Avoid `'unsafe-inline'` and broad host allowlists.** They make CSP easy to bypass.
- **Roll it out with `Content-Security-Policy-Report-Only`** first, watch the violation reports, then enforce.

## Limiting what a successful attack gets

- **`HttpOnly` cookies** keep XSS from *reading* the session. XSS can still *use* it while the page is open, so this limits theft but doesn't stop the attack. See [sessions vs tokens](https://levelcode.org/concepts/sessions-vs-tokens).
- **Don't keep tokens in `localStorage`.**
- **Require re-authentication** for sensitive actions such as changing email or password, or payouts.

LLM output is untrusted input too. Rendering model-generated markdown or HTML without sanitizing it is an XSS hole.
