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:
BIN
EnterpriseAppProtection.png
Normal file
BIN
EnterpriseAppProtection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 401 KiB |
BIN
ExtensionPopup.png
Normal file
BIN
ExtensionPopup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
125
README.md
Normal file
125
README.md
Normal 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)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### ⚠️ 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
BIN
SettingPage.png
Normal file
BIN
SettingPage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 238 KiB |
100
background.js
Normal file
100
background.js
Normal 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
9
config.js
Normal 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
240
content.js
Normal 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
225
domains.json
Normal 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
205
domains_management.html
Normal 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
39
domains_management.js
Normal 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
BIN
icons/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
icons/icon128.png
Normal file
BIN
icons/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
icons/icon16.png
Normal file
BIN
icons/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 810 B |
BIN
icons/icon48.png
Normal file
BIN
icons/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
48
manifest.json
Normal file
48
manifest.json
Normal 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
141
options.html
Normal 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
54
options.js
Normal 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
93
popup.html
Normal 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
74
popup.js
Normal 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
132
testsite.html
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user