Skip to content
fearchitect
Security

XSS, CSRF & Clickjacking

Three browser-level attacks and the headers that stop them.

By Abas TurabliReviewed

Summary

XSS injects script into a page; CSRF forges cross-site requests using the victim's cookies; clickjacking tricks users into clicking inside an invisible iframe. Each has a distinct attack surface and a distinct defense: output encoding and CSP for XSS, SameSite cookies and tokens for CSRF, and `X-Frame-Options` / `frame-ancestors` for clickjacking.

Jump to the interview angle

Three threats at a glance

AttackVectorPrimary defense
Stored XSSMalicious script saved to DB, served to all usersOutput encode on render; strict CSP
Reflected XSSScript in URL echoed back in responseOutput encode; avoid echoing raw query params
DOM XSSClient-side JS writes attacker-controlled data to the DOMAvoid innerHTML; use DOMPurify or trusted types
CSRFForged cross-origin request rides victim's session cookieSameSite=Strict/Lax cookie; CSRF token or double-submit
ClickjackingVictim clicks UI element inside a hidden iframeContent-Security-Policy: frame-ancestors 'none'

DOM XSS: the dangerouslySetInnerHTML trap and DOMPurify fix

React's dangerouslySetInnerHTML bypasses its own escaping. Pass untrusted HTML through DOMPurify before setting it.

Sanitize user HTML with DOMPurifytsx
import DOMPurify from "dompurify";

// BAD — executes any script in userHtml
function UnsafeRichText({ userHtml }: { userHtml: string }) {
  return <div dangerouslySetInnerHTML={{ __html: userHtml }} />;
}

// GOOD — DOMPurify strips event handlers and script tags
function SafeRichText({ userHtml }: { userHtml: string }) {
  const clean = DOMPurify.sanitize(userHtml, {
    USE_PROFILES: { html: true },
  });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

DOMPurify.sanitize removes <script>, onerror=, javascript: hrefs, and similar sinks. USE_PROFILES: { html: true } allows safe block/inline elements while stripping executable content.

Layered XSS defense

  1. 1

    Output-encode on render

    Escape <, >, &, ", ' in any context where user data appears in HTML. Frameworks like React do this automatically for text nodes — the danger is only when you opt into raw HTML via dangerouslySetInnerHTML or innerHTML.

  2. 2

    Add a Content Security Policy

    A Content-Security-Policy header tells the browser which script origins are allowed. script-src 'self' blocks inline scripts and third-party injections even if encoding fails. Start in report-only mode with a report-uri endpoint to catch violations before enforcing.

  3. 3

    Block dangerous sinks with Trusted Types

    The Trusted Types API (Chromium-only) makes innerHTML, eval, and script URLs accept only pre-approved wrapper objects, not raw strings. Enforce via require-trusted-types-for 'script' in CSP. Angular and Closure support it natively.

CSRF and clickjacking defenses

  • **`SameSite=Strict`**: session cookie never sent on cross-site requests — strongest CSRF defense for same-domain apps.
  • **`SameSite=Lax`** (browser default since 2020): blocks cross-site POST but allows top-level GET navigations.
  • **CSRF tokens** (synchronizer token pattern) add a server-generated secret to forms; the server rejects requests missing it.
  • **Double-submit cookie**: echo a secret in cookie and request header; server validates they match — no session store needed.
  • **`frame-ancestors 'none'`** in CSP (or legacy `X-Frame-Options: DENY`) stops your page being embedded in an iframe, blocking clickjacking.

`SameSite=Lax` is not a complete CSRF defense

Lax blocks cross-site POST but allows cross-site GET. Any state-mutating GET endpoint is still vulnerable. Use Strict or pair Lax with a CSRF token. Also note: SameSite is a per-cookie attribute — it only applies if your API and front end share the same registrable domain (e.g., both on example.com).

Interview angle

Interviewers expect you to distinguish the three attacks by attack surface, not just name them. Cover why dangerouslySetInnerHTML is the React-specific XSS sink, why CSRF tokens matter even with SameSite, and why frame-ancestors in CSP supersedes X-Frame-Options.

Soundbite: "Encode output for XSS, set SameSite cookies for CSRF, and set frame-ancestors 'none' for clickjacking — three attacks, three headers."

Key terms

XSS (Cross-Site Scripting)
An injection attack where untrusted script executes in another user's browser under the victim site's origin.
CSRF (Cross-Site Request Forgery)
An attack that forges authenticated requests by exploiting the browser's automatic cookie attachment on cross-origin requests.
SameSite cookie
A cookie attribute (Strict/Lax/None) restricting when the browser includes the cookie on cross-site requests.
Content Security Policy (CSP)
An HTTP response header declaring allowed sources for scripts, styles, and frames; mitigates XSS and clickjacking.
Trusted Types
A browser API enforced via CSP that requires DOM sinks like innerHTML to accept typed wrapper objects, not raw strings.

Further reading

Search fearchitect

Jump to a topic, mode, or action.