· 8 min read
Beyond the Basics: Advanced JavaScript Security Techniques for 2024
A practical, in-depth guide to advanced JavaScript security for 2024: cutting-edge sanitization techniques, runtime protections, supply-chain hardening, and secure-coding patterns that go beyond the usual recommendations.
Introduction
Modern JavaScript applications are more powerful - and more exposed - than ever. Single-page apps, rich client-side logic, WebAssembly, service workers, and a sprawling npm ecosystem offer incredible capabilities but expand the attack surface. This article goes beyond the basics (no eval, escape your output) to present advanced, practical techniques you can adopt in 2024 to harden your JavaScript apps.
We’ll cover advanced sanitization strategies, runtime controls, supply-chain mitigations, secure-coding idioms, and testing/automation patterns - with code examples and references so you can apply each technique right away.
Threat model and principles
Be explicit about what you defend against. Typical web threats include: reflected and stored XSS (including DOM-based), open-redirects, prototype pollution, supply-chain compromise (malicious or tampered packages), insecure use of third-party scripts, logic manipulation (client-side tampering), and data exfiltration.
Core principles to follow:
- Defense-in-depth: combine server-side validation, client-side runtime protections, and content restrictions.
- Least privilege: restrict capabilities (what scripts, frames, APIs can do).
- Fail-closed: if sanitization or validation fails, show a safe fallback, not raw data.
- Assume breach: prepare detection, logging, and fast rollback.
Advanced sanitization: the new baseline
Why advance beyond simple libraries? Many apps still rely on naive escaping or allowlists that miss edge cases like SVG/MathML, CSS/URL-based exfiltration, or attribute-based XSS. Here are modern, layered approaches.
- Prefer a native HTML sanitization API where available
The [HTML Sanitizer API] (browser support varies as of 2024) standardizes a secure interface for sanitizing HTML. When supported, prefer it over custom ad-hoc sanitizers because it’s designed by browser vendors to avoid edge-case bypasses and handle DOM complexity consistently. Check runtime and feature-detect prior to use.
- MDN: HTML Sanitizer API: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
- Use Trusted Types + a vetted sanitizer as a sink
Trusted Types is a powerful browser mechanism that lets you require that dangerous sinks (innerHTML, insertAdjacentHTML, document.write, etc.) can only be assigned TrustedType objects. Combine Trusted Types with a proven sanitizer like [DOMPurify] to ensure only safe content reaches HTML sinks.
Example pattern (with graceful fallback):
// Feature detect
const supportsTT = typeof window.trustedTypes !== 'undefined';
if (supportsTT) {
// Create a policy that delegates to DOMPurify
trustedTypes.createPolicy('sanitize-with-dompurify', {
createHTML: input => DOMPurify.sanitize(input),
});
}
// Usage: only use the policy's output for sinks
const safeHtml = supportsTT
? trustedTypes.getPolicy('sanitize-with-dompurify').createHTML(userInput)
: DOMPurify.sanitize(userInput);
containerElement.innerHTML = safeHtml; // safe in either branch
- DOMPurify: https://github.com/cure53/DOMPurify
- Trusted Types MDN: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types
- Sanitize SVGs and CSS separately
SVG contains scripts, external references, and event attributes. If you accept user-uploaded SVGs, process them server-side or sanitize them with a library that explicitly handles SVG (DOMPurify has SVG support but configure it tightly). Likewise, CSS in style attributes or style tags can contain url() and expression-like constructs. Consider stripping inline styles entirely unless absolutely needed.
- Validate and normalize URLs before injecting into attributes
Never blindly place a user-provided string into href/src/srcset. Instead, parse with the URL constructor and allow only whitelisted schemes.
function isSafeUrl(raw) {
try {
const u = new URL(raw, location.href);
return ['http:', 'https:', 'mailto:'].includes(u.protocol);
} catch (e) {
return false;
}
}
if (isSafeUrl(userHref)) element.setAttribute('href', userHref);
- Use output encoding for non-HTML contexts
For JSON-in-JS templates, CSS, JS, and URL contexts, apply context-appropriate escaping rather than HTML sanitization. For example, when inserting data into a JavaScript context inside a script tag on server-side render, use JSON.stringify to safely serialize values.
DOM-based XSS - treat DOM sinks as first-class
DOM-based XSS occurs when attacker-controlled data from the DOM (location.hash, location.search, postMessage, storage, etc.) feeds dangerous sinks. Make an inventory of client-side sinks in your app (innerHTML, srcdoc, eval, setAttribute for event handlers, iframe srcdoc, etc.) and systematically protect them:
- Prefer textContent or node-based construction over innerHTML.
- Use Trusted Types to protect sinks.
- Sanitize or validate any data read from window.location / messages / storage.
Prototype pollution and object safety
Prototype pollution is a high-impact vulnerability where attacker-controlled object keys set properties on Object.prototype, changing app behavior.
Mitigation patterns:
- When merging objects (e.g., config, deep-merge), reject dangerous keys like ‘proto’, ‘constructor’, ‘prototype’.
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
continue;
target[key] = source[key];
}
return target;
}
- Use Object.create(null) for pure dictionaries that should not inherit from Object.prototype.
- Prefer libraries that explicitly guard against prototype pollution and audit deep-merge logic.
Runtime protections and browser features
- Content Security Policy (CSP) and Report-Only
CSP remains a cornerstone of runtime defense. Modern best practices for 2024 include:
Use strict policies: disallow ‘unsafe-inline’ and ‘unsafe-eval’ where feasible; prefer nonces or script hashes.
Combine CSP with Subresource Integrity (SRI) for CDN-hosted scripts.
Deploy CSP in report-only mode initially to learn and iterate, then enforce.
MDN CSP reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Subresource Integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
- Trusted Types (again) - enforce via CSP
You can require Trusted Types via a CSP directive (trusted-types) so that sinks reject plain strings. This is a powerful defense against XSS, especially when you migrate incrementally (report only first).
- Subresource Integrity for third-party scripts
SRI verifies that a fetched script or stylesheet matches a known hash. Use it for vendor scripts where you control updates, and prefer hosting critical scripts with integrity checks.
- Permissions Policy (Feature Policy), COOP/COEP, and Cross-Origin Isolation
Use Permissions Policy to disable features you don’t need (geolocation, camera, payment, etc.) and to limit cross-origin embedding. If you need high-resolution timers or SharedArrayBuffer for performance or WebAssembly use-cases, enable cross-origin isolation with COOP/COEP headers.
- Cookies and same-site controls
Set cookies with Secure, HttpOnly, and SameSite=strict (or lax where appropriate) to mitigate CSRF and cookie theft.
Web Crypto and random
Do not implement custom crypto. Use the Web Crypto API for client-side cryptography (SubtleCrypto) and crypto.getRandomValues for secure randomness.
- Web Crypto MDN: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
Client-side auth: consider WebAuthn (FIDO2) and Passkeys to move away from passwords and mitigate phishing.
Service workers and CSP
Service workers run powerful code with broad control over network and caching. Protect them by:
- Only registering service workers from origins you control.
- Carefully validate messages (postMessage) sent to the worker.
- Avoid dynamic import of worker scripts from untrusted origins.
- Use strict CSP to control which origins scripts can be loaded from.
WebAssembly (Wasm)
Wasm modules are powerful but can bring new risks (supply chain, side channels). Best practices:
- Validate and SRI-check remote wasm modules.
- Run untrusted Wasm in isolated origins and avoid granting extra privileges.
- Be mindful of Spectre-style side channels; follow browser guidance on cross-origin isolation when necessary.
Supply-chain and dependency security
In 2024, a major class of JavaScript incidents arises from compromised dependencies or malicious packages. Strengthen your supply chain with these measures:
- Pin exact versions in lockfiles and use reproducible installs (npm ci), and CI checks that validate lockfile integrity.
- Use automated SCA tools (Snyk, GitHub Dependabot, npm audit, etc.) but don’t rely on one tool alone.
- Run static analysis (CodeQL, Semgrep) to detect high-risk patterns in dependencies and your code.
- Use sigstore / in-toto / SLSA principles for artifact signing and provenance where possible.
- Avoid running
postinstall
scripts from packages unless necessary; evaluate risks of install scripts.
References:
- Sigstore: https://sigstore.dev
- SLSA: https://slsa.dev
- Semgrep: https://semgrep.dev
Static analysis, fuzzing, and testing
- SAST: Integrate tools such as ESLint security rules, CodeQL, or commercial SAST into CI to catch anti-patterns early.
- DAST: Run dynamic scanners against your running app to detect XSS, open redirects, insecure headers, etc.
- Fuzzing: For serialization/parsing endpoints, use fuzzers to discover edge-case parsing issues.
- Unit tests: cover sanitization code paths and explicitly assert that malicious payloads are rejected.
Example ESLint and tighter rules
Enable security-focused ESLint plugins and custom rules:
- eslint-plugin-security
- eslint-plugin-no-unsanitized (to prevent unsanitized innerHTML usage)
CI example: run lint, unit tests, SCA, and a security regression test on every PR.
Operational practices
- Use layered logging that records suspicious inputs and CSP reports. CSP reports via report-uri/report-to are invaluable for tuning policies.
- Establish a rapid rollback plan for dependencies and a patching cadence.
- Rotate secrets and avoid shipping credentials in client-side bundles. Use short-lived tokens and server-side checks.
Checklist: Practical steps you can implement this week
- Inventory all DOM sinks in your app (innerHTML, insertAdjacentHTML, iframe srcdoc, eval, Function, setAttribute event handlers).
- Deploy or prototype Trusted Types + DOMPurify for your app; start with report-only mode.
- Tighten or create a CSP with report-only first; iterate until it is stable, then enforce.
- Add SRI for all third-party scripts you control.
- Audit dependencies for postinstall scripts, recent changes, and high-risk transitive packages.
- Add simple URL validation for any user-controlled links; deny suspicious schemes.
- Introduce prototype-pollution guards where you merge external objects.
- Add security-focused static analysis (CodeQL/Semgrep) and run it in CI.
Tooling and libraries (recommended)
- DOMPurify: robust HTML sanitization: https://github.com/cure53/DOMPurify
- Trusted Types: use via browser APIs and enforce via CSP: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types
- Snyk / Dependabot / npm audit: supply-chain scanning
- CodeQL / Semgrep: static analysis
- Sigstore & SLSA: artifact provenance and signing
Further reading
- OWASP XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Prevention_Cheat_Sheet.html
- MDN Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Subresource Integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
Closing thoughts
Security is not a single control; it’s a program. In 2024, effective JavaScript security means combining modern browser defenses (CSP, Trusted Types, COOP/COEP), careful sanitization (trusted sanitizers, HTML Sanitizer API where available), rigorous supply-chain practices (signing, pinning, scanning), and secure coding patterns (prototype-pollution awareness, proper URL validation, avoiding dangerous sinks). Start small (inventory and CSP report-only) and iterate toward enforcement. Over time, these layered defenses dramatically reduce the likelihood and impact of client-side attacks.
References
- DOMPurify GitHub: https://github.com/cure53/DOMPurify
- Trusted Types (MDN): https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types
- HTML Sanitizer API (MDN): https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
- CSP (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Subresource Integrity (MDN): https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
- OWASP XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Prevention_Cheat_Sheet.html
- Sigstore: https://sigstore.dev
- SLSA: https://slsa.dev
- Semgrep: https://semgrep.dev