SEC//AUDITv6
Enter URL and click Scan
AI-Powered Web Security Audit v6
Comprehensive black-box security scanning powered by Claude AI. Full proxy mode for deep scanning.
HTTP HeadersTLS/HTTPS File ExposureJS Analysis DNS & EmailRecon CVE / StackXSS/SQLi WAF DetectionOSINT API DiscoveryCookie Security
Example targets
🌐example.com
🐙github.com
📋 Scan History
Scan Settings
Backend Proxy
No proxy - browser mode
WAF Override
Will apply on next scan
Custom Cookies
Swagger URL (optional)
&xss=1&sql=1%27+OR+1%3D1--"; var wp=await fetch(wafProbeUrl,{method:"GET",mode:"cors",cache:"no-store",signal:AbortSignal.timeout(4000)}); var wpBody=(await wp.text().catch(function(){return"";})).toLowerCase(); if(wp.status===403||wp.status===406||wp.status===429||wp.status===503||wp.status===501){ activeWafDetected=true; activeWafNote="Active payload blocked (HTTP "+wp.status+") — WAF is intercepting malicious requests."; wafScores["Active Block"]=(wafScores["Active Block"]||0)+5; } else if(wpBody.indexOf("blocked")>=0||wpBody.indexOf("forbidden")>=0||wpBody.indexOf("attack")>=0||wpBody.indexOf("security")>=0&&wpBody.indexOf("request")>=0){ activeWafDetected=true; activeWafNote="Response body contains block/security message for malicious payload."; wafScores["Active Block"]=(wafScores["Active Block"]||0)+3; } }catch(e){} // Recalculate after active probe var topWaf2=Object.entries(wafScores).sort(function(a,b){return b[1]-a[1];})[0]; if(topWaf2&&topWaf2[1]>=3){hasWAF=true;if(wafName==="None detected"&&topWaf2[0]!=="Active Block")wafName=topWaf2[0];} if(activeWafDetected&&wafName==="None detected")wafName="Unknown WAF"; if(activeWafDetected)hasWAF=true; var wafDetail=(wafNotes[wafName]||wafNotes[wafName.replace(" (manual)","")]||"WAF: "+wafName)+" | "+wafConfLabel; cW.push(hasWAF?mkPass("waf_detected","WAF Detected",wafDetail,wafName):mkFail("waf_detected","WAF Detected","high","No WAF signatures detected - site may be directly exposed.",null)); cW.push(activeWafDetected?mkPass("payload_blocked","Payload Blocked",activeWafNote,null):mkInfo("payload_blocked","Payload Blocked",hasWAF?"WAF detected passively - active block test inconclusive.":"No WAF detected.")); cW.push(hasWAF?mkPass("waf_vendor","WAF Vendor","Identified: "+wafName+" ("+wafConfLabel+")",wafName):mkFail("waf_vendor","WAF Vendor","high","No WAF vendor detected. Consider Cloudflare, Imperva, or CheckPoint.",null)); if(hasWAF&&Object.keys(wafScores).length>0){ var allSigs=Object.entries(wafScores).sort(function(a,b){return b[1]-a[1];}).filter(function(e){return e[0]!=="Active Block";}).map(function(e){return e[0]+" ("+e[1]+"pts)";}).join(", "); if(allSigs)cW.push(mkInfo("waf_signals","Detection Signals","Scored vendors: "+allSigs)); } allData["waf"]=cW; // PLATFORM var cPL=[]; cPL.push(mkPass("platform_detection","Platform Detection","Detected: "+platform,(Object.keys(H).slice(0,6).join(", ")||"no headers"))); cPL.push(hasWAF?mkPass("waf_cdn","WAF/CDN","WAF/CDN layer: "+wafName,wafName):mkWarn("waf_cdn","WAF/CDN","medium","No WAF/CDN detected.",null)); cPL.push(mkInfo("config_file","Config File","See File Exposure category.")); allData["platform"]=cPL; // COOKIES var cCK=[]; if(proxyData&&proxyData.cookies&&proxyData.cookies.length>0){ var cookies=proxyData.cookies; var noHO=cookies.filter(function(c){return!c.httpOnly;}); var noSec=cookies.filter(function(c){return!c.secure;}); var noSS=cookies.filter(function(c){return!c.sameSite;}); cCK.push(noHO.length?mkFail("cookie_httponly","Cookie HttpOnly","medium",noHO.length+" cookie(s) missing HttpOnly",noHO.length+"/"+cookies.length+" affected"):mkPass("cookie_httponly","Cookie HttpOnly","All cookies have HttpOnly.",cookies.length+" checked")); cCK.push(noSec.length?mkFail("cookie_secure","Cookie Secure","high",noSec.length+" cookie(s) missing Secure flag",noSec.length+"/"+cookies.length+" affected"):mkPass("cookie_secure","Cookie Secure","All cookies have Secure flag.",cookies.length+" checked")); cCK.push(noSS.length?mkWarn("cookie_samesite","Cookie SameSite","medium",noSS.length+" cookie(s) missing SameSite",noSS.length+"/"+cookies.length+" affected"):mkPass("cookie_samesite","Cookie SameSite","All cookies have SameSite.",null)); } else { cCK.push(mkInfo("cookie_httponly","Cookie HttpOnly","No cookies in response - check authenticated endpoints.")); cCK.push(mkInfo("cookie_secure","Cookie Secure","Proxy required for full cookie analysis.")); cCK.push(mkInfo("cookie_samesite","Cookie SameSite","No cookies detected.")); } allData["cookies_sec"]=cCK; // HTTP METHODS var cHM=[]; if(proxyData&&proxyData.httpMethods){ var hm=proxyData.httpMethods; var dangerousMethods=[]; ["PUT","DELETE","TRACE","CONNECT"].forEach(function(method){ if(hm[method]&&hm[method].status&&hm[method].status<405)dangerousMethods.push(method+"("+hm[method].status+")"); }); cHM.push(dangerousMethods.length?mkFail("http_methods_dangerous","Dangerous Methods","high","Dangerous methods accepted: "+dangerousMethods.join(", "),dangerousMethods.join(", ")):mkPass("http_methods_dangerous","Dangerous Methods","No dangerous methods accepted.")); var traceR=hm["TRACE"]; cHM.push(traceR&&traceR.status===200?mkFail("http_trace","TRACE Method","medium","TRACE enabled - XST attacks possible."):mkPass("http_trace","TRACE Method","TRACE disabled.")); } else { cHM.push(mkInfo("http_methods_dangerous","Dangerous Methods","Proxy required.")); cHM.push(mkInfo("http_trace","TRACE Method","Proxy required.")); } allData["http_methods"]=cHM; // PORT SCAN var cPS=[]; if(proxyData&&proxyData.portScan){ var ps=proxyData.portScan; var DPORTS={21:"FTP",23:"Telnet",445:"SMB",3306:"MySQL",3389:"RDP",5432:"PostgreSQL",5900:"VNC",6379:"Redis",9200:"Elasticsearch",27017:"MongoDB"}; var openDangerous=[],openAll=[]; Object.entries(ps).forEach(function(e){ if(e[1].open){openAll.push(e[0]+"/"+e[1].service);if(DPORTS[e[0]])openDangerous.push(e[0]+"/"+e[1].service);} }); cPS.push(openDangerous.length?mkFail("port_dangerous","Dangerous Ports","critical","High-risk ports open: "+openDangerous.join(", "),openDangerous.join(", ")):mkPass("port_dangerous","Dangerous Ports","No dangerous ports detected.")); cPS.push(openAll.length?mkWarn("port_open","Open Ports","low","Open: "+openAll.join(", "),openAll.join(", ")):mkPass("port_open","Open Ports","Only 80/443 detected.")); } else { cPS.push(mkInfo("port_dangerous","Dangerous Ports","Proxy required.")); cPS.push(mkInfo("port_open","Open Ports","Proxy required.")); } allData["port_scan"]=cPS; // OPEN REDIRECT var cOR=[]; if(proxyData&&proxyData.openRedirects){ var ors=proxyData.openRedirects; cOR.push(ors.length?mkFail("open_redirect","Open Redirect","high","Vulnerability found! "+ors.length+" payload(s).",ors.map(function(r){return r.payload+"->"+r.location;}).join(", ")):mkPass("open_redirect","Open Redirect","No open redirects detected.")); } else cOR.push(mkInfo("open_redirect","Open Redirect","Proxy required.")); allData["open_redirect"]=cOR; // SUBDOMAINS var cSE=[]; if(proxyData&&proxyData.subdomains){ var subs=proxyData.subdomains; var sensitiveSubs=subs.filter(function(s){ var n=s.subdomain.split(".")[0]; return["admin","dev","staging","test","beta","internal","vpn","git","jenkins"].indexOf(n)>=0; }); cSE.push(mkInfo("subdomain_count","Subdomains Found",subs.length+" found",subs.length+" total")); cSE.push(sensitiveSubs.length?mkWarn("subdomain_sensitive","Sensitive Subdomains","medium","Sensitive: "+sensitiveSubs.map(function(s){return s.subdomain;}).join(", ")):mkPass("subdomain_sensitive","Sensitive Subdomains","No sensitive subdomain names.")); } else { cSE.push(mkInfo("subdomain_count","Subdomains Found","Proxy required.")); cSE.push(mkInfo("subdomain_sensitive","Sensitive Subdomains","Proxy required.")); } allData["subdomain_enum"]=cSE; // DIR BRUTE var cDB=[]; if(proxyData&&proxyData.dirBruteforce){ var db=proxyData.dirBruteforce; var found=Object.entries(db).filter(function(e){return e[1].interesting;}); var exposed=found.filter(function(e){return e[1].status===200;}); cDB.push(exposed.length?mkWarn("dir_exposed","Exposed Directories","medium",exposed.length+" accessible: "+exposed.map(function(e){return e[0];}).join(", ")):mkPass("dir_exposed","Exposed Directories","No sensitive dirs returned 200.")); } else { cDB.push(mkInfo("dir_exposed","Exposed Directories","Proxy required.")); cDB.push(mkInfo("dir_protected","Protected Directories","Proxy required.")); } allData["dir_brute"]=cDB; // API DISCOVERY var cAD=[]; if(proxyData&&proxyData.apiEndpoints){ var apis=proxyData.apiEndpoints; var openApis=apis.filter(function(a){return a.status===200||a.status===201;}); var docsE=apis.filter(function(a){return(a.path.indexOf("swagger")>=0||a.path.indexOf("openapi")>=0)&&a.status===200;}); cAD.push(openApis.length?mkWarn("api_open","Open API Endpoints","medium",openApis.length+" unauthenticated endpoints."):mkPass("api_open","Open API Endpoints","No unauthenticated endpoints.")); cAD.push(docsE.length?mkWarn("api_docs","API Docs Exposed","medium","Public API docs: "+docsE.map(function(a){return a.path;}).join(", ")):mkPass("api_docs","API Docs Exposed","No public API docs found.")); } else { cAD.push(mkInfo("api_open","Open API Endpoints","Proxy required.")); cAD.push(mkInfo("api_docs","API Docs Exposed","Proxy required.")); } allData["api_disc"]=cAD; // FILE UPLOAD var cFU=[]; if(proxyData&&proxyData.fileUpload&&Object.keys(proxyData.fileUpload).length>0){ var fu=proxyData.fileUpload; var vulnEndpoints=[]; Object.keys(fu).forEach(function(ep){ var tests=fu[ep].tests||{}; var vulns=[]; if(tests.phpShell&&!tests.phpShell.blocked)vulns.push("PHP shell"); if(tests.svgXss&&!tests.svgXss.blocked)vulns.push("SVG/XSS"); if(vulns.length>0)vulnEndpoints.push({ep:ep,vulns:vulns}); }); if(vulnEndpoints.length>0){ vulnEndpoints.forEach(function(v){ cFU.push(mkFail("fu_"+v.ep.replace(/\//g,"_"),"Upload Vuln: "+v.ep,"critical","Dangerous uploads not blocked: "+v.vulns.join(", "),v.ep)); }); } else cFU.push(mkPass("fu_blocked","Upload Security","All upload endpoints block dangerous files.")); } else { cFU.push(mkInfo("fu_endpoints","Upload Endpoints","No upload endpoints found or proxy required.")); } allData["file_upload"]=cFU; // SQLI var cSQLI=[]; if(proxyData&&proxyData.sqli&&proxyData.sqli.length>0){ proxyData.sqli.forEach(function(r){ cSQLI.push(mkFail("sqli_"+r.path.replace(/\W/g,"_"),"SQLi: "+r.path,"critical","SQL error indicator: "+r.indicator,r.path)); }); } else if(proxyData&&proxyData.sqli){ cSQLI.push(mkPass("sqli_active","Active SQLi Scan","No SQL error indicators detected.")); } var sqliPatterns=[ {re:new RegExp("sql syntax.*mysql","i"),db:"MySQL"}, {re:new RegExp("warning.*mysql_","i"),db:"MySQL"}, {re:new RegExp("unclosed quotation mark","i"),db:"MSSQL"}, {re:new RegExp("microsoft ole db.*sql","i"),db:"MSSQL"}, {re:new RegExp("ora-[0-9]{5}","i"),db:"Oracle"}, {re:new RegExp("pg::syntaxerror","i"),db:"PostgreSQL"}, {re:new RegExp("sqlite_exception","i"),db:"SQLite"}, {re:new RegExp("you have an error in your sql","i"),db:"MySQL"}, ]; var sqliFound=false; sqliPatterns.forEach(function(p){ if(p.re.test(fetchedBody)){ sqliFound=true; cSQLI.push(mkFail("sqli_passive_"+p.db.toLowerCase(),"SQL Error Leak: "+p.db,"critical","Database error message in page - SQLi indicator.",p.db+" error signature in HTML")); } }); if(!sqliFound&&cSQLI.length===0){ var urlParamsSQL=[]; try{urlParamsSQL=[...new URL(t).searchParams.keys()];}catch(e){} if(urlParamsSQL.length>0){ cSQLI.push(mkWarn("sqli_params","URL Parameters Found","medium",urlParamsSQL.length+" parameter(s) - test for SQLi: "+urlParamsSQL.slice(0,5).join(", "),"Run: sqlmap -u \""+t+"\" --batch")); } else { cSQLI.push(mkInfo("sqli_info","SQL Injection","No URL params found on main page. Test POST endpoints manually.")); } } allData["sqli"]=cSQLI; // XSS var cXSS=[]; if(proxyData&&proxyData.xss&&proxyData.xss.length>0){ proxyData.xss.forEach(function(r){ cXSS.push(mkFail("xss_"+r.path.replace(/\W/g,"_"),"Reflected XSS","high","Payload reflected unencoded.",r.path)); }); } var sinkNames=["document.write()","innerHTML=","outerHTML=","eval()"]; var sinkPatterns=[ new RegExp("document\\.write\\s*\\(","g"), new RegExp("innerHTML\\s*=","g"), new RegExp("outerHTML\\s*=","g"), new RegExp("eval\\s*\\(","g") ]; var highSinks=[]; sinkPatterns.forEach(function(re,i){ var m=fetchedBody.match(re); if(m&&m.length>0)highSinks.push(sinkNames[i]+" x"+m.length); }); if(highSinks.length>0) cXSS.push(mkWarn("xss_dom","DOM XSS Sinks","medium","Dangerous JS sinks in page source: "+highSinks.join(", "),highSinks.join(", "))); else if(!proxyData) cXSS.push(mkPass("xss_dom","DOM XSS Sinks","No dangerous DOM sinks detected.")); var urlParamsXSS=[]; try{ new URL(t).searchParams.forEach(function(v,k){ if(v.length>1&&fetchedBody.indexOf(v)>=0)urlParamsXSS.push(k); }); }catch(e){} if(urlParamsXSS.length>0) cXSS.push(mkWarn("xss_reflect","URL Param Reflected","medium","Parameters reflected in response: "+urlParamsXSS.join(", "),urlParamsXSS.join(", "))); if(csp&&(csp.indexOf("unsafe-inline")>=0||csp.indexOf("unsafe-eval")>=0)) cXSS.push(mkWarn("xss_csp","CSP Allows XSS Bypass","medium","CSP contains unsafe directives.",csp.substring(0,100))); if(cXSS.length===0) cXSS.push(mkInfo("xss_info","XSS Analysis","No passive XSS indicators. Use Burp Suite for active testing.")); allData["xss"]=cXSS; // RATE LIMIT var cRL=[]; if(proxyData&&proxyData.rateLimit&&proxyData.rateLimit.tested){ var rlD=proxyData.rateLimit; if(rlD.limited) cRL.push(mkPass("rl_detected","Rate Limiting","Rate limiting detected."+(rlD.limitAt?" blocked after "+rlD.limitAt+" requests":""))); else cRL.push(mkFail("rl_missing","Rate Limiting","high","No rate limiting detected after 10 rapid requests.")); } else { cRL.push(rlH?mkPass("rl_headers","Rate Limiting","Rate limit headers present.",rlH):mkWarn("rl_warn","Rate Limiting","medium","No rate-limit headers. Proxy required for full test.",null)); } allData["rate_limit"]=cRL; // SESSION var cSES=[]; if(proxyData&&proxyData.cookies&&proxyData.cookies.length>0){ var sessionCookies=proxyData.cookies.filter(function(c){ var n=(c.name||"").toLowerCase(); return n.indexOf("session")>=0||n.indexOf("jsession")>=0||n.indexOf("sess")>=0||n.indexOf("token")>=0||n.indexOf("auth")>=0; }); if(sessionCookies.length>0){ var noHOSes=sessionCookies.filter(function(c){return!c.httpOnly;}); var noSecSes=sessionCookies.filter(function(c){return!c.secure;}); var noSSSes=sessionCookies.filter(function(c){return!c.sameSite;}); if(noSecSes.length) cSES.push(mkFail("ses_secure","Session Cookie Secure","high",noSecSes.length+" session cookie(s) missing Secure flag.",noSecSes.map(function(c){return c.name;}).join(", "))); else cSES.push(mkPass("ses_secure","Session Cookie Secure","Session cookies have Secure flag.")); if(noHOSes.length) cSES.push(mkFail("ses_httponly","Session Cookie HttpOnly","high",noHOSes.length+" session cookie(s) missing HttpOnly.",noHOSes.map(function(c){return c.name;}).join(", "))); else cSES.push(mkPass("ses_httponly","Session Cookie HttpOnly","Session cookies have HttpOnly.")); if(noSSSes.length) cSES.push(mkWarn("ses_samesite","Session Cookie SameSite","medium",noSSSes.length+" session cookie(s) missing SameSite.",noSSSes.map(function(c){return c.name;}).join(", "))); else cSES.push(mkPass("ses_samesite","Session Cookie SameSite","Session cookies have SameSite.")); } else { cSES.push(mkInfo("session_none","Session Management","No session/auth cookies on main page — check authenticated endpoints.")); } } else { cSES.push(mkInfo("session_none","Session Management","No session cookies on main page — check authenticated endpoints.")); } allData["session"]=cSES; // CSRF var cCSRF=[]; if(proxyData&&proxyData.csrf){ var csrf=proxyData.csrf; if(csrf.tokenFound||csrf.sameSiteCookie) cCSRF.push(mkPass("csrf_token","CSRF Protection",(csrf.tokenFound?"CSRF token found. ":"")+(csrf.sameSiteCookie?"SameSite cookie present.":""))); else cCSRF.push(mkFail("csrf_missing","CSRF Protection","high","No CSRF token or SameSite cookie detected.")); } else { cCSRF.push(mkInfo("csrf_info","CSRF Protection","Proxy required for CSRF detection.")); } allData["csrf"]=cCSRF; // XXE allData["xxe"]=[mkInfo("xxe_info","XXE Injection","Active XML injection testing requires proxy.")]; // SSRF var cSSRF=[]; if(proxyData&&proxyData.ssrf&&proxyData.ssrf.length>0){ proxyData.ssrf.forEach(function(r){ cSSRF.push(r.sev==="critical"?mkFail("ssrf_"+r.param,"SSRF: "+r.param,"critical",r.finding,r.payload):mkWarn("ssrf_"+r.param,"SSRF: "+r.param,"medium",r.finding,r.payload)); }); } else cSSRF.push(mkInfo("ssrf_info","SSRF Detection","Active SSRF testing requires proxy.")); allData["ssrf"]=cSSRF; // SMUGGLING allData["smuggling"]=[mkInfo("smuggling_info","HTTP Smuggling","Use Burp Suite HTTP Request Smuggler for testing.")]; // API SECURITY var cAPI2=[]; if(proxyData&&proxyData.apiSecurity&&proxyData.apiSecurity.graphql&&proxyData.apiSecurity.graphql.endpoint){ var gql=proxyData.apiSecurity.graphql; if(gql.introspectionEnabled) cAPI2.push(mkFail("gql_intro","GraphQL Introspection","high","Introspection enabled - "+gql.typesCount+" types exposed.",gql.endpoint)); else cAPI2.push(mkPass("gql_disabled","GraphQL Introspection","Found at "+gql.endpoint+" - introspection disabled.",gql.endpoint)); } else { cAPI2.push(mkInfo("gql_none","GraphQL","No GraphQL endpoints detected.")); cAPI2.push(mkInfo("api_sec_info","API Security","Mass assignment, JWT checks require proxy.")); } allData["api_security"]=cAPI2; // SWAGGER var cSW=[]; if(proxyData&&proxyData.swaggerScan&&proxyData.swaggerScan.found&&proxyData.swaggerScan.found.length>0){ var sw=proxyData.swaggerScan; sw.found.forEach(function(f){ cSW.push(mkFail("swagger_found","Swagger/OpenAPI Found","high","API documentation publicly accessible.",f.url+" ("+f.size+"B)")); }); if(sw.info)cSW.push(mkInfo("swagger_info","API Info",(sw.info.title||"Unknown")+" v"+(sw.info.version||"?")+" - "+sw.info.totalEndpoints+" endpoints.")); } else cSW.push(mkPass("swagger_notfound","Swagger/OpenAPI","No public API documentation found.")); allData["swagger_scan"]=cSW; // ── Custom Rules ── var cCUSTOM=[]; if(customRules.filter(function(r){return r.enabled!==false;}).length===0){ cCUSTOM.push(mkInfo("cr_none","Custom Rules","No custom rules defined. Click ⚙ Custom Rules to add.")); } else { var scanData={ headers:H, body:fetchedBody, statusCode:httpOk?200:0, portScan:proxyData&&proxyData.portScan||{}, score:curScore, redirects:proxyData&&proxyData.redirects||[], dns:proxyData&&proxyData.dns||{}, cookies:proxyData&&proxyData.cookies||[], tls:proxyData&&proxyData.tls||{}, tlsDeep:proxyData&&proxyData.tlsDeep||{}, bodySize:fetchedBody?fetchedBody.length:0, responseTime:null }; var triggered=runCustomRules(scanData); var activeRules=customRules.filter(function(r){return r.enabled!==false;}); var passedRules=activeRules.filter(function(r){ return !triggered.find(function(t){return t.rule.id===r.id;}); }); triggered.forEach(function(t){ var r=t.rule; cCUSTOM.push(mkFail("cr_"+r.id,r.name,r.sev,r.message,r.type+(r.p1?" · "+r.p1:"")+(r.p2?" = "+r.p2:""))); }); passedRules.forEach(function(r){ cCUSTOM.push(mkPass("crp_"+r.id,r.name,r.message||"Rule passed.")); }); if(triggered.length===0) cCUSTOM.push(mkPass("cr_allpass","All Custom Rules","All "+activeRules.length+" custom rules passed ✓")); } allData["custom_rules"]=cCUSTOM; // ── Shodan Intel ── var cSHODAN=[]; if(proxyData&&proxyData.shodan){ var sh=proxyData.shodan; if(!sh.available){ cSHODAN.push(mkInfo("shodan_na","Shodan Intel",sh.reason||"Set SHODAN_API_KEY env var to enable.")); } else { // Host info cSHODAN.push(mkInfo("shodan_host","Host Info", [sh.org,sh.isp,sh.country,sh.city,sh.asn].filter(Boolean).join(" | "),sh.ip)); // Open ports var openPorts=(sh.ports||[]).filter(function(p){return p.open!==false;}); var dangerousPorts=openPorts.filter(function(p){return p.isDangerous;}); if(dangerousPorts.length>0){ cSHODAN.push(mkFail("shodan_ports","Dangerous Ports (Shodan)","high", dangerousPorts.map(function(p){return p.port+"/"+(p.product||p.service||"unknown");}).join(", "), dangerousPorts.length+" dangerous port(s) open")); } if(openPorts.length>0){ cSHODAN.push(mkWarn("shodan_allports","All Open Ports","low", openPorts.map(function(p){return p.port+(p.product?" ("+p.product+(p.version?" "+p.version:"")+")":"");}).join(", "), openPorts.length+" port(s)")); } // CVEs var vulns=sh.vulns||[]; var critVulns=vulns.filter(function(v){return v.sev==="critical"||v.sev==="high";}); if(critVulns.length>0){ critVulns.slice(0,5).forEach(function(v){ cSHODAN.push(mkFail("shodan_cve_"+v.cve.replace(/\W/g,"_"),v.cve,v.sev, v.summary||"CVSS: "+v.cvss+" on port "+v.port+" ("+v.product+")", "CVSS: "+v.cvss)); }); if(vulns.length>5) cSHODAN.push(mkWarn("shodan_more_cves","More CVEs","medium",(vulns.length-5)+" additional CVEs — check Shodan directly.",null)); } else if(vulns.length>0){ cSHODAN.push(mkWarn("shodan_low_cves","CVEs Detected","low",vulns.length+" low/medium CVEs detected.",null)); } else { cSHODAN.push(mkPass("shodan_nocves","CVEs","No known CVEs detected by Shodan.")); } // Exposure flags var flags=sh.exposureFlags||[]; flags.forEach(function(f){ if(f.sev==="critical"||f.sev==="high") cSHODAN.push(mkFail("shodan_flag_"+f.flag.replace(/\W/g,"_").substring(0,20),"Shodan: "+f.flag.substring(0,50),f.sev,f.flag,null)); else if(f.sev==="medium") cSHODAN.push(mkWarn("shodan_flag_m","Shodan: "+f.flag.substring(0,50),"medium",f.flag,null)); else cSHODAN.push(mkInfo("shodan_flag_i","Shodan: "+f.flag.substring(0,50),f.flag)); }); // Subdomains from Shodan DNS if(sh.dns&&sh.dns.subdomains&&sh.dns.subdomains.length>0){ cSHODAN.push(mkInfo("shodan_dns","Shodan Subdomains", sh.dns.subdomains.length+" subdomains in Shodan DNS", sh.dns.subdomains.slice(0,10).join(", ")+(sh.dns.subdomains.length>10?" ...":""))); } // Tags if(sh.tags&&sh.tags.length>0) cSHODAN.push(mkInfo("shodan_tags","Shodan Tags",sh.tags.join(", "))); if(sh.os) cSHODAN.push(mkWarn("shodan_os","OS Detected","low","Operating system exposed: "+sh.os,sh.os)); } } else { cSHODAN.push(mkInfo("shodan_info","Shodan Intel","Proxy required. Set SHODAN_API_KEY env var.")); } allData["shodan"]=cSHODAN; // ── NEW v2.5 categories — map proxy data to frontend checks ── // Subdomain Takeover var cSTKO=[]; if(proxyData&&proxyData.subdomainTakeover){ var stko=proxyData.subdomainTakeover; if(stko.length===0) cSTKO.push(mkPass("stko_clean","Subdomain Takeover","No dangling CNAMEs detected.")); else stko.forEach(function(f){ cSTKO.push(f.sev==="critical"?mkFail("stko_"+f.cname,"Takeover: "+f.cname,"critical",f.finding,f.cname):mkWarn("stko_"+f.cname,"CNAME: "+f.cname,"medium",f.finding,f.cname)); }); } else cSTKO.push(mkInfo("stko_info","Subdomain Takeover","Proxy required.")); allData["subdomain_tko2"]=cSTKO; // Admin Panels var cAP=[]; if(proxyData&&proxyData.adminPanels){ var ap=proxyData.adminPanels; if(ap.length===0) cAP.push(mkPass("ap_clean","Admin Panels","No exposed admin panels detected.")); else ap.forEach(function(f){ if(f.sev==="high") cAP.push(mkFail("ap_"+f.path.replace(/\//g,"_"),f.name,"high",f.finding,f.path)); else if(f.sev==="medium") cAP.push(mkWarn("ap_"+f.path.replace(/\//g,"_"),f.name,"medium",f.finding,f.path)); else cAP.push(mkInfo("ap_"+f.path.replace(/\//g,"_"),f.name,f.finding)); }); } else cAP.push(mkInfo("ap_info","Admin Panels","Proxy required.")); allData["admin_panels"]=cAP; // JWT Security var cJWT=[]; if(proxyData&&proxyData.jwtSecurity){ var jwts=proxyData.jwtSecurity; if(jwts.length===0) cJWT.push(mkPass("jwt_clean","JWT Security","No JWT tokens found or no issues detected.")); else jwts.forEach(function(jf,i){ jf.issues.forEach(function(issue){ var sev=issue.sev||"medium"; if(sev==="critical"||sev==="high") cJWT.push(mkFail("jwt_"+i+"_"+sev,"JWT: "+issue.issue.substring(0,40),sev,issue.issue,jf.jwt)); else cJWT.push(mkWarn("jwt_"+i+"_"+sev,"JWT: "+issue.issue.substring(0,40),sev,issue.issue,jf.jwt)); }); }); } else cJWT.push(mkInfo("jwt_info","JWT Security","Proxy required.")); allData["jwt_security"]=cJWT; // Dependency Confusion var cDC=[]; if(proxyData&&proxyData.dependencyConfusion&&proxyData.dependencyConfusion.checked){ var dc=proxyData.dependencyConfusion; if(dc.findings&&dc.findings.length>0){ dc.findings.forEach(function(f){ if(f.sev==="critical"||f.sev==="high") cDC.push(mkFail("dc_"+f.package,"Dep: "+f.package,f.sev,f.finding,f.package)); else cDC.push(mkWarn("dc_"+f.package,"Dep: "+f.package||"package.json",f.sev||"medium",f.finding,null)); }); } else cDC.push(mkPass("dc_clean","Dependency Confusion","package.json not publicly exposed.")); } else cDC.push(mkInfo("dc_info","Dependency Confusion","Proxy required.")); allData["dep_confusion"]=cDC; // CRLF var cCRLF=[]; if(proxyData&&proxyData.crlf){ if(proxyData.crlf.length===0) cCRLF.push(mkPass("crlf_clean","CRLF Injection","No CRLF injection detected.")); else proxyData.crlf.forEach(function(f){ cCRLF.push(mkFail("crlf_"+f.path.replace(/\W/g,"_"),"CRLF Injection","high",f.finding,f.path)); }); } else cCRLF.push(mkInfo("crlf_info","CRLF Injection","Proxy required.")); allData["crlf"]=cCRLF; // DNS Zone Transfer — show real AXFR results var cDNSZ=[]; if(proxyData&&proxyData.dnsZoneTransfer&&proxyData.dnsZoneTransfer.attempted){ var dzt=proxyData.dnsZoneTransfer; if(dzt.vulnerable){ dzt.records.filter(function(r){return r.result==="vulnerable";}).forEach(function(r){ cDNSZ.push(mkFail("dnsz_vuln_"+r.nameserver.replace(/\./g,"_"),"Zone Transfer OPEN: "+r.nameserver,"critical",r.note,r.nameserver+" ("+r.ancount+" records)")); }); } dzt.records.filter(function(r){return r.result!=="vulnerable";}).forEach(function(r){ if(r.result==="refused"||r.result==="empty") cDNSZ.push(mkPass("dnsz_ok_"+r.nameserver.replace(/\./g,"_"),"Zone Transfer: "+r.nameserver,r.note,r.nameserver)); else if(r.result==="port_closed") cDNSZ.push(mkPass("dnsz_closed_"+r.nameserver.replace(/\./g,"_"),"Zone Transfer: "+r.nameserver,r.note,r.nameserver)); else if(r.result==="unresolved") cDNSZ.push(mkInfo("dnsz_unres_"+r.nameserver.replace(/\./g,"_"),"Zone Transfer: "+r.nameserver,r.note)); else cDNSZ.push(mkInfo("dnsz_"+r.nameserver.replace(/\./g,"_"),"Zone Transfer: "+r.nameserver,r.note)); }); if(cDNSZ.length===0) cDNSZ.push(mkPass("dnsz_clean","DNS Zone Transfer","All nameservers refuse AXFR — correctly protected.")); } else { cDNSZ.push(mkInfo("dnsz_info","DNS Zone Transfer","Proxy required.")); } // Certificate Transparency var cCT=[]; if(proxyData&&proxyData.certTransparency&&proxyData.certTransparency.checked){ var ct=proxyData.certTransparency; cCT.push(mkInfo("ct_count","Subdomains via crt.sh",ct.subdomains.length+" subdomains found in CT logs.",ct.subdomains.slice(0,5).join(", "))); if(ct.sensitiveFound&&ct.sensitiveFound.length>0) cCT.push(mkWarn("ct_sensitive","Sensitive Subdomains","medium","Sensitive names in CT: "+ct.sensitiveFound.join(", "),ct.sensitiveFound.join(", "))); else cCT.push(mkPass("ct_ok","Sensitive Subdomains","No sensitive subdomain names in CT logs.")); } else cCT.push(mkInfo("ct_info","Cert Transparency","Proxy required.")); allData["cert_trans"]=cCT; // HaveIBeenPwned var cHIBP=[]; if(proxyData&&proxyData.leakedCredentials&&proxyData.leakedCredentials.checked){ var hibpD=proxyData.leakedCredentials; if(hibpD.breachCount>0){ cHIBP.push(mkFail("hibp_breaches","Domain in Breaches","high",hibpD.breachCount+" breach(es) found for this domain!",hibpD.breaches.map(function(b){return b.name;}).join(", "))); hibpD.breaches.slice(0,3).forEach(function(b){ cHIBP.push(mkWarn("hibp_"+b.name,"Breach: "+b.name,"medium","Date: "+b.date+" | Records: "+(b.pwnCount||"?"),b.dataClasses?b.dataClasses.join(", "):null)); }); } else cHIBP.push(mkPass("hibp_clean","Leaked Credentials",hibpD.note||"No breaches found for this domain.")); } else cHIBP.push(mkInfo("hibp_info","Leaked Credentials","Proxy required. Set HIBP_API_KEY env var.")); allData["hibp"]=cHIBP; // Cloud Misconfig var cCLOUD=[]; if(proxyData&&proxyData.cloudMisconfig){ if(proxyData.cloudMisconfig.length===0) cCLOUD.push(mkPass("cloud_clean","Cloud Misconfig","No public cloud buckets found.")); else proxyData.cloudMisconfig.forEach(function(f){ if(f.sev==="critical") cCLOUD.push(mkFail("cloud_"+f.bucket,"Public Bucket: "+f.bucket,"critical",f.finding,f.url)); else cCLOUD.push(mkWarn("cloud_"+f.bucket,"Bucket Exists: "+f.bucket,"low",f.finding,f.url)); }); } else cCLOUD.push(mkInfo("cloud_info","Cloud Misconfig","Proxy required.")); allData["cloud_misconfig"]=cCLOUD; // TLS Chain — show full SANs var cTLSC=[]; if(proxyData&&proxyData.tlsChain&&!proxyData.tlsChain.skipped){ var tc=proxyData.tlsChain; var ci=tc.certInfo||{}; // Show cert info card if(ci.subject){ var certDetail="Issued to: "+ci.subject+" | Issuer: "+(ci.issuer||"unknown")+" | Expires in: "+ci.daysUntilExpiry+" days"; cTLSC.push(mkInfo("tlsc_cert","Certificate Info",certDetail,ci.validTo||null)); } // Show SANs if(tc.sans&&tc.sans.length>0){ var sanDisplay=tc.sans.join(", "); cTLSC.push(mkInfo("tlsc_sans","SANs ("+tc.sans.length+")",sanDisplay,null)); } // Show issues if(!tc.issues||tc.issues.length===0){ cTLSC.push(mkPass("tlsc_ok","TLS Chain","Certificate chain valid. Domain covered by SANs.",ci.subject||null)); } else { tc.issues.forEach(function(iss){ var val=iss.detail||null; if(iss.sev==="critical"||iss.sev==="high") cTLSC.push(mkFail("tlsc_"+iss.sev,iss.issue,iss.sev,iss.detail||iss.issue,val)); else cTLSC.push(mkWarn("tlsc_"+iss.sev,iss.issue,iss.sev,iss.detail||iss.issue,val)); }); } } else { cTLSC.push(mkInfo("tlsc_info","TLS Chain","Proxy required or site is HTTP.")); } allData["tls_chain"]=cTLSC; // GraphQL Depth var cGQLD=[]; if(proxyData&&proxyData.graphqlDepth&&proxyData.graphqlDepth.tested){ var gqld=proxyData.graphqlDepth; if(gqld.vulnerable) cGQLD.push(mkFail("gqld_vuln","GraphQL Depth Limit","medium",gqld.finding,gqld.endpoint)); else if(gqld.finding) cGQLD.push(mkPass("gqld_ok","GraphQL Depth Limit",gqld.finding,gqld.endpoint)); else cGQLD.push(mkInfo("gqld_none","GraphQL Depth","No GraphQL endpoint found.")); } else cGQLD.push(mkInfo("gqld_info","GraphQL Depth","Proxy required.")); allData["graphql_depth"]=cGQLD; // CORS Advanced var cCORSA=[]; if(proxyData&&proxyData.corsMisconfig&&proxyData.corsMisconfig.tested){ var corsA=proxyData.corsMisconfig; corsA.issues.forEach(function(iss){ if(iss.sev==="critical") cCORSA.push(mkFail("cors_"+iss.sev,"CORS: "+iss.issue.substring(0,40),"critical",iss.issue,null)); else if(iss.sev==="high") cCORSA.push(mkFail("cors_h_"+iss.sev,"CORS: "+iss.issue.substring(0,40),"high",iss.issue,null)); else if(iss.sev==="pass") cCORSA.push(mkPass("cors_ok","CORS Advanced",iss.issue)); else cCORSA.push(mkWarn("cors_w","CORS Warning","medium",iss.issue,null)); }); } else cCORSA.push(mkInfo("corsa_info","CORS Advanced","Proxy required.")); allData["cors_adv"]=cCORSA; // WebSocket var cWS=[]; if(proxyData&&proxyData.websocket){ var ws=proxyData.websocket; ws.issues.forEach(function(iss){ if(iss.sev==="high") cWS.push(mkFail("ws_"+iss.sev,"WebSocket: "+iss.issue.substring(0,40),"high",iss.issue,null)); else if(iss.sev==="medium") cWS.push(mkWarn("ws_m","WebSocket Warning","medium",iss.issue,null)); else cWS.push(mkInfo("ws_info","WebSocket",iss.issue)); }); if(cWS.length===0) cWS.push(mkPass("ws_ok","WebSocket","No WebSocket security issues detected.")); } else cWS.push(mkInfo("ws_info","WebSocket","Proxy required.")); allData["websocket"]=cWS; // Path Traversal var cPT=[]; if(proxyData&&proxyData.pathTraversal){ if(proxyData.pathTraversal.length===0) cPT.push(mkPass("pt_clean","Path Traversal","No path traversal detected.")); else proxyData.pathTraversal.forEach(function(f){ cPT.push(mkFail("pt_"+f.param.replace(/\W/g,"_"),"Path Traversal",f.sev,f.finding,f.param+f.payload)); }); } else cPT.push(mkInfo("pt_info","Path Traversal","Proxy required.")); allData["path_traversal"]=cPT; // Prototype Pollution var cPP=[]; if(proxyData&&proxyData.prototypePollution){ if(proxyData.prototypePollution.length===0) cPP.push(mkPass("pp_clean","Prototype Pollution","No prototype pollution detected.")); else proxyData.prototypePollution.forEach(function(f){ cPP.push(mkFail("pp_"+f.endpoint.replace(/\W/g,"_"),"Proto Pollution: "+f.endpoint,f.sev,f.finding,f.endpoint)); }); } else cPP.push(mkInfo("pp_info","Prototype Pollution","Proxy required.")); allData["proto_pollution"]=cPP; // OSINT var cO=[]; var robotsRes=await probeFile("/robots.txt","robots.txt","robots_txt",true); cO.push(robotsRes); cO.push(mkInfo("email_exposure","Email Exposure","Google dork: site:"+domain2+" @"+rootDomain)); cO.push(mkInfo("breach_indicators","Breach Indicators","Check haveibeenpwned.com for domain breaches.")); allData["osint"]=cO; // RENDER ALL setStep("cats","Analyzing "+CATS.length+" categories"); for(var i=0;i0&&actionable.every(function(r){return r.status==="pass";}))bonus=Math.min(bonus+1,10); }); s=Math.max(0,Math.min(100,Math.round(s+bonus))); rawScore=s; // ── Averaging over last 3 scans on same domain ── var avgResult=getAvgScore(t,s); avgScore=avgResult.avg; avgCount=avgResult.count; var displayScore=avgCount>=2?avgScore:s; var g=displayScore>=90?"A+":displayScore>=80?"A":displayScore>=70?"B+":displayScore>=60?"B":displayScore>=50?"C":displayScore>=40?"D":"F"; curScore=displayScore;curGrade=g; var gc=displayScore>=80?"#00e5a0":displayScore>=60?"#f59e0b":"#ff4060"; document.getElementById("scorebox").style.display="block"; document.getElementById("scoreNum").textContent=displayScore; document.getElementById("scoreNum").style.color=gc; document.getElementById("scoreGrade").textContent="Grade: "+g; document.getElementById("scoreGrade").style.color=gc; // show raw score + avg indicator var scoreLabel=document.getElementById("scoreLabel"); if(avgCount>=2){ scoreLabel.innerHTML='Avg of '+avgCount+' scans' +'
this scan: '+s+''; } else { scoreLabel.textContent="Security Score"; } var nP=allR.filter(function(r){return r.status==="pass";}).length; var nF=allR.filter(function(r){return r.status==="fail";}).length; var nW=allR.filter(function(r){return r.status==="warn";}).length; var nI=allR.filter(function(r){return r.status==="info";}).length; document.getElementById("sP").textContent=nP;document.getElementById("sF").textContent=nF; document.getElementById("sW").textContent=nW;document.getElementById("sI").textContent=nI; // AI setStep("ai","Writing Hebrew security report..."); var crits=allR.filter(function(r){return r.sev==="critical";}).map(function(r){return r.name;}); var fails=allR.filter(function(r){return r.status==="fail";}).map(function(r){return r.name;}); var warns=allR.filter(function(r){return r.status==="warn";}).map(function(r){return r.name;}); try{ aiText=await callClaude( "You are a senior security researcher. Write a security report in Hebrew about these findings.\n"+ "Target: "+t+"\nScore: "+s+"/100 ("+g+")\nPlatform: "+platform+"\nWAF: "+wafName+"\n"+ "Critical: "+(crits.join(", ")||"none")+"\nFailed: "+(fails.join(", ")||"none")+"\nWarnings: "+(warns.join(", ")||"none")+"\n\n"+ "## Executive Summary\n## Critical Findings and Fixes\n## Hardening Recommendations (5 points)\n## Action Items (High/Medium/Low)\n\n"+ "Be specific. Do not invent findings not in the list." ); }catch(e){aiText="Score: "+s+"/100 ("+g+")\nFailed: "+(fails.join(", ")||"none");} renderCat("platform",allData["platform"]||[]); // FIX: save WAF + platform globally for PDF access lastWafName=wafName; lastHasWAF=hasWAF; lastPlatform=platform; var entry={url:t,score:displayScore,rawScore:s,grade:g,time:Date.now(),fails:nF,warns:nW,passes:nP,crits:crits.length,platform:platform,waf:wafName}; hist=[entry].concat(hist.filter(function(x){return x.url!==t;})).slice(0,50); saveHist(); ["dashBtn","remBtn","cmpBtn","mitreBtn","pdfBtn"].forEach(function(id){document.getElementById(id).style.display="inline-block";}); setStep("done","Score: "+displayScore+"/100 ("+g+")"); sb.textContent="Scan";sb.classList.remove("run");sb.disabled=false; document.getElementById("cancelBtn").style.display="none"; scanning=false;stopTimer(); // Hook dashboard filter buttons to also navigate to scan view setTimeout(function(){ ["all","critical","high","medium","low","fail","warn","pass"].forEach(function(f){ var btn=document.getElementById("flt_"+f); if(!btn)return; btn.addEventListener("click",function(){ // If in dashboard, switch to scan view with filter applied var sv=document.getElementById("scanView"); if(sv&&sv.style.display!=="block"&&Object.keys(allData).length>0){ // go to first category that has matching items var target=CATS[0].id; showCat(target); setTimeout(function(){setFilter(f);},50); } }); }); },100); } function showDash(){ showView("dashView"); var dv=document.getElementById("dashView"); var allR=Object.values(allData).flat(); var nP=allR.filter(function(r){return r.status==="pass";}).length; var nF=allR.filter(function(r){return r.status==="fail";}).length; var nW=allR.filter(function(r){return r.status==="warn";}).length; var sc=curScore||0,gr=curGrade||"--"; var gc=sc>=80?"#00e5a0":sc>=60?"#f59e0b":"#ff4060"; var segs=[ ["CRIT",allR.filter(function(r){return r.sev==="critical";}).length,"#ff4060"], ["HIGH",allR.filter(function(r){return r.sev==="high";}).length,"#f59e0b"], ["MED",allR.filter(function(r){return r.sev==="medium";}).length,"#a855f7"], ["LOW",allR.filter(function(r){return r.sev==="low";}).length,"#38bdf8"], ["PASS",nP,"#00e5a0"] ]; var riskBar='
'; segs.forEach(function(s){if(s[1]>0)riskBar+='
';}); riskBar+='
'; segs.forEach(function(s){if(s[1]>0)riskBar+='■ '+s[0]+' ('+s[1]+')';}); riskBar+='
'; // FIX: Category health bars - clean implementation var catBars=""; CATS.forEach(function(cat){ var it=allData[cat.id]||[];if(!it.length)return; var actionable=it.filter(function(r){return r.status!=="info";}); var f=it.filter(function(r){return r.status==="fail";}).length; var w=it.filter(function(r){return r.status==="warn";}).length; var p=it.filter(function(r){return r.status==="pass";}).length; var isInfoOnly=actionable.length===0; var cannotTest=!isInfoOnly&&actionable.every(function(r){ var d=(r.detail||"").toLowerCase(); return d.indexOf("proxy")>=0||d.indexOf("requires")>=0||d.indexOf("run:")>=0||d.indexOf("dig ")>=0||d.indexOf("manual")>=0||d.indexOf("skipped")>=0; }); var hasRealResult=actionable.some(function(r){return(r.status==="fail"||r.status==="pass")&&(r.detail||"").toLowerCase().indexOf("proxy")<0;}); if((isInfoOnly||cannotTest)&&!hasRealResult){ catBars+='
'+ '
'+cat.ico+' '+cat.name+'
'+ '
'+ '
N/A
'; return; } var passScore=p+(w*0.5); var pct=actionable.length>0?Math.round((passScore/actionable.length)*100):0; var col=f>0?"#ff4060":w>0?"#f59e0b":"#00e5a0"; catBars+='
'+ '
'+cat.ico+' '+cat.name+'
'+ '
'+ '
'+pct+'%
'; }); var tops=allR.filter(function(r){return r.sev==="critical"||r.sev==="high"||r.status==="fail";}).slice(0,8); var findHTML=tops.length?tops.map(function(r){ var bc=SC[r.sev]||"#6b7fa8",cn=""; CATS.forEach(function(cat){if((allData[cat.id]||[]).indexOf(r)!==-1)cn=cat.name;}); return'
'+(r.sev||"info")+''+ '
'+esc(r.name)+'
'+esc(cn)+(r.detail?" - "+esc(r.detail.substring(0,70)):"")+'
'; }).join(""):'
No critical findings
'; // Radar chart var rcats=CATS.slice(0,8),cx=110,cy=110,rad=80; var ptData=rcats.map(function(cat,i){ var angle=(i/rcats.length)*Math.PI*2-Math.PI/2; var it=allData[cat.id]||[]; var actionable=it.filter(function(r){return r.status!=="info";}); var p2=it.filter(function(r){return r.status==="pass";}).length; var f2=it.filter(function(r){return r.status==="fail";}).length; var w2=it.filter(function(r){return r.status==="warn";}).length; var score2=actionable.length===0?0.6:Math.max(0.08,(p2-(f2*0.8)-(w2*0.3))/Math.max(actionable.length,1)); var rv=rad*Math.min(1,Math.max(0.08,score2)); return{x:(cx+Math.cos(angle)*rv).toFixed(1),y:(cy+Math.sin(angle)*rv).toFixed(1), lx:(cx+Math.cos(angle)*(rad+22)).toFixed(1),ly:(cy+Math.sin(angle)*(rad+22)).toFixed(1),name:cat.name.split(" ")[0]}; }); var gridPts=rcats.map(function(_,i){var a=(i/rcats.length)*Math.PI*2-Math.PI/2;return{x:(cx+Math.cos(a)*rad).toFixed(1),y:(cy+Math.sin(a)*rad).toFixed(1)};}); var svg=''; [0.25,0.5,0.75,1].forEach(function(f){ var pts=gridPts.map(function(p){return(cx+(p.x-cx)*f).toFixed(1)+","+(cy+(p.y-cy)*f).toFixed(1);}).join(" "); svg+=''; }); gridPts.forEach(function(p){svg+='';}); svg+=''; ptData.forEach(function(p){ svg+=''+ ''+p.name+''; }); svg+=''; dv.innerHTML= '
'+ '
Security Dashboard
'+ '
'+new Date().toLocaleString()+'
'+ '
'+ buildFilterBar(allR)+ '
'+ '
'+ '
Security Score
'+ '
'+sc+' /100
'+ '
Grade: '+gr+'
'+ '
'+ '
Checks Passed
'+nP+'
out of '+allR.length+'
'+ '
Checks Failed
'+nF+'
'+allR.filter(function(r){return r.sev==="critical";}).length+' critical
'+ '
Warnings
'+nW+'
need review
'+ '
'+ '
'+ '
Risk Distribution
'+riskBar+'
'+ '
Category Radar
'+svg+'
'+ '
'+ '
'+ '
Category Health
'+catBars+'
'+ '
Top Findings
'+findHTML+'
'+ '
'; } function renderHistList(filter){ var list=document.getElementById("histList"); var filtered=filter?hist.filter(function(h){return h.url.toLowerCase().indexOf(filter.toLowerCase())>=0;}):hist; if(!filtered.length){list.innerHTML='
No scans yet
';return;} // Group by domain for timeline var byDomain={}; filtered.forEach(function(h){ var dom="";try{dom=new URL(h.url).hostname;}catch(e){dom=h.url;} if(!byDomain[dom])byDomain[dom]=[]; byDomain[dom].push(h); }); var html=""; Object.entries(byDomain).forEach(function(entry){ var dom=entry[0],scans=entry[1]; // Sort oldest→newest for timeline var sorted=scans.slice().sort(function(a,b){return a.time-b.time;}); var latest=scans[0]; // hist is newest-first var gc=latest.score>=80?"#00e5a0":latest.score>=60?"#f59e0b":"#ff4060"; // Draw inline SVG timeline if >1 scan var timelineSvg=""; if(sorted.length>1){ var W=180,H=36,pad=8; var scores=sorted.map(function(s){return s.score;}); var minS=Math.max(0,Math.min.apply(null,scores)-10); var maxS=Math.min(100,Math.max.apply(null,scores)+10); var pts=scores.map(function(s,i){ var x=pad+(i/(scores.length-1))*(W-pad*2); var y=H-pad-((s-minS)/(maxS-minS||1))*(H-pad*2); return{x:x.toFixed(1),y:y.toFixed(1),s:s}; }); var polyline=pts.map(function(p){return p.x+","+p.y;}).join(" "); // Fill area under line var fillPts=""+pad+","+(H-pad)+" "+polyline+" "+(W-pad)+","+(H-pad); var trend=scores[scores.length-1]-scores[0]; var lineColor=trend>0?"#00e5a0":trend<0?"#ff4060":"#f59e0b"; timelineSvg=''+ ''+ ''+ ''+ ''+ ''+ ''+ pts.map(function(p,i){ var isLatest=i===pts.length-1; return''+ (isLatest?''+p.s+'':""); }).join("")+ // trend arrow label ''+scores[0]+''+ ''; } // Domain header html+='
'+ ''+esc(dom)+''+ (scans.length>1?''+scans.length+' scans':'')+ '
'; // Timeline chart row (if multiple scans) if(sorted.length>1){ var trend=sorted[sorted.length-1].score-sorted[0].score; var trendCol=trend>0?"#00e5a0":trend<0?"#ff4060":"#f59e0b"; var trendStr=(trend>0?"+":"")+trend+" pts"; html+='
'+ timelineSvg+ '
'+ '
Trend over '+sorted.length+' scans
'+ '
'+trendStr+'
'+ '
'+ sorted.map(function(s){ var c=s.score>=80?"#00e5a0":s.score>=60?"#f59e0b":"#ff4060"; return''+s.score+''; }).join(' → ')+ '
'+ '
'+ '
'; } // Individual scan items scans.forEach(function(h){ var gc2=h.score>=80?"#00e5a0":h.score>=60?"#f59e0b":"#ff4060"; var d=Math.round((Date.now()-h.time)/60000); var rel=d<1?"just now":d<60?d+"m ago":d<1440?Math.round(d/60)+"h ago":Math.round(d/1440)+"d ago"; var realIdx=hist.indexOf(h); html+='
'+ '
'+ '
'+ (h.waf&&h.waf!=="None detected"?''+esc(h.waf)+'':'')+ ''+h.fails+' fail'+ (h.rawScore&&h.rawScore!==h.score?'raw: '+h.rawScore+'':'')+ '
'+ '
'+ '
'+ ''+h.score+''+ ''+rel+''+ '
'+ '🗑'+ '
'; }); }); list.innerHTML=html; } function deleteHistItem(i){hist.splice(i,1);saveHist();renderHistList(document.getElementById("histSearch").value);toast("Deleted","success",1500);} document.getElementById("histBtn").onclick=function(){showView("histPanel");renderHistList("");}; document.getElementById("histCloseBtn").onclick=function(){showHome();}; document.getElementById("histClearBtn").onclick=function(){if(!confirm("Clear all history?"))return;hist=[];saveHist();renderHistList("");toast("Cleared");}; document.getElementById("histSearch").oninput=function(){renderHistList(this.value);}; document.getElementById("cmpBtn").onclick=function(){ showView("cmpView"); var cv=document.getElementById("cmpView"); if(hist.length<2){ cv.innerHTML='
Compare
Run at least 2 scans to compare.
'; return; } var opts=hist.map(function(h,i){ var dom="";try{dom=new URL(h.url).hostname;}catch(e){dom=h.url;} var d=Math.round((Date.now()-h.time)/60000); var rel=d<1?"just now":d<60?d+"m ago":d<1440?Math.round(d/60)+"h ago":Math.round(d/1440)+"d ago"; return''; }).join(""); cv.innerHTML= '
Scan Diff
'+ '
'+ '
Scan A (older)
'+ '
'+ '
'+ '
Scan B (newer)
'+ '
'+ '
'+ '
'; // default: first vs second if(hist.length>=2){ document.getElementById("cmpA").value="1"; document.getElementById("cmpB").value="0"; } renderCmp(); }; function renderCmp(){ var aIdx=parseInt(document.getElementById("cmpA").value||"0"); var bIdx=document.getElementById("cmpB").value; if(bIdx==="")return; bIdx=parseInt(bIdx); if(isNaN(bIdx)||aIdx===bIdx){ document.getElementById("cmpResult").innerHTML='
Select two different scans.
'; return; } var a=hist[aIdx],b=hist[bIdx]; var diff=b.score-a.score; var diffCol=diff>0?"#00e5a0":diff<0?"#ff4060":"#6b7fa8"; var gA=a.score>=80?"#00e5a0":a.score>=60?"#f59e0b":"#ff4060"; var gB=b.score>=80?"#00e5a0":b.score>=60?"#f59e0b":"#ff4060"; var domA="";try{domA=new URL(a.url).hostname;}catch(e){domA=a.url;} var domB="";try{domB=new URL(b.url).hostname;}catch(e){domB=b.url;} var dA=new Date(a.time).toLocaleString(); var dB=new Date(b.time).toLocaleString(); // ── Score header ── var header= '
'+ '
'+ '
Scan A
'+ '
'+a.score+'
'+ '
'+esc(domA)+'
'+ '
'+dA+'
'+ '
'+ ''+a.fails+' fail'+ ''+a.warns+' warn'+ ''+a.passes+' pass'+ '
'+ '
'+ '
'+ '
'+(diff>0?"+":"")+diff+'
'+ '
pts
'+ '
'+(diff>0?"📈":diff<0?"📉":"➡")+'
'+ '
'+ '
'+ '
Scan B
'+ '
'+b.score+'
'+ '
'+esc(domB)+'
'+ '
'+dB+'
'+ '
'+ ''+b.fails+' fail'+ ''+b.warns+' warn'+ ''+b.passes+' pass'+ '
'+ '
'+ '
'; // ── WAF diff ── var wafA=a.waf||"None detected"; var wafB=b.waf||"None detected"; var wafDiff=""; if(wafA!==wafB){ wafDiff='
'+ '⚠ WAF changed: '+esc(wafA)+''+esc(wafB)+''+ '
'; } // ── Stats diff bars ── var statRows=[ {label:"Fail", vA:a.fails, vB:b.fails, better:"lower", col:"#ff4060"}, {label:"Warn", vA:a.warns, vB:b.warns, better:"lower", col:"#f59e0b"}, {label:"Pass", vA:a.passes, vB:b.passes, better:"higher",col:"#00e5a0"}, {label:"Crits", vA:a.crits||0,vB:b.crits||0,better:"lower",col:"#ff4060"}, ]; var statsHtml=statRows.map(function(r){ var d=r.vB-r.vA; var improved=(r.better==="lower"&&d<0)||(r.better==="higher"&&d>0); var worsened=(r.better==="lower"&&d>0)||(r.better==="higher"&&d<0); var dCol=improved?"#00e5a0":worsened?"#ff4060":"#6b7fa8"; var maxV=Math.max(r.vA,r.vB,1); return'
'+ '
'+r.vA+'
'+ '
'+ '
'+ '
'+ '
'+r.label+'
'+ '
'+ '
'+ '
'+ '
'+r.vB+ (d!==0?''+(d>0?"+":"")+d+'':'')+ '
'+ '
'; }).join(""); // ── Category diff ── // Compare category statuses stored in history // We'll compare WAF, platform, fails, warns as proxy for category health var catDiffs=[]; // URL change if(a.url!==b.url) catDiffs.push({type:"url",label:"URL",a:a.url,b:b.url,sev:"info"}); // Platform change if((a.platform||"")!==(b.platform||"")) catDiffs.push({type:"platform",label:"Platform",a:a.platform||"Unknown",b:b.platform||"Unknown",sev:"warn"}); // WAF change if(wafA!==wafB) catDiffs.push({type:"waf",label:"WAF",a:wafA,b:wafB,sev:wafB==="None detected"?"fail":"pass"}); // Score change if(diff!==0) catDiffs.push({type:"score",label:"Score",a:String(a.score),b:String(b.score),sev:diff>0?"pass":"fail"}); // Fail count var failDiff=b.fails-a.fails; if(failDiff!==0) catDiffs.push({type:"fails",label:"Failed checks",a:String(a.fails),b:String(b.fails),sev:failDiff>0?"fail":"pass"}); // Critical count var critDiff=(b.crits||0)-(a.crits||0); if(critDiff!==0) catDiffs.push({type:"crits",label:"Critical findings",a:String(a.crits||0),b:String(b.crits||0),sev:critDiff>0?"fail":"pass"}); var diffRows=catDiffs.length>0?catDiffs.map(function(d){ var icon=d.sev==="pass"?"✅":d.sev==="fail"?"❌":d.sev==="warn"?"⚠️":"ℹ️"; var col=d.sev==="pass"?"#00e5a0":d.sev==="fail"?"#ff4060":d.sev==="warn"?"#f59e0b":"#6b7fa8"; return'
'+ ''+icon+''+ '
'+ '
'+esc(d.label)+'
'+ '
'+esc(d.a)+'
'+ '
'+ '
'+ '
'+esc(d.b)+'
'+ '
'; }).join(""): '
No differences detected between scans.
'; // ── Verdict ── var verdict=""; if(diff>=10) verdict='
✅ Significant improvement — +'+diff+' points. Security posture improved.
'; else if(diff>=1) verdict='
✅ Minor improvement — +'+diff+' points.
'; else if(diff<=-10)verdict='
🔴 Significant regression — '+diff+' points. Investigate new findings immediately.
'; else if(diff<0) verdict='
⚠ Minor regression — '+diff+' points. Review new findings.
'; else verdict='
➡ No score change. Review individual findings for shifts.
'; document.getElementById("cmpResult").innerHTML= header+ verdict+ '
'+ wafDiff+ '
'+ '
Stats comparison
'+ statsHtml+ '
'+ '
'+ '
Changes detected
'+ diffRows+ '
'+ '
'; } // ── Fix snippets database ── var FIX_DB={ // HTTP Headers "csp":{ title:"Content-Security-Policy", iis:'', nginx:"add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';\" always;", apache:'Header always set Content-Security-Policy "default-src \'self\'; script-src \'self\' \'unsafe-inline\'; img-src \'self\' data: https:; frame-ancestors \'none\';"', node:"app.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: [\"'self'\"], scriptSrc: [\"'self'\", \"'unsafe-inline'\"], imgSrc: [\"'self'\", \"data:\", \"https:\"], frameAncestors: [\"'none'\"] } }));", }, "hsts":{ title:"Strict-Transport-Security", iis:'', nginx:"add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\" always;", apache:'Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"', node:'app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));', }, "xfo":{ title:"X-Frame-Options", iis:'', nginx:"add_header X-Frame-Options \"DENY\" always;", apache:'Header always set X-Frame-Options "DENY"', node:'app.use(helmet.frameguard({ action: "deny" }));', }, "rp":{ title:"Referrer-Policy", iis:'', nginx:'add_header Referrer-Policy "strict-origin-when-cross-origin" always;', apache:'Header always set Referrer-Policy "strict-origin-when-cross-origin"', node:'app.use(helmet.referrerPolicy({ policy: "strict-origin-when-cross-origin" }));', }, "pp":{ title:"Permissions-Policy", iis:'', nginx:'add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;', apache:'Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"', node:'app.use(helmet.permittedCrossDomainPolicies()); // also add manually: res.setHeader("Permissions-Policy", "camera=(), microphone=()")', }, "xcto":{ title:"X-Content-Type-Options", iis:'', nginx:'add_header X-Content-Type-Options "nosniff" always;', apache:'Header always set X-Content-Type-Options "nosniff"', node:'app.use(helmet.noSniff());', }, "coep":{ title:"Cross-Origin-Embedder-Policy", iis:'', nginx:'add_header Cross-Origin-Embedder-Policy "require-corp" always;', apache:'Header always set Cross-Origin-Embedder-Policy "require-corp"', node:'res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");', }, "coop":{ title:"Cross-Origin-Opener-Policy", iis:'', nginx:'add_header Cross-Origin-Opener-Policy "same-origin" always;', apache:'Header always set Cross-Origin-Opener-Policy "same-origin"', node:'res.setHeader("Cross-Origin-Opener-Policy", "same-origin");', }, "corp":{ title:"Cross-Origin-Resource-Policy", iis:'', nginx:'add_header Cross-Origin-Resource-Policy "same-origin" always;', apache:'Header always set Cross-Origin-Resource-Policy "same-origin"', node:'res.setHeader("Cross-Origin-Resource-Policy", "same-origin");', }, "cc":{ title:"Cache-Control", iis:'', nginx:'add_header Cache-Control "no-store, no-cache, must-revalidate" always;', apache:'Header always set Cache-Control "no-store, no-cache, must-revalidate"', node:'res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");', }, // Security issues "open_redirect":{ title:"Open Redirect Fix", iis:"// In your controller/handler:\nstring[] allowedHosts = { \"yoursite.com\", \"www.yoursite.com\" };\nif (!allowedHosts.Contains(new Uri(returnUrl).Host))\n return Redirect(\"/\");", nginx:"# Block common redirect params at Nginx level:\nif ($arg_redirect ~* \"^https?://(?!yoursite\\.com)\") { return 403; }\nif ($arg_url ~* \"^https?://(?!yoursite\\.com)\") { return 403; }", apache:"RewriteCond %{QUERY_STRING} (redirect|url|next|return)=https?://(?!yoursite\\.com) [NC]\nRewriteRule .* - [F,L]", node:"const allowedHosts = ['yoursite.com'];\nconst returnUrl = req.query.redirect || req.query.url;\nif (returnUrl) {\n const host = new URL(returnUrl).hostname;\n if (!allowedHosts.includes(host)) return res.redirect('/');\n}", }, "csrf_missing":{ title:"CSRF Protection", iis:"// ASP.NET MVC — add to controller:\n[ValidateAntiForgeryToken]\npublic ActionResult Submit(FormModel model) { ... }\n// In view:\n@Html.AntiForgeryToken()", nginx:"# CSRF must be handled in application code, not Nginx.\n# Add SameSite cookie attribute:\nproxy_cookie_flags ~ secure httponly samesite=strict;", apache:"# In PHP:\nsession_start();\nif (empty($_SESSION['csrf_token'])) {\n $_SESSION['csrf_token'] = bin2hex(random_bytes(32));\n}\n// Validate on POST:\nif ($_POST['csrf_token'] !== $_SESSION['csrf_token']) die('Invalid CSRF token');", node:"const csrf = require('csurf');\napp.use(csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }));\napp.get('/form', (req, res) => res.render('form', { csrfToken: req.csrfToken() }));", }, "cookie_secure":{ title:"Cookie Secure + HttpOnly + SameSite", iis:"\n", nginx:"proxy_cookie_flags ~ secure httponly samesite=strict;", apache:"Header always edit Set-Cookie ^(.*)$ \"$1; Secure; HttpOnly; SameSite=Strict\"", node:"app.use(session({\n cookie: {\n secure: true,\n httpOnly: true,\n sameSite: 'strict',\n maxAge: 3600000\n }\n}));", }, "cors":{ title:"CORS Fix (Wildcard)", iis:'\n', nginx:"# Replace wildcard with specific origin:\nadd_header Access-Control-Allow-Origin \"https://yoursite.com\" always;\n# Or use map for multiple origins:\nmap $http_origin $cors_origin {\n default \"\";\n \"https://yoursite.com\" $http_origin;\n \"https://app.yoursite.com\" $http_origin;\n}\nadd_header Access-Control-Allow-Origin $cors_origin always;", apache:'Header always set Access-Control-Allow-Origin "https://yoursite.com"', node:"const allowedOrigins = ['https://yoursite.com', 'https://app.yoursite.com'];\napp.use(cors({ origin: (origin, cb) => allowedOrigins.includes(origin) ? cb(null, true) : cb(new Error('Not allowed')) }));", }, "rate_limiting":{ title:"Rate Limiting", iis:"\n\n \n \n", nginx:"limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;\nlimit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;\nlocation /api/ { limit_req zone=api burst=40 nodelay; limit_req_status 429; }\nlocation /login { limit_req zone=login burst=3 nodelay; }", apache:"# Install mod_evasive:\n\n DOSHashTableSize 3097\n DOSPageCount 5\n DOSSiteCount 100\n DOSPageInterval 1\n DOSSiteInterval 1\n DOSBlockingPeriod 10\n", node:"const rateLimit = require('express-rate-limit');\napp.use('/api/', rateLimit({ windowMs: 60000, max: 60, standardHeaders: true }));\napp.use('/login', rateLimit({ windowMs: 900000, max: 10 }));", }, "spf":{ title:"SPF DNS Record", iis:"# Add TXT record to DNS:\n# Name: @ (root domain)\n# Value: v=spf1 include:spf.protection.outlook.com -all\n\n# For Office 365:\nv=spf1 include:spf.protection.outlook.com -all\n# For Google Workspace:\nv=spf1 include:_spf.google.com -all\n# For custom mail server:\nv=spf1 ip4:YOUR.SERVER.IP -all", nginx:"# SPF is DNS-level — configure at your DNS provider\n# Record type: TXT\n# Name: @ or yourdomain.com\n# Value: v=spf1 ip4:YOUR.IP include:mailprovider.com -all", apache:"# SPF is DNS-level — configure at your DNS provider\n# Record type: TXT\n# Name: @ or yourdomain.com\n# Value: v=spf1 ip4:YOUR.IP include:mailprovider.com -all", node:"// SPF is DNS-level — configure at your DNS provider\n// Record: TXT @ \"v=spf1 ip4:YOUR.IP include:mailprovider.com -all\"", }, "dmarc":{ title:"DMARC DNS Record", iis:"# Add TXT record to DNS:\n# Name: _dmarc.yourdomain.com\n# Value:\nv=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s\n\n# Stages:\n# 1. Start: p=none (monitor only)\n# 2. Move to: p=quarantine\n# 3. Finally: p=reject (full enforcement)", nginx:"# DMARC is DNS-level:\n# TXT _dmarc.yourdomain.com\n# \"v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com\"", apache:"# DMARC is DNS-level:\n# TXT _dmarc.yourdomain.com\n# \"v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com\"", node:"// DMARC is DNS-level:\n// TXT _dmarc.yourdomain.com\n// \"v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com\"", }, "dmarc_enforcement":{ title:"DMARC Enforcement", iis:"# Change DMARC TXT record at DNS:\n# From: v=DMARC1; p=none; ...\n# To: v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com\n# Or enforce fully: p=reject", nginx:"# DMARC enforcement is DNS-level\n# Update TXT _dmarc.yourdomain.com to p=quarantine or p=reject", apache:"# DMARC enforcement is DNS-level\n# Update TXT _dmarc.yourdomain.com to p=quarantine or p=reject", node:"// DMARC enforcement is DNS-level\n// Update TXT _dmarc.yourdomain.com to p=quarantine or p=reject", }, "caa":{ title:"CAA DNS Records", iis:"# Add CAA records to DNS:\n# Name: yourdomain.com\n# Type: CAA\n# Values:\n0 issue \"letsencrypt.org\"\n0 issue \"digicert.com\"\n0 issuewild \";\"\n0 iodef \"mailto:security@yourdomain.com\"", nginx:"# CAA is DNS-level — add at your DNS provider:\n# CAA 0 issue \"letsencrypt.org\"\n# CAA 0 issuewild \";\"", apache:"# CAA is DNS-level — add at your DNS provider:\n# CAA 0 issue \"letsencrypt.org\"\n# CAA 0 issuewild \";\"", node:"// CAA is DNS-level — add at your DNS provider:\n// CAA 0 issue \"letsencrypt.org\"\n// CAA 0 issuewild \";\"", }, "mta_sts":{ title:"MTA-STS Setup", iis:"# 1. Create policy file at https://mta-sts.yourdomain.com/.well-known/mta-sts.txt\nversion: STSv1\nmode: enforce\nmx: mail.yourdomain.com\nmax_age: 604800\n\n# 2. Add DNS TXT record:\n# Name: _mta-sts.yourdomain.com\n# Value: v=STSv1; id=20260101001\n\n# 3. Add TLS-RPT record:\n# Name: _smtp._tls.yourdomain.com\n# Value: v=TLSRPTv1; rua=mailto:tls-reports@yourdomain.com", nginx:"# Host policy file:\nserver {\n server_name mta-sts.yourdomain.com;\n location /.well-known/mta-sts.txt {\n return 200 \"version: STSv1\\nmode: enforce\\nmx: mail.yourdomain.com\\nmax_age: 604800\";\n add_header Content-Type text/plain;\n }\n}", apache:"# Host policy file:\nAlias /.well-known/mta-sts.txt /var/www/mta-sts.txt\n# Create file with:\n# version: STSv1\n# mode: enforce\n# mx: mail.yourdomain.com\n# max_age: 604800", node:"app.get('/.well-known/mta-sts.txt', (req, res) => {\n res.type('text/plain');\n res.send('version: STSv1\\nmode: enforce\\nmx: mail.yourdomain.com\\nmax_age: 604800');\n});", }, "waf_detected":{ title:"Add WAF Protection", iis:"# Options for Windows/IIS:\n# 1. Azure Front Door WAF (recommended)\n# → Portal: Front Door > WAF Policy > Create\n# 2. Cloudflare (free tier available)\n# → cloudflare.com > Add Site > DNS proxy\n# 3. IIS URL Rewrite + request filtering:\n\n \n \n \n", nginx:"# Cloudflare (recommended — free):\n# Change DNS A record to Cloudflare proxy IPs\n# Enable WAF in Cloudflare Dashboard\n\n# ModSecurity (self-hosted):\napt install libapache2-mod-security2 # or nginx-module-security\n# Download OWASP CRS:\ngit clone https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs", apache:"# ModSecurity:\napt install libapache2-mod-security2\na2enmod security2\n# Download OWASP CRS:\ngit clone https://github.com/coreruleset/coreruleset /etc/modsecurity/crs\ncp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf", node:"// Use Cloudflare as reverse proxy (recommended)\n// Or use express-rate-limit + helmet as baseline:\nconst helmet = require('helmet');\nconst rateLimit = require('express-rate-limit');\napp.use(helmet());\napp.use(rateLimit({ windowMs: 60000, max: 100 }));", }, "https_active":{ title:"Enable HTTPS", iis:"\n\n \n \n \n \n \n \n \n \n \n", nginx:"server {\n listen 80;\n server_name yourdomain.com;\n return 301 https://$host$request_uri;\n}\n# Get free cert with Let's Encrypt:\ncertbot --nginx -d yourdomain.com -d www.yourdomain.com", apache:"\n ServerName yourdomain.com\n Redirect permanent / https://yourdomain.com/\n\n# Get free cert:\ncertbot --apache -d yourdomain.com", node:"const https = require('https');\nconst fs = require('fs');\nconst opts = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') };\nhttps.createServer(opts, app).listen(443);\n// Redirect HTTP:\nhttp.createServer((req, res) => res.writeHead(301, { Location: 'https://' + req.headers.host + req.url }).end()).listen(80);", }, "env_exposure":{ title:"Protect .env file", iis:"\n\n \n \n \n \n \n \n \n \n", nginx:"location ~ /\\.(env|git|htaccess) {\n deny all;\n return 404;\n}", apache:"\n Require all denied\n", node:"// Never serve .env — use environment variables:\n// process.env.MY_SECRET (not from file)\n// Add to .gitignore:\n// .env\n// .env.*", }, }; // ── Detect server platform from scan ── function detectPlatform(){ var srv=(proxyData&&proxyData.headers&&proxyData.headers["server"]||"").toLowerCase(); var powered=(proxyData&&proxyData.headers&&proxyData.headers["x-powered-by"]||"").toLowerCase(); if(srv.includes("iis")||powered.includes("asp"))return"iis"; if(srv.includes("nginx"))return"nginx"; if(srv.includes("apache"))return"apache"; if(powered.includes("express")||powered.includes("node"))return"node"; return"iis"; // default for Ituran } // ── Show fix modal ── var fixModal=null; function showFix(itemId,itemName){ var fix=null; // find fix by matching item id prefix Object.keys(FIX_DB).forEach(function(k){ if(itemId&&itemId.toLowerCase().includes(k.toLowerCase())) fix=FIX_DB[k]; }); // try name match if(!fix){ Object.keys(FIX_DB).forEach(function(k){ if(itemName&&itemName.toLowerCase().includes(k.toLowerCase())) fix=FIX_DB[k]; }); } if(!fix){ toast("No fix template available for this finding","error",2000); return; } var detectedPlatform=detectPlatform(); var platforms=["iis","nginx","apache","node"]; var platformLabels={"iis":"IIS / web.config","nginx":"Nginx","apache":"Apache","node":"Node.js / Express"}; // Remove old modal if exists var old=document.getElementById("fixModal"); if(old)old.remove(); var modal=document.createElement("div"); modal.id="fixModal"; modal.className="modal-overlay open"; modal.innerHTML= ''; modal.addEventListener("click",function(e){if(e.target===modal)modal.remove();}); document.body.appendChild(modal); fixModal={fix:fix,current:detectedPlatform}; } function switchFixPlatform(p){ if(!fixModal)return; fixModal.current=p; var fc=document.getElementById("fixCode"); if(fc)fc.textContent=fixModal.fix[p]||fixModal.fix.iis||"No fix available for this platform"; var platformLabels={"iis":"IIS / web.config","nginx":"Nginx","apache":"Apache","node":"Node.js / Express"}; ["iis","nginx","apache","node"].forEach(function(pl){ var btn=document.getElementById("fixBtn_"+pl); if(!btn)return; var isActive=pl===p; btn.style.borderColor=isActive?"var(--accent)":"var(--border2)"; btn.style.background=isActive?"rgba(0,229,160,.1)":"var(--bg3)"; btn.style.color=isActive?"var(--accent)":"var(--text2)"; btn.textContent=platformLabels[pl]+(isActive?" ✓":""); }); } function copyFix(){ var code=document.getElementById("fixCode"); if(!code)return; navigator.clipboard.writeText(code.textContent).then(function(){ toast("Code copied to clipboard"); }).catch(function(){ // fallback var ta=document.createElement("textarea"); ta.value=code.textContent; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); toast("Code copied"); }); } document.getElementById("remBtn").onclick=function(){ showView("remView"); var rv=document.getElementById("remView"); var items=buildRemItems(); var res=0;items.forEach(function(it,i){if(doneSet.has(it.id+"_"+i))res++;}); var pct=items.length?Math.round((res/items.length)*100):0; var html='
Remediation Plan
'+ ''+res+"/"+items.length+' resolved
'+ '
'; if(!items.length)html+='
No issues to remediate!
'; else{ items.forEach(function(item,i){ var k=item.id+"_"+i,isDone=doneSet.has(k),bc=SC[item.sev]||"#6b7fa8"; // check if fix exists var hasFix=false; Object.keys(FIX_DB).forEach(function(fk){ if(item.id&&item.id.toLowerCase().includes(fk.toLowerCase())) hasFix=true; if(item.name&&item.name.toLowerCase().includes(fk.toLowerCase())) hasFix=true; }); html+='
'+ ''+ '
'+ '
'+ ''+(isDone?""+esc(item.name)+"":esc(item.name))+''+ ''+item.sev+''+ ''+esc(item.catName)+''+ '
'+ '
'+esc(item.detail)+'
'+ (hasFix?'': '📄 Manual fix required')+ '
'; }); } rv.innerHTML=html; }; // FIX: Use proper function call instead of .onclick() function toggleDone(k,checked){ if(checked)doneSet.add(k);else doneSet.delete(k); document.getElementById("remBtn").click(); } document.getElementById("pdfBtn").onclick=function(){ var sc2=curScore||0,gr2=curGrade||"--"; var gc2=sc2>=80?"#00e5a0":sc2>=60?"#f59e0b":"#ff4060"; var allR=Object.values(allData).flat(); var nF=allR.filter(function(r){return r.status==="fail";}).length; var nW=allR.filter(function(r){return r.status==="warn";}).length; var nP2=allR.filter(function(r){return r.status==="pass";}).length; var nI2=allR.filter(function(r){return r.status==="info";}).length; var targetUrl=document.getElementById("urlInput").value; // FIX: use global WAF variables instead of local (which were undefined here) var wafNameSafe = lastWafName || "N/A"; var hasWAFSafe = lastHasWAF || false; var remItems=buildRemItems(); var remBySev={critical:[],high:[],medium:[],low:[]}; remItems.forEach(function(item){if(remBySev[item.sev])remBySev[item.sev].push(item);}); var SEV_LABELS={critical:{label:"Critical",color:"#ff4060"},high:{label:"High",color:"#f59e0b"},medium:{label:"Medium",color:"#a855f7"},low:{label:"Low",color:"#38bdf8"}}; var catSections=CATS.map(function(cat){ var items=allData[cat.id]||[];if(!items.length)return""; var col=catColor(cat.id); return'
'+ '
'+ ''+cat.name+''+ ''+catLabel(cat.id)+''+ '
'+ items.map(function(item){ var stc=STC[item.status]||"#3d4f6e",bc=SC[item.sev]||"#6b7fa8"; return'
'+ '
'+ ''+item.name+''+ (item.sev&&item.sev!=="pass"&&item.sev!=="info"?''+item.sev+'':'')+ '
'+ '
'+item.detail+'
'+ (item.value?'
'+item.value+'
':'')+ '
'; }).join("")+ '
'; }).join(""); var aiSection=aiText? '
'+ '
Claude AI Analysis
'+ '
'+aiText+'
'+ '
':""; var remSection='
'+ '
Remediation Plan
'+ '
'+ Object.entries(remBySev).map(function(e){ var info=SEV_LABELS[e[0]]; return'
'+ '
'+e[1].length+'
'+ '
'+e[0]+'
'+ '
'; }).join("")+ '
'; Object.entries(remBySev).forEach(function(e){ var sev=e[0],items2=e[1],info=SEV_LABELS[sev]; if(!items2.length)return; remSection+='
'+ '
'+ ''+sev.toUpperCase()+''+ '
'; items2.forEach(function(item){ remSection+= '
'+ '
'+ ''+item.name+''+ ''+sev+''+ ''+esc(item.catName)+''+ '
'+ '
'+item.detail+'
'+ (item.value?'
'+item.value+'
':'')+ '
'; }); remSection+='
'; }); remSection+='
'; var html=''+ ''+ 'SEC//AUDIT - '+targetUrl+''+ ''+ '
'+ '
'+ '
'+ '
SEC//AUDIT v5
'+ '
Security Audit Report
'+ '
'+targetUrl+'
'+ '
'+new Date().toLocaleString()+'
'+ '
WAF: '+esc(wafNameSafe)+'
'+ '
'+ '
'+ '
'+sc2+'
'+ '
/100 - Grade '+gr2+'
'+ '
'+nF+' fail - '+nW+' warn - '+nP2+' pass
'+ '
'+ '
'+ '
'+ [["FAIL",nF,"#ff4060"],["WARN",nW,"#f59e0b"],["PASS",nP2,"#00e5a0"],["INFO",nI2,"#3d4f6e"]].map(function(s){ return'
'+ '
'+s[1]+'
'+ '
'+s[0]+'
'+ '
'; }).join("")+ '
'+ catSections+aiSection+remSection+ '
SEC//AUDIT v5 - AI-Powered Security Scanning - '+new Date().toLocaleDateString()+'
'+ '
'; var w=window.open("","_blank","width=1000,height=800"); if(w){w.document.write(html);w.document.close();setTimeout(function(){w.print();},800);toast("PDF dialog opened");} else{ var a=document.createElement("a"); a.href=URL.createObjectURL(new Blob([html],{type:"text/html"})); a.download="sec-audit-"+Date.now()+".html";a.click(); toast("Downloaded as HTML"); } }; document.getElementById("dlBtn").onclick=function(){ var allR=Object.values(allData).flat(); var sc2=curScore||0,gr2=curGrade||"--",gc2=sc2>=80?"#00e5a0":sc2>=60?"#f59e0b":"#ff4060"; var rows=CATS.map(function(cat){ return"

"+cat.name+"

"+ (allData[cat.id]||[]).map(function(item){ var stc=STC[item.status]||"#3d4f6e",bc=SC[item.sev]||"#6b7fa8"; return"
"+ ""+item.name+" "+ "["+item.sev+"]"+ "
"+item.detail+"
"+ "
"; }).join(""); }).join(""); var html="SEC//AUDIT Report"+ ""+ "
SEC//AUDIT v5
"+ "

Target: "+esc(document.getElementById("urlInput").value)+"

"+ "
"+sc2+"/100 ("+gr2+")
"+ rows+ (aiText?"

AI Analysis

"+ "
"+esc(aiText)+"
":"")+ ""; var a=document.createElement("a"); a.href=URL.createObjectURL(new Blob([html],{type:"text/html"})); a.download="sec-audit-"+Date.now()+".html";a.click(); toast("HTML report downloaded"); }; // MITRE function showMitreView(){ if(Object.keys(allData).length===0){toast("Run a scan first","error");return;} showView("dashView"); var dv=document.getElementById("dashView"); var MMAP={ "csp":{id:"T1059.007",tactic:"Defense Evasion",name:"XSS via missing CSP"}, "hsts":{id:"T1557.002",tactic:"Credential Access",name:"SSL Stripping"}, "xfo":{id:"T1185",tactic:"Collection",name:"Clickjacking"}, "xcto":{id:"T1059.007",tactic:"Defense Evasion",name:"MIME Sniffing"}, "env_exposure":{id:"T1552.001",tactic:"Credential Access",name:".env Credential Exposure"}, "git_exposure":{id:"T1552.001",tactic:"Credential Access",name:".git Config Exposure"}, "spf":{id:"T1566.002",tactic:"Initial Access",name:"Email Spoofing (no SPF)"}, "dmarc":{id:"T1566.002",tactic:"Initial Access",name:"Email Spoofing (no DMARC)"}, "cors":{id:"T1190",tactic:"Initial Access",name:"Wildcard CORS"}, "waf_detected":{id:"T1190",tactic:"Initial Access",name:"No WAF Protection"}, "cookie_secure":{id:"T1539",tactic:"Credential Access",name:"Cookie Theft"}, "csrf_missing":{id:"T1185",tactic:"Collection",name:"CSRF Attack"}, "rl_missing": {id:"T1110", tactic:"Credential Access", name:"Brute Force"}, "stko_": {id:"T1584.001",tactic:"Resource Development",name:"Subdomain Takeover"}, "ap_": {id:"T1190", tactic:"Initial Access", name:"Exposed Admin Panel"}, "jwt_": {id:"T1550.001",tactic:"Credential Access", name:"JWT Weakness"}, "dc_": {id:"T1195.001",tactic:"Initial Access", name:"Dependency Confusion"}, "crlf_": {id:"T1059.007",tactic:"Defense Evasion", name:"HTTP Response Splitting"}, "cloud_": {id:"T1530", tactic:"Collection", name:"Cloud Storage Object Access"}, "pt_": {id:"T1083", tactic:"Discovery", name:"Path Traversal"}, "pp_": {id:"T1059.007",tactic:"Defense Evasion", name:"Prototype Pollution"}, "cors_": {id:"T1190", tactic:"Initial Access", name:"CORS Misconfiguration"}, "hibp_breaches": {id:"T1589.001",tactic:"Reconnaissance", name:"Leaked Credentials"}, }; var findings=[]; Object.entries(allData).forEach(function(e){ var catId=e[0],checks=e[1]; var cat=CATS.find(function(c){return c.id===catId;}); (checks||[]).forEach(function(check){ if(check.status==="fail"||check.status==="warn"){ var mitre=MMAP[check.id]||null; findings.push({check:check,cat:cat,mitre:mitre}); } }); }); var byTactic={}; findings.forEach(function(f){ if(f.mitre){var tac=f.mitre.tactic;if(!byTactic[tac])byTactic[tac]=[];byTactic[tac].push(f);} }); var techniques=[...new Set(findings.filter(function(f){return f.mitre;}).map(function(f){return f.mitre.id;}))]; // FIX: Cleaned up MITRE tactic row rendering - removed broken HTML fragments var tacticRows=Object.entries(byTactic).map(function(e){ var tactic=e[0],items=e[1]; return'
'+ '
'+tactic+' ('+items.length+')
'+ items.map(function(f){ var bc=f.check.status==="fail"?"#ff4060":"#f59e0b"; var m=f.mitre; return'
'+ '
'+ ''+esc(f.check.name)+''+ (m?''+esc(m.id)+'':'')+ '
'+ '
'+esc(f.check.detail)+'
'+ '
'; }).join("")+ '
'; }).join(""); dv.innerHTML= '
MITRE ATT&CK Mapping
'+ '
'+ '
Techniques
'+techniques.length+'
'+ '
Tactics
'+Object.keys(byTactic).length+'
'+ '
'+ '
'+(tacticRows||'
No findings to map.
')+'
'; } document.getElementById("keyBtn").onclick=function(e){ e.stopPropagation(); document.getElementById("keyModal").classList.add("open"); document.getElementById("keyInput").value=apiKey; var kst=document.getElementById("kst"); kst.innerHTML=apiKey? '
Key saved (ends ...'+apiKey.slice(-6)+')
': '
No key - required outside claude.ai
'; }; document.getElementById("keyCancelBtn").onclick=function(){safeStyle("keyModal","display","none");document.getElementById("keyModal").classList.remove("open");}; document.getElementById("keySaveBtn").onclick=function(){ var v=(document.getElementById("keyInput")||{}).value||""; v=v.trim(); if(v){apiKey=v;try{localStorage.setItem("sa3_key",v);}catch(e){}} document.getElementById("keyModal").classList.remove("open"); var kb=document.getElementById("keyBtn");if(kb)kb.style.color=v?"#00e5a0":""; toast("API key saved"); }; if(apiKey){var kb=document.getElementById("keyBtn");if(kb)kb.style.color="#00e5a0";} document.getElementById("wafBtn").onclick=function(e){ e.stopPropagation(); var p=document.getElementById("wafPanel"); var isHidden=p.style.display==="none"||p.style.display===""; p.style.display=isHidden?"block":"none"; if(isHidden){ var rect=this.getBoundingClientRect(); p.style.top=(rect.bottom+6)+"px"; p.style.right=(window.innerWidth-rect.right)+"px"; p.style.left="auto"; var saved=localStorage.getItem("sa3_proxy")||""; if(saved)document.getElementById("proxyUrl").value=saved; } }; document.getElementById("proxyTestBtn").onclick=async function(){ var url=(document.getElementById("proxyUrl").value||"").trim().replace(/\/$/,""); var statusEl=document.getElementById("proxyStatus"); if(!url){if(statusEl)statusEl.textContent="Enter proxy URL";return;} if(statusEl){statusEl.textContent="Testing...";statusEl.style.color="var(--text3)";} try{ var r=await fetch(url+"/ping",{cache:"no-store",signal:AbortSignal.timeout(5000)}); if(r.ok){ var d=await r.json().catch(function(){return{version:"?"};}); proxyConnected=true; localStorage.setItem("sa3_proxy",url); var psEl=document.getElementById("proxyStatus"); if(psEl){psEl.textContent="Connected - proxy v"+(d.version||"?");psEl.style.color="var(--accent)";} var wb=document.getElementById("wafBtn");if(wb)wb.classList.add("active"); var pb=document.getElementById("proxyBadge");if(pb)pb.style.display="inline"; toast("Proxy connected"); }else throw new Error("HTTP "+r.status); }catch(e){ proxyConnected=false; var errMsg=e&&e.message?e.message:(e&&e.name==="TimeoutError"?"Timeout after 5s":"Connection refused"); var psEl2=document.getElementById("proxyStatus"); if(psEl2){psEl2.textContent="Failed: "+errMsg;psEl2.style.color="var(--red)";} var wb=document.getElementById("wafBtn");if(wb)wb.classList.remove("active"); var pb=document.getElementById("proxyBadge");if(pb)pb.style.display="none"; toast("Proxy failed: "+errMsg,"error"); } }; (function(){ var saved=localStorage.getItem("sa3_proxy")||""; if(saved){ var puEl=document.getElementById("proxyUrl"); if(puEl) puEl.value=saved; fetch(saved+"/ping",{cache:"no-store"}).then(function(r){return r.json();}).then(function(d){ proxyConnected=true; var psEl=document.getElementById("proxyStatus"); if(psEl){psEl.textContent="Connected - proxy v"+d.version;psEl.style.color="var(--accent)";} var wb=document.getElementById("wafBtn");if(wb)wb.classList.add("active"); var pb=document.getElementById("proxyBadge");if(pb)pb.style.display="inline"; }).catch(function(){proxyConnected=false;}); } })(); document.getElementById("wafOverride").onchange=function(){ var v=this.value; var btn=document.getElementById("wafBtn"); if(v&&v!==""){if(btn)btn.textContent=v;} else{if(btn)btn.innerHTML="⚙ Settings";} document.getElementById("wafPanel").style.display="none"; }; document.addEventListener("click",function(e){ var p=document.getElementById("wafPanel");if(p&&!p.contains(e.target)&&e.target.id!=="wafBtn")p.style.display="none"; document.getElementById("keyModal").classList.remove("open"); }); document.getElementById("keyModal").addEventListener("click",function(e){e.stopPropagation();}); document.getElementById("mitreBtn").onclick=function(e){e.stopPropagation();showMitreView();}; document.getElementById("dashBtn").onclick=function(e){ e.stopPropagation(); if(Object.keys(allData).length===0){toast("Run a scan first","error");return;} showDash(); }; document.getElementById("authBtn").onclick=function(){ document.getElementById("authTargetUrl").value=document.getElementById("urlInput").value; document.getElementById("authLoginUrl").value=""; document.getElementById("authStatus").textContent=""; document.getElementById("authModal").classList.add("open"); }; document.getElementById("authCancelBtn").onclick=function(){document.getElementById("authModal").classList.remove("open");}; document.getElementById("authRunBtn").onclick=async function(){ var targetUrl=document.getElementById("authTargetUrl").value.trim(); var username=document.getElementById("authUser").value.trim(); var password=document.getElementById("authPass").value; if(!targetUrl||!username||!password){document.getElementById("authStatus").textContent="Required fields missing.";return;} var proxyUrlVal=(localStorage.getItem("sa3_proxy")||"").trim().replace(/\/$/,""); if(!proxyConnected||!proxyUrlVal){document.getElementById("authStatus").textContent="Proxy must be connected for auth scan.";return;} document.getElementById("authStatus").textContent="Running authenticated scan..."; document.getElementById("authRunBtn").disabled=true; try{ var resp=await fetch(proxyUrlVal+"/scan-auth",{method:"POST",headers:{"Content-Type":"application/json"}, body:JSON.stringify({url:targetUrl,loginUrl:document.getElementById("authLoginUrl").value.trim()||undefined,username:username,password:password})}); await resp.json(); document.getElementById("authModal").classList.remove("open"); toast("Auth scan complete"); }catch(e){document.getElementById("authStatus").textContent="Error: "+e.message;} document.getElementById("authRunBtn").disabled=false; }; // ════════════════════════════════════════════════ // CUSTOM CHECK RULES ENGINE // ════════════════════════════════════════════════ var customRules=[]; try{customRules=JSON.parse(localStorage.getItem("sa_custom_rules")||"[]");}catch(e){} var RULE_PRESETS={ owasp:[ {id:"r_csp", name:"CSP Missing", type:"header_missing", p1:"content-security-policy", p2:"", sev:"high", message:"Content-Security-Policy header missing — XSS attacks have no mitigation (OWASP A03)"}, {id:"r_hsts", name:"HSTS Missing", type:"header_missing", p1:"strict-transport-security",p2:"", sev:"medium", message:"HSTS missing — SSL stripping possible (OWASP A02)"}, {id:"r_xcto", name:"X-Content-Type Missing",type:"header_missing", p1:"x-content-type-options", p2:"", sev:"medium", message:"X-Content-Type-Options missing — MIME sniffing possible (OWASP A05)"}, {id:"r_cors_wc", name:"Wildcard CORS", type:"header_equals", p1:"access-control-allow-origin",p2:"*", sev:"high", message:"Wildcard CORS — any origin can read responses (OWASP A05)"}, {id:"r_server", name:"Server Header Exposed", type:"header_contains", p1:"server", p2:".", sev:"low", message:"Server header exposes technology fingerprint (OWASP A05)"}, ], headers:[ {id:"r_xfo", name:"X-Frame-Options", type:"header_missing", p1:"x-frame-options", p2:"", sev:"medium", message:"X-Frame-Options missing — clickjacking possible"}, {id:"r_rp", name:"Referrer-Policy", type:"header_missing", p1:"referrer-policy", p2:"", sev:"low", message:"Referrer-Policy missing — URL leakage to third parties"}, {id:"r_pp", name:"Permissions-Policy", type:"header_missing", p1:"permissions-policy", p2:"", sev:"low", message:"Permissions-Policy missing — no browser feature restrictions"}, {id:"r_coep", name:"COEP Missing", type:"header_missing", p1:"cross-origin-embedder-policy",p2:"", sev:"low", message:"Cross-Origin-Embedder-Policy missing"}, {id:"r_coop", name:"COOP Missing", type:"header_missing", p1:"cross-origin-opener-policy",p2:"", sev:"low", message:"Cross-Origin-Opener-Policy missing"}, {id:"r_cache", name:"Cache-Control", type:"header_missing", p1:"cache-control", p2:"", sev:"low", message:"Cache-Control missing — sensitive data may be cached"}, ], api:[ {id:"r_api_ver", name:"API Version in Header", type:"header_contains", p1:"x-api-version", p2:".", sev:"low", message:"API version exposed in header — aids attacker reconnaissance"}, {id:"r_api_err", name:"Stack Trace in Body", type:"body_contains", p1:"", p2:"at Object.",sev:"high",message:"Stack trace exposed in response body — leaks internals"}, {id:"r_api_sql", name:"SQL Error in Body", type:"body_contains", p1:"", p2:"SQL syntax",sev:"critical",message:"SQL error message in response — possible SQLi"}, {id:"r_api_key", name:"API Key in Body", type:"body_contains", p1:"", p2:"sk_live_",sev:"critical",message:"Stripe live API key exposed in response"}, {id:"r_powered", name:"X-Powered-By Exposed", type:"header_contains", p1:"x-powered-by", p2:".", sev:"low", message:"X-Powered-By exposes technology stack"}, ], pci:[ {id:"r_pci_https",name:"HTTPS Required", type:"status_code", p1:"", p2:"301", sev:"critical",message:"PCI-DSS requires HTTPS — HTTP should redirect to HTTPS"}, {id:"r_pci_hsts", name:"HSTS Required (PCI)", type:"header_missing", p1:"strict-transport-security",p2:"", sev:"high", message:"PCI-DSS 4.0 requires HSTS on all cardholder data pages"}, {id:"r_pci_tls", name:"TLS 1.0 Forbidden", type:"body_not_contains", p1:"", p2:"TLSv1\b",sev:"high", message:"PCI-DSS forbids TLS 1.0 — only TLS 1.2+ allowed"}, {id:"r_pci_csp", name:"CSP Required (PCI 6.4)", type:"header_missing", p1:"content-security-policy", p2:"", sev:"high", message:"PCI-DSS 4.0 req 6.4.3 requires Content-Security-Policy"}, ], email:[ {id:"r_spf", name:"SPF Record", type:"dns_spf_missing", p1:"",p2:"", sev:"high", message:"SPF record missing — email spoofing possible"}, {id:"r_spf_sf", name:"SPF Softfail", type:"dns_spf_softfail", p1:"",p2:"", sev:"medium", message:"SPF uses ~all (softfail) — change to -all for strict enforcement"}, {id:"r_dmarc", name:"DMARC Record", type:"dns_dmarc_missing", p1:"",p2:"", sev:"high", message:"DMARC record missing — email spoofing possible"}, {id:"r_dmarc_rej",name:"DMARC not reject", type:"dns_dmarc_not_reject", p1:"",p2:"reject",sev:"medium",message:"DMARC policy is not p=reject — emails may not be blocked"}, {id:"r_caa", name:"CAA Records", type:"dns_caa_missing", p1:"",p2:"", sev:"low", message:"CAA records missing — any CA can issue certificates"}, {id:"r_mtasts", name:"MTA-STS Missing", type:"dns_mta_sts_missing", p1:"",p2:"", sev:"medium", message:"MTA-STS missing — email transport may not be encrypted"}, ], iso27001:[ {id:"iso_csp", name:"ISO A.14: CSP", type:"header_missing", p1:"content-security-policy",p2:"",sev:"high", message:"ISO 27001 A.14.2 — CSP header required for web application security"}, {id:"iso_hsts", name:"ISO A.10: HSTS", type:"header_missing", p1:"strict-transport-security",p2:"",sev:"high",message:"ISO 27001 A.10.1 — HSTS required for cryptographic controls"}, {id:"iso_xcto", name:"ISO A.14: X-Content-Type",type:"header_missing", p1:"x-content-type-options",p2:"",sev:"medium",message:"ISO 27001 A.14.2 — X-Content-Type-Options required"}, {id:"iso_tls", name:"ISO A.10: TLS Required", type:"tls_old_protocol", p1:"",p2:"", sev:"high", message:"ISO 27001 A.10.1 — TLS 1.0/1.1 forbidden, use TLS 1.2+"}, {id:"iso_spf", name:"ISO A.13: SPF", type:"dns_spf_missing", p1:"",p2:"", sev:"high", message:"ISO 27001 A.13.2 — SPF record required for information transfer"}, {id:"iso_dmarc", name:"ISO A.13: DMARC", type:"dns_dmarc_missing", p1:"",p2:"", sev:"high", message:"ISO 27001 A.13.2 — DMARC record required for email security"}, {id:"iso_cookie_s",name:"ISO A.14: Cookie Secure",type:"cookie_missing_secure", p1:"",p2:"", sev:"medium", message:"ISO 27001 A.14.2 — session cookies must have Secure flag"}, {id:"iso_cookie_h",name:"ISO A.14: Cookie HttpOnly",type:"cookie_missing_httponly",p1:"",p2:"", sev:"medium", message:"ISO 27001 A.14.2 — session cookies must have HttpOnly flag"}, ], gdpr:[ {id:"gdpr_https", name:"GDPR Art.32: HTTPS", type:"header_missing", p1:"strict-transport-security",p2:"",sev:"critical",message:"GDPR Art.32 requires encryption in transit — HSTS missing"}, {id:"gdpr_cookie_ss",name:"GDPR: Cookie SameSite",type:"cookie_missing_samesite",p1:"",p2:"", sev:"medium", message:"GDPR requires CSRF protection — cookies missing SameSite attribute"}, {id:"gdpr_csp", name:"GDPR Art.25: CSP", type:"header_missing", p1:"content-security-policy",p2:"",sev:"high", message:"GDPR Art.25 (Privacy by Design) — CSP helps prevent data leakage via XSS"}, {id:"gdpr_rp", name:"GDPR: Referrer-Policy", type:"header_missing", p1:"referrer-policy",p2:"", sev:"medium", message:"GDPR — Referrer-Policy missing, user data may leak to third parties"}, {id:"gdpr_rp_perm",name:"GDPR: Referrer permissive",type:"header_equals", p1:"referrer-policy",p2:"no-referrer-when-downgrade",sev:"medium",message:"GDPR — Referrer-Policy too permissive, use strict-origin"}, {id:"gdpr_pp", name:"GDPR: Permissions-Policy",type:"header_missing", p1:"permissions-policy",p2:"", sev:"low", message:"GDPR — Permissions-Policy missing, browser APIs not restricted"}, {id:"gdpr_server",name:"GDPR: Server header", type:"header_contains", p1:"server",p2:".", sev:"low", message:"GDPR — Server header exposes technology, aids attacker profiling"}, ], wordpress:[ {id:"wp_ver", name:"WordPress Version", type:"body_contains", p1:"",p2:"?ver=",sev:"medium",message:"WordPress version exposed in asset URLs — update or hide version"}, {id:"wp_login", name:"WP Login Exposed", type:"body_contains", p1:"",p2:"wp-login.php",sev:"medium",message:"WordPress login page reference found — consider using custom login URL"}, {id:"wp_api", name:"WP REST API", type:"body_contains", p1:"",p2:"/wp-json/",sev:"low",message:"WordPress REST API endpoint referenced — ensure sensitive endpoints are protected"}, {id:"wp_xmlrpc", name:"XMLRPC Exposed", type:"body_contains", p1:"",p2:"xmlrpc.php",sev:"high",message:"WordPress XMLRPC referenced — disable to prevent brute force attacks"}, {id:"wp_debug", name:"WP Debug Mode", type:"body_contains", p1:"",p2:"wp_debug",sev:"critical",message:"WordPress debug mode ON — sensitive error info may be exposed"}, {id:"wp_readme", name:"README.html", type:"body_contains", p1:"",p2:"readme.html",sev:"low",message:"WordPress readme.html may expose version info"}, {id:"wp_csp", name:"WP Missing CSP", type:"header_missing", p1:"content-security-policy",p2:"",sev:"high",message:"WordPress site missing CSP — vulnerable to XSS via plugins"}, ], azure:[ {id:"az_hsts", name:"Azure: HSTS", type:"header_missing", p1:"strict-transport-security",p2:"",sev:"high",message:"Azure best practice — HSTS should be configured in Azure Front Door"}, {id:"az_csp", name:"Azure: CSP", type:"header_missing", p1:"content-security-policy",p2:"",sev:"high",message:"Azure Security Center — CSP missing"}, {id:"az_cors", name:"Azure: Wildcard CORS", type:"header_equals", p1:"access-control-allow-origin",p2:"*",sev:"high",message:"Azure API Management — wildcard CORS violates Azure security baseline"}, {id:"az_powered", name:"Azure: X-Powered-By", type:"header_contains", p1:"x-powered-by",p2:".",sev:"low",message:"Azure — X-Powered-By exposes ASP.NET version, remove in web.config"}, {id:"az_aspnet", name:"Azure: X-AspNet-Version",type:"header_contains", p1:"x-aspnet-version",p2:".",sev:"low",message:"Azure — X-AspNet-Version exposed, disable in system.web section"}, {id:"az_tls10", name:"Azure: TLS 1.0", type:"tls_old_protocol", p1:"",p2:"", sev:"high", message:"Azure Security Center — TLS 1.0/1.1 deprecated, enforce TLS 1.2+"}, {id:"az_cookie", name:"Azure: Cookie Security", type:"cookie_missing_secure", p1:"",p2:"", sev:"medium", message:"Azure App Service — session cookies must have Secure flag enabled"}, ], financial:[ {id:"sox_https", name:"SOX: HTTPS Enforcement",type:"header_missing", p1:"strict-transport-security",p2:"",sev:"critical",message:"SOX compliance requires HTTPS — HSTS must be configured"}, {id:"sox_csp", name:"SOX: XSS Prevention", type:"header_missing", p1:"content-security-policy",p2:"",sev:"critical",message:"SOX requires XSS protection — CSP header missing"}, {id:"sox_tls", name:"SOX: TLS Protocol", type:"tls_old_protocol", p1:"",p2:"", sev:"critical",message:"SOX compliance — TLS 1.0/1.1 prohibited for financial data"}, {id:"sox_dmarc", name:"SOX: Email Auth", type:"dns_dmarc_missing", p1:"",p2:"", sev:"high", message:"SOX — email authentication (DMARC) required to prevent phishing"}, {id:"sox_cookie", name:"SOX: Session Security", type:"cookie_missing_httponly", p1:"",p2:"", sev:"high", message:"SOX — session cookies must have HttpOnly to prevent credential theft"}, {id:"sox_score", name:"SOX: Min Security Score",type:"score_below", p1:"",p2:"75", sev:"high", message:"SOX — overall security score below minimum threshold of 75"}, {id:"sox_xcto", name:"SOX: Content Type", type:"header_missing", p1:"x-content-type-options",p2:"",sev:"medium",message:"SOX — X-Content-Type-Options required for financial applications"}, ], israel8779:[ {id:"il_https", name:"8779: HTTPS חובה", type:"header_missing", p1:"strict-transport-security",p2:"",sev:"critical",message:"תקן 8779 סעיף 4.2 — HSTS חובה, כל תקשורת חייבת להיות מוצפנת"}, {id:"il_csp", name:"8779: CSP", type:"header_missing", p1:"content-security-policy",p2:"",sev:"high", message:"תקן 8779 סעיף 4.5 — Content-Security-Policy חובה להגנה מ-XSS"}, {id:"il_tls", name:"8779: TLS 1.2+", type:"tls_old_protocol", p1:"",p2:"", sev:"critical",message:"תקן 8779 — TLS 1.0/1.1 אסורים, חובה TLS 1.2 ומעלה"}, {id:"il_cookie", name:"8779: Cookie Secure", type:"cookie_missing_secure", p1:"",p2:"", sev:"high", message:"תקן 8779 — עוגיות session חייבות להיות עם דגל Secure"}, {id:"il_cookieh", name:"8779: Cookie HttpOnly", type:"cookie_missing_httponly", p1:"",p2:"", sev:"high", message:"תקן 8779 — עוגיות session חייבות להיות עם דגל HttpOnly"}, {id:"il_dmarc", name:"8779: DMARC", type:"dns_dmarc_missing", p1:"",p2:"", sev:"high", message:"תקן 8779 — DMARC חובה למניעת זיוף אימייל"}, {id:"il_spf", name:"8779: SPF", type:"dns_spf_missing", p1:"",p2:"", sev:"high", message:"תקן 8779 — SPF חובה להגנה על אימייל ארגוני"}, {id:"il_xframe", name:"8779: Clickjacking", type:"header_missing", p1:"x-frame-options",p2:"", sev:"medium", message:"תקן 8779 — X-Frame-Options חובה למניעת Clickjacking"}, {id:"il_score", name:"8779: ציון מינימלי", type:"score_below", p1:"",p2:"80", sev:"high", message:"תקן 8779 — ציון האבטחה מתחת ל-80, נדרש שיפור מיידי"}, ], }; function saveRules(){ try{localStorage.setItem("sa_custom_rules",JSON.stringify(customRules));}catch(e){} } function evaluateRule(rule, scanData){ var h = scanData.headers || {}; var body = (scanData.body || "").toLowerCase(); var sc = scanData.statusCode; var p1 = (rule.p1||"").toLowerCase(); var p2 = (rule.p2||"").toLowerCase(); var dns = scanData.dns || {}; var cookies = scanData.cookies || []; var tls = scanData.tls || {}; var tlsDeep = scanData.tlsDeep || {}; try { switch(rule.type){ // ── Original ── case "header_missing": return !h[p1]; case "header_contains": return !!(h[p1] && h[p1].toLowerCase().includes(p2)); case "header_not_contains": return !!(h[p1] && !h[p1].toLowerCase().includes(p2)); case "header_equals": return !!(h[p1] && h[p1].toLowerCase().trim()===p2.trim()); case "body_contains": return body.includes(p2); case "body_not_contains": return !body.includes(p2); case "status_code": return String(sc)===p2; case "port_open": return !!(scanData.portScan&&scanData.portScan[parseInt(p1)]&&scanData.portScan[parseInt(p1)].open); case "port_closed": return !!(scanData.portScan&&scanData.portScan[parseInt(p1)]&&!scanData.portScan[parseInt(p1)].open); case "score_below": return (scanData.score||0)parseInt(p2||"3000")); case "body_size_large": return !!(scanData.bodySize && scanData.bodySize>parseInt(p2||"500000")); default: return false; } } catch(e){ return false; } } function runCustomRules(scanData){ if(!customRules.length||!scanData) return []; return customRules.filter(function(r){return r.enabled!==false;}).map(function(rule){ var triggered=evaluateRule(rule, scanData); return{rule:rule, triggered:triggered}; }).filter(function(r){return r.triggered;}); } function updateRuleBuilder(){ var type=document.getElementById("ruleTypeInput").value; var l1=document.getElementById("ruleParam1Label"); var l2=document.getElementById("ruleParam2Label"); var i1=document.getElementById("ruleParam1"); var i2=document.getElementById("ruleParam2"); var w1=document.getElementById("ruleParam1Wrap"); var w2=document.getElementById("ruleParam2Wrap"); // reset w1.style.display=""; w2.style.display=""; i1.style.display=""; i2.style.display=""; i1.value=""; i2.value=""; switch(type){ // Original types case "header_missing": l1.textContent="Header name"; i1.placeholder="x-frame-options"; w2.style.display="none"; break; case "header_contains": l1.textContent="Header name"; l2.textContent="Contains"; i1.placeholder="server"; i2.placeholder="nginx"; break; case "header_not_contains": l1.textContent="Header name"; l2.textContent="Must not contain"; i1.placeholder="server"; i2.placeholder="apache"; break; case "header_equals": l1.textContent="Header name"; l2.textContent="Exact value"; i1.placeholder="x-frame-options"; i2.placeholder="DENY"; break; case "body_contains": w1.style.display="none"; l2.textContent="Body contains"; i2.placeholder="error"; break; case "body_not_contains": w1.style.display="none"; l2.textContent="Must not contain"; i2.placeholder="password"; break; case "status_code": w1.style.display="none"; l2.textContent="Status code"; i2.placeholder="200"; break; case "port_open": l1.textContent="Port number"; w2.style.display="none"; i1.placeholder="6379"; break; case "port_closed": l1.textContent="Port number"; w2.style.display="none"; i1.placeholder="443"; break; case "score_below": w1.style.display="none"; l2.textContent="Score threshold"; i2.placeholder="70"; break; case "redirect_to": w1.style.display="none"; l2.textContent="URL contains"; i2.placeholder="evil.com"; break; // DNS case "dns_spf_missing": case "dns_dmarc_missing": case "dns_caa_missing": case "dns_mta_sts_missing": w1.style.display="none"; w2.style.display="none"; break; case "dns_spf_softfail": w1.style.display="none"; w2.style.display="none"; break; case "dns_dmarc_not_reject": w1.style.display="none"; l2.textContent="Required policy"; i2.placeholder="reject"; break; // Cookies case "cookie_missing_httponly": case "cookie_missing_secure": case "cookie_missing_samesite": w1.style.display="none"; w2.style.display="none"; break; case "cookie_samesite_not_strict": w1.style.display="none"; l2.textContent="Required value"; i2.placeholder="Strict"; break; // TLS case "tls_expiry_days": w1.style.display="none"; l2.textContent="Days threshold"; i2.placeholder="30"; break; case "tls_self_signed": case "tls_weak_cipher": case "tls_old_protocol": w1.style.display="none"; w2.style.display="none"; break; // Performance case "response_slow": w1.style.display="none"; l2.textContent="Threshold (ms)"; i2.placeholder="3000"; break; case "body_size_large": w1.style.display="none"; l2.textContent="Max size (bytes)"; i2.placeholder="500000"; break; default: break; } } function addRule(){ var name=document.getElementById("ruleNameInput").value.trim(); var type=document.getElementById("ruleTypeInput").value; var p1=document.getElementById("ruleParam1").value.trim(); var p2=document.getElementById("ruleParam2").value.trim(); var sev=document.getElementById("ruleSevInput").value; var msg=document.getElementById("ruleMessageInput").value.trim(); if(!name){alert("חובה להזין שם לחוק");return;} var rule={id:"custom_"+Date.now(),name:name,type:type,p1:p1,p2:p2,sev:sev,message:msg||name,enabled:true,custom:true}; customRules.push(rule); saveRules(); renderRulesList(); // clear inputs ["ruleNameInput","ruleParam1","ruleParam2","ruleMessageInput"].forEach(function(id){document.getElementById(id).value="";}); toast("Rule added: "+name); } function deleteRule(id){ customRules=customRules.filter(function(r){return r.id!==id;}); saveRules(); renderRulesList(); toast("Rule deleted","success",1500); } function toggleRule(id,enabled){ var r=customRules.find(function(r){return r.id===id;}); if(r) r.enabled=enabled; saveRules(); renderRulesList(); } function testRule(){ var el=document.getElementById("ruleTestResult"); if(!proxyData){el.style.color="var(--yellow)";el.textContent="Run a scan first to test rules.";return;} var type=document.getElementById("ruleTypeInput").value; var p1=document.getElementById("ruleParam1").value.trim(); var p2=document.getElementById("ruleParam2").value.trim(); var rule={type:type,p1:p1,p2:p2}; var scanData={headers:proxyData.headers||{},body:proxyData.body||"",statusCode:proxyData.statusCode,portScan:proxyData.portScan,score:curScore,redirects:proxyData.redirects}; var triggered=evaluateRule(rule,scanData); el.style.color=triggered?"#ff4060":"#00e5a0"; el.textContent=triggered?"✗ Rule TRIGGERED on last scan — would create a finding":"✓ Rule not triggered on last scan"; } function renderRulesList(){ var el=document.getElementById("rulesList"); var countEl=document.getElementById("rulesCount"); if(countEl) countEl.textContent=customRules.length; if(!customRules.length){ el.innerHTML='
אין חוקים מותאמים אישית. הוסף חוק למעלה או טען תבנית.
'; return; } var SC2={critical:"#ff4060",high:"#f59e0b",medium:"#a855f7",low:"#38bdf8"}; el.innerHTML=customRules.map(function(r){ var col=SC2[r.sev]||"#6b7fa8"; return'
'+ ''+ '
'+ '
'+esc(r.name)+'
'+ '
'+r.type+(r.p1?" · "+r.p1:"")+(r.p2?" = "+r.p2:"")+'
'+ '
'+ ''+r.sev+''+ '🗑'+ '
'; }).join(""); } function loadPreset(name){ var preset=RULE_PRESETS[name]||[]; var added=0; preset.forEach(function(r){ if(!customRules.find(function(x){return x.id===r.id;})){ customRules.push(Object.assign({},r,{enabled:true,custom:false})); added++; } }); saveRules(); renderRulesList(); toast("Added "+added+" rules from "+name+" preset"); } function exportRules(){ var json=JSON.stringify(customRules,null,2); var a=document.createElement("a"); a.href=URL.createObjectURL(new Blob([json],{type:"application/json"})); a.download="sec-audit-rules-"+Date.now()+".json"; a.click(); toast("Rules exported"); } function importRules(input){ var file=input.files[0]; if(!file)return; var reader=new FileReader(); reader.onload=function(e){ try{ var imported=JSON.parse(e.target.result); if(!Array.isArray(imported)){throw new Error("Invalid format");} var added=0; imported.forEach(function(r){ if(r.id&&r.name&&r.type&&!customRules.find(function(x){return x.id===r.id;})){ customRules.push(r);added++; } }); saveRules();renderRulesList(); toast("Imported "+added+" rules"); }catch(err){toast("Import failed: "+err.message,"error");} }; reader.readAsText(file); input.value=""; } document.getElementById("rulesBtn").onclick=function(){ document.getElementById("rulesModal").classList.add("open"); renderRulesList(); updateRuleBuilder(); }; document.getElementById("rulesModal").addEventListener("click",function(e){e.stopPropagation();}); var multiResults={}; var multiRunning=false; document.getElementById("multiBtn").onclick=function(){ document.getElementById("multiModal").classList.add("open"); document.getElementById("multiStatus").textContent=""; document.getElementById("multiRunBtn").disabled=false; document.getElementById("multiRunBtn").textContent="▶ Run Multi Scan"; }; document.getElementById("multiCancelBtn").onclick=function(){ document.getElementById("multiModal").classList.remove("open"); }; document.getElementById("multiRunBtn").onclick=async function(){ var raw=document.getElementById("multiUrls").value.trim(); if(!raw)return; var urls=raw.split("\n").map(function(u){return u.trim();}).filter(function(u){return u.length>0;}).map(function(u){return u.startsWith("http")?u:"https://"+u;}); if(urls.length===0)return; if(urls.length>10){document.getElementById("multiStatus").textContent="Max 10 domains at once.";return;} var depth=document.getElementById("multiDepth").value; var proxyUrlVal=(localStorage.getItem("sa3_proxy")||"").trim().replace(/\/$/,""); if(!proxyConnected||!proxyUrlVal){document.getElementById("multiStatus").textContent="Proxy must be connected for multi-scan.";return;} document.getElementById("multiRunBtn").disabled=true; document.getElementById("multiRunBtn").textContent="Scanning..."; multiRunning=true; multiResults={}; document.getElementById("multiModal").classList.remove("open"); showView("multiView"); renderMultiProgress(urls,[]); var done=[]; var promises=urls.map(async function(url){ try{ var resp=await fetch(proxyUrlVal+"/scan",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:url,depth:depth})}); var data=await resp.json(); multiResults[url]=data; }catch(e){ multiResults[url]={error:e.message,url:url,reachable:false}; } done.push(url); renderMultiProgress(urls,done); }); await Promise.allSettled(promises); multiRunning=false; renderMultiDashboard(urls); }; function renderMultiProgress(urls,done){ var mv=document.getElementById("multiView"); var pct=Math.round((done.length/urls.length)*100); var html='
🔁 Multi Domain Scan
'+ '
'+ '
'+ 'Progress'+done.length+'/'+urls.length+'
'+ '
'+ '
'+ '
'+ '
'; html+=urls.map(function(url){ var isDone=done.indexOf(url)>=0; var r=multiResults[url]; var gc=r?(r.score>=80?"#00e5a0":r.score>=60?"#f59e0b":"#ff4060"):"#3d4f6e"; var dom="";try{dom=new URL(url).hostname;}catch(e){dom=url;} return'
'+ ''+(isDone?(r&&r.error?"❌":"✅"):"⏳")+''+ ''+esc(dom)+''+ (isDone&&r&&!r.error?''+r.score+'':'')+ (isDone&&r&&r.error?'Error':'')+ (!isDone?'scanning...':'')+ '
'; }).join(""); mv.innerHTML=html; } function renderMultiDashboard(urls){ var mv=document.getElementById("multiView"); var results=urls.map(function(url){return{url:url,data:multiResults[url]||{}};}).filter(function(r){return !r.data.error;}); results.sort(function(a,b){return(b.data.score||0)-(a.data.score||0);}); // Bar chart var maxScore=100; var barChart=results.map(function(r){ var sc=r.data.score||0; var gc=sc>=80?"#00e5a0":sc>=60?"#f59e0b":"#ff4060"; var dom="";try{dom=new URL(r.url).hostname;}catch(e){dom=r.url;} var pct=Math.round((sc/maxScore)*100); return'
'+ '
'+ ''+esc(dom)+''+ ''+sc+''+ '
'+ '
'+ '
'+ '
'+ '
'; }).join(""); // Comparison table var COMPARE_CATS=["HTTP Headers","TLS / HTTPS","DNS & Email","WAF Detection","Cookie Security","Open Redirect","CSRF Protection"]; var tableHead='Category'+ results.map(function(r){ var dom="";try{dom=new URL(r.url).hostname;}catch(e){dom=r.url;} return''+esc(dom.split(".")[0])+''; }).join("")+''; var CATS_MAP={"HTTP Headers":"headers","TLS / HTTPS":"tls","DNS & Email":"dns","WAF Detection":"waf","Cookie Security":"cookies_sec","Open Redirect":"open_redirect","CSRF Protection":"csrf"}; var STATUS_ICONS={"CLEAN":"✅","ISSUES":"❌","WARN":"⚠","OK":"✅","N/A":"—"}; var tableRows=COMPARE_CATS.map(function(catName){ var cells=results.map(function(r){ // derive status from allData-equivalent in result var catId=CATS_MAP[catName]; var waf=r.data.waf; var icon="—"; var color="var(--text3)"; // Simple heuristic from result data if(catName==="WAF Detection"){ icon=waf&&waf!=="None detected"?"✅":"❌"; color=waf&&waf!=="None detected"?"#00e5a0":"#ff4060"; } return''+icon+''; }).join(""); return''+catName+''+cells+''; }).join(""); // Top issues per domain var issueCards=results.map(function(r){ var sc=r.data.score||0; var gc=sc>=80?"#00e5a0":sc>=60?"#f59e0b":"#ff4060"; var dom="";try{dom=new URL(r.url).hostname;}catch(e){dom=r.url;} var wafStr=r.data.waf&&r.data.waf!=="None detected"?r.data.waf:"No WAF"; var wafCol=r.data.waf&&r.data.waf!=="None detected"?"#00e5a0":"#ff4060"; return'
'+ '
'+esc(dom)+'
'+ '
'+sc+'
'+ '
'+ (sc>=90?"A+":sc>=80?"A":sc>=70?"B+":sc>=60?"B":sc>=50?"C":sc>=40?"D":"F")+ '
'+ '
'+ ''+(r.data.fails||0)+' fail'+ ''+(r.data.warns||0)+' warn'+ ''+esc(wafStr)+''+ '
'+ '
'; }).join(""); mv.innerHTML= '
'+ '
🔁 Multi Scan Results
'+ ''+ '
'+ // Score cards '
'+issueCards+'
'+ // Bar chart '
'+ '
Score Comparison
'+ barChart+ '
'+ // Comparison table '
'+ '
Category Comparison
'+ ''+tableHead+tableRows+'
'+ '
'+ // Rescan buttons '
'+ results.map(function(r){ var dom="";try{dom=new URL(r.url).hostname;}catch(e){dom=r.url;} return''; }).join("")+ '
'; } // ── Filter state ── var activeFilter="all"; function setFilter(f){ activeFilter=f; // Update button styles ["all","critical","high","medium","low","fail","warn","pass"].forEach(function(id){ var btn=document.getElementById("flt_"+id); if(!btn)return; var isActive=id===f; btn.style.borderColor=isActive?"var(--accent)":"var(--border2)"; btn.style.background=isActive?"rgba(0,229,160,.1)":"var(--bg3)"; btn.style.color=isActive?"var(--accent)":"var(--text2)"; }); // Apply filter to all visible category sections document.querySelectorAll(".csec").forEach(function(sec){ var cards=sec.querySelectorAll(".card"); var anyVisible=false; cards.forEach(function(card){ var show=shouldShowCard(card,f); card.style.display=show?"flex":"none"; if(show)anyVisible=true; }); // Show/hide "no results" message var noRes=sec.querySelector(".filter-empty"); if(anyVisible){ if(noRes)noRes.style.display="none"; } else { if(!noRes){ noRes=document.createElement("div"); noRes.className="filter-empty"; noRes.style.cssText="padding:16px;text-align:center;color:var(--text3);font-family:var(--mono);font-size:10px"; noRes.textContent="No "+f+" findings in this category."; sec.appendChild(noRes); } noRes.style.display="block"; noRes.textContent="No "+f+" findings in this category."; } }); } function shouldShowCard(card,f){ if(f==="all") return true; var status=card.dataset.status||"info"; var sev=card.dataset.sev||"info"; switch(f){ case "critical": return sev==="critical"; case "high": return sev==="high"; case "medium": return sev==="medium"; case "low": return sev==="low"; case "fail": return status==="fail"; case "warn": return status==="warn"; case "pass": return status==="pass"; default: return true; } } var isLight=false; try{ isLight=localStorage.getItem("sa_theme")==="light"; }catch(e){} function applyTheme(light){ isLight=light; document.documentElement.classList.toggle("light",light); var btn=document.getElementById("themeBtn"); if(btn) btn.textContent=light?"🌙":"☀️"; try{ localStorage.setItem("sa_theme",light?"light":"dark"); }catch(e){} } // Init on load applyTheme(isLight); document.getElementById("themeBtn").onclick=function(){ applyTheme(!isLight); };