Skip to content

09 - Security ​


What: Controls when cookies are sent with cross-site requests, defending against CSRF attacks.

SameSite=Strict
  Cookie is ONLY sent for same-site requests.
  Clicking a link from email to your site → cookie NOT sent → user must re-login.
  Most secure but worst UX.

SameSite=Lax (DEFAULT in modern browsers)
  Cookie sent for same-site requests + top-level navigations (clicking links).
  NOT sent for cross-site POST, iframe, AJAX, image loads.
  Good balance of security + UX.

SameSite=None; Secure
  Cookie sent with ALL requests (including cross-site).
  MUST have Secure flag (HTTPS only).
  Required for: third-party cookies, cross-origin API calls with cookies,
  embedded checkout SDKs in iframes.

Example — checkout SDK in iframe:

Set-Cookie: session=abc123; SameSite=None; Secure; HttpOnly
// SameSite=None needed because the SDK runs in an iframe on a merchant's domain
// (different origin = cross-site)

2. CSRF vs XSS Mitigation ​

CSRF (Cross-Site Request Forgery) ​

What: Attacker tricks an authenticated user into submitting a request to your API.

User is logged into mybank.com (has session cookie).
User visits evil.com which has:
  <form action="https://mybank.com/api/transfer" method="POST">
    <input name="to" value="attacker-account">
    <input name="amount" value="10000">
  </form>
  <script>document.forms[0].submit();</script>
// Browser sends the form WITH the session cookie → transfer happens!

Mitigations:

  1. SameSite cookies (Lax/Strict) — cookie not sent from cross-site form
  2. CSRF tokens — unique token per session/form, server validates it
  3. Check Origin/Referer headers — reject requests from unexpected origins
  4. Double-submit cookie pattern — CSRF token in both cookie and request body
tsx
// CSRF token pattern
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;

fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken, // server validates this matches session
  },
  body: JSON.stringify({ to: 'account', amount: 100 }),
});

XSS (Cross-Site Scripting) ​

What: Attacker injects malicious script into your page, which runs with the user's privileges.

Types:

  • Stored XSS: Malicious script saved in DB, served to all users (<script>steal(cookies)</script> in a comment field)
  • Reflected XSS: Malicious script in URL, reflected in response (/search?q=<script>alert('xss')</script>)
  • DOM XSS: Script manipulates DOM directly via client-side JS (element.innerHTML = userInput)

Mitigations:

  1. Escape output — React does this by default (JSX escapes strings)
  2. Never use dangerouslySetInnerHTML — or sanitize with DOMPurify first
  3. Content Security Policy (CSP) — blocks inline scripts
  4. HttpOnly cookies — JS can't access auth cookies
  5. Input validation — validate and sanitize on server
tsx
// React is safe by default
<p>{userInput}</p> // userInput = "<script>alert('xss')</script>"
// Renders as text, not executed

// DANGEROUS:
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // XSS!

// Safe with sanitization:
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />

3. Content Security Policy (CSP) ​

What: HTTP header that tells the browser which sources of content are allowed. Blocks inline scripts, unauthorized script sources, and other injection vectors.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://images.example.com;
  connect-src 'self' https://api.example.com;
  frame-src https://checkout.example.com;
  font-src 'self' https://fonts.gstatic.com;
  object-src 'none';
  base-uri 'self';

Key directives:

DirectiveControls
script-srcJavaScript sources
style-srcCSS sources
connect-srcfetch/XHR/WebSocket targets
img-srcImage sources
frame-srciframe sources
default-srcFallback for unspecified directives

Nonce-based CSP (for inline scripts):

html
<!-- Server generates unique nonce per request -->
Content-Security-Policy: script-src 'nonce-abc123';

<script nonce="abc123">
  // This runs because nonce matches
</script>

<script>
  // This is BLOCKED — no matching nonce
</script>

Report-only mode (for testing):

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
// Doesn't block anything, just logs violations — use during rollout

4. Trusted Types ​

What: A browser API that prevents DOM XSS by requiring typed objects instead of raw strings for dangerous DOM sinks.

DOM sinks (dangerous operations):

  • element.innerHTML = string
  • document.write(string)
  • eval(string)
  • script.src = string
  • a.href = string

Without Trusted Types:

js
element.innerHTML = userInput; // potential XSS — browser allows it

With Trusted Types:

js
// Enable via CSP
// Content-Security-Policy: require-trusted-types-for 'script'

element.innerHTML = userInput; // BLOCKED — TypeError: not a TrustedHTML

// Must create a policy
const policy = trustedTypes.createPolicy('sanitize', {
  createHTML: (input) => DOMPurify.sanitize(input),
});

element.innerHTML = policy.createHTML(userInput); // Allowed — sanitized

Why it matters: Prevents ALL DOM XSS at the browser level, not just the ones you remember to sanitize.


5. DOM Clobbering ​

What: An attack where HTML elements with id or name attributes overwrite global JavaScript variables, because browsers make elements accessible via window.elementId and document.elementName.

html
<!-- Attacker injects this HTML (via stored XSS, user-generated content, etc.) -->
<img id="config">
<a id="config" name="config" href="https://evil.com/config.json">

<!-- Your JavaScript: -->
<script>
  // You expect window.config to be your app config
  // But it's now the DOM element!
  console.log(window.config); // HTMLImageElement or HTMLCollection
  console.log(config.href);   // "https://evil.com/config.json"

  // If your code does:
  fetch(config.href); // fetches from attacker's server!
</script>

Mitigations:

  1. Don't rely on global variables — use const, modules, closures
  2. Use Object.freeze() on config objects
  3. Sanitize user HTML (DOMPurify removes clobbering vectors)
  4. Use CSP to prevent unauthorized script execution
  5. Always check types: if (typeof config === 'object' && !(config instanceof HTMLElement))

6. Prototype Pollution ​

What: An attacker modifies Object.prototype, affecting ALL objects in the application.

js
// Vulnerable merge function
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object') {
      target[key] = merge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Attacker sends this JSON:
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, malicious);

// Now EVERY object has isAdmin:
const user = {};
console.log(user.isAdmin); // true! Prototype is polluted.

Real impact:

tsx
// In your code somewhere:
if (user.isAdmin) {
  showAdminPanel(); // attacker gets admin access
}

Mitigations:

  1. Check for __proto__, constructor, prototype keys:
    js
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
  2. Use Object.create(null) for dictionaries (no prototype chain)
  3. Use Map instead of plain objects for dynamic keys
  4. Freeze prototype: Object.freeze(Object.prototype) (aggressive)
  5. Use safe merge libraries (lodash's merge is safe in recent versions)
  6. Validate JSON input on the server before processing

Frontend interview preparation reference.