Spaces:
Running
Running
| // ============================================================ | |
| // PhishGuard AI - content.js | |
| // Content script: runs inside every page. | |
| // Detects phishing signals and injects feedback banner. | |
| // ============================================================ | |
| (function() { | |
| "use strict"; | |
| // ββ Page Signal Detection ββββββββββββββββββββββββββββββββββββββ | |
| function detectPageSignals() { | |
| const signals = []; | |
| const title = document.title || ""; | |
| const bodyText = (document.body?.innerText || "").substring(0, 2000).toLowerCase(); | |
| const url = window.location.href.toLowerCase(); | |
| // 1. Password form posting to external domain | |
| const forms = document.querySelectorAll("form"); | |
| forms.forEach(form => { | |
| const hasPassword = form.querySelector('input[type="password"]'); | |
| const action = (form.getAttribute("action") || "").toLowerCase(); | |
| if (hasPassword && action.startsWith("http") && !action.includes(window.location.hostname)) { | |
| signals.push("password_form_external_action"); | |
| } | |
| }); | |
| // 2. Brand name in title mismatching hostname | |
| const brands = ["paypal","google","apple","microsoft","amazon","netflix", | |
| "facebook","instagram","chase","wellsfargo","bankofamerica"]; | |
| const hostname = window.location.hostname.toLowerCase(); | |
| for (const brand of brands) { | |
| if (title.toLowerCase().includes(brand) && !hostname.includes(brand)) { | |
| signals.push(`brand_mismatch:${brand}`); | |
| } | |
| } | |
| // 3. Urgency language | |
| const urgencyPhrases = [ | |
| "your account has been", "verify immediately", "suspended", | |
| "unusual activity", "click here now", "act now", | |
| "confirm your identity", "limited time", "expires soon" | |
| ]; | |
| for (const phrase of urgencyPhrases) { | |
| if (bodyText.includes(phrase)) { | |
| signals.push("urgency_language"); | |
| break; | |
| } | |
| } | |
| // 4. Hidden iframes | |
| const iframes = document.querySelectorAll("iframe"); | |
| iframes.forEach(iframe => { | |
| const style = window.getComputedStyle(iframe); | |
| const w = parseInt(style.width) || iframe.width; | |
| const h = parseInt(style.height) || iframe.height; | |
| if (style.display === "none" || style.visibility === "hidden" || | |
| (w <= 1 && h <= 1)) { | |
| signals.push("hidden_iframe"); | |
| } | |
| }); | |
| return { | |
| title, | |
| snippet: bodyText.substring(0, 500), | |
| signals, | |
| }; | |
| } | |
| // Send signals to background.js | |
| try { | |
| const pageData = detectPageSignals(); | |
| chrome.runtime.sendMessage({ | |
| type: "page_signals", | |
| url: window.location.href, | |
| ...pageData, | |
| }); | |
| } catch (e) { | |
| // Extension context may be invalidated | |
| } | |
| // ββ Feedback Banner Injection ββββββββββββββββββββββββββββββββββ | |
| // Listen for messages from background.js to inject banner | |
| chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { | |
| if (msg.type === "inject_feedback_banner") { | |
| injectFeedbackBanner(msg.verdict, msg.confidence, msg.urlHash, msg.tier); | |
| sendResponse({ success: true }); | |
| } | |
| }); | |
| function injectFeedbackBanner(verdict, confidence, urlHash, tier) { | |
| // Don't inject if already present | |
| if (document.getElementById("phishguard-feedback-banner")) return; | |
| const isPhishing = verdict === "phishing"; | |
| const confPct = Math.round(confidence * 100); | |
| const tierText = `Tier ${tier}`; | |
| const banner = document.createElement("div"); | |
| banner.id = "phishguard-feedback-banner"; | |
| banner.style.cssText = ` | |
| position: fixed; top: 0; left: 0; right: 0; z-index: 2147483647; | |
| background: ${isPhishing ? "linear-gradient(135deg, #1a0000, #3a0000)" : "linear-gradient(135deg, #001a00, #003a00)"}; | |
| color: white; padding: 10px 20px; | |
| display: flex; align-items: center; gap: 12px; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| font-size: 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); | |
| border-bottom: 2px solid ${isPhishing ? "#ef4444" : "#22c55e"}; | |
| `; | |
| const icon = isPhishing ? "π‘οΈ" : "β "; | |
| const statusText = isPhishing ? "PhishGuard flagged this page" : "PhishGuard: Page looks safe"; | |
| banner.innerHTML = ` | |
| <span style="font-size: 18px">${icon}</span> | |
| <span style="flex: 1">${statusText} Β· ${confPct}% Β· ${tierText}</span> | |
| <button id="pg-correct" style=" | |
| background: rgba(34,197,94,0.2); border: 1px solid #22c55e; color: #22c55e; | |
| padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; | |
| font-weight: 600; transition: all 0.2s; | |
| ">π Correct</button> | |
| <button id="pg-wrong" style=" | |
| background: rgba(239,68,68,0.2); border: 1px solid #ef4444; color: #ef4444; | |
| padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; | |
| font-weight: 600; transition: all 0.2s; | |
| ">π Wrong</button> | |
| ${isPhishing ? `<button id="pg-proceed" style=" | |
| background: transparent; border: 1px solid rgba(255,255,255,0.2); color: #999; | |
| padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; | |
| transition: all 0.2s; | |
| ">Proceed Anyway</button>` : ""} | |
| <button id="pg-close" style=" | |
| background: none; border: none; color: #666; cursor: pointer; | |
| font-size: 18px; padding: 0 4px; | |
| ">Γ</button> | |
| `; | |
| document.body.prepend(banner); | |
| document.body.style.marginTop = (banner.offsetHeight) + "px"; | |
| // Button handlers | |
| document.getElementById("pg-correct")?.addEventListener("click", () => { | |
| submitBannerFeedback(urlHash, "correct", banner); | |
| }); | |
| document.getElementById("pg-wrong")?.addEventListener("click", () => { | |
| submitBannerFeedback(urlHash, "incorrect", banner); | |
| }); | |
| document.getElementById("pg-proceed")?.addEventListener("click", () => { | |
| chrome.runtime.sendMessage({ type: "whitelist_url", url: window.location.href }); | |
| removeBanner(banner); | |
| }); | |
| document.getElementById("pg-close")?.addEventListener("click", () => { | |
| removeBanner(banner); | |
| }); | |
| } | |
| function submitBannerFeedback(urlHash, feedback, banner) { | |
| chrome.runtime.sendMessage({ | |
| type: "submit_feedback", | |
| url_hash: urlHash, | |
| feedback: feedback, | |
| }, (response) => { | |
| if (response?.success) { | |
| banner.innerHTML = ` | |
| <span style="font-size: 18px">β </span> | |
| <span style="flex: 1; color: #22c55e">Thanks! Your feedback helps improve PhishGuard</span> | |
| `; | |
| setTimeout(() => removeBanner(banner), 3000); | |
| } | |
| }); | |
| } | |
| function removeBanner(banner) { | |
| document.body.style.marginTop = ""; | |
| banner.remove(); | |
| } | |
| })(); | |