# Security reference ## HTTPS everywhere **Enforce HTTPS:** ```html ``` Avoid protocol-relative URLs (`//example.com/...`) — they're an HTTP-era pattern with no benefit on HTTPS-only sites and hide the actual scheme from reviewers. **HSTS Header:** ``` Strict-Transport-Security: max-age=31536000; includeSubDomains; preload ``` ## Content Security Policy (CSP) ```html ``` **CSP Header (recommended):** ``` Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123' https://trusted.com; style-src 'self' 'nonce-abc123'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; ``` **Using nonces for inline scripts:** ```html ``` ## Trusted Types (modern DOM-XSS defense) A strict CSP blocks loading untrusted *script files*, but it doesn't stop a string from reaching `innerHTML`, `eval`, or other DOM-XSS sinks. Trusted Types — Baseline across all major browsers since early 2026 — closes that hole by making sinks reject raw strings and accept only typed objects produced by a named policy. ``` Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default; ``` ```javascript // One central policy that does the sanitization const escape = trustedTypes.createPolicy('default', { createHTML: (s) => DOMPurify.sanitize(s, { RETURN_TRUSTED_TYPE: false }) }); // ❌ This now throws TypeError under enforcement element.innerHTML = userInput; // ✅ Goes through the policy element.innerHTML = escape.createHTML(userInput); ``` Roll out with `Content-Security-Policy-Report-Only` first to find every sink usage, then flip to enforcement. Keep the sanitizer current. Framework integration is not interchangeable: * **Angular** documents built-in sanitization plus the `angular` Trusted Types policy required for enforcement; lazy loading, JIT, upgrades, and explicit sanitizer bypasses need additional named policies. Follow [Angular's maintained security guide](https://angular.dev/best-practices/security#enforcing-trusted-types). * **React** does not sanitize `dangerouslySetInnerHTML`. Keep untrusted input out of it or pass content sanitized by an application-owned policy, and verify the deployed React version under report-only enforcement. Do not assume the framework creates `TrustedHTML`; see [React's security warning](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html). ## Subresource Integrity (SRI) for third-party scripts Pin every ` ``` `integrity` accepts space-separated hashes; include the next version's hash before rotating to avoid downtime. Generate with `openssl dgst -sha384 -binary file.js | openssl base64 -A`. SRI requires `crossorigin` and an `Access-Control-Allow-Origin` response header from the CDN. ## Security headers ``` # Prevent clickjacking — prefer CSP `frame-ancestors` (above); X-Frame-Options # is the legacy fallback for older browsers. X-Frame-Options: DENY # Prevent MIME type sniffing X-Content-Type-Options: nosniff # Do NOT send X-XSS-Protection. The legacy browser XSS auditor was deprecated # and removed (Chrome 78, Edge 17), and in some cases it introduced its own # vulnerabilities. Use a strict CSP + Trusted Types (below) instead. # Control referrer information Referrer-Policy: strict-origin-when-cross-origin # Permissions policy (formerly Feature-Policy) Permissions-Policy: geolocation=(), microphone=(), camera=() ``` ## No vulnerable libraries ```bash # Check for vulnerabilities npm audit yarn audit # Auto-fix when possible npm audit fix # Check specific package npm ls lodash ``` **Keep dependencies updated:** ```json // package.json { "scripts": { "audit": "npm audit --audit-level=moderate", "update": "npm update && npm audit fix" } } ``` **Known vulnerable patterns to avoid:** ```javascript // ❌ Recursive merges of untrusted input can pollute Object.prototype // via __proto__, constructor, or prototype keys. _.merge(target, userInput); // lodash <4.17.20 $.extend(true, {}, target, userInput); // jQuery deep extend Object.assign(target, ...userInputs); // safe by itself (shallow), but unsafe // when target IS Object.prototype-derived // and userInput contains __proto__ // ✅ For untrusted bags, use a null-prototype object so __proto__ is just a key const safe = Object.create(null); Object.assign(safe, userInput); // shallow, no recursion → safe by construction // ✅ For deep copies, structuredClone drops __proto__ and functions const deepSafe = structuredClone(userInput); // ✅ For deep merges, use a library that explicitly blocks dangerous keys // (e.g. lodash ≥4.17.21 _.mergeWith with a customizer, or deepmerge-ts). ``` ## Input sanitization ```javascript // ❌ XSS vulnerable element.innerHTML = userInput; document.write(userInput); // ✅ Safe text content element.textContent = userInput; // ✅ If HTML needed, sanitize import DOMPurify from 'dompurify'; element.innerHTML = DOMPurify.sanitize(userInput); ``` ## Secure cookies ```javascript // ❌ Insecure cookie document.cookie = "session=abc123"; // ✅ Secure cookie (server-side) Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/ ``` --- ## Sources - [MDN web security](https://developer.mozilla.org/en-US/docs/Web/Security) - [OWASP Content Security Policy Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) - [DOMPurify](https://github.com/cure53/DOMPurify)