Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.

XSS & CSRF Prevention

Preventing cross-site scripting and cross-site request forgery: context-aware output encoding, avoiding innerHTML with user data, CSP as defense in depth, strict sanitization allowlists, and SameSite cookies with CSRF tokens.

Mby @markdownersPublished August 21, 2026 · ~3 min read

0 downloads · Used by 0 stacks

XSS lets an attacker run their JavaScript in another user's session; CSRF lets an attacker trigger an authenticated action without the user's knowledge. Both are prevented at output (encode/sanitize what gets rendered) and at the request boundary (verify what's allowed to trigger state change) — never by trying to blocklist "dangerous" input on the way in.

Context-aware output encoding

  • Encode output for the exact context it's rendered into — HTML body text, HTML attribute, JS string literal, URL query param, and CSS value each have different unsafe characters and different encoding rules; encoding for the wrong context (e.g. HTML-encoding a value injected into a <script> block) does not protect it.
  • Prefer frameworks that auto-escape by default (JSX text content, template engines with auto-escaping on) and treat any explicit "render raw" escape hatch (dangerouslySetInnerHTML, v-html, {% autoescape off %}) as a flagged, reviewed exception, never a default.
  • Never build HTML by string concatenation with user data, even for "just an attribute" — attribute-context injection (breaking out of a href or style attribute) is as exploitable as body-context injection.

Never use innerHTML with user data

  • Never assign user-controlled data to innerHTML, outerHTML, document.write, or equivalent DOM-sink APIs — use textContent/innerText for plain text, and DOM APIs (createElement, setAttribute on an allowlisted attribute) for structure.
  • If rich HTML genuinely must be rendered (user-authored Markdown, WYSIWYG content), sanitize server-side with a strict allowlist library before it ever reaches a rendering sink — never sanitize client-side only, since that step is trivially bypassed by calling the API directly.
  • Treat every jQuery .html(), template-literal-into-DOM, or "just this once" raw-HTML injection the same as a bare innerHTML assignment — the API name doesn't change the risk.

Content Security Policy as defense in depth

  • Ship a CSP that disallows unsafe-inline and unsafe-eval for scripts, restricting script-src to your own origin (and specific trusted hosts, ideally with hashes/nonces) — this doesn't replace output encoding, but it stops a large class of injected-script payloads from executing even if an encoding gap slips through.
  • Set object-src 'none' and a restrictive frame-ancestors to close plugin-based and clickjacking-adjacent vectors CSP also covers.
  • Treat CSP as the second layer, not the first — an application that relies on CSP instead of fixing the underlying injection is one browser quirk or CSP bypass away from full exposure.

Sanitizing rich text safely

  • When accepting user-authored HTML/Markdown for rendering to other users, sanitize with an allowlist of permitted tags and attributes (never a blocklist of forbidden ones) — strip everything not explicitly allowed, including <script>, inline event handlers (onerror, onclick), javascript:/data: URLs, and <style>/<iframe> unless specifically needed and independently sanitized.
  • Apply the same sanitization schema consistently everywhere the content is rendered (main view, preview, RSS/API export, notification email) — a schema enforced in one rendering path and skipped in another reopens the hole for that one path.
  • Re-sanitize on every render, not only once at write time — a sanitizer library upgrade that tightens its rules should retroactively protect previously stored content, which only happens if sanitization runs at read/render time (or content is re-sanitized on schema changes).

SameSite cookies and CSRF tokens

  • Set authentication cookies with SameSite=Lax or Strict as a baseline CSRF defense — Strict for cookies that never need to be sent on cross-site navigation, Lax where a top-level GET link into the app must stay authenticated.
  • For every state-changing request (POST/PUT/PATCH/DELETE) authenticated by cookie, additionally require a CSRF token (double-submit cookie or synchronizer token) — SameSite alone does not cover all browsers/scenarios and does not protect state-changing GET-triggered actions, so never design an endpoint where a GET request changes state.
  • Never rely on checking the Origin/Referer header alone as your only CSRF defense — treat it as a secondary signal, since some legitimate clients omit or truncate these headers.
  • Reject a state-changing request outright if its CSRF token is missing or does not match, and never accept a request purely because it carries a valid session cookie — cookie presence proves the browser was logged in, not that the user intended this specific action.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/xss-csrf-prevention)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion