phishguard-api / content.js
prashanth135's picture
Upload 38 files
bebe233 verified
// ============================================================
// 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();
}
})();