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.

4 mincross-site scriptingCSPinnerHTMLoutput encoding

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.

TypeThe payload comes from
StoredYour database (a comment, a profile name), shown to other users
ReflectedThe request (a search query in the URL), echoed into the response
DOM-basedClient-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.
  • 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.

Learn it in depth