Skip to content

JavaScript Security — XSS, CSRF, CSP, and Frontend Hardening

DodaTech Updated 2026-06-29 5 min read

In this tutorial, you will learn about JavaScript Security. We cover key concepts, practical examples, and best practices to help you master this topic.

Frontend security is often overlooked — but JavaScript running in the browser is your first line of defense against attacks. XSS, CSRF, and data leaks are common vulnerabilities that every web developer must understand.

DodaTech builds security tools, so understanding these attack vectors and mitigations is essential for our platform and for anyone building web applications.

What You'll Learn

  • Cross-Site Scripting (XSS) — types and prevention
  • Cross-Site Request Forgery (CSRF)
  • Content Security Policy (CSP)
  • Secure cookie configuration
  • Trusted types
  • Subresource Integrity (SRI)
  • Sanitization libraries (DOMPurify)
  • Browser security features

Cross-Site Scripting (XSS)

Reflected XSS

// VULNERABLE: URL parameter rendered directly
// URL: ?q=<script>alert('xss')</script>
const searchTerm = new URLSearchParams(location.search).get("q");
document.getElementById("results").innerHTML =
  `Search results for: ${searchTerm}`; // XSS!

// SAFE: textContent instead of innerHTML
document.getElementById("results").textContent =
  `Search results for: ${searchTerm}`;

// SAFE: use DOMPurify if HTML is needed
import DOMPurify from "dompurify";
document.getElementById("results").innerHTML =
  DOMPurify.sanitize(`Search results for: ${searchTerm}`);

Stored XSS

// VULNERABLE: user-generated content rendered as HTML
// User posts comment: <img src=x onerror="fetch('/steal?cookie='+document.cookie)">
function renderComment(comment) {
  // XSS!
  return `<div class="comment">${comment.body}</div>`;
}

// SAFE: sanitize on render (defense in depth — sanitize on input too)
function renderCommentSafe(comment) {
  const div = document.createElement("div");
  div.className = "comment";
  div.textContent = comment.body;
  return div.outerHTML;
}

DOM-based XSS

// VULNERABLE: location.hash
// URL: page.html#<img src=x onerror=alert(1)>
const hash = location.hash.slice(1);
document.body.innerHTML = hash; // XSS!

// VULNERABLE: eval of JSON-like data
const data = JSON.parse(someUntrustedJSON);
// If data.name contains <script>...
document.write(`Hello ${data.name}`);

// SAFE PROXY for common XSS sinks:
function safeSetHTML(element, html) {
  element.innerHTML = DOMPurify.sanitize(html);
}

// Sinks to watch:
// - innerHTML, outerHTML
// - document.write, document.writeln
// - eval, setTimeout(string), Function(string)
// - insertAdjacentHTML, range.createContextualFragment
// - srcdoc attribute on iframes

Content Security Policy (CSP)

# HTTP header
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

# Meta tag equivalent
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

CSP Directives

// Common CSP directives:
const CSP = [
  // Scripts: only from same origin
  "script-src 'self'",
  // Allow specific CDN
  "script-src 'self' https://cdn.example.com",
  // Nonce-based (recommended over 'unsafe-inline')
  // script-src 'nonce-{random}'
  // Then: <script nonce="{random}">...</script>

  // Styles
  "style-src 'self' 'unsafe-inline'",

  // Images
  "img-src 'self' data: https:",
  // Connections
  "connect-src 'self' https://api.example.com",
  // Frames
  "frame-ancestors 'none'",
].join("; ");

CSP with Nonce

<!-- Server generates random nonce per request -->
<script nonce="xyz123">
  // This script runs
</script>
<script>
  // This script BLOCKED by CSP
</script>
<!-- style with nonce -->
<style nonce="xyz123">
  .dangerous { color: red; }
</style>

CSRF Protection

// VULNERABLE: POST without CSRF token
// <form action="https://bank.com/transfer" method="POST">
//   <input name="amount" value="1000">
//   <input name="to" value="attacker">
// </form>
// <script>document.forms[0].submit();</script>

// PROTECTION 1: CSRF Token
// Server includes token in page or via API
const csrfToken = document.querySelector("[name=csrf-token]").content;

fetch("/api/transfer", {
  method: "POST",
  headers: {
    "X-CSRF-Token": csrfToken,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ amount: 100, to: "alice" })
});

// PROTECTION 2: SameSite Cookie
// Set-Cookie: session=abc123; SameSite=Strict; HttpOnly; Secure

// PROTECTION 3: Custom headers
// CORS preflight blocks simple cross-origin requests with custom headers
fetch("/api/transfer", {
  method: "POST",
  headers: { "X-Requested-With": "XMLHttpRequest" }
});

Secure Cookies

// JavaScript cookie setting (document.cookie)
document.cookie = "session=abc123; Secure; SameSite=Strict; Path=/; HttpOnly";

// Flags explained:
// Secure: only sent over HTTPS
// HttpOnly: inaccessible from JavaScript (prevents XSS from stealing)
// SameSite=Strict: not sent on cross-origin requests
// SameSite=Lax: sent on top-level navigations (GET only)
// SameSite=None: sent on all requests (requires Secure)

// Reading cookies (HttpOnly cookies won't appear)
function getCookie(name) {
  const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
  return match ? decodeURIComponent(match[2]) : null;
}

Trusted Types (Advanced)

Trusted Types is a browser API that prevents DOM XSS by restricting dangerous sinks:

// Enforce via CSP:
// require-trusted-types-for 'script'

// With Trusted Types enabled:
// element.innerHTML = userInput;  // TypeError!
// Instead:
element.innerHTML = new TrustedHTML(DOMPurify.sanitize(userInput));

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

element.innerHTML = sanitizePolicy.createHTML(userInput);

Subresource Integrity (SRI)

<!-- Ensure CDN script hasn't been tampered with -->
<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-abc123def456..."
  crossorigin="anonymous"
></script>

<!-- Generate hash: -->
<!-- cat library.js | openssl dgst -sha384 -binary | openssl base64 -A -->

Sanitization with DOMPurify

import DOMPurify from "dompurify";

// Basic sanitization
const clean = DOMPurify.sanitize(dirtyHTML);

// Allow specific tags
const cleanWithImages = DOMPurify.sanitize(dirtyHTML, {
  ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "img"],
  ALLOWED_ATTR: ["href", "src", "alt", "title"]
});

// Remove all HTML
const justText = DOMPurify.sanitize(dirtyHTML, {
  ALLOWED_TAGS: []
});

Practice Questions

  1. Implement a function that safely renders user-generated HTML (using DOMPurify).

  2. Write a CSP header generator that creates a strict policy from a configuration object.

  3. Implement a CSRF token utility that fetches a token from the server and includes it in all fetch requests.

  4. Write a function that validates and sanitizes file names before upload (prevent path traversal).

  5. Create a clickjacking prevention technique using the frame-ancestors CSP directive and X-Frame-Options.

Challenge: Secure Form Handler

Build a class that handles form submission with all security protections:

  • CSRF token injection
  • Input validation and sanitization
  • Rate limit awareness (disable submit while pending)
  • Content-Type enforcement (reject unexpected types)
  • XSS prevention in error display
  • Logging without exposing sensitive data

This is the exact pattern DodaTech uses in its customer-facing forms for configuring security scans and Compliance policies.

Real-World Task: Security Headers Middleware

Write a middleware function (Express-style) that sets all security headers:

function securityHeaders(req, res, next) {
  // CSP
  res.setHeader("Content-Security-Policy", "...");
  // Prevent MIME type sniffing
  res.setHeader("X-Content-Type-Options", "nosniff");
  // Prevent clickjacking
  res.setHeader("X-Frame-Options", "DENY");
  // Enable XSS filter (legacy)
  res.setHeader("X-XSS-Protection", "0"); // Disabled, use CSP instead
  // HSTS
  res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
  // Referrer policy
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
  // Permissions policy
  res.setHeader("Permissions-Policy", "camera=(), microphone=()");
  next();
}

This is the exact set of headers DodaTech applies to all web responses to protect customers from common web attacks.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro