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
hreforstyleattribute) 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 — usetextContent/innerTextfor plain text, and DOM APIs (createElement,setAttributeon 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 bareinnerHTMLassignment — the API name doesn't change the risk.
Content Security Policy as defense in depth
- Ship a CSP that disallows
unsafe-inlineandunsafe-evalfor scripts, restrictingscript-srcto 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 restrictiveframe-ancestorsto 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=LaxorStrictas a baseline CSRF defense —Strictfor cookies that never need to be sent on cross-site navigation,Laxwhere 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) —
SameSitealone 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/Refererheader 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.
[](https://markdowners.com/m/markdowners/xss-csrf-prevention)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.