feat: Add Enterprise App Protection extension with settings and popup UI

- Implement manifest.json for Chrome extension with necessary permissions and background scripts.
- Create options.html for user settings including Google Safe Browsing API key, domains database URL, update interval, and warning message template.
- Develop options.js to handle loading and saving settings using Chrome storage.
- Design popup.html to display suspicious links and provide options to update the database and manage domains.
- Implement popup.js to manage interactions in the popup, including updating the database and resetting the suspicious links counter.
- Add testsite.html for dynamic testing of the extension with both official and fake links.
This commit is contained in:
becarta
2025-05-10 03:25:49 +02:00
commit ba83c95914
21 changed files with 1485 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
EnterpriseAppProtection.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

BIN
ExtensionPopup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

125
README.md Normal file
View File

@@ -0,0 +1,125 @@
# Enterprise App Protection
## 🔍 What This Extension Does
Protect yourself and your organization from phishing attacks that impersonate common enterprise applications like DocuSign, Salesforce, Microsoft 365, and hundreds more. This extension:
-**Automatically scans links** in your browser and emails in real time
-**Alerts you** when a link claims to be from a trusted enterprise app but leads to an unofficial domain
-**Uses Google Safe Browsing API** to detect phishing and malware threats beyond known fake domains
-**Maintains an up-to-date database** of legitimate enterprise application domains
-**Detects dynamically added links** (e.g., in Outlook Web, Teams, SharePoint)
-**Works with 150+ enterprise applications**
-**Functions completely offline** after initial setup (except for Safe Browsing checks)
![Enterprise App Protection Screenshot](https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/EnterpriseAppProtection.png)
---
## ⚙️ How It Works
When you visit a webpage or open an email, the extension:
1. **Scans all links** and detects if any enterprise applications (like "DocuSign" or "Salesforce") are mentioned
2. **Verifies if the associated links actually go to official domains**
3. **Checks Google Safe Browsing** to detect malware and phishing links not in its internal database
4. **Detects links inside dynamically loaded content** (like Outlook Web, Microsoft Teams, SharePoint)
5. **Shows a clear warning** if a potential impersonation attempt is detected
---
## 🔐 Privacy & Security
- **Zero Data Collection:** This extension does not collect, store, or transmit any personal data, browsing history, or email content.
- **Completely Offline:** After initial installation, all domain checks are performed locally on your device.
- **No Cloud Processing:** All link analysis happens directly in your browser.
- **Uses Google Safe Browsing API:** Checks URLs against Googles real-time phishing and malware database.
- **Open Source:** All code is available for review.
---
## 🚫 What This Extension Doesn't Do
- ❌ Does **NOT** access, read, or store your email content or attachments.
- ❌ Does **NOT** track your browsing history.
- ❌ Does **NOT** require an account or registration.
- ❌ Does **NOT** send any data back to our servers.
- ❌ Does **NOT** modify or alter any content—it only shows warnings.
- ❌ Does **NOT** prevent you from visiting any websites.
---
## 🔹 Trusted & Blocked Domains
- **Trusted Domains:** These domains are always allowed and will not be flagged.
- **Blocked Domains:** These domains will always be marked as unsafe.
To modify trusted/blocked domains:
1. Open the **extension options page**.
2. Add or remove domains under **"Trusted Domains"** or **"Blocked Domains"**.
3. Click **"Update Database"** to apply changes.
---
## 🔍 Google Safe Browsing API
This extension integrates with **Google Safe Browsing** to detect additional phishing and malware sites.
If Google **does not recognize a site as unsafe**, it will not be flagged unless it is in the **blocked domains list**.
🔹 **Report new phishing domains to Google** → [Submit a phishing site](https://safebrowsing.google.com/safebrowsing/report_phish/)
---
## 👥 Perfect For
- **Business professionals** who regularly use enterprise applications
- **IT security teams** looking to protect their organizations
- **Anyone concerned about phishing attacks** targeting business services
- **Organizations using multiple cloud-based enterprise applications**
- **Microsoft 365 users** (Outlook, Teams, SharePoint) who want extra security
---
## 🖥️ System Requirements
- **Google Chrome 88+ / Microsoft Edge 88+**
- **Works with Microsoft Outlook Web, Teams, and SharePoint**
- **Internet connection required for Safe Browsing checks (optional)**
---
## 🛠️ Troubleshooting
### **❓ Why is a suspicious site not flagged?**
- It might **not be in the `domains.json` database**.
- Google Safe Browsing **does not recognize it as a phishing site**.
- The domain may be a **legitimate subdomain** of an official service.
### **❓ Why is a link incorrectly flagged?**
- If the link **contains a word matching an app name** but is not actually phishing.
- You can add the domain to **"Trusted Domains"** in the options page.
---
## 🔥 Latest Updates
### ✅ **Final Version Features**
- **⚡ Dynamic Link Scanning:** Detects phishing links inside emails, Teams, and SharePoint without reloading the page.
- **🎯 Google Safe Browsing Support:** Detects additional phishing sites beyond known fake domains.
- **🛡️ Improved Matching:** Ensures only full app names trigger warnings.
- **🚀 Optimized Performance:** No duplicate warnings, reduced false positives.
- **📡 No More Debugging Logs:** Production-ready version with clean console logs.
---
## Screenshots
### 🔧 Settings Page
The settings page allows users to configure the Google Safe Browsing API key and manage trusted and blocked domains. Users can enter domains manually to whitelist or blacklist specific sites.
![Settings Page](https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/SettingPage.png)
### ⚠️ Extension Popup
The extension popup provides real-time feedback when navigating websites. If a domain is flagged as unsafe, the user receives an alert, helping to prevent phishing and malicious activity.
![Extension Popup](https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/ExtensionPopup.png)
---

BIN
SettingPage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

100
background.js Normal file
View File

@@ -0,0 +1,100 @@
// background.js
console.log("Background service worker loaded");
let domainsDB = {};
async function updateDomainsDB() {
chrome.storage.local.get(["domainsDBURL"], async function (result) {
const domainsDBURL = result.domainsDBURL || "https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json";
console.log("updateDomainsDB: Starting update from URL:", domainsDBURL);
try {
const response = await fetch(domainsDBURL);
if (!response.ok) {
console.error("updateDomainsDB: Fetch failed with status", response.status, response.statusText);
return;
}
domainsDB = await response.json();
chrome.storage.local.set({
domainsDB: domainsDB,
lastUpdate: Date.now()
}, () => {
console.log("updateDomainsDB: Database updated successfully at", new Date().toLocaleString());
});
} catch (error) {
console.error("updateDomainsDB: Failed to update domains database:", error);
}
});
}
// Run updates when the extension is installed or started
chrome.runtime.onInstalled.addListener(() => {
console.log("Extension installed");
updateDomainsDB();
});
chrome.runtime.onStartup.addListener(() => {
console.log("Service worker started");
updateDomainsDB();
});
// Periodic update based on UPDATE_INTERVAL (in hours)
setInterval(() => {
chrome.storage.local.get(["updateInterval"], function (result) {
const updateInterval = result.updateInterval || 24;
console.log("Periodic update triggered");
updateDomainsDB();
});
}, 3600000);
// Listen for manual update messages (e.g., from the popup)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("onMessage: Received message:", message);
if (message.action === "updateDB") {
console.log("onMessage: Manual update triggered via Update Now button");
updateDomainsDB().then(() => {
console.log("onMessage: Manual update completed");
sendResponse({ status: "updated" });
});
return true; // Keep message channel open for async response
}
if (message.action === "updatePopup") {
// Store flagged links and update the total suspicious count
chrome.storage.local.get(["totalSuspiciousLinks"], (result) => {
let totalCount = result.totalSuspiciousLinks || 0;
totalCount += message.flaggedLinks.length; // Add new links found in this session
chrome.storage.local.set({
flaggedLinks: message.flaggedLinks,
totalSuspiciousLinks: totalCount
}, () => {
console.log(`Updated totalSuspiciousLinks: ${totalCount}`);
});
});
}
if (message.action === "getSuspiciousLinks") {
// Retrieve both flagged links and total suspicious link count
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], (data) => {
sendResponse({
count: data.totalSuspiciousLinks || 0,
links: data.flaggedLinks || []
});
});
return true; // Keep message channel open for async response
}
});
// Debug Mode: Keep-alive mechanism for testing
const DEBUG_MODE = false; // Set to 'true' for testing
if (DEBUG_MODE) {
chrome.alarms.create("keepAlive", { delayInMinutes: 0.1, periodInMinutes: 0.1 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "keepAlive") {
console.log("keepAlive alarm triggered, service worker is active");
}
});
}

9
config.js Normal file
View File

@@ -0,0 +1,9 @@
// config.js
const CONFIG = {
// URL to fetch the approved enterprise domains list
DOMAINS_DB_URL: 'https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json',
// Update interval in hours
UPDATE_INTERVAL: 24,
// Warning message template for suspicious links
WARNING_TEMPLATE: "Warning: This link claims to be {app} but goes to an unofficial domain."
};

240
content.js Normal file
View File

@@ -0,0 +1,240 @@
// content.js
let SAFE_BROWSING_API_KEY = "";
let domainsDB = {};
let trustedDomains = [];
let blockedDomains = [];
let warningTemplate = "Warning: This link claims to be {app} but goes to an unofficial domain.";
let isDomainsLoaded = false;
// ✅ Get the top-level domain (TLD)
function getTopLevelDomain(hostname) {
const domainParts = hostname.split(".");
const knownTLDs = ["co.uk", "com.au", "gov.uk", "edu.au"];
if (domainParts.length > 2) {
const lastTwoParts = domainParts.slice(-2).join(".");
if (knownTLDs.includes(lastTwoParts)) {
return domainParts.slice(-3).join(".");
}
}
return domainParts.slice(-2).join(".");
}
// ✅ Check if a domain is valid
function isValidDomain(domain, validDomains, trustedDomains) {
const extractedTLD = getTopLevelDomain(domain);
if (trustedDomains.includes(domain) || trustedDomains.includes(extractedTLD)) {
return true; // User has explicitly marked this domain as safe
}
return validDomains.some(validDomain => {
const validTLD = getTopLevelDomain(validDomain);
return extractedTLD === validTLD;
});
}
// ✅ Check Google Safe Browsing API for dangerous links
async function checkGoogleSafeBrowsing(url) {
console.log("🔍 Checking Google Safe Browsing for:", url);
if (!SAFE_BROWSING_API_KEY) {
console.warn("⚠️ API key is not set. Skipping Safe Browsing check.");
return false;
}
const response = await fetch(`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${SAFE_BROWSING_API_KEY}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client: { clientId: "enterprise-app-protection", clientVersion: "1.0" },
threatInfo: {
threatTypes: ["MALWARE", "SOCIAL_ENGINEERING"],
platformTypes: ["ANY_PLATFORM"],
threatEntryTypes: ["URL"],
threatEntries: [{ url }]
}
})
});
const data = await response.json();
console.log("🔹 Google Safe Browsing API Response:", data);
return data.matches ? true : false;
}
// ✅ Scan page content for suspicious links
function analyzePageContent() {
if (!isDomainsLoaded || !domainsDB || Object.keys(domainsDB).length === 0) {
console.warn("⚠️ domainsDB is not ready yet. Skipping analysis.");
return;
}
const links = document.querySelectorAll("a:not(.checked-by-extension)");
let newFlaggedLinks = new Set();
links.forEach(link => {
try {
// ✅ Validate the URL before using it
let url;
try {
url = new URL(link.href);
} catch (e) {
console.warn("⚠️ Skipping invalid URL:", link.href);
return; // Stop processing this link
}
const domain = url.hostname.toLowerCase();
const linkText = link.innerText.trim();
// ✅ Mark link as processed
link.classList.add("checked-by-extension");
// ✅ Skip trusted domains
if (trustedDomains.includes(domain) || trustedDomains.includes(getTopLevelDomain(domain))) {
console.log("Skipping trusted domain:", domain);
return;
}
let matchedApp = null;
// ✅ Find the most specific matching app name (ONLY full word matches)
for (const [appName, validDomains] of Object.entries(domainsDB)) {
const regex = new RegExp(`\\b${appName}\\b`, "i"); // Ensure full-word match
if (regex.test(linkText)) {
matchedApp = appName; // Keep the most specific match
}
}
if (matchedApp && domainsDB[matchedApp]) {
const validDomains = domainsDB[matchedApp];
const isValid = isValidDomain(domain, validDomains, trustedDomains);
if (!isValid) {
newFlaggedLinks.add(url.href);
// ✅ Add warning immediately
const warning = document.createElement("div");
warning.classList.add("warning-alert");
warning.style.cssText = `
background: #fff3cd;
color: #856404;
padding: 10px;
border: 1px solid #ffeeba;
border-radius: 4px;
margin: 5px 0;
font-size: 14px;
`;
warning.textContent = `⚠️ ${warningTemplate.replace("{app}", matchedApp)}`;
warning.setAttribute("title", `This link goes to ${domain} instead of an official ${matchedApp} domain.`);
link.parentElement.insertBefore(warning, link.nextSibling);
// ✅ Update warning if Google Safe Browsing confirms danger
checkGoogleSafeBrowsing(url.href).then(isUnsafe => {
if (isUnsafe) {
warning.textContent = `⚠️ This link is confirmed dangerous by Google Safe Browsing!`;
}
});
}
}
} catch (e) {
console.error("Unexpected error processing link:", e);
}
});
// ✅ Store flagged links without waiting for Safe Browsing API
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], function (result) {
let existingLinks = new Set(result.flaggedLinks || []);
let totalCount = existingLinks.size;
let newLinksToAdd = [...newFlaggedLinks].filter(link => !existingLinks.has(link));
if (newLinksToAdd.length > 0) {
totalCount += newLinksToAdd.length;
chrome.storage.local.set({
totalSuspiciousLinks: totalCount,
flaggedLinks: [...existingLinks, ...newLinksToAdd]
}, () => {
chrome.runtime.sendMessage({ action: "updatePopup", flaggedLinks: [...existingLinks, ...newLinksToAdd], totalCount });
});
}
});
}
// ✅ Load domains and start analysis once ready
function loadDomainsAndAnalyze() {
chrome.storage.local.get(["domainsDB", "trustedDomains", "blockedDomains", "safeBrowsingApiKey", "warningTemplate"], function (result) {
domainsDB = result.domainsDB || {};
trustedDomains = result.trustedDomains || [];
blockedDomains = result.blockedDomains || [];
SAFE_BROWSING_API_KEY = result.safeBrowsingApiKey || "";
warningTemplate = result.warningTemplate || "Warning: This link claims to be {app} but goes to an unofficial domain.";
console.log("✅ Loaded API Key:", SAFE_BROWSING_API_KEY);
isDomainsLoaded = true;
console.log("✅ Domains database loaded:", domainsDB);
analyzePageContent();
});
}
// Debounce function to limit how often a function can be called
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Enhanced error handling wrapper
function withErrorHandling(fn, errorMessage) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
console.error(`${errorMessage}:`, error);
// You could add error reporting service here
return null;
}
};
}
// Debounced version of analyzePageContent
const debouncedAnalyzePageContent = debounce(analyzePageContent, 250);
// Enhanced version of analyzePageContent with better error handling
const enhancedAnalyzePageContent = withErrorHandling(debouncedAnalyzePageContent, "Error analyzing page content");
// Update the observer to use the enhanced version
function observePageForLinks() {
const observer = new MutationObserver(() => enhancedAnalyzePageContent());
observer.observe(document.body, { childList: true, subtree: true });
function observeIframes() {
document.querySelectorAll("iframe").forEach((iframe) => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
observer.observe(iframeDoc.body, { childList: true, subtree: true });
console.log("Observing links inside iframe:", iframe.src);
}
} catch (e) {
console.warn("Cannot access iframe due to cross-origin restrictions:", iframe.src);
}
});
}
// ✅ Run iframe observer every 3 seconds for Office 365 dynamic content
setInterval(observeIframes, 3000);
}
// ✅ Initialize extension scanning
document.addEventListener("DOMContentLoaded", loadDomainsAndAnalyze);
window.addEventListener("load", loadDomainsAndAnalyze);
observePageForLinks();

225
domains.json Normal file
View File

@@ -0,0 +1,225 @@
{
"1Password": ["1password.com", "1password.eu", "agilebits.com"],
"2checkout": ["2checkout.com", "2co.com", "verifone.cloud"],
"3CX": ["3cx.com", "3cx.net", "3cx.cloud"],
"3M Cloud": ["3m.com", "3mcloud.com"],
"4me": ["4me.com", "4me.app"],
"6sense": ["6sense.com", "6sense.io", "6sense.ai"],
"7digital": ["7digital.com", "7digital.net"],
"8x8": ["8x8.com", "8x8.net", "8x8.vc"],
"10x Genomics": ["10xgenomics.com", "10xgenomics.net"],
"A10 Networks": ["a10networks.com", "a10networks.net"],
"Aason": ["aason.com", "aason.cloud"],
"Abacus": ["abacus.com", "abacusfi.com"],
"ABB": ["abb.com", "abb.net"],
"AbbVie": ["abbvie.com", "abbvie.net"],
"Abiomed": ["abiomed.com", "abiomed.net"],
"Abnormal Security": ["abnormalsecurity.com", "abnormal.com"],
"Absolute Software": ["absolute.com", "absolutesoftware.com"],
"Absolutdata": ["absolutdata.com", "absolutdata.net"],
"Abukai": ["abukai.com", "abukaiexpense.com"],
"Accenture": ["accenture.com", "accenture.net"],
"AccuWeather": ["accuweather.com", "accuweather.net"],
"Achieve3000": ["achieve3000.com", "achieve3000.net"],
"Acquia": ["acquia.com", "acquia.net"],
"Acronis": ["acronis.com", "acronis.net"],
"ActionIQ": ["actioniq.com", "actioniq.ai"],
"Actionstep": ["actionstep.com", "actionstep.net"],
"Active Campaign": ["activecampaign.com", "activehosted.com"],
"ActiveBatch": ["advsyscon.com", "activebatch.com"],
"Acuity Scheduling": ["acuityscheduling.com", "squareup.com"],
"Acumatica": ["acumatica.com", "acumatica.net"],
"Adaptive Insights": ["adaptiveinsights.com", "adaptive.com"],
"AdaptiveShield": ["adaptiveshield.com", "adaptiveshield.io"],
"Addteq": ["addteq.com", "addteq.net"],
"Adobe": ["adobe.com", "adobe.net", "adobelogin.com", "adobe.io", "creative.adobe.com", "acrobat.com", "behance.net"],
"ADP": ["adp.com", "workforcenow.adp.com", "run.adp.com", "adpself-service.com"],
"Adroll": ["adroll.com", "adroll.net"],
"Adswerve": ["adswerve.com", "analytics-360.com"],
"Advantech": ["advantech.com", "advantech.net"],
"Aerospike": ["aerospike.com", "aerospike.net"],
"Agiloft": ["agiloft.com", "agiloft.net"],
"Aha!": ["aha.io", "aha.com"],
"Airbnb": ["airbnb.com", "airbnb.co.uk", "airbnb.ca", "airbnbchina.cn"],
"Airtable": ["airtable.com", "airtable.net", "airtableusercontent.com"],
"Airwatch": ["air-watch.com", "awmdm.com"],
"Akamai": ["akamai.com", "akamai.net", "akamaiedge.net", "akamaized.net"],
"Akka": ["akka.io", "lightbend.com"],
"Alarm.com": ["alarm.com", "alarm.net"],
"Alation": ["alation.com", "alation.net"],
"Alcatel-Lucent": ["al-enterprise.com", "alcatel-lucent.com"],
"AlertMedia": ["alertmedia.com", "alertmedia.net"],
"Alfresco": ["alfresco.com", "alfresco.net"],
"Algolia": ["algolia.com", "algolia.net", "algolianet.com"],
"AlienVault": ["alienvault.com", "att.com"],
"AliCloud": ["aliyun.com", "alibabacloud.com", "alipay.com"],
"Allocadia": ["allocadia.com", "allocadia.net"],
"Allscripts": ["allscripts.com", "allscriptscloud.com"],
"Alluxio": ["alluxio.com", "alluxio.io"],
"Alpha Software": ["alphasoftware.com", "alpha-anywhere.com"],
"Alteryx": ["alteryx.com", "alteryx.net"],
"Alto": ["alto.com", "alto.sh"],
"Altova": ["altova.com", "altova.net"],
"Alyne": ["alyne.com", "alyne.ai"],
"Amazon AWS": ["aws.amazon.com", "amazonaws.com", "cloudfront.net", "amazon.com", "awsstatic.com"],
"Amelia": ["amelia.ai", "amelia.com"],
"Amplitude": ["amplitude.com", "amplitude.net"],
"AMS Neve": ["ams-neve.com", "neve.com"],
"Anaplan": ["anaplan.com", "anaplan.net"],
"Anchore": ["anchore.com", "anchore.io"],
"Andela": ["andela.com", "andela.co"],
"Angular": ["angular.io", "angularjs.org"],
"Anime": ["anime.com", "crunchyroll.com"],
"Anova": ["anova.com", "anovaculinary.com"],
"Ansible": ["ansible.com", "redhat.com"],
"Anthos": ["cloud.google.com/anthos", "googleapis.com"],
"AntWorks": ["ant.works", "antworks.com"],
"Anydesk": ["anydesk.com", "anydesk.net"],
"Aon": ["aon.com", "aon.net"],
"Apache": ["apache.org", "apachecloud.com"],
"Apigee": ["apigee.com", "apigee.net"],
"Appcues": ["appcues.com", "appcues.net"],
"AppDirect": ["appdirect.com", "appdirect.net"],
"AppDynamics": ["appdynamics.com", "appdynamics.net"],
"Appery.io": ["appery.io", "appery.net"],
"Appfolio": ["appfolio.com", "appfolio.net"],
"Appian": ["appian.com", "appian.net"],
"Apple Business": ["apple.com", "icloud.com"],
"ApplicationHA": ["veritas.com", "symantec.com"],
"Apprenda": ["apprenda.com", "apprenda.net"],
"AppSense": ["appsense.com", "ivanti.com"],
"Apptio": ["apptio.com", "apptio.net"],
"Apptus": ["apptus.com", "apptus.net"],
"AppViewX": ["appviewx.com", "appviewx.net"],
"Apptentive": ["apptentive.com", "apptentive.net"],
"Aprimo": ["aprimo.com", "aprimo.net"],
"ArangoDB": ["arangodb.com", "arangodb.net"],
"Aravo": ["aravo.com", "aravo.net"],
"Arcadia Data": ["arcadiadata.com", "arcadia.com"],
"Archer": ["archerirm.com", "rsa.com"],
"Archi": ["archimatetool.com", "archi.com"],
"Archibus": ["archibus.com", "archibus.net"],
"Arctic Wolf": ["arcticwolf.com", "arcticwolf.net"],
"Aria Systems": ["ariasystems.com", "aria.com"],
"Arista": ["arista.com", "aristanetworks.com"],
"Arkadin": ["arkadin.com", "arkadin.net"],
"ArmisIT": ["armis.com", "armis.io"],
"Armorblox": ["armorblox.com", "armorblox.io"],
"Asana": ["asana.com", "asana.net", "app.asana.com"],
"Ascender": ["ascenderpay.com", "ascender.com"],
"AspenTech": ["aspentech.com", "aspentech.net"],
"Assemble": ["assemble.com", "assemblesystems.com"],
"Assessteam": ["assessteam.com", "assessteam.net"],
"Atlassian": ["atlassian.com", "jira.com", "atlassian.net"],
"Atlassian Jira": ["atlassian.com", "jira.com", "atlassian.net"],
"Aternity": ["aternity.com", "aternity.net"],
"Atos": ["atos.net", "atosglobal.com"],
"Attentive": ["attentive.com", "attentivemobile.com"],
"Auth0": ["auth0.com", "auth0.net"],
"Autodesk": ["autodesk.com", "autodesk.net", "autocad360.com"],
"Automation Anywhere": ["automationanywhere.com", "autoany.com"],
"Avalara": ["avalara.com", "avalara.net"],
"Avaya": ["avaya.com", "avaya.net"],
"Aveva": ["aveva.com", "aveva.net"],
"Basecamp": ["basecamp.com", "basecamp.net"],
"BigCommerce": ["bigcommerce.com", "bigcommerce.net"],
"Bitbucket": ["bitbucket.org", "atlassian.com"],
"Bitdefender": ["bitdefender.com", "bitdefender.net"],
"BlueJeans": ["bluejeans.com", "bluejeansnet.com"],
"Box": ["box.com", "boxcdn.net"],
"Buffer": ["buffer.com", "bufferapp.com"],
"Carbon Black": ["carbonblack.com", "vmware.com"],
"Canva": ["canva.com", "canvaassets.com"],
"Chef": ["chef.io", "progress.com"],
"Cisco AnyConnect": ["cisco.com", "vpn.com"],
"Cisco Jabber": ["jabber.com", "cisco.com"],
"Cisco WebEx": ["webex.com", "cisco.com"],
"Citrix": ["citrix.com", "cloud.com"],
"Cloudflare": ["cloudflare.com", "cloudflare.net", "cfassets.net"],
"Confluence": ["confluence.com", "atlassian.com", "atlassian.net"],
"CrowdStrike": ["crowdstrike.com", "crowdstrike.net"],
"CrowdStrike Falcon": ["crowdstrike.com", "falcon.crowdstrike.com"],
"Datadog": ["datadoghq.com", "datadog.com"],
"DigitalOcean": ["digitalocean.com", "digitaloceanspaces.com"],
"Docker": ["docker.com", "docker.io"],
"Docusign": ["docusign.com", "docusign.net", "docusigncdn.com"],
"Dropbox": ["dropbox.com", "dropboxusercontent.com", "dropboxapi.com"],
"Dropbox Paper": ["paper.dropbox.com", "dropboxusercontent.com"],
"Elastic": ["elastic.co", "elasticsearch.com"],
"ExpressVPN": ["expressvpn.com", "expressvpn.net"],
"Fastly": ["fastly.com", "fastly.net"],
"Flock": ["flock.com", "flock.co"],
"Fortinet": ["fortinet.com", "fortiguard.com", "forticloud.com"],
"Freshdesk": ["freshdesk.com", "freshworks.com"],
"GitHub": ["github.com", "githubusercontent.com"],
"GitLab": ["gitlab.com", "gitlab.net"],
"GoDaddy": ["godaddy.com", "godaddy.net"],
"Google Workspace": ["google.com", "workspace.google.com", "googleusercontent.com"],
"Grafana": ["grafana.com", "grafana.net"],
"Heroku": ["heroku.com", "salesforce.com", "herokuapp.com"],
"Hootsuite": ["hootsuite.com", "hootsuitecdn.com"],
"HubSpot": ["hubspot.com", "hubspot.net", "hs-sites.com"],
"InVision": ["invisionapp.com", "invision.com"],
"Intuit": ["intuit.com", "quickbooks.com"],
"Kaspersky": ["kaspersky.com", "kaspersky.net"],
"Keeper": ["keepersecurity.com", "keepersecurity.net"],
"Kubernetes": ["kubernetes.io", "cloud.google.com"],
"LastPass": ["lastpass.com", "logmein.com"],
"LinkedIn": ["linkedin.com", "linkedin.net", "licdn.com"],
"LogMeIn": ["logmein.com", "logmeinrescue.com"],
"Lucidchart": ["lucidchart.com", "lucid.co"],
"Magento": ["magento.com", "magento.net"],
"Mailchimp": ["mailchimp.com", "mailchimp.net"],
"Marketo": ["marketo.com", "adobe.com"],
"McAfee": ["mcafee.com", "mcafee.net"],
"Microsoft 365": ["microsoft.com", "office.com", "onmicrosoft.com"],
"Microsoft Teams": ["teams.microsoft.com", "office.com"],
"Miro": ["miro.com", "mirocdn.com"],
"Monday.com": ["monday.com", "monday.net"],
"Netlify": ["netlify.com", "netlify.app"],
"New Relic": ["newrelic.com", "newrelic.net"],
"NordVPN": ["nordvpn.com", "nordvpn.net"],
"Notion": ["notion.so", "notion.com"],
"Okta": ["okta.com", "okta.net"],
"Olark": ["olark.com", "olark.net"],
"Oracle": ["oracle.com", "oraclecloud.com"],
"Palo Alto Networks": ["paloaltonetworks.com", "paloaltonetworks.net"],
"PagerDuty": ["pagerduty.com", "pagerduty.net"],
"PayPal": ["paypal.com", "paypalobjects.com", "paypal-cdn.com"],
"Pardot": ["pardot.com", "salesforce.com"],
"Proofpoint": ["proofpoint.com", "proofpoint.net"],
"Prometheus": ["prometheus.io", "cloud.google.com"],
"Puppet": ["puppet.com", "puppetlabs.com"],
"Red Hat": ["redhat.com", "openshift.com"],
"RingCentral": ["ringcentral.com", "ringcentral.net"],
"SAP": ["sap.com", "sap-anywhere.com"],
"SAP Concur": ["concur.com", "sap.com"],
"Salesforce": ["salesforce.com", "force.com"],
"SentinelOne": ["sentinelone.com", "sentinelone.net"],
"ServiceNow": ["servicenow.com", "now.com"],
"Shopify": ["shopify.com", "shopifycdn.com"],
"Slack": ["slack.com", "slack-edge.com"],
"Sophos": ["sophos.com", "sophos.net"],
"Splashtop": ["splashtop.com", "splashtop.net"],
"Splunk": ["splunk.com", "splunkcloud.com"],
"Square": ["squareup.com", "square.com"],
"Stripe": ["stripe.com", "stripe.net"],
"Symantec": ["symantec.com", "broadcom.com"],
"Tableau": ["tableau.com", "salesforce.com"],
"Tanium": ["tanium.com", "tanium.net"],
"Terraform": ["terraform.io", "hashicorp.com"],
"TeamViewer": ["teamviewer.com", "teamviewer.net"],
"TOPdesk": ["topdesk.com", "topdesk.net"],
"Trello": ["trello.com", "atlassian.com"],
"Twilio": ["twilio.com", "twilio.ai"],
"Vercel": ["vercel.com", "vercel.app"],
"Vonage": ["vonage.com", "vonagebusiness.com"],
"WeTransfer": ["wetransfer.com", "wetransfer.net"],
"Wix": ["wix.com", "wixstatic.com"],
"Workday": ["workday.com", "workday.net"],
"Zendesk": ["zendesk.com", "zdassets.com"],
"Zendesk Sell": ["sell.zendesk.com", "zdassets.com"],
"Zoom": ["zoom.us", "zoom.com", "zoomgov.com"],
"ZoomInfo": ["zoominfo.com", "zoominfo.net"],
"Zoom Phone": ["zoom.us", "zoom.com"]
}

205
domains_management.html Normal file
View File

@@ -0,0 +1,205 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enterprise App Protection - Domain Management</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
}
.container {
max-width: 600px;
width: 100%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
color: #333;
text-align: center;
margin-bottom: 15px;
}
h3 {
font-size: 18px;
color: #555;
margin-top: 20px;
}
p {
font-size: 14px;
color: #666;
}
textarea {
width: 100%;
padding: 10px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 14px;
height: 120px;
resize: vertical;
box-sizing: border-box;
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 5px;
font-size: 14px;
overflow-x: auto;
}
.button-container {
text-align: center;
margin-top: 20px;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background-color: #0056b3;
}
/* Styled Popup */
.popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
width: 300px;
text-align: center;
}
.popup-content {
font-size: 16px;
color: #333;
}
.popup button {
background-color: #007bff;
color: white;
border: none;
padding: 8px 16px;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
.popup button:hover {
background-color: #0056b3;
}
.hidden {
display: none;
}
.visible {
display: block;
}
/* Style the placeholder text to make it look distinct from user input */
input::placeholder,
textarea::placeholder {
color: #aaa;
/* Lighter gray color */
font-style: italic;
/* Makes it look like a hint */
opacity: 1;
/* Ensures it's fully visible */
}
/* Ensure actual input text is clear */
input,
textarea {
color: #333;
/* Dark text for actual input */
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 5px;
font-size: 14px;
overflow-x: auto;
font-style: italic;
color: #333;
/* Make text italic */
}
</style>
</head>
<body>
<div class="container">
<h1>Enterprise App Protection - Domain Management</h1>
<!-- Restored Instructions -->
<h3>✅ How to Use</h3>
<ul>
<li>✅ Enter <strong>one domain per line</strong> (no commas or spaces).</li>
<li><strong>Do not include</strong> <code>https://</code> or <code>www.</code> (only enter the domain
name).</li>
<li>✅ You can add subdomains if needed (e.g., <code>sub.example.com</code>).</li>
<li><strong>Trusted Domains:</strong> These sites will <u>not</u> be flagged as unsafe.</li>
<li><strong>Blocked Domains:</strong> These sites will <u>always</u> be flagged, even if Google Safe
Browsing does not detect them.</li>
<li>🚨 <strong>Example Entries:</strong></li>
</ul>
<pre><em>
example.com
secure-site.org
sub.example.com
malicious-site.net
</em></pre>
<h3>✅ Trusted Domains (Safe List)</h3>
<textarea id="trusted" placeholder="Enter trusted domains here..."></textarea>
<h3>❌ Blocked Domains (Unsafe List)</h3>
<textarea id="blocked" placeholder="Enter blocked domains here..."></textarea>
<div class="button-container">
<button id="save">💾 Save Domains</button>
</div>
</div>
<!-- Styled Popup -->
<div id="popupMessage" class="popup hidden">
<div class="popup-content">
<p id="popupText">Changes saved successfully!</p>
<button id="popupClose">OK</button>
</div>
</div>
<script src="domains_management.js"></script>
</body>
</html>

39
domains_management.js Normal file
View File

@@ -0,0 +1,39 @@
document.addEventListener("DOMContentLoaded", function () {
const saveButton = document.getElementById("save");
const trustedInput = document.getElementById("trusted");
const blockedInput = document.getElementById("blocked");
// Load saved domains
chrome.storage.local.get(["trustedDomains", "blockedDomains"], function (data) {
if (data.trustedDomains) trustedInput.value = data.trustedDomains.join("\n");
if (data.blockedDomains) blockedInput.value = data.blockedDomains.join("\n");
});
// Save button event
saveButton.addEventListener("click", function () {
// Retrieve domains from input fields
const trustedDomains = trustedInput.value.split("\n").map(d => d.trim()).filter(d => d);
const blockedDomains = blockedInput.value.split("\n").map(d => d.trim()).filter(d => d);
// Save to Chrome storage
chrome.storage.local.set({ trustedDomains, blockedDomains }, function () {
showPopup("✅ Changes saved successfully!");
});
});
// Function to show styled popup message
function showPopup(message) {
const popup = document.getElementById("popupMessage");
const popupText = document.getElementById("popupText");
const popupClose = document.getElementById("popupClose");
popupText.innerText = message;
popup.classList.remove("hidden");
popup.classList.add("visible");
popupClose.addEventListener("click", function () {
popup.classList.remove("visible");
popup.classList.add("hidden");
});
}
});

BIN
icons/.DS_Store vendored Normal file

Binary file not shown.

BIN
icons/icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

BIN
icons/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

48
manifest.json Normal file
View File

@@ -0,0 +1,48 @@
{
"manifest_version": 3,
"name": "Enterprise App Protection",
"version": "1.0",
"description": "Warns when enterprise tool mentions are linked to non-official sites",
"permissions": [
"storage",
"activeTab",
"alarms",
"scripting",
"tabs"
],
"host_permissions": [
"https://raw.githubusercontent.com/*"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["config.js", "content.js"],
"run_at": "document_idle"
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"options_page": "options.html",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
},
"web_accessible_resources": [{
"resources": ["icons/*"],
"matches": ["<all_urls>"]
}]
}

141
options.html Normal file
View File

@@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enterprise App Protection - Settings</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
}
.container {
max-width: 600px;
width: 100%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
color: #333;
text-align: center;
margin-bottom: 15px;
}
h3 {
font-size: 18px;
color: #555;
margin-top: 20px;
}
p {
font-size: 14px;
color: #666;
}
input, textarea {
width: 100%;
padding: 10px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 14px;
box-sizing: border-box; /* Ensures padding is included in width */
}
textarea {
height: 120px;
resize: vertical;
}
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 5px;
font-size: 14px;
overflow-x: auto;
}
.button-container {
text-align: center;
margin-top: 20px;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background-color: #0056b3;
}
/* Style the placeholder text to make it look distinct from user input */
input::placeholder, textarea::placeholder {
color: #aaa; /* Lighter gray color */
font-style: italic; /* Makes it look like a hint */
opacity: 1; /* Ensures it's fully visible */
}
/* Ensure actual input text is clear */
input, textarea {
color: #333; /* Dark text for actual input */
}
</style>
</head>
<body>
<div class="container">
<h1>Enterprise App Protection - Settings</h1>
<h3>🔑 Google Safe Browsing API Key</h3>
<p>
To use Google Safe Browsing, you need an API key.
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer">
Click here to create a key
</a> (you may need to enable the Safe Browsing API in Google Cloud Console).
</p>
<input type="text" id="apiKey" placeholder="Enter your API key here" />
<h3>🌐 Domains Database URL</h3>
<p>Enter the URL for the domains database (JSON format):</p>
<input type="text" id="domainsDBURL" placeholder="Enter the URL here" value="https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json" />
<h3>⏱️ Update Interval (hours)</h3>
<p>Enter the interval (in hours) to check for updates to the domains database:</p>
<input type="number" id="updateInterval" placeholder="Enter the update interval here" value="24" />
<h3>⚠️ Warning Message Template</h3>
<p>Enter the template for the warning message (use <code>{app}</code> to represent the application name):</p>
<input type="text" id="warningTemplate" placeholder="Enter the warning message template here" value="Warning: This link claims to be {app} but goes to an unofficial domain." />
<div class="button-container">
<button id="save">💾 Save Settings</button>
</div>
</div>
<script src="options.js"></script>
</body>
</html>

54
options.js Normal file
View File

@@ -0,0 +1,54 @@
document.addEventListener("DOMContentLoaded", function () {
// Get UI elements safely
const apiKeyElement = document.getElementById("apiKey");
const domainsDBURLElement = document.getElementById("domainsDBURL");
const updateIntervalElement = document.getElementById("updateInterval");
const warningTemplateElement = document.getElementById("warningTemplate");
const saveButton = document.getElementById("save");
// Check if warningTemplateElement exists before setting value
if (!warningTemplateElement) {
console.error("⚠️ Element with ID 'warningTemplate' not found in options.html!");
}
// Default values
const defaultValues = {
safeBrowsingApiKey: "",
domainsDBURL: "https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json",
updateInterval: 24,
warningTemplate: "Warning: This link claims to be {app} but goes to an unofficial domain."
};
// Load stored settings and apply defaults where needed
chrome.storage.local.get(["safeBrowsingApiKey", "domainsDBURL", "updateInterval", "warningTemplate"], function (data) {
if (apiKeyElement) apiKeyElement.value = data.safeBrowsingApiKey || defaultValues.safeBrowsingApiKey;
if (domainsDBURLElement) domainsDBURLElement.value = data.domainsDBURL || defaultValues.domainsDBURL;
if (updateIntervalElement) updateIntervalElement.value = data.updateInterval || defaultValues.updateInterval;
if (warningTemplateElement) {
warningTemplateElement.value = data.warningTemplate || defaultValues.warningTemplate;
}
});
// Save settings when clicking "Save"
if (saveButton) {
saveButton.addEventListener("click", function () {
const apiKey = apiKeyElement && apiKeyElement.value.trim() ? apiKeyElement.value.trim() : defaultValues.safeBrowsingApiKey;
const domainsDBURL = domainsDBURLElement && domainsDBURLElement.value.trim() ? domainsDBURLElement.value.trim() : defaultValues.domainsDBURL;
const updateInterval = updateIntervalElement && updateIntervalElement.value.trim()
? parseInt(updateIntervalElement.value.trim()) || defaultValues.updateInterval
: defaultValues.updateInterval;
const warningTemplate = warningTemplateElement && warningTemplateElement.value.trim()
? warningTemplateElement.value.trim()
: defaultValues.warningTemplate;
chrome.storage.local.set({
safeBrowsingApiKey: apiKey,
domainsDBURL: domainsDBURL,
updateInterval: updateInterval,
warningTemplate: warningTemplate
}, function () {
alert("✅ Settings saved successfully!");
});
});
}
});

93
popup.html Normal file
View File

@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enterprise App Protection</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 15px;
width: 280px;
background-color: #f8f9fa;
text-align: center;
border-radius: 8px;
}
h2 {
margin-top: 0;
color: #333;
font-size: 18px;
}
p {
font-size: 14px;
margin: 10px 0;
color: #555;
}
.counter {
font-size: 16px;
font-weight: bold;
color: #dc3545;
}
ul {
list-style: none;
padding: 0;
text-align: left;
max-height: 120px;
overflow-y: auto;
background: #fff;
border-radius: 5px;
padding: 5px;
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
}
li {
font-size: 13px;
padding: 5px;
border-bottom: 1px solid #eee;
}
li:last-child {
border-bottom: none;
}
button {
width: 100%;
padding: 10px;
margin-top: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.update-btn {
background: #007bff;
color: white;
}
.settings-btn {
background: #28a745;
color: white;
}
.domains-btn {
background: #17a2b8;
color: white;
}
button:hover {
opacity: 0.8;
}
</style>
</head>
<body>
<h2>Enterprise App Protection</h2>
<p>Suspicious Links Detected: <span class="counter" id="suspiciousCount">0</span></p>
<ul id="suspiciousLinks"></ul>
<p id="lastUpdate">Last Updated: Never</p>
<button id="updateDB" class="update-btn">🔄 Update Database</button>
<button id="settings" class="settings-btn">⚙️ Open Settings</button>
<button id="manageDomains" class="domains-btn">🌐 Manage Domains</button>
<h3>🔄 Reset Suspicious Links Counter</h3>
<button id="resetCounter">♻️ Reset Counter</button>
<script src="popup.js"></script>
</body>
</html>

74
popup.js Normal file
View File

@@ -0,0 +1,74 @@
// popup.js
// Open the options/settings page when the "Settings" button is clicked
document.getElementById("settings").addEventListener("click", function () {
chrome.runtime.openOptionsPage();
});
// Update the domains database when clicking the "Update Database" button
document.getElementById("updateDB").addEventListener("click", () => {
chrome.runtime.sendMessage({ action: "updateDB" }, (response) => {
console.log("Update response:", response);
chrome.storage.local.get(["lastUpdate"], function(result) {
if (result.lastUpdate) {
const date = new Date(result.lastUpdate);
// Format: dd-MM-yyyy HH:mm (24-hour format, no seconds)
const formattedDate = `${String(date.getDate()).padStart(2, '0')}-${String(date.getMonth() + 1).padStart(2, '0')}-${date.getFullYear()} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
const lastUpdateElement = document.getElementById("lastUpdate");
if (lastUpdateElement) {
lastUpdateElement.textContent = `Last Updated: ${formattedDate}`;
}
} else {
const lastUpdateElement = document.getElementById("lastUpdate");
if (lastUpdateElement) {
lastUpdateElement.textContent = "Last Updated: Never";
}
}
});
// Show an alert to the user
alert(response.status === "updated" ? "✅ Database successfully updated!" : "❌ Failed to update database.");
});
});
// Open the domains management page when the "Manage Domains" button is clicked
document.getElementById("manageDomains").addEventListener("click", function () {
chrome.tabs.create({ url: chrome.runtime.getURL("domains_management.html") });
});
// Load total suspicious links count and update UI
chrome.storage.local.get(["totalSuspiciousLinks"], function (result) {
document.getElementById("suspiciousCount").innerText = result.totalSuspiciousLinks || 0;
});
// Retrieve flagged links and display them
chrome.runtime.sendMessage({ action: "getSuspiciousLinks" }, response => {
document.getElementById("suspiciousLinks").innerHTML = response.links.length
? response.links.map(link => `<li>${link}</li>`).join("")
: "<li>No suspicious links detected.</li>";
});
// Listen for updates from content.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "updatePopup") {
document.getElementById("suspiciousCount").innerText = message.totalCount; // Persist total count
document.getElementById("suspiciousLinks").innerHTML = message.flaggedLinks.length
? message.flaggedLinks.map(link => `<li>${link}</li>`).join("")
: "<li>No suspicious links detected.</li>";
}
});
document.getElementById("resetCounter").addEventListener("click", function () {
chrome.storage.local.set({ totalSuspiciousLinks: 0, flaggedLinks: [] }, function () {
alert("Suspicious links counter has been reset.");
// Force the UI to update immediately
document.getElementById("suspiciousCount").innerText = "0";
document.getElementById("suspiciousLinks").innerHTML = "<li>No suspicious links detected.</li>";
// Notify the background script (optional, if needed)
chrome.runtime.sendMessage({ action: "resetCounter" });
});
});

132
testsite.html Normal file

File diff suppressed because one or more lines are too long