Concept · Application & cloud security
XSS and Content Security Policy
Untrusted text rendered as HTML becomes the attacker’s script; escaping prevents it and CSP limits the damage.
Cross-site scripting (XSS) is 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 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/templateand 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.writeandtemplate.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
hrefneed 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.
el.textContent = userInput // ✓ text
el.innerHTML = userInput // ✗ HTMLContent 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:
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-Onlyfirst, watch the violation reports, then enforce.
Limiting what a successful attack gets
HttpOnlycookies 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.- 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.