THN Interview Prep

XSS & Content-Security-Policy

Cross-Site Scripting (XSS) occurs when an application receives untrusted data and sends it to the web browser without proper validation or escaping, allowing malicious client-side scripts to run in the user's browser context.


Core details

TypeOriginExecution ContextPrimary Defense
Stored XSSDatabase, persistent storageRendered to arbitrary usersContext-aware output encoding, HTML sanitization
Reflected XSSHTTP query parameters, headersEchoed back immediately in responseContext-aware output encoding, validation
DOM-based XSSClient-side source (location.hash)Dynamic write to unsafe DOM sinkAvoid dangerous sinks, sanitize, Trusted Types

Dangerous DOM Sinks

A DOM sink is a browser API that can compile or execute strings passed to it.

  • HTML Sinks: element.innerHTML, element.outerHTML, document.write
  • Execution Sinks: eval(), setTimeout(string), Function()
  • Navigation Sinks: location.href, window.open (vulnerable to javascript: scheme)

[!IMPORTANT] HTML escaping (e.g., converting < to &lt;) is only safe when rendering content inside standard HTML tags (e.g., <div>userText</div>). If you are placing data inside attribute values (e.g., <input value="userData">), CSS styles, or script blocks, you must use context-specific escaping.


Understanding

Content Security Policy (CSP)

CSP is a browser-enforced header that restricts where resources (scripts, stylesheets, images) can be loaded and executed from.

  • Legacy CSP: Allowlist-based (e.g., script-src 'self' apis.google.com). Vulnerable to bypasses via JSONP endpoints on allowlisted domains.
  • Strict CSP: Nonce or hash-based. The server generates a unique, single-use random value (nonce) for every page render and attaches it to the header: Content-Security-Policy: script-src 'nonce-rAnd0m123' 'strict-dynamic' https:; Only scripts with a matching nonce attribute are executed by the browser.

Trusted Types

Trusted Types lock down the DOM sinks at the browser engine level. Instead of accepting raw strings, sinks like innerHTML only accept certified TrustedHTML objects created by pre-approved sanitization policies.


Senior understanding

When rendering links provided by users (e.g., user profiles or markdown links), you must validate the URL scheme to prevent javascript: and data: execution:

import React from 'react';

function sanitizeUrl(url: string): string {
  const trimmed = url.trim().toLowerCase();
  
  if (!trimmed) {
    return 'about:blank';
  }

  // 1. Block dangerous schemes
  if (trimmed.startsWith('javascript:') || trimmed.startsWith('data:') || trimmed.startsWith('vbscript:')) {
    return 'about:blank';
  }

  // 2. Validate protocols (http, https, mailto, tel)
  if (/^(https?|mailto|tel):/i.test(trimmed)) {
    return url;
  }

  // Fallback to relative path or safe default
  return '#';
}

interface UserLinkProps {
  href: string;
  label: string;
}

export function UserLink({ href, label }: UserLinkProps) {
  const safeHref = sanitizeUrl(href);
  
  return (
    <a 
      href={safeHref} 
      target="_blank" 
      rel="noopener noreferrer" // Prevents tab-nabbing / reverse tabnabbing
    >
      {label}
    </a>
  );
}

Diagram

Loading diagram…

See also

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page