/* 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 (