commit ba83c959141cb1be1bccdad9c9cb17951518809b Author: becarta Date: Sat May 10 03:25:49 2025 +0200 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. diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8b97c28 Binary files /dev/null and b/.DS_Store differ diff --git a/EnterpriseAppProtection.png b/EnterpriseAppProtection.png new file mode 100644 index 0000000..90ffd68 Binary files /dev/null and b/EnterpriseAppProtection.png differ diff --git a/ExtensionPopup.png b/ExtensionPopup.png new file mode 100644 index 0000000..02c60a5 Binary files /dev/null and b/ExtensionPopup.png differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1e5f56 --- /dev/null +++ b/README.md @@ -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 Google’s 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) + +--- \ No newline at end of file diff --git a/SettingPage.png b/SettingPage.png new file mode 100644 index 0000000..7fb55e1 Binary files /dev/null and b/SettingPage.png differ diff --git a/background.js b/background.js new file mode 100644 index 0000000..7ae8429 --- /dev/null +++ b/background.js @@ -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"); + } + }); +} \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..70c80dd --- /dev/null +++ b/config.js @@ -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." +}; diff --git a/content.js b/content.js new file mode 100644 index 0000000..ed5d1cf --- /dev/null +++ b/content.js @@ -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(); \ No newline at end of file diff --git a/domains.json b/domains.json new file mode 100644 index 0000000..20f00d8 --- /dev/null +++ b/domains.json @@ -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"] +} \ No newline at end of file diff --git a/domains_management.html b/domains_management.html new file mode 100644 index 0000000..1ed49bc --- /dev/null +++ b/domains_management.html @@ -0,0 +1,205 @@ + + + + + + + Enterprise App Protection - Domain Management + + + + +
+

Enterprise App Protection - Domain Management

+ + +

βœ… How to Use

+ +

+    example.com
+    secure-site.org
+    sub.example.com
+    malicious-site.net
+        
+ +

βœ… Trusted Domains (Safe List)

+ + +

❌ Blocked Domains (Unsafe List)

+ + +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/domains_management.js b/domains_management.js new file mode 100644 index 0000000..855671f --- /dev/null +++ b/domains_management.js @@ -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"); + }); + } +}); \ No newline at end of file diff --git a/icons/.DS_Store b/icons/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/icons/.DS_Store differ diff --git a/icons/icon128.png b/icons/icon128.png new file mode 100644 index 0000000..8ced81f Binary files /dev/null and b/icons/icon128.png differ diff --git a/icons/icon16.png b/icons/icon16.png new file mode 100644 index 0000000..83e1571 Binary files /dev/null and b/icons/icon16.png differ diff --git a/icons/icon48.png b/icons/icon48.png new file mode 100644 index 0000000..a12d1fa Binary files /dev/null and b/icons/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..2227992 --- /dev/null +++ b/manifest.json @@ -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": [""], + "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": [""] + }] +} \ No newline at end of file diff --git a/options.html b/options.html new file mode 100644 index 0000000..1788efb --- /dev/null +++ b/options.html @@ -0,0 +1,141 @@ + + + + + + Enterprise App Protection - Settings + + + +
+

Enterprise App Protection - Settings

+ +

πŸ”‘ Google Safe Browsing API Key

+

+ To use Google Safe Browsing, you need an API key. + + Click here to create a key + (you may need to enable the Safe Browsing API in Google Cloud Console). +

+ + +

🌐 Domains Database URL

+

Enter the URL for the domains database (JSON format):

+ + +

⏱️ Update Interval (hours)

+

Enter the interval (in hours) to check for updates to the domains database:

+ + +

⚠️ Warning Message Template

+

Enter the template for the warning message (use {app} to represent the application name):

+ + + +
+ +
+
+ + + + \ No newline at end of file diff --git a/options.js b/options.js new file mode 100644 index 0000000..4b0c2c8 --- /dev/null +++ b/options.js @@ -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!"); + }); + }); + } +}); \ No newline at end of file diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..e5867b7 --- /dev/null +++ b/popup.html @@ -0,0 +1,93 @@ + + + + + + Enterprise App Protection + + + +

Enterprise App Protection

+

Suspicious Links Detected: 0

+ + +

Last Updated: Never

+ + + + +

πŸ”„ Reset Suspicious Links Counter

+ + + + + \ No newline at end of file diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..2dbe9ab --- /dev/null +++ b/popup.js @@ -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 => `
  • ${link}
  • `).join("") + : "
  • No suspicious links detected.
  • "; +}); + +// 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 => `
  • ${link}
  • `).join("") + : "
  • No suspicious links detected.
  • "; + } +}); + +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 = "
  • No suspicious links detected.
  • "; + + // Notify the background script (optional, if needed) + chrome.runtime.sendMessage({ action: "resetCounter" }); + }); +}); \ No newline at end of file diff --git a/testsite.html b/testsite.html new file mode 100644 index 0000000..4a3ea77 --- /dev/null +++ b/testsite.html @@ -0,0 +1,132 @@ + + + + + + Enterprise App Protection - Fixed Dynamic Test + + + +

    Enterprise App Protection - Fixed Dynamic Test

    + +

    This page contains randomly selected fake and official URLs for testing the browser extension.

    + +

    βœ… Official Enterprise Links (Should Not Trigger Warnings)

    + ADP (Official) - adp.com + Canva (Official) - canva.com + Red Hat (Official) - redhat.com + Anchore (Official) - anchore.com + Oracle (Official) - oracle.com + Allocadia (Official) - allocadia.com + Airtable (Official) - airtable.com + 4me (Official) - 4me.com + ExpressVPN (Official) - expressvpn.com + Active Campaign (Official) - activecampaign.com + + +

    ❌ Fake or Suspicious Links (Should Trigger Warnings)

    + ServiceNow (Fake) - secure-now.com + LinkedIn (Fake) - secure-licdn.com + ArangoDB (Fake) - verify-arangodb.net + Active Campaign (Fake) - activecampaign-security.com + AppViewX (Fake) - appviewx-login.com + Pardot (Fake) - verify-salesforce.com + Absolute Software (Fake) - absolutesoftware-login.com + Chef (Fake) - verify-progress.com + Bitdefender (Fake) - secure-bitdefender.net + Box (Fake) - verify-boxcdn.net + CrowdStrike (Fake) - crowdstrike-security.com + Agiloft (Fake) - verify-agiloft.net + 3M Cloud (Fake) - verify-3mcloud.com + Absolutdata (Fake) - verify-absolutdata.net + Netlify (Fake) - netlify-security.com + Docker (Fake) - verify-docker.io + Allscripts (Fake) - secure-allscriptscloud.com + Aon (Fake) - aon-login.com + Atlassian (Fake) - atlassian-login.com + Adaptive Insights (Fake) - adaptiveinsights-login.com + Tanium (Fake) - tanium-login.com + Puppet (Fake) - verify-puppetlabs.com + AppSense (Fake) - appsense-login.com + AccuWeather (Fake) - accuweather-login.com + Aerospike (Fake) - aerospike-login.com + CrowdStrike Falcon (Fake) - verify-falcon.crowdstrike.com + Alluxio (Fake) - verify-alluxio.io + Okta (Fake) - okta-login.com + SAP Concur (Fake) - sapconcur-security.com + McAfee (Fake) - mcafee-security.com + Aria Systems (Fake) - ariasystems-login.com + Magento (Fake) - magento-login.com + Trello (Fake) - secure-atlassian.com + Aravo (Fake) - verify-aravo.net + Fastly (Fake) - fastly-login.com + Alto (Fake) - secure-alto.sh + Apptus (Fake) - apptus-login.com + Prometheus (Fake) - secure-cloud.google.com + AlienVault (Fake) - verify-att.com + Freshdesk (Fake) - secure-freshworks.com + ArmisIT (Fake) - verify-armis.io + 3CX (Fake) - verify-3cx.cloud + Salesforce (Fake) - salesforce-login.com + WeTransfer (Fake) - secure-wetransfer.net + Accenture (Fake) - accenture-login.com + SentinelOne (Fake) - verify-sentinelone.net + 7digital (Fake) - secure-7digital.net + Microsoft 365 (Fake) - verify-onmicrosoft.com + InVision (Fake) - invision-login.com + Notion (Fake) - verify-notion.com + + +

    πŸ› οΈ Dynamic Content (To Test MutationObserver)

    + +
    + + + + +