Security patterns for TypeScript / Node.js: injection, XSS, SSRF, secrets, dep audit, SSR hydration risks, prototype pollution. Apply when reviewing or writing any code that handles untrusted input or external services.
SQL injection. Never build SQL with string concatenation. Use parameter binding:
// WRONG
await db.query(`SELECT * FROM users WHERE id = '${id}'`);
// RIGHT
await db.query('SELECT * FROM users WHERE id = $1', [id]);
For ORMs (Drizzle, Prisma, Kysely), audit any code that calls raw()
or $queryRaw.
Shell injection. Use spawn or execFile with an argv array, not
exec:
// WRONG
exec(`git log --author=${author}`);
// RIGHT
spawn('git', ['log', `--author=${author}`]);
dangerouslySetInnerHTML is
the only footgun — sanitize with DOMPurify.href, src) need scheme checks. Reject
javascript: URIs:
if (/^\s*javascript:/i.test(url)) throw new Error('bad scheme');
Server-side code that fetches URLs from user input must:
new URL(input) and inspect hostname.Use ssrf-req-filter or ipaddr.js rather than writing this by hand.
.env in dev, a secrets manager in prod.authorization,
cookie, set-cookie.git-secrets, gitleaks, or trufflehog in pre-commit hooks catches
accidental commits.
pnpm audit --prod # known CVEs
pnpm outdated # stale deps
pnpm dedupe # collapse duplicates
Object.assign({}, userInput) and lodash.merge can walk prototype
chains if __proto__ is set. Use:
const safe = structuredClone(userInput);
// or
Object.assign(Object.create(null), userInput);
Validate with Zod before merging — it strips unknown keys by default.
httpOnly: true, secure: true, sameSite: 'lax' (or strict).maxAge, refresh on activity.dangerouslySetInnerHTML on the server.sameSite=strict.Authorization headers, not cookies.alg header — reject alg: none and algorithm confusion.jose, @auth/core).for tool in node pnpm; do
command -v "$tool" >/dev/null && echo "ok: $tool" || echo "MISSING: $tool"
done
jose JWT library: https://github.com/panva/joseDOMPurify: https://github.com/cure53/DOMPurify