JavaScript Security Checklist Cheat Sheet

A pocket checklist for safer JavaScript: treat input as hostile, keep secrets out of code, and spot risky patterns fast. You can also download the information as a printable cheat sheet:

Free Bonus: JavaScript Security Checklist Cheat Sheet

Get the JavaScript Security Checklist Cheat Sheet (PDF), a printable reference to XSS and textContent, eval, prototype pollution, input validation with zod, secrets and env vars, node:crypto, CSP, CORS, and safe child processes:

JavaScript Security Checklist Cheat Sheet

Want a guided path instead of a reference? Start learning at realjs.com.

Stop XSS

  • Cross-site scripting (XSS) runs attacker code in your page
  • Insert text with textContent, not markup
  • Browser-only: the DOM snippets here can’t run in Node.js
Language: JavaScript Filename: Text, Not HTML
const name = "<img src=x onerror=hack()>";

el.innerHTML = name;     // Runs the handler!
el.textContent = name;   // Shown as text
Language: JavaScript Filename: Escape Before Building HTML
const esc = (s) => s.replace(
  /[&<>"']/g,
  (c) => `&#${c.charCodeAt(0)};`,
);
esc(`<b class="x">`);
// "&#60;b class=&#34;x&#34;&#62;"
Language: JavaScript Filename: Sanitize Untrusted HTML
// Not Baseline yet: feature-check first
if ("setHTML" in Element.prototype) {
  el.setHTML(untrustedHtml);
}
// Otherwise use DOMPurify.sanitize()

Avoid eval

  • eval and new Function run any string as code
  • Never feed them user input, URLs, or JSON
Language: JavaScript Filename: Parse Data Instead of Running It
eval("1 + 1");             // 2, risky
new Function("return 1")(); // 1, risky
JSON.parse('{"a":1}');     // { a: 1 }

Prototype Pollution

  • __proto__ keys in JSON can reach Object.prototype
  • A naive recursive merge then adds properties to every object
Language: JavaScript Filename: The Attack
function merge(target, src) {
  for (const k in src) {
    if (typeof src[k] === "object") {
      merge(target[k] ??= {}, src[k]);
    } else target[k] = src[k];
  }
}
merge({}, JSON.parse(
  '{"__proto__":{"admin":true}}',
));
({}).admin;               // true
Language: JavaScript Filename: Safer Containers
const safe = Object.create(null);
safe.toString;            // undefined

const m = new Map([["__proto__", 1]]);
m.get("__proto__");       // 1
m.get("toString");        // undefined
Language: JavaScript Filename: Check Own Keys Only
const o = { a: 1 };
"toString" in o;              // true
Object.hasOwn(o, "toString"); // false
Object.hasOwn(o, "a");        // true
Language: JavaScript Filename: Freeze the Prototype
"use strict";
Object.freeze(Object.prototype);
Object.prototype.x = 1;
// TypeError: Cannot add property x,
// object is not extensible

Validate Input

  • Validate at every boundary: requests, files, env vars
  • Parse into a trusted shape, then use only that
  • Run npm install zod for schemas
Language: JavaScript Filename: Parse With a Schema
import { z } from "zod";

const User = z.object({
  email: z.email(),
  age: z.coerce.number().int().min(0),
});
User.parse({ email: "[email protected]", age: "42" });
// { email: '[email protected]', age: 42 }
Language: JavaScript Filename: Handle Bad Input
const r = User.safeParse({
  email: "nope",
  age: -1,
});
r.success;                // false
r.error.issues.map((i) => i.path[0]);
// [ 'email', 'age' ]

Secrets and Env Vars

  • Never hardcode keys, and never commit .env
  • Anything in browser bundles is public
  • A leaked key stays in Git history: rotate it
Language: Shell Filename: Load a Local .env File
$ echo "API_KEY=sk-test" > .env
$ echo ".env" >> .gitignore
$ node --env-file=.env app.js
Language: JavaScript Filename: Fail Fast When Missing
const key = process.env.API_KEY;
if (!key) {
  throw new Error("API_KEY missing");
}

Crypto

  • Never use Math.random() for tokens
  • Never roll your own crypto or use MD5 for passwords
  • Store the salt with each hash; compare in constant time
  • Node’s default scrypt cost (N=16384) is low: OWASP advises N=2**17, r=8, p=1 (needs a higher maxmem)
Language: JavaScript Filename: Random IDs and Tokens
import { randomUUID, randomBytes }
  from "node:crypto";
randomUUID();            // Unique ID
randomBytes(32).toString("base64url");
// Browser: crypto.randomUUID()
Language: JavaScript Filename: Hash Passwords With scrypt
import { scrypt, randomBytes,
  timingSafeEqual } from "node:crypto";
import { promisify } from "node:util";
const kdf = promisify(scrypt);

const salt = randomBytes(16);
const hash = await kdf("pw", salt, 64);
const again = await kdf("pw", salt, 64);
timingSafeEqual(hash, again);  // true
// Throws if the lengths differ

Run Commands Safely

  • exec runs a shell string, so metacharacters bite
  • execFile passes arguments as an array, with no shell
Language: JavaScript Filename: Avoid the Shell
import { execFile, exec }
  from "node:child_process";
const arg = "a; echo pwned";

exec(`echo ${arg}`, show);
// "a\npwned\n" (injected!)
execFile("echo", [arg], show);
// "a; echo pwned\n"

Browser Defenses

  • Headers are enforced by the browser, not by Node.js
  • Test them with DevTools: the Network tab shows each header
  • CORS only relaxes browsers; it never blocks other clients
Language: JavaScript Filename: Content Security Policy
res.setHeader(
  "Content-Security-Policy",
  "default-src 'self'; base-uri 'self'; "
    + "frame-ancestors 'none'",
);
// Blocks inline scripts, other origins,
// and framing by other sites
Language: JavaScript Filename: CORS Allowlist
const ALLOWED = new Set([
  "https://app.example.com",
]);
const origin = req.headers.origin;
res.setHeader("Vary", "Origin");
if (ALLOWED.has(origin)) {
  res.setHeader(
    "Access-Control-Allow-Origin", origin,
  );
}

Dependencies

  • Dependencies run with your privileges: see the RealJS npm supply chain cheat sheet
  • Run npm audit and commit your lockfile

You can download this information as a printable cheat sheet:

Free Bonus: JavaScript Security Checklist Cheat Sheet

Get the JavaScript Security Checklist Cheat Sheet (PDF), a printable reference to XSS and textContent, eval, prototype pollution, input validation with zod, secrets and env vars, node:crypto, CSP, CORS, and safe child processes:

JavaScript Security Checklist Cheat Sheet