${content}
`; } function buildWelcomeEmail(name){ return emailBase(`

Welcome to AzemSec, ${esc(name)}.

Your account has been created. Before you can log in, you need to verify your email address — Firebase just sent you a separate verification link. Click it, then come back and log in.


What you get on the free plan:

✓  10 scans per day
✓  Email phishing detection — paste any email, get a risk score
✓  Sender address check — verify who actually sent it
✓  URL and domain scanner — before you click any link
✓  File scan — check attachments for malware


Need unlimited scans, scan history, and priority results? Pro is €7/month.

View Pro Plan →

If you didn't create this account, you can safely ignore this email. No action is needed.

`); } function buildScanResultEmail(name, type, query, score, verdict, findings){ const colour = score>=60?'#ff2d55':score>=30?'#ffcc00':'#00e676'; const icon = score>=60?'🚨':score>=30?'⚠️':'✅'; const findingsHtml = findings.slice(0,6).map(f=>` ${f.ic} ${esc(f.lb)}
${esc(f.vl)} `).join(''); return emailBase(`

${icon} Your scan result is ready

Hi ${esc(name||'there')}, here's the result for your recent ${esc(type.toLowerCase())} scan.

${score}
RISK
${esc(verdict)}
${esc(type)} Scan
${esc(query.substring(0,60))}${query.length>60?'…':''}
${findings.length>0?`

Key findings:

${findingsHtml}
`:''}

⚠️ This result is for informational purposes only. A safe verdict doesn't guarantee something is completely safe — new threats emerge every day. Always use your own judgement.

To run another scan, visit AzemSec.

`); } // ── EMAIL SENDING via EmailJS ────────────────────────────────────────── // EmailJS sends directly from the browser — no backend, no Supabase needed let _ejsReady = false; function initEmailJS(){ if(_ejsReady || !window.emailjs || !EMAILJS_PUBLIC_KEY || EMAILJS_PUBLIC_KEY==='YOUR_PUBLIC_KEY') return; window.emailjs.init({ publicKey: EMAILJS_PUBLIC_KEY }); _ejsReady = true; } async function sendEmail(to, subject, html){ initEmailJS(); if(!_ejsReady){ console.warn('EmailJS not configured — set EMAILJS_SERVICE_ID, TEMPLATE_ID, PUBLIC_KEY'); return; } try{ await window.emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, { to_email: to, subject: subject, message: html, from_name: 'AzemSec', reply_to: 'support@azemsec.com' }); }catch(e){ console.warn('EmailJS send failed:', e); } } async function maybeSendScanEmail(type, query, score, verdict, findings){ const tog = document.getElementById('emailResultToggle'); if(!tog.checked || !window._currentUser) return; const user = window._currentUser; const name = user.displayName || user.email.split('@')[0]; await sendEmail( user.email, `AzemSec: ${verdict} on your ${type.toLowerCase()} scan`, buildScanResultEmail(name, type, query, score, verdict, findings) ); } // ════════════════════════════════════════════════════════════════════════ // SCAN LOGIC // ════════════════════════════════════════════════════════════════════════ function switchTab(tab, btn){ activeTab = tab; document.querySelectorAll('.panel').forEach(p=>p.classList.remove('on')); document.querySelectorAll('.tab').forEach(t=>t.classList.remove('on')); document.getElementById('panel-'+tab).classList.add('on'); btn.classList.add('on'); clearResults(); } function toggleFaq(btn){ btn.closest('.faq-item').classList.toggle('open'); } async function runScan(){ if(scansToday >= FREE_LIMIT){ openModal('pro'); return; } const btn = document.getElementById('scanBtn'); btn.disabled=true; clearResults(); try{ if(activeTab==='email'){ const t = document.getElementById('emailInput').value.trim(); if(!t){ alert('Paste an email or sender address first.'); btn.disabled=false; return; } await scanEmail(t); } else if(activeTab==='url'){ const v = document.getElementById('urlInput').value.trim(); if(!v){ alert('Enter a URL or domain.'); btn.disabled=false; return; } await scanURL(v); } else if(activeTab==='file'){ if(!selectedFile||!fileHash){ alert('Select a file first.'); btn.disabled=false; return; } await scanFileHash(selectedFile, fileHash); } consumeScan(); }catch(err){ showError(err.message||'Something went wrong. Check your connection and try again.'); } btn.disabled=false; } // ── SECURITY HELPERS ── function esc(s){ if(s===null||s===undefined)return''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); } function sanitise(s){ if(!s)return''; return String(s).replace(/<[^>]*>/g,'').replace(/javascript:/gi,'').replace(/on\w+\s*=/gi,'').substring(0,50000); } function rateLimit(key,max,ms){ const now=Date.now(); if(!window._rl)window._rl={}; if(!window._rl[key])window._rl[key]=[]; window._rl[key]=window._rl[key].filter(t=>now-t=max)return false; window._rl[key].push(now); return true; } function extractDomain(input){ input=sanitise(input).trim().replace(/^https?:\/\//i,'').replace(/^www\./i,'').split('/')[0]; const at=input.indexOf('@'); return(at!==-1?input.substring(at+1):input).toLowerCase(); } function delay(ms){ return new Promise(r=>setTimeout(r,ms)); } async function fetchJ(url, ms=7000){ const c=new AbortController(); const t=setTimeout(()=>c.abort(),ms); try{ const r=await fetch(url,{signal:c.signal}); clearTimeout(t); if(!r.ok)throw new Error('HTTP '+r.status); return r.json(); } catch(e){ clearTimeout(t); throw e; } } // ── EMAIL SCAN ── async function scanEmail(text){ text = sanitise(text); // Detect sender-only input: single line, has @, short const isSenderOnly = !text.includes('\n') && text.includes('@') && text.length < 300; showLoading(['Parsing headers & metadata…','Extracting domains & links…','Querying PhishDestroy + URLhaus…','Running 60+ detection rules…','Building threat report…']); step(0,'act'); await delay(80); // ── EXTRACT ──────────────────────────────────────────────────────── const domains = new Set(), urls = []; const urlRe = /(https?:\/\/[^\s"'<>\]]+)/gi; let m; while((m=urlRe.exec(text))!==null){ const u = m[1].replace(/[.,;!?)]+$/,''); urls.push(u); try{ domains.add(new URL(u).hostname.replace(/^www\./,'').toLowerCase()); }catch(e){} } const fromLine = text.match(/[Ff]rom\s*:\s*([^\n\r]+)/)?.[1]||''; const fromAddr = fromLine.match(/[\w.+%-]+@([\w.-]+\.[a-z]{2,})/i)?.[0]||''; const fromDom = fromAddr.includes('@') ? fromAddr.split('@')[1].toLowerCase() : ''; const replyLine = text.match(/[Rr]eply-[Tt]o\s*:\s*([^\n\r]+)/)?.[1]||''; const replyAddr = replyLine.match(/[\w.+%-]+@([\w.-]+\.[a-z]{2,})/i)?.[0]||''; const replyDom = replyAddr.includes('@') ? replyAddr.split('@')[1].toLowerCase() : ''; const returnPath = text.match(/[Rr]eturn-[Pp]ath\s*:\s*([^\n\r]+)/)?.[1]||''; const spfHeader = text.match(/[Rr]eceived-SPF\s*:\s*([^\n\r]+)/)?.[1]||''; const dkimSig = /DKIM-Signature\s*:/i.test(text); const dmarcRes = text.match(/Authentication-Results[^:]*:\s*([^\n\r]+)/)?.[1]||''; const subjectLine= text.match(/[Ss]ubject\s*:\s*([^\n\r]+)/)?.[1]||''; // Sender-only mode let scanAddr = fromAddr, scanDom = fromDom; if(isSenderOnly){ scanAddr = text.trim(); scanDom = scanAddr.includes('@') ? scanAddr.split('@')[1].toLowerCase() : scanAddr.replace(/^https?:\/\//,'').split('/')[0].toLowerCase(); } if(scanDom) domains.add(scanDom); if(replyDom && replyDom!==fromDom) domains.add(replyDom); step(0,'done'); step(1,'act'); await delay(80); // ── QUERY ALL DATABASES IN PARALLEL ─────────────────────────────── const domChecks = {}; await Promise.allSettled([...domains].slice(0,8).map(async d => { domChecks[d] = {pd:null, uh:null, rdap:null, dns:null}; await Promise.allSettled([ (async()=>{ try{ if(rateLimit('pd',25,60000)) domChecks[d].pd=await fetchJ(`https://api.destroy.tools/v1/check?domain=${encodeURIComponent(d)}`,6000); }catch(e){} })(), (async()=>{ try{ const r=await fetch('https://urlhaus-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`query=get_host&host=${encodeURIComponent(d)}`}); const j=await r.json(); if(j.query_status==='is_host') domChecks[d].uh=j; }catch(e){} })(), (async()=>{ try{ domChecks[d].rdap=await fetchJ(`https://rdap.org/domain/${encodeURIComponent(d)}`,7000); }catch(e){} })(), (async()=>{ try{ domChecks[d].dns=await fetchJ(`https://dns.google/resolve?name=${encodeURIComponent(d)}&type=A`,5000); }catch(e){} })(), ]); })); // Also check extracted URLs against URLhaus const uhUrlHits=[]; await Promise.allSettled(urls.slice(0,4).map(async u=>{ try{ const r=await fetch('https://urlhaus-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`query=get_url&url=${encodeURIComponent(u)}`}); const j=await r.json(); if(j.query_status==='is_available'&&j.url_status==='online') uhUrlHits.push({url:u,data:j}); }catch(e){} })); step(1,'done'); step(2,'act'); await delay(80); const h = emailHeur(text,domChecks,scanDom,scanAddr,replyDom,replyAddr,urls,uhUrlHits,spfHeader,dkimSig,dmarcRes,subjectLine,returnPath,isSenderOnly); step(2,'done'); step(3,'act'); await delay(120); step(3,'done'); step(4,'act'); await delay(80); step(4,'done'); hideLoading(); renderEmail(domChecks,h,isSenderOnly,scanAddr,scanDom); addHist('EMAIL',(scanAddr||text).substring(0,55)+'…',h.score,h.verdict,h.findings); } function emailHeur(text,domChecks,fromDom,fromAddr,replyDom,replyAddr,urls,uhUrlHits,spfHeader,dkimSig,dmarcRes,subjectLine,returnPath,isSenderOnly){ let score=0; const findings=[],conf=[]; const tl=text.toLowerCase(), sl=subjectLine.toLowerCase(); const BRANDS=['paypal','amazon','apple','google','microsoft','netflix','facebook','instagram','twitter','tiktok','whatsapp','linkedin','dropbox','adobe','spotify','dhl','fedex','ups','usps','royalmail','dpd','hermes','evri','hsbc','barclays','lloyds','natwest','santander','halifax','monzo','revolut','wise','sparkasse','commerzbank','volksbank','postbank','bnpparibas','creditagricole','societegenerale','ing','ebay','etsy','aliexpress','shopify','stripe','coinbase','binance','kraken','steam','playstation','xbox','nintendo','gov','hmrc','irs','nhs','outlook','office','icloud','gmail','yahoo']; // ── DATABASE HITS ────────────────────────────────────────────────── const pdHits = Object.entries(domChecks).filter(([,r])=>r.pd?.threat); const uhHits = Object.entries(domChecks).filter(([,r])=>r.uh?.query_status==='is_host'); const pdClean= Object.entries(domChecks).filter(([,r])=>r.pd&&!r.pd.threat); if(pdHits.length>0){ score+=65; conf.push('phishing_db'); pdHits.forEach(([d,r])=>findings.push({lv:'danger',ic:'🚨',lb:'Confirmed Phishing Domain — PhishDestroy',vl:`"${esc(d)}" is in PhishDestroy's blacklist of 770,000+ verified phishing domains. Risk score: ${r.pd.risk_score}/100`})); } if(uhHits.length>0){ score+=50; conf.push('malware_host'); uhHits.forEach(([d,r])=>findings.push({lv:'danger',ic:'🦠',lb:'Active Malware Host — URLhaus (abuse.ch)',vl:`"${esc(d)}" is actively distributing malware. ${r.uh.urls_count||'Multiple'} malicious URL(s) reported by the abuse.ch community.`})); } if(uhUrlHits.length>0){ score+=45; conf.push('malware_url'); uhUrlHits.forEach(u=>findings.push({lv:'danger',ic:'🔗',lb:'Live Malware URL — URLhaus',vl:`${esc(u.url.substring(0,70))} is confirmed live and serving malware. Do NOT click.`})); } if(pdHits.length===0&&uhHits.length===0&&pdClean.length>0){ score-=8; findings.push({lv:'safe',ic:'✅',lb:`Domains Clear — PhishDestroy + URLhaus`,vl:`${pdClean.length} domain(s) checked against 770,000+ known phishing domains — all clean`}); } // ── SPF / DKIM / DMARC ──────────────────────────────────────────── if(spfHeader){ const spfl=spfHeader.toLowerCase(); if(spfl.includes('fail')&&!spfl.includes('softfail')){ score+=40; conf.push('spf_fail'); findings.push({lv:'danger',ic:'🛡️',lb:'SPF FAILED',vl:`Sender's mail server is NOT authorised to send email for this domain. Strong indicator of spoofing. Header: "${esc(spfHeader.substring(0,80))}"`}); } else if(spfl.includes('softfail')){ score+=20; findings.push({lv:'warn',ic:'🛡️',lb:'SPF Soft Fail',vl:`SPF soft fail — sender may not be authorised. Header: "${esc(spfHeader.substring(0,60))}"`}); } else if(spfl.includes('pass')){ score-=10; findings.push({lv:'safe',ic:'🛡️',lb:'SPF Passed',vl:'Sender domain is authorised to send this email — good sign'}); } } if(dkimSig){ score-=5; findings.push({lv:'safe',ic:'🔏',lb:'DKIM Signature Present',vl:'Email is cryptographically signed — content has not been tampered with in transit'}); } else if(fromAddr&&!isSenderOnly) findings.push({lv:'warn',ic:'🔏',lb:'No DKIM Signature',vl:'Email lacks a cryptographic signature — authenticity cannot be fully verified'}); if(dmarcRes){ if(dmarcRes.toLowerCase().includes('dmarc=fail')){ score+=35; conf.push('dmarc_fail'); findings.push({lv:'danger',ic:'🚫',lb:'DMARC FAILED',vl:'Email failed domain authentication policy — high probability of spoofing'}); } else if(dmarcRes.toLowerCase().includes('dmarc=pass')){ score-=8; findings.push({lv:'safe',ic:'🚫',lb:'DMARC Passed',vl:'Email passed DMARC authentication checks'}); } } // ── REPLY-TO MISMATCH ────────────────────────────────────────────── if(replyDom&&fromDom&&replyDom!==fromDom){ score+=35; conf.push('reply_mismatch'); findings.push({lv:'danger',ic:'↩️',lb:'Reply-To Mismatch',vl:`Email appears from "${esc(fromDom)}" but replies go to "${esc(replyDom)}" — classic technique to intercept your response without you noticing`}); } // ── RETURN-PATH MISMATCH ─────────────────────────────────────────── if(returnPath&&fromDom){ const rpDom=(returnPath.match(/@([\w.-]+)/)?.[1]||'').toLowerCase(); if(rpDom&&rpDom!==fromDom&&!rpDom.includes(fromDom.split('.')[0])){ score+=20; findings.push({lv:'warn',ic:'📮',lb:'Return-Path Mismatch',vl:`Return-Path domain "${esc(rpDom)}" differs from sender "${esc(fromDom)}" — can indicate spoofing`}); } } // ── SENDER + DOMAIN ANALYSIS ─────────────────────────────────────── if(fromDom||isSenderOnly){ const checkDom = fromDom||(fromAddr.includes('@')?fromAddr.split('@')[1]:fromAddr); const spoofed = BRANDS.filter(b=>{ const lc=checkDom.toLowerCase(); return lc.includes(b)&&!lc.match(new RegExp(`^(www\\.)?(${b})\\.(com|co\\.uk|de|fr|es|it|net|org|gov|io)$`)); }); if(spoofed.length>0){ score+=55; conf.push('brand_spoof'); findings.push({lv:'danger',ic:'🎭',lb:'Brand Impersonation',vl:`Domain "${esc(checkDom)}" contains "${spoofed[0]}" but is NOT the official domain. One of the most common phishing tactics.`}); } else if(fromAddr) findings.push({lv:'info',ic:'✉️',lb:'Sender Address',vl:esc(fromAddr)}); if(fromDom){ const dc=domChecks[fromDom]||{}; const created=rdapDate(dc.rdap,'registration'); if(created){ const ageDays=Math.floor((Date.now()-new Date(created).getTime())/86400000); if(ageDays<7){ score+=40; conf.push('new_domain'); findings.push({lv:'danger',ic:'📅',lb:'Sender Domain Registered < 7 Days Ago',vl:`"${esc(fromDom)}" was registered only ${ageDays} day(s) ago. Phishing domains are almost always brand new.`}); } else if(ageDays<30){ score+=25; findings.push({lv:'danger',ic:'📅',lb:'Sender Domain Very New',vl:`"${esc(fromDom)}" is ${ageDays} days old — high risk`}); } else if(ageDays<180){ score+=10; findings.push({lv:'warn',ic:'📅',lb:'Sender Domain Under 6 Months Old',vl:`"${esc(fromDom)}" registered ${Math.floor(ageDays/30)} month(s) ago`}); } else findings.push({lv:'safe',ic:'📅',lb:'Sender Domain Age',vl:`"${esc(fromDom)}" — ${ageDays<365?Math.floor(ageDays/30)+' months':(ageDays/365).toFixed(1)+' years'} old — established domain`}); } const dns=dc.dns; if(dns?.Status===3||dns?.Answer?.length===0){ score+=15; findings.push({lv:'warn',ic:'🌐',lb:'Sender Domain Has No DNS Records',vl:`"${esc(fromDom)}" does not resolve — may be fabricated or very new`}); } if(/\d{3,}/.test(fromDom)){ score+=15; findings.push({lv:'warn',ic:'🔢',lb:'Numbers in Sender Domain',vl:`"${esc(fromDom)}" — legitimate services rarely use long number strings in their domain name`}); } if((fromDom.match(/-/g)||[]).length>2){ score+=12; findings.push({lv:'warn',ic:'⚠️',lb:'Hyphenated Sender Domain',vl:`"${esc(fromDom)}" has multiple hyphens — uncommon in legitimate service domains`}); } } } // ── SUBJECT LINE ─────────────────────────────────────────────────── if(sl){ const subjectFlags=['urgent','account suspended','verify immediately','action required','account blocked','unusual activity','confirm your','click immediately','you have won','prize','lottery','inheritance','million','bitcoin','your password','security alert','suspicious login','access restricted']; const subHits=subjectFlags.filter(w=>sl.includes(w)); if(subHits.length>=3){ score+=25; conf.push('alarm_subject'); findings.push({lv:'danger',ic:'📌',lb:'High-Pressure Subject Line',vl:`"${esc(subjectLine.substring(0,90))}" — ${subHits.length} alarm keywords detected: ${subHits.slice(0,3).join(', ')}`}); } else if(subHits.length>=1){ score+=12; findings.push({lv:'warn',ic:'📌',lb:'Suspicious Subject',vl:`"${esc(subjectLine.substring(0,90))}" contains "${subHits[0]}"`}); } if(sl===sl.toUpperCase()&&sl.replace(/\s/g,'').length>5){ score+=10; findings.push({lv:'warn',ic:'📢',lb:'ALL-CAPS Subject Line',vl:'Shouting in the subject is a manipulation tactic to create false urgency'}); } if((subjectLine.match(/!/g)||[]).length>=2){ score+=8; findings.push({lv:'warn',ic:'❗',lb:'Multiple Exclamation Marks in Subject',vl:'High-pressure formatting used by scammers to trigger an emotional reaction'}); } } // ── URGENCY & PRESSURE ───────────────────────────────────────────── const urgWords=['urgent','immediately','click here','account locked','account suspended','verify now','action required','confirm your','expires in','act now','limited time','within 24 hours','within 48 hours','failure to','will be terminated','will be deleted','will be suspended','legal action','law enforcement','dringend','sofort','ihr konto','umgehend','gesperrt','immédiatement','votre compte','suspendu','urgente','inmediatamente']; const urgCount=urgWords.filter(w=>tl.includes(w)).length; if(urgCount>=5){ score+=35; conf.push('extreme_urgency'); findings.push({lv:'danger',ic:'⚡',lb:'Extreme Pressure Tactics',vl:`${urgCount} urgency/pressure phrases found. Scammers manufacture urgency to stop you thinking clearly before acting.`}); } else if(urgCount>=3){ score+=20; findings.push({lv:'warn',ic:'⚡',lb:'Urgency Language',vl:`${urgCount} pressure phrase(s) detected — take your time, verify independently`}); } else if(urgCount>=1){ score+=8; findings.push({lv:'warn',ic:'⚡',lb:'Mild Urgency Language',vl:`"${urgWords.find(w=>tl.includes(w))}" — stay alert`}); } // ── SENSITIVE DATA REQUESTS ──────────────────────────────────────── const sensitiveTerms=[ {t:'password',lb:'Password Requested',vl:'Legitimate services NEVER ask for your password via email'}, {t:'passwort',lb:'Password Requested (DE)',vl:'Seriöse Anbieter fragen niemals per E-Mail nach Passwörtern'}, {t:'mot de passe',lb:'Password Requested (FR)',vl:'Les services légitimes ne demandent jamais un mot de passe par e-mail'}, {t:'credit card',lb:'Credit Card Requested',vl:'No legitimate organisation asks for card details over email'}, {t:'card number',lb:'Card Number Requested',vl:'Banks and payment services never ask for card numbers by email'}, {t:'bank account',lb:'Bank Account Details Requested',vl:'Your bank will never request account details by email'}, {t:'sort code',lb:'Sort Code Requested',vl:'Banks do not ask for sort codes via email'}, {t:'social security',lb:'SSN Requested',vl:'Government agencies never request Social Security Numbers by email'}, {t:'national insurance',lb:'NI Number Requested',vl:'HMRC or DWP will never ask for your NI number via email'}, {t:'seed phrase',lb:'Crypto Seed Phrase Requested',vl:'This is a cryptocurrency theft attempt — NEVER share your seed phrase with anyone'}, {t:'private key',lb:'Private Key Requested',vl:'No legitimate service ever needs your private key'}, {t:'pin number',lb:'PIN Requested',vl:'Banks never ask for PINs via email'}, {t:'cvv',lb:'CVV/Security Code Requested',vl:'Card security codes should never be shared outside a secure checkout'}, ]; sensitiveTerms.forEach(({t,lb,vl})=>{ if(tl.includes(t)){ score+=25; conf.push('data_request'); findings.push({lv:'danger',ic:'🔑',lb,vl}); } }); // ── SUSPICIOUS LINKS ─────────────────────────────────────────────── const suspTLDs=['.xyz','.top','.click','.link','.tk','.ml','.ga','.cf','.gq','.work','.loan','.win','.download','.zip','.mov','.men','.accountant','.stream','.faith','.racing','.review','.webcam','.date','.party']; const suspUrls=urls.filter(u=>suspTLDs.some(t=>u.toLowerCase().split('?')[0].includes(t))); if(suspUrls.length>0){ score+=30; conf.push('susp_url'); findings.push({lv:'danger',ic:'🔗',lb:'High-Risk URL Extensions',vl:`${suspUrls.length} link(s) use TLDs heavily associated with phishing: ${esc(suspUrls[0].substring(0,70))}`}); } const ipUrls=urls.filter(u=>/https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(u)); if(ipUrls.length>0){ score+=35; conf.push('ip_url'); findings.push({lv:'danger',ic:'🖥️',lb:'Links to Raw IP Address',vl:`${esc(ipUrls[0].substring(0,60))} — legitimate sites use domain names, not bare IP addresses`}); } if(urls.length>6){ score+=12; findings.push({lv:'warn',ic:'🔗',lb:`${urls.length} Links Found`,vl:'Large number of links — phishing emails often include many to overwhelm or confuse'}); } else if(urls.length>0&&!isSenderOnly) findings.push({lv:'info',ic:'🔗',lb:`${urls.length} Link(s) Extracted & Checked`,vl:'All extracted domains queried against PhishDestroy and URLhaus'}); // ── GENERIC GREETINGS ────────────────────────────────────────────── const genericGreetings=['dear customer','dear user','dear account holder','dear valued customer','dear member','hello user','dear beneficiary','dear friend']; const genericHit=genericGreetings.find(w=>tl.includes(w)); if(genericHit){ score+=12; findings.push({lv:'warn',ic:'📝',lb:'Generic Greeting',vl:`"${genericHit}" — real companies address you by name. Generic greetings suggest a mass-phishing campaign.`}); } // ── ATTACHMENT REFERENCES ────────────────────────────────────────── const attachWords=['see attached','open the file','download the file','invoice attached','document attached','open attached','view attachment']; if(attachWords.some(w=>tl.includes(w))){ score+=18; findings.push({lv:'warn',ic:'📎',lb:'Attachment Referenced',vl:'Never open attachments from senders you don\'t fully trust, especially unexpected invoices or documents'}); } // ── FINAL ────────────────────────────────────────────────────────── score=Math.max(0,Math.min(Math.round(score),100)); if(findings.length===0) findings.push({lv:'info',ic:'ℹ️',lb:'No Threats Detected',vl:'No phishing indicators found across 60+ detection rules. Stay vigilant — new scams emerge daily.'}); const verdict=score>=60?'DANGER':score>=30?'SUSPICIOUS':'SAFE'; const confidence=conf.length>=4?'High':conf.length>=2?'Medium':'Low'; const summary=score>=60?`⛔ HIGH RISK — ${conf.length} strong indicator(s) confirmed (${confidence} confidence). Do NOT click links, open attachments, reply, or provide any information. Verify through official channels only.` :score>=30?`⚠️ SUSPICIOUS — ${conf.length} risk signal(s) detected (${confidence} confidence). Verify by going directly to the official website or calling a known number — not contacts in this email.` :`✅ LOW RISK — No major threats detected (${confidence} confidence). Scams evolve constantly — if something still feels off, trust your instinct.`; return {score,verdict,summary,findings,confidence,urlCount:urls.length,domCount:Object.keys(domChecks).length}; } function renderEmail(domChecks,h,isSenderOnly,scanAddr,fromDom){ const sc=getSC(h.score); const doms=Object.keys(domChecks); const pdHits=doms.filter(d=>domChecks[d].pd?.threat); const uhHits=doms.filter(d=>domChecks[d].uh?.query_status==='is_host'); const statsBar=`
${doms.length?`🌐 ${doms.length} domain${doms.length>1?'s':''} scanned`:''} ${h.urlCount?`🔗 ${h.urlCount} link${h.urlCount>1?'s':''} found`:''} ${pdHits.length?`🚨 PhishDestroy: ${pdHits.length} HIT`:`✅ PhishDestroy: clean`} ${uhHits.length?`🦠 URLhaus: ${uhHits.length} HIT`:`✅ URLhaus: clean`} 60+ rules checked
`; const domTable=doms.length?`
// Domain Intelligence
${doms.map(d=>{ const r=domChecks[d]; const rdap=r.rdap; const created=rdapDate(rdap,'registration'); const ageDays=created?Math.floor((Date.now()-new Date(created).getTime())/86400000):null; const ageStr=ageDays===null?'?':ageDays<30?`${ageDays}d 🔴`:ageDays<365?`${Math.floor(ageDays/30)}mo`:`${(ageDays/365).toFixed(1)}yr`; const reg=rdap?.entities?.find(e=>e.roles?.includes('registrar'))?.vcardArray?.[1]?.find(v=>v[0]==='fn')?.[3]||'?'; return`
${esc(d)}
Age:${esc(ageStr)}
${esc(reg)}
PD:${r.pd?.threat?'⛔':r.pd?'✅':'—'}
UH:${r.uh?'⛔':'✅'}
`; }).join('')}
PD = PhishDestroy · UH = URLhaus/abuse.ch · Age = domain registration age
`:''; const confBadge=`Confidence: ${esc(h.confidence)}`; showResults(`
${WARN_BANNER}${rHead(h.score,sc,h.verdict+confBadge,isSenderOnly?`Sender: ${esc(scanAddr)}`:'Email Analysis','PhishDestroy · URLhaus · RDAP · DNS · 60+ rules')}${rMeter(h.score,sc)}${statsBar}
// Summary
🤖
${esc(h.summary)}
// Findings (${h.findings.length})
${h.findings.map(f=>fItem(f)).join('')}
${domTable}
`); animMeter(h.score); } // ── URL / DOMAIN SCAN ────────────────────────────────────────────────── async function scanURL(raw){ const url=sanitise(raw).trim(); let domain=''; try{ domain=new URL(url.startsWith('http')?url:'https://'+url).hostname.replace(/^www\./,'').toLowerCase(); } catch(e){ domain=url.replace(/^https?:\/\//i,'').replace(/^www\./i,'').split(/[/?#]/)[0].toLowerCase(); } showLoading(['Parsing URL structure…','Querying PhishDestroy…','Querying URLhaus + RDAP…','DNS check…','Heuristic analysis…']); step(0,'act'); await delay(80); const heur=urlHeur(url,domain); const dbR={pd:null,uh:null,rdap:null,dns:null}; let uhUrl=null; await Promise.allSettled([ (async()=>{ try{ if(rateLimit('pd',25,60000)) dbR.pd=await fetchJ(`https://api.destroy.tools/v1/check?domain=${encodeURIComponent(domain)}`,6000); }catch(e){} })(), (async()=>{ try{ const r=await fetch('https://urlhaus-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`query=get_host&host=${encodeURIComponent(domain)}`}); const j=await r.json(); if(j.query_status==='is_host') dbR.uh=j; }catch(e){} })(), (async()=>{ try{ const r=await fetch('https://urlhaus-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`query=get_url&url=${encodeURIComponent(url)}`}); const j=await r.json(); if(j.query_status==='is_available') uhUrl=j; }catch(e){} })(), (async()=>{ try{ dbR.rdap=await fetchJ(`https://rdap.org/domain/${encodeURIComponent(domain)}`,7000); }catch(e){} })(), (async()=>{ try{ dbR.dns=await fetchJ(`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=A`,5000); }catch(e){} })(), ]); step(0,'done'); step(1,'done'); step(2,'done'); step(3,'done'); step(4,'act'); await delay(80); step(4,'done'); hideLoading(); renderURL(url,domain,dbR,uhUrl,heur); const sc=urlScore(dbR,uhUrl,heur); addHist('URL',url.length>50?url.substring(0,50)+'…':url,sc.score,sc.verdict,heur.findings); } function urlHeur(url,domain){ const findings=[]; let score=0; const BRANDS=['paypal','amazon','apple','google','microsoft','netflix','facebook','instagram','twitter','ebay','dhl','fedex','ups','hsbc','barclays','lloyds','natwest','sparkasse','coinbase','binance','steam','gov','hmrc','irs']; const spoofed=BRANDS.filter(b=>domain.includes(b)&&!domain.match(new RegExp(`^(www\\.)?(${b})\\.(com|co\\.uk|de|fr|es|it|net|org|gov)$`))); if(spoofed.length){ score+=50; findings.push({lv:'danger',ic:'🎭',lb:'Brand Impersonation in Domain',vl:`"${esc(domain)}" contains "${spoofed[0]}" but is NOT the official website. Go directly to ${spoofed[0]}.com instead.`}); } const suspTLDs=['.xyz','.top','.click','.link','.tk','.ml','.ga','.cf','.gq','.work','.loan','.win','.download','.zip','.mov','.men','.accountant','.stream','.faith','.racing','.review','.webcam','.date','.party','.science']; if(suspTLDs.some(t=>domain.endsWith(t))){ score+=38; findings.push({lv:'warn',ic:'🔗',lb:'High-Risk TLD',vl:`"${esc(domain)}" uses an extension heavily associated with phishing and spam distribution`}); } // Malicious keyword patterns in domain const maliciousWords=['malware','phish','hack','exploit','crack','steal','evil','trojan','ransomware','virus','botnet']; const loginKeywords=['login','signin','secure','verify','account','wallet','recover','confirm','update','alert']; const hasMalWord=maliciousWords.some(w=>domain.includes(w)); const loginCount=loginKeywords.filter(w=>domain.includes(w)).length; if(hasMalWord){ score+=40; findings.push({lv:'danger',ic:'☠️',lb:'Malicious Keywords in Domain',vl:`"${esc(domain)}" contains explicit malicious terms`}); } if(loginCount>=2){ score+=25; findings.push({lv:'danger',ic:'🔐',lb:'Multiple Login/Security Keywords in Domain',vl:`${loginCount} keywords like "login", "secure", "verify" — used to build false trust`}); } else if(loginCount>=1){ score+=12; findings.push({lv:'warn',ic:'🔐',lb:'Security Keyword in Domain',vl:`"${loginKeywords.find(w=>domain.includes(w))}" in domain — common in phishing pages`}); } if(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(domain)){ score+=40; findings.push({lv:'danger',ic:'🖥️',lb:'IP Address Instead of Domain',vl:`Navigating to a raw IP (${esc(domain)}) instead of a named site — legitimate services always use domain names`}); } const parts=domain.split('.'); if(parts.length>=5){ score+=28; findings.push({lv:'danger',ic:'🔀',lb:'Excessive Subdomains',vl:`${parts.length} domain levels in "${esc(domain)}" — used to fake legitimacy (e.g. paypal.com.login.evil.xyz)`}); } if(domain.length>45){ score+=15; findings.push({lv:'warn',ic:'📏',lb:'Very Long Domain',vl:`${domain.length} characters — phishing domains are often deliberately verbose`}); } if(/\d{4,}/.test(domain)){ score+=12; findings.push({lv:'warn',ic:'🔢',lb:'Long Number String in Domain',vl:`"${esc(domain)}" — legitimate services avoid this`}); } const hyphenCount=(domain.match(/-/g)||[]).length; if(hyphenCount>=4){ score+=18; findings.push({lv:'warn',ic:'⚠️',lb:`${hyphenCount} Hyphens in Domain`,vl:'Common in typosquatting domains designed to look like real ones'}); } if(!url.startsWith('https')){ score+=20; findings.push({lv:'warn',ic:'🔓',lb:'No HTTPS / Unencrypted',vl:'Connection is not encrypted. Any data you submit can be intercepted.'}); } if(url.includes('@')){ score+=35; findings.push({lv:'danger',ic:'@',lb:'@ Symbol in URL',vl:'Everything before @ is ignored by browsers — used to disguise the real destination behind a trusted-looking string'}); } if(url.toLowerCase().includes('redirect=')||url.toLowerCase().includes('url=')||url.toLowerCase().includes('goto=')||url.toLowerCase().includes('link=')){ score+=18; findings.push({lv:'warn',ic:'↪️',lb:'Redirect Parameter in URL',vl:'URL contains a redirect parameter — you may end up at a different site than expected'}); } const encCount=(url.match(/%[0-9A-Fa-f]{2}/g)||[]).length; if(encCount>8){ score+=12; findings.push({lv:'warn',ic:'🔤',lb:'Heavy URL Encoding',vl:`${encCount} encoded characters — can be used to hide the real destination`}); } if(findings.length===0) findings.push({lv:'safe',ic:'✅',lb:'URL Structure Looks Normal',vl:'No suspicious structural patterns detected in this URL'}); return{score,findings}; } function urlScore(dbR,uhUrl,heur){ let score=heur.score; if(dbR.pd?.threat) score+=60; if(dbR.uh?.query_status==='is_host') score+=50; if(uhUrl?.url_status==='online') score+=55; return{score:Math.max(0,Math.min(Math.round(score),100)),verdict:score>=60?'DANGER':score>=30?'SUSPICIOUS':'SAFE'}; } function renderURL(url,domain,dbR,uhUrl,heur){ const{score,verdict}=urlScore(dbR,uhUrl,heur); const sc=getSC(score); const rdap=dbR.rdap, dns=dbR.dns; const created=rdapDate(rdap,'registration'), expiry=rdapDate(rdap,'expiration'); let ageDays=null, ageStr='Unknown'; if(created){ ageDays=Math.floor((Date.now()-new Date(created).getTime())/86400000); ageStr=ageDays<7?`${ageDays}d — BRAND NEW 🔴`:ageDays<30?`${ageDays} days ⚠️`:ageDays<365?`${Math.floor(ageDays/30)} months`:`${(ageDays/365).toFixed(1)} years`; } const registrar=rdap?.entities?.find(e=>e.roles?.includes('registrar'))?.vcardArray?.[1]?.find(v=>v[0]==='fn')?.[3]||'Unknown'; const ns=(rdap?.nameservers||[]).map(n=>n.ldhName).filter(Boolean).slice(0,3).join(', ')||'Unknown'; const resolves=dns?.Answer?.length>0; const findings=[...heur.findings]; if(dbR.pd?.threat) findings.unshift({lv:'danger',ic:'🚨',lb:'Confirmed Phishing Domain — PhishDestroy',vl:`Listed in PhishDestroy's database of 770,000+ verified phishing domains. Risk: ${dbR.pd.risk_score}/100`}); else if(dbR.pd) findings.unshift({lv:'safe', ic:'✅',lb:'Not on PhishDestroy',vl:'Domain not found in 770,000+ known phishing domain database'}); if(dbR.uh) findings.unshift({lv:'danger',ic:'🦠',lb:'Active Malware Host — URLhaus (abuse.ch)',vl:`This domain is distributing malware. ${dbR.uh.urls_count||'Multiple'} malicious URL(s) reported.`}); else findings.push( {lv:'safe', ic:'✅',lb:'Not on URLhaus',vl:'Domain not found in URLhaus malware host database'}); if(uhUrl?.url_status==='online') findings.unshift({lv:'danger',ic:'🔗',lb:'Live Malware URL — URLhaus',vl:`This exact URL is confirmed live and distributing malware (type: ${esc(uhUrl.threat||'unknown')})`}); if(ageDays!==null){ if(ageDays<7) findings.push({lv:'danger',ic:'📅',lb:'Domain Registered in Last 7 Days',vl:'Phishing sites are almost always newly registered. Treat with extreme caution.'}); else if(ageDays<30) findings.push({lv:'danger',ic:'📅',lb:'Domain Very New',vl:`Only ${ageDays} days old — high risk`}); else if(ageDays<180) findings.push({lv:'warn', ic:'📅',lb:'Domain Under 6 Months Old',vl:ageStr}); else findings.push({lv:'safe',ic:'📅',lb:'Domain Age',vl:`${ageStr} old — established`}); } if(!resolves) findings.push({lv:'warn',ic:'🌐',lb:'Domain Does Not Resolve',vl:'No DNS A records — site may be offline, very new, or the domain may be fabricated'}); const wgrid=`
Domain
${esc(domain)}
Age
${esc(ageStr)}
Registrar
${esc(registrar)}
Created
${created?new Date(created).toLocaleDateString('en-GB'):'Unknown'}
Expires
${expiry?new Date(expiry).toLocaleDateString('en-GB'):'Unknown'}
Nameservers
${esc(ns)}
DNS Resolves
${resolves?'Yes ✓':'No ✗'}
PhishDestroy
${dbR.pd?.threat?`⛔ FLAGGED (${dbR.pd.risk_score}/100)`:dbR.pd?'✅ Clean':'Unavailable'}
URLhaus
${dbR.uh?'⛔ Malware host':'✅ Clean'}
`; const safeUrl=esc(sanitise(url)); showResults(`
${WARN_BANNER}${rHead(score,sc,verdict,safeUrl.length>65?safeUrl.substring(0,65)+'…':safeUrl,'PhishDestroy · URLhaus · RDAP · DNS · Heuristics')}${rMeter(score,sc)}${wgrid}
// Findings (${findings.length})
${findings.map(f=>fItem(f)).join('')}
`); animMeter(score); } // ── FILE SCAN ────────────────────────────────────────────────────────── async function handleFile(input, file){ const f=file||input?.files?.[0]; if(!f) return; selectedFile=f; document.getElementById('fileChosen').textContent=`📎 ${f.name} (${fmtBytes(f.size)})`; const buf=await f.arrayBuffer(); const hashBuf=await crypto.subtle.digest('SHA-256',buf); fileHash=Array.from(new Uint8Array(hashBuf)).map(b=>b.toString(16).padStart(2,'0')).join(''); } function fmtBytes(b){ if(b<1024)return b+'B'; if(b<1048576)return(b/1024).toFixed(1)+'KB'; return(b/1048576).toFixed(1)+'MB'; } async function scanFileHash(file,hash){ showLoading(['Computing SHA-256 fingerprint…','Querying MalwareBazaar (abuse.ch)…','Querying ThreatFox (abuse.ch)…','Magic byte & structure analysis…','Building report…']); step(0,'done'); step(1,'act'); await delay(100); let mb=null, tf=null; await Promise.allSettled([ (async()=>{ try{ const r=await fetch('https://mb-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`query=get_info&hash=${hash}`}); mb=await r.json(); }catch(e){} })(), (async()=>{ try{ const r=await fetch('https://threatfox-api.abuse.ch/api/v1/',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:'search_ioc',search_term:hash})}); tf=await r.json(); }catch(e){} })(), ]); step(1,'done'); step(2,'done'); step(3,'act'); const magic=await analyseFile(file); step(3,'done'); step(4,'act'); await delay(60); step(4,'done'); hideLoading(); renderFile(file,hash,mb,tf,magic); } function analyseFile(file){ return new Promise(resolve=>{ const reader=new FileReader(); reader.onload=ev=>{ const bytes=new Uint8Array(ev.target.result.slice(0,32)); const hex=Array.from(bytes).map(b=>b.toString(16).padStart(2,'0')).join(''); const findings=[]; let detectedType='Unknown'; let typeRisk='none'; const MAGIC=[ {sig:'4d5a', type:'Windows PE Executable', exts:['exe','dll','com','scr','sys','drv'], risk:'high'}, {sig:'7f454c46', type:'Linux/Unix ELF Binary', exts:['elf','so','out'], risk:'high'}, {sig:'cafebabe', type:'Java Class / Mach-O', exts:['class','dylib'], risk:'medium'}, {sig:'504b0304', type:'ZIP / Office (DOCX/XLSX)', exts:['zip','jar','docx','xlsx','pptx','apk','odt'],risk:'low'}, {sig:'526172211a07',type:'RAR Archive', exts:['rar'], risk:'low'}, {sig:'377abcaf271c',type:'7-Zip Archive', exts:['7z'], risk:'low'}, {sig:'1f8b08', type:'GZIP Compressed', exts:['gz','tgz'], risk:'low'}, {sig:'25504446', type:'PDF Document', exts:['pdf'], risk:'low'}, {sig:'d0cf11e0', type:'Legacy Office / OLE2', exts:['doc','xls','ppt','msi'], risk:'medium'}, {sig:'ffd8ff', type:'JPEG Image', exts:['jpg','jpeg'], risk:'none'}, {sig:'89504e47', type:'PNG Image', exts:['png'], risk:'none'}, {sig:'47494638', type:'GIF Image', exts:['gif'], risk:'none'}, {sig:'4c000000', type:'Windows Shortcut (LNK)', exts:['lnk'], risk:'high'}, {sig:'23212f', type:'Script File (shebang)', exts:['sh','py','rb','pl','bash'], risk:'medium'}, {sig:'3c3f706870', type:'PHP Script', exts:['php'], risk:'medium'}, {sig:'4d5a9000', type:'PE Executable (packed)', exts:['exe','dll'], risk:'high'}, {sig:'d4c3b2a1', type:'PCAP Network Capture', exts:['pcap','pcapng'], risk:'medium'}, ]; const ext=file.name.split('.').pop().toLowerCase(); for(const mg of MAGIC){ if(hex.startsWith(mg.sig)){ detectedType=mg.type; typeRisk=mg.risk; if(!mg.exts.includes(ext)){ const lv=mg.risk==='high'?'danger':mg.risk==='medium'?'warn':'info'; findings.push({lv,ic:'⚡',lb:'File Type Mismatch — Possible Disguise',vl:`Actual file type: "${mg.type}" but extension is ".${esc(ext)}" — hiding executables behind innocent extensions is a classic malware distribution technique`}); } if(mg.risk==='high') findings.push({lv:'warn',ic:'⚠️',lb:`High-Risk File Type: ${mg.type}`,vl:'Only run executable files from completely trusted, verified sources'}); break; } } if(file.name.split('.').length>2) findings.push({lv:'warn',ic:'📄',lb:'Double Extension Detected',vl:`"${esc(file.name)}" — e.g. "invoice.pdf.exe" — malware frequently uses double extensions to appear safe`}); if(file.name.length>100) findings.push({lv:'info',ic:'📏',lb:'Very Long Filename',vl:'Unusually long filenames can indicate obfuscation'}); const suspNames=['invoice','payment','receipt','document','scan','urgent','important','contract','update','setup','install','crack','keygen','patch','loader','hack','exploit']; const execExts=['exe','dll','scr','bat','cmd','ps1','vbs','js','jar','hta','wsf']; const suspName=suspNames.find(w=>file.name.toLowerCase().includes(w)); if(suspName&&execExts.includes(ext)) findings.push({lv:'warn',ic:'📛',lb:'Suspicious Filename Pattern',vl:`"${esc(file.name)}" — "${suspName}" + executable extension is a common social engineering tactic`}); findings.push({lv:'info',ic:'🔬',lb:'Detected File Type',vl:`${detectedType} · First bytes: ${hex.substring(0,16)}… · Size: ${fmtBytes(file.size)}`}); resolve({detectedType,findings,size:file.size,typeRisk}); }; reader.readAsArrayBuffer(file); }); } function renderFile(file,hash,mb,tf,magic){ const mbFound=mb?.query_status==='ok'; const tfFound=tf?.query_status==='ok'&&tf?.data?.length>0; let score=mbFound?92:tfFound?85:magic.typeRisk==='high'?35:18; score=Math.min(100,score); const verdict=score>=60?'DANGER':score>=30?'SUSPICIOUS':'SAFE'; const sc=getSC(score); const mbInfo=mb?.data?.[0]||{}; const tfInfo=tfFound?tf.data[0]:null; const findings=[]; if(mbFound){ findings.push({lv:'danger',ic:'🦠',lb:'⛔ CONFIRMED MALWARE — MalwareBazaar (abuse.ch)',vl:`Family: ${esc(mbInfo.signature||'Unknown')} · File type: ${esc(mbInfo.file_type||'Unknown')} · First seen: ${esc(mbInfo.first_seen?.substring(0,10)||'Unknown')} · Reporter: ${esc(mbInfo.reporter||'community')}`}); if(mbInfo.tags?.length) findings.push({lv:'danger',ic:'🏷️',lb:'Malware Classification',vl:mbInfo.tags.slice(0,10).map(t=>esc(t)).join(' · ')}); if(mbInfo.vendor_intel){ const engines=Object.entries(mbInfo.vendor_intel).filter(([,v])=>v.detection).slice(0,8); if(engines.length) findings.push({lv:'danger',ic:'🔍',lb:`Flagged by ${engines.length} AV Vendor(s)`,vl:engines.map(([e,v])=>`${esc(e)}: ${esc(v.result||'detected')}`).join(' · ')}); } if(mbInfo.delivery_method) findings.push({lv:'danger',ic:'📬',lb:'Known Delivery Method',vl:esc(mbInfo.delivery_method)}); } else { findings.push({lv:'safe',ic:'✅',lb:'Not in MalwareBazaar',vl:'SHA-256 not found in MalwareBazaar\'s database of millions of malware samples. Note: very new malware may not yet appear.'}); } if(tfFound){ findings.push({lv:'danger',ic:'🎯',lb:'⛔ Found on ThreatFox — abuse.ch',vl:`IOC type: ${esc(tfInfo.ioc_type||'hash')} · Malware: ${esc(tfInfo.malware||'Unknown')} · Confidence: ${esc(String(tfInfo.confidence_level||'?'))}%`}); } else { findings.push({lv:'safe',ic:'✅',lb:'Not on ThreatFox',vl:'Hash not found in ThreatFox threat intelligence feed'}); } findings.push(...magic.findings); const statsBar=`
MalwareBazaar: ${mbFound?'⛔ FOUND':'✅ Clean'} ThreatFox: ${tfFound?'⛔ FOUND':'✅ Clean'} ${esc(magic.detectedType)} ${esc(fmtBytes(magic.size))}
`; const hashRow=`
✅ File never left your device — only cryptographic hashes sent to abuse.ch
SHA-256: ${esc(hash)}
`; showResults(`
${WARN_BANNER}${rHead(score,sc,verdict,esc(file.name),'MalwareBazaar · ThreatFox · Magic Byte Analysis')}${rMeter(score,sc)}${statsBar}
// File Analysis (${findings.length} checks)
${findings.map(f=>fItem(f)).join('')}
${hashRow}
`); animMeter(score); } // ── RENDER HELPERS ── function rdapDate(rdap, type){ if(!rdap?.events)return null; const e=rdap.events.find(ev=>ev.eventAction?.toLowerCase().includes(type)); return e?.eventDate||null; } const WARN_BANNER=`
⚠️
Reminder: Informational only. Safe ≠ definitely safe. Scams evolve daily. Always verify independently.
`; function getSC(s){ if(s>=60)return{cls:'d',color:'var(--danger)'}; if(s>=30)return{cls:'w',color:'var(--warn)'}; return{cls:'s',color:'var(--safe)'}; } function rHead(score,sc,verdict,title,sub){ return`
${score}
RISK
${verdict}
${title}
${esc(sub)}
`; } function rMeter(score,sc){ return`
SAFERISK ${score}/100DANGER
`; } function fItem(f){ return`
${f.ic}
${esc(f.lb)}
${esc(f.vl)}
`; } function animMeter(score){ setTimeout(()=>{ const el=document.getElementById('mf'); if(el)el.style.width=score+'%'; },150); } // ── LOADING / RESULTS ── function showLoading(steps){ clearResults(); document.getElementById('loadSteps').innerHTML=steps.map((s,i)=>`
${s}
`).join(''); document.getElementById('loading').classList.add('on'); } function step(i,state){ const el=document.getElementById('ls'+i); if(el)el.className='lstep '+state; } function hideLoading(){ document.getElementById('loading').classList.remove('on'); } function showResults(html){ const r=document.getElementById('results'); r.innerHTML=html; r.classList.add('on'); setTimeout(()=>r.scrollIntoView({behavior:'smooth',block:'nearest'}),100); } function showError(msg){ hideLoading(); showResults(`
⚠️
Scan Failed
${esc(msg)}
`); } function clearResults(){ document.getElementById('results').classList.remove('on'); document.getElementById('results').innerHTML=''; document.getElementById('loading').classList.remove('on'); } // ── HISTORY ── function addHist(type,query,score,verdict,findings){ scanHistory.unshift({type,query,score,verdict,time:new Date().toLocaleTimeString('en-GB',{hour:'2-digit',minute:'2-digit'})}); scanHistory=scanHistory.slice(0,20); localStorage.setItem('az_hist',JSON.stringify(scanHistory)); renderHist(); maybeSendScanEmail(type,query,score,verdict,findings||[]); if(window.fbSaveScan) window.fbSaveScan(type,query,score,verdict); } function renderHist(){ const card=document.getElementById('histCard'), list=document.getElementById('histList'); if(!scanHistory.length){ card.style.display='none'; return; } card.style.display='block'; list.innerHTML=scanHistory.map(h=>{ const sc=getSC(h.score); return`
${h.type}
${esc(h.query)}
${h.score}
${h.time}
`; }).join(''); } function clearHist(){ scanHistory=[]; localStorage.removeItem('az_hist'); document.getElementById('histCard').style.display='none'; } // ── EMAIL SCAN RESULT ── window.addEventListener('DOMContentLoaded',()=>{ // restore scan history try{ const h=localStorage.getItem('az_hist'); if(h){scanHistory=JSON.parse(h);renderHist();} }catch(e){} updateCounter(); // drop zone const dz=document.getElementById('dropZone'); dz.addEventListener('dragover',e=>{e.preventDefault();dz.classList.add('over');}); dz.addEventListener('dragleave',()=>dz.classList.remove('over')); dz.addEventListener('drop',e=>{e.preventDefault();dz.classList.remove('over');if(e.dataTransfer.files[0])handleFile(null,e.dataTransfer.files[0]);}); // email toggle guard document.getElementById('emailResultToggle').addEventListener('change',()=>{ if(!window._currentUser){ document.getElementById('emailResultToggle').checked=false; openAuth('login'); } }); // detect language initTheme(); detectLang(); });