import type { DevToolbarRule } from "./types.ts"; import { createIssue } from "./helpers.ts"; const SECRET_KEY = /pass(word)?|token|secret|api[-_]?key|authorization|session/i; export const securityRules: DevToolbarRule[] = [ { id: "security/page", category: "security", defaultSeverity: "warning", description: "Checks development-visible security mistakes.", run: ({ document, window, url, root }) => { const issues = []; for (const [key] of url.searchParams) { if (SECRET_KEY.test(key)) { issues.push( createIssue({ ruleId: "security/secret-query", category: "security", severity: "error", title: "Potential secret appears in URL", message: `The query parameter “${key}” may contain sensitive information.`, recommendation: "Send secrets in a secure request body or authorization header, not a URL.", }), ); } } for (const form of root.querySelectorAll('form[action^="http://"]')) { issues.push( createIssue({ ruleId: "security/insecure-form", category: "security", severity: "error", title: "Form submits over HTTP", message: "Form values may be transmitted without transport encryption.", element: form, recommendation: "Submit to an HTTPS endpoint.", }), ); } for (const form of root.querySelectorAll("form")) { const method = (form.method || "get").toUpperCase(); if ( !["GET", "HEAD"].includes(method) && !form.querySelector('input[name="wrn-csrf"], input[name="_csrf"]') ) { issues.push( createIssue({ ruleId: "security/missing-csrf", category: "security", severity: "error", title: "State-changing form has no CSRF token", message: `${method} form does not contain a recognized CSRF field.`, element: form, recommendation: "Enable WRNexus CSRF middleware and use the generated form token.", }), ); } } for (const script of root.querySelectorAll("script:not([src])")) { if (script.type === "application/json" || script.hasAttribute("nonce")) continue; issues.push( createIssue({ ruleId: "security/inline-script", category: "security", severity: "warning", title: "Inline script has no CSP nonce", message: "A strict Content Security Policy will block this inline script.", element: script, recommendation: "Move code into a client module or attach the request CSP nonce.", }), ); } for (const element of root.querySelectorAll("[style]")) { issues.push( createIssue({ ruleId: "security/inline-style", category: "security", severity: "suggestion", title: "Inline style weakens strict CSP", message: "The element uses a style attribute.", element, recommendation: "Prefer extracted CSS classes or a nonce/hash-compatible style strategy.", confidence: "medium", }), ); } for (const element of root.querySelectorAll("[src], [href], [action]")) { const raw = element.getAttribute("src") ?? element.getAttribute("href") ?? element.getAttribute("action"); if (window.location.protocol === "https:" && raw?.startsWith("http://")) { issues.push( createIssue({ ruleId: "security/mixed-content", category: "security", severity: "error", title: "Mixed-content resource", message: `${raw} is loaded over insecure HTTP.`, element, recommendation: "Use HTTPS or serve the resource from the same secure origin.", }), ); } } for (const frame of root.querySelectorAll("iframe:not([sandbox])")) { issues.push( createIssue({ ruleId: "security/iframe-sandbox", category: "security", severity: "warning", title: "Iframe is not sandboxed", message: "Third-party or untrusted iframe content has broad browser capabilities.", element: frame, recommendation: "Add the narrowest possible sandbox and permissions policy.", }), ); } try { for (let index = 0; index < window.localStorage.length; index += 1) { const key = window.localStorage.key(index) ?? ""; if (SECRET_KEY.test(key)) { issues.push( createIssue({ ruleId: "security/sensitive-local-storage", category: "security", severity: "error", title: "Sensitive value may be stored in localStorage", message: `Storage key “${key}” looks authentication- or secret-related.`, recommendation: "Keep sessions and credentials in Secure, HttpOnly cookies.", }), ); } } } catch { // Storage may be unavailable in restricted browser contexts. } const hydration = document.querySelectorAll( 'script[type="application/json"][data-wrnexus-state], script[data-wrnexus-store-state]', ); for (const script of hydration) { if (SECRET_KEY.test(script.textContent ?? "")) { issues.push( createIssue({ ruleId: "security/hydration-secret", category: "security", severity: "error", title: "Hydration payload may contain a secret", message: "Sensitive-looking field names were found in serialized client state.", element: script, recommendation: "Move sensitive state to server-only state and regenerate the page.", confidence: "medium", }), ); } } return issues; }, }, ];