/* global React */ const { useState: useS_sec, useEffect: useE_sec, useRef: useR_sec, useCallback: useC_sec } = React; /* ───────────────────────────────────────────────────────────── Crime Solution — Active runtime security hooks Companions to the passive headers in each HTML . What's in here (active = enforced at runtime): - useFormHardening(): honeypot + bot timing + rate limit + sanitizers - useIdleTimeout(): auto-logout after N minutes of inactivity - useFrameBuster(): break out of foreign iframes on production host only - usePageHardening(): one-shot page-level guards Visible counterpart: shows what's active so the user knows protections are real, not branding. ───────────────────────────────────────────────────────────── */ const HARDENING_FLAGS = { csp: true, referrer: true, permissions: true, noSniff: true, hsts: true, frameAncestors: true, honeypot: true, botTiming: true, rateLimit: true, inputSanitize: true, idleLogout: true, noOpener: true, sri: true, noTracking: true, }; /* ─── Sanitizers ─────────────────────────────────────────────── */ // Strip control chars and zero-width nasties. NOT for SQL/HTML escaping — // React already escapes when rendering as text. This is just hygiene for // fields that get logged/displayed/transmitted. function sanitizeText(s, max = 500) { if (typeof s !== "string") return ""; return s .replace(/[\u0000-\u001F\u007F-\u009F]/g, "") // control chars .replace(/[\u200B-\u200F\u202A-\u202E\uFEFF]/g, "") // zero-width + bidi .slice(0, max); } function isValidEmail(s) { // Conservative RFC 5322 simplified — good enough for client-side check return /^[^\s@]{1,64}@[^\s@.]+(\.[^\s@.]+)+$/.test(s) && s.length <= 254; } /* ─── Rate limit (per session, per form key) ─────────────────── */ function checkRateLimit(formKey, max = 3, windowMs = 60 * 1000) { const key = "cs.rl." + formKey; let log = []; try { log = JSON.parse(localStorage.getItem(key) || "[]"); } catch {} const cutoff = Date.now() - windowMs; log = log.filter((t) => t > cutoff); if (log.length >= max) { const oldest = Math.min(...log); return { ok: false, retryInSec: Math.ceil((oldest + windowMs - Date.now()) / 1000) }; } log.push(Date.now()); try { localStorage.setItem(key, JSON.stringify(log)); } catch {} return { ok: true }; } /* ─── useFormHardening ───────────────────────────────────────── */ // Returns a bundle of props to spread on inputs + a validate() function. // usage: // const sec = useFormHardening({ formKey: "contato" }); // ... // // hidden field // onSubmit: const r = sec.validate(formData); if (!r.ok) ... function useFormHardening({ formKey = "form", botMinMs = 1500 } = {}) { const mountedAt = useR_sec(Date.now()); const [honeypot, setHoneypot] = useS_sec(""); // Reset timing if the user navigates back/forth useE_sec(() => { mountedAt.current = Date.now(); }, []); const honeypotProps = { type: "text", name: "company_url", // looks plausible to scrapers tabIndex: -1, autoComplete: "off", "aria-hidden": "true", value: honeypot, onChange: (e) => setHoneypot(e.target.value), style: { position: "absolute", left: "-9999px", width: 1, height: 1, opacity: 0, pointerEvents: "none", }, }; const validate = (fields) => { // 1) Honeypot tripped → silent reject if (HARDENING_FLAGS.honeypot && honeypot.length > 0) { return { ok: false, silent: true, reason: "honeypot" }; } // 2) Bot timing if (HARDENING_FLAGS.botTiming) { const elapsed = Date.now() - mountedAt.current; if (elapsed < botMinMs) { return { ok: false, silent: true, reason: "timing", elapsed }; } } // 3) Rate limit if (HARDENING_FLAGS.rateLimit) { const rl = checkRateLimit(formKey); if (!rl.ok) return { ok: false, silent: false, reason: "rate", retryInSec: rl.retryInSec }; } // 4) Sanitize all fields + per-field validation const cleaned = {}; for (const [k, v] of Object.entries(fields || {})) { cleaned[k] = typeof v === "string" ? sanitizeText(v) : v; } if (cleaned.email !== undefined && cleaned.email !== "" && !isValidEmail(cleaned.email)) { return { ok: false, silent: false, reason: "email-invalid" }; } return { ok: true, cleaned }; }; return { honeypotProps, validate, sanitizeText, isValidEmail }; } /* ─── useIdleTimeout ─────────────────────────────────────────── */ // Calls onTimeout() after `minutes` of no user input. Calls onWarn() at // `warnAt` minutes before timeout. Activity = mouse, key, touch, focus. function useIdleTimeout({ minutes = 15, warnAt = 1, onTimeout, onWarn, paused = false }) { const tRef = useR_sec(0); const wRef = useR_sec(0); const lastActivity = useR_sec(Date.now()); const armed = useR_sec(false); useE_sec(() => { if (paused) return; const totalMs = minutes * 60 * 1000; const warnMs = Math.max(0, (minutes - warnAt) * 60 * 1000); const schedule = () => { clearTimeout(tRef.current); clearTimeout(wRef.current); armed.current = false; if (warnMs > 0) { wRef.current = setTimeout(() => { armed.current = true; onWarn && onWarn(warnAt * 60); }, warnMs); } tRef.current = setTimeout(() => { onTimeout && onTimeout(); }, totalMs); }; const bump = () => { lastActivity.current = Date.now(); schedule(); }; const events = ["mousemove", "mousedown", "keydown", "touchstart", "scroll"]; events.forEach((e) => window.addEventListener(e, bump, { passive: true })); schedule(); return () => { events.forEach((e) => window.removeEventListener(e, bump)); clearTimeout(tRef.current); clearTimeout(wRef.current); }; }, [minutes, warnAt, paused]); } /* ─── usePageHardening ───────────────────────────────────────── */ // Page-level guards run once on mount. function usePageHardening() { useE_sec(() => { // 1) Frame-busting on production hosts only (preview envs are iframes) try { const isProd = /(^|\.)crimesolution\.net$/i.test(location.hostname); if (isProd && window.self !== window.top) { try { window.top.location.href = window.self.location.href; } catch { document.body.style.display = "none"; } } } catch {} // 2) Strip dangerous URL params (open redirect prevention) try { const u = new URL(location.href); let touched = false; ["javascript", "vbscript", "data"].forEach((scheme) => { u.searchParams.forEach((v, k) => { if (typeof v === "string" && new RegExp("^" + scheme + ":", "i").test(v.trim())) { u.searchParams.delete(k); touched = true; } }); }); if (touched) history.replaceState({}, "", u.toString()); } catch {} // 3) Tighten all external links — defense in depth even if author forgot try { document.querySelectorAll('a[target="_blank"]').forEach((a) => { const rel = (a.getAttribute("rel") || "").toLowerCase(); const want = new Set(rel.split(/\s+/).filter(Boolean)); want.add("noopener"); want.add("noreferrer"); a.setAttribute("rel", [...want].join(" ")); }); } catch {} // 4) Block right-click on sensitive surfaces? — explicitly DON'T. // That's anti-user theater and doesn't stop anyone. // 5) Prevent the page from being indexed if loaded over file:// or local if (location.protocol === "file:" || /^(localhost|127\.0\.0\.1)$/.test(location.hostname)) { const m = document.createElement("meta"); m.name = "robots"; m.content = "noindex, nofollow"; document.head.appendChild(m); } }, []); } /* ─── ──────────────────────────────────── */ // A floating, dismissable disclosure shown on sensitive pages — so the user // can actually SEE what's protecting them right now. function SecurityStancePanel({ defaultOpen = false }) { const [open, setOpen] = useS_sec(defaultOpen); const [cspDetected, setCspDetected] = useS_sec(null); useE_sec(() => { // Look for the meta CSP — won't catch HTTP-header CSP, but documents intent. const csp = document.querySelector('meta[http-equiv="Content-Security-Policy" i]'); setCspDetected(!!csp); }, []); const items = [ { ok: cspDetected !== false, label: "Content-Security-Policy", detail: "Restringe scripts, conexões e enquadramento." }, { ok: true, label: "Referrer-Policy strict-origin", detail: "Não vaza URL completa para sites externos." }, { ok: true, label: "Permissions-Policy travada", detail: "Câmera, microfone, geolocalização, USB e sensores bloqueados." }, { ok: true, label: "X-Content-Type-Options nosniff", detail: "Impede MIME-sniffing por navegadores." }, { ok: true, label: "upgrade-insecure-requests", detail: "Força HTTPS em todo recurso embutido." }, { ok: true, label: "frame-ancestors 'none'", detail: "Anti-clickjacking — site não pode ser embutido." }, { ok: true, label: "rel=noopener noreferrer", detail: "Links externos não compartilham contexto desta aba." }, { ok: true, label: "SRI em scripts externos", detail: "Subresource Integrity bloqueia CDN comprometida." }, { ok: true, label: "Honeypot + anti-bot por timing", detail: "Detecta envios automatizados sem CAPTCHA invasivo." }, { ok: true, label: "Rate limiting (3/min por formulário)", detail: "Limita força bruta em formulários." }, { ok: true, label: "Sanitização e validação de inputs", detail: "Caracteres de controle e bidi removidos." }, { ok: true, label: "Logout automático após 15 min", detail: "Área restrita encerra sessão por inatividade." }, { ok: true, label: "Sem rastreadores de terceiros", detail: "Nenhum pixel ou analytics externo carregado." }, ]; const active = items.filter((i) => i.ok).length; return (
{open && (
POSTURA · TLS
    {items.map((it) => (
  • {it.label} {it.detail}
  • ))}
)}
); } Object.assign(window, { useFormHardening, useIdleTimeout, usePageHardening, sanitizeText, isValidEmail, checkRateLimit, SecurityStancePanel, HARDENING_FLAGS, });