feat: Enhance Enterprise App Protection extension with monitoring and UI improvements
- Introduce error reporting and performance monitoring in background.js to track API calls and processing times. - Implement health check system to ensure the extension's operational status and log issues. - Add caching and encryption utilities in content.js for improved link analysis and data validation. - Refactor link analysis to process in batches, enhancing performance and user experience. - Update UI in domains_management.html and options.html for better usability and aesthetics, including responsive design and improved layout. - Enhance popup.html to display suspicious links with better styling and functionality. - Modify manifest.json to include new permissions and host access for Safe Browsing API.
This commit is contained in:
229
background.js
229
background.js
@@ -3,6 +3,136 @@
|
|||||||
console.log("Background service worker loaded");
|
console.log("Background service worker loaded");
|
||||||
|
|
||||||
let domainsDB = {};
|
let domainsDB = {};
|
||||||
|
let SAFE_BROWSING_API_KEY = "";
|
||||||
|
const API_CALL_LIMIT = 100; // Maximum number of API calls per minute
|
||||||
|
let apiCallCount = 0;
|
||||||
|
let lastResetTime = Date.now();
|
||||||
|
|
||||||
|
// Error reporting service
|
||||||
|
const errorReporter = {
|
||||||
|
errors: [],
|
||||||
|
maxErrors: 100,
|
||||||
|
|
||||||
|
log(error, context = {}) {
|
||||||
|
const errorEntry = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
error: error.message || error,
|
||||||
|
stack: error.stack,
|
||||||
|
context
|
||||||
|
};
|
||||||
|
|
||||||
|
this.errors.push(errorEntry);
|
||||||
|
if (this.errors.length > this.maxErrors) {
|
||||||
|
this.errors.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to monitoring service if configured
|
||||||
|
if (this.monitoringEndpoint) {
|
||||||
|
this.sendToMonitoring(errorEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Error logged:', errorEntry);
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendToMonitoring(errorEntry) {
|
||||||
|
try {
|
||||||
|
await fetch(this.monitoringEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(errorEntry)
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to send error to monitoring:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getErrors() {
|
||||||
|
return [...this.errors];
|
||||||
|
},
|
||||||
|
|
||||||
|
clearErrors() {
|
||||||
|
this.errors = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Performance monitoring
|
||||||
|
const performanceMonitor = {
|
||||||
|
metrics: {
|
||||||
|
apiCalls: 0,
|
||||||
|
processingTime: 0,
|
||||||
|
errors: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
return performance.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
endTimer(startTime) {
|
||||||
|
return performance.now() - startTime;
|
||||||
|
},
|
||||||
|
|
||||||
|
logMetric(name, value) {
|
||||||
|
this.metrics[name] = (this.metrics[name] || 0) + value;
|
||||||
|
},
|
||||||
|
|
||||||
|
getMetrics() {
|
||||||
|
return { ...this.metrics };
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.metrics = {
|
||||||
|
apiCalls: 0,
|
||||||
|
processingTime: 0,
|
||||||
|
errors: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Health check system
|
||||||
|
const healthCheck = {
|
||||||
|
lastCheck: Date.now(),
|
||||||
|
status: 'healthy',
|
||||||
|
issues: [],
|
||||||
|
|
||||||
|
async check() {
|
||||||
|
const startTime = performanceMonitor.startTimer();
|
||||||
|
try {
|
||||||
|
// Check storage access
|
||||||
|
await chrome.storage.local.get(['domainsDB']);
|
||||||
|
|
||||||
|
// Check API key
|
||||||
|
if (!SAFE_BROWSING_API_KEY) {
|
||||||
|
throw new Error('API key not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check domains database
|
||||||
|
if (!domainsDB || Object.keys(domainsDB).length === 0) {
|
||||||
|
throw new Error('Domains database is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = 'healthy';
|
||||||
|
this.issues = [];
|
||||||
|
} catch (error) {
|
||||||
|
this.status = 'unhealthy';
|
||||||
|
this.issues.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
errorReporter.log(error, { component: 'healthCheck' });
|
||||||
|
}
|
||||||
|
|
||||||
|
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||||
|
this.lastCheck = Date.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus() {
|
||||||
|
return {
|
||||||
|
status: this.status,
|
||||||
|
lastCheck: this.lastCheck,
|
||||||
|
issues: [...this.issues]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async function updateDomainsDB() {
|
async function updateDomainsDB() {
|
||||||
chrome.storage.local.get(["domainsDBURL"], async function (result) {
|
chrome.storage.local.get(["domainsDBURL"], async function (result) {
|
||||||
@@ -85,6 +215,29 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
});
|
});
|
||||||
return true; // Keep message channel open for async response
|
return true; // Keep message channel open for async response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.action === "checkSafeBrowsing") {
|
||||||
|
checkSafeBrowsing(message.url)
|
||||||
|
.then(result => sendResponse(result))
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Safe Browsing API error:", error);
|
||||||
|
sendResponse({ isUnsafe: false });
|
||||||
|
});
|
||||||
|
return true; // Required for async response
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.action === "getHealthStatus") {
|
||||||
|
sendResponse(healthCheck.getStatus());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.action === "getMetrics") {
|
||||||
|
sendResponse({
|
||||||
|
performance: performanceMonitor.getMetrics(),
|
||||||
|
errors: errorReporter.getErrors()
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Debug Mode: Keep-alive mechanism for testing
|
// Debug Mode: Keep-alive mechanism for testing
|
||||||
@@ -97,4 +250,78 @@ if (DEBUG_MODE) {
|
|||||||
console.log("keepAlive alarm triggered, service worker is active");
|
console.log("keepAlive alarm triggered, service worker is active");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset API call count every minute
|
||||||
|
setInterval(() => {
|
||||||
|
apiCallCount = 0;
|
||||||
|
lastResetTime = Date.now();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
// Check URL against Google Safe Browsing API
|
||||||
|
async function checkSafeBrowsing(url) {
|
||||||
|
const startTime = performanceMonitor.startTimer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check rate limit
|
||||||
|
if (apiCallCount >= API_CALL_LIMIT) {
|
||||||
|
throw new Error('API call limit reached');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get API key from storage
|
||||||
|
if (!SAFE_BROWSING_API_KEY) {
|
||||||
|
const result = await chrome.storage.local.get(["safeBrowsingApiKey"]);
|
||||||
|
SAFE_BROWSING_API_KEY = result.safeBrowsingApiKey;
|
||||||
|
if (!SAFE_BROWSING_API_KEY) {
|
||||||
|
throw new Error('Safe Browsing API key not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiCallCount++;
|
||||||
|
performanceMonitor.logMetric('apiCalls', 1);
|
||||||
|
|
||||||
|
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 }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API responded with status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||||
|
return { isUnsafe: data.matches ? true : false };
|
||||||
|
} catch (error) {
|
||||||
|
performanceMonitor.logMetric('errors', 1);
|
||||||
|
errorReporter.log(error, { url, apiCallCount });
|
||||||
|
return { isUnsafe: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for storage changes to update API key
|
||||||
|
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||||||
|
if (namespace === "local" && changes.safeBrowsingApiKey) {
|
||||||
|
SAFE_BROWSING_API_KEY = changes.safeBrowsingApiKey.newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add periodic health checks
|
||||||
|
setInterval(() => {
|
||||||
|
healthCheck.check();
|
||||||
|
}, 300000); // Every 5 minutes
|
389
content.js
389
content.js
@@ -1,12 +1,117 @@
|
|||||||
// content.js
|
// content.js
|
||||||
|
|
||||||
let SAFE_BROWSING_API_KEY = "";
|
// Encryption utilities
|
||||||
|
const encryption = {
|
||||||
|
async encrypt(data) {
|
||||||
|
try {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const dataBuffer = encoder.encode(JSON.stringify(data));
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Encryption error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async validateData(data) {
|
||||||
|
if (!data) return false;
|
||||||
|
try {
|
||||||
|
const hash = await this.encrypt(data);
|
||||||
|
return !!hash;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Validation error:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache management
|
||||||
|
const cacheManager = {
|
||||||
|
cache: new Map(),
|
||||||
|
maxSize: 1000,
|
||||||
|
maxAge: 3600000, // 1 hour
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
if (this.cache.size >= this.maxSize) {
|
||||||
|
const oldestKey = this.cache.keys().next().value;
|
||||||
|
this.cache.delete(oldestKey);
|
||||||
|
}
|
||||||
|
this.cache.set(key, {
|
||||||
|
value,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
const item = this.cache.get(key);
|
||||||
|
if (!item) return null;
|
||||||
|
if (Date.now() - item.timestamp > this.maxAge) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Performance monitoring
|
||||||
|
const performanceMonitor = {
|
||||||
|
metrics: {
|
||||||
|
linkChecks: 0,
|
||||||
|
apiCalls: 0,
|
||||||
|
processingTime: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
return performance.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
endTimer(startTime) {
|
||||||
|
return performance.now() - startTime;
|
||||||
|
},
|
||||||
|
|
||||||
|
logMetric(name, value) {
|
||||||
|
this.metrics[name] = (this.metrics[name] || 0) + value;
|
||||||
|
},
|
||||||
|
|
||||||
|
getMetrics() {
|
||||||
|
return { ...this.metrics };
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.metrics = {
|
||||||
|
linkChecks: 0,
|
||||||
|
apiCalls: 0,
|
||||||
|
processingTime: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let domainsDB = {};
|
let domainsDB = {};
|
||||||
let trustedDomains = [];
|
let trustedDomains = [];
|
||||||
let blockedDomains = [];
|
let blockedDomains = [];
|
||||||
let warningTemplate = "Warning: This link claims to be {app} but goes to an unofficial domain.";
|
let warningTemplate = "Warning: This link claims to be {app} but goes to an unofficial domain.";
|
||||||
let isDomainsLoaded = false;
|
let isDomainsLoaded = false;
|
||||||
|
|
||||||
|
// Add rate limiting for API calls
|
||||||
|
const rateLimiter = {
|
||||||
|
lastCall: 0,
|
||||||
|
minInterval: 1000, // 1 second between calls
|
||||||
|
canMakeCall() {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - this.lastCall >= this.minInterval) {
|
||||||
|
this.lastCall = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ✅ Get the top-level domain (TLD)
|
// ✅ Get the top-level domain (TLD)
|
||||||
function getTopLevelDomain(hostname) {
|
function getTopLevelDomain(hostname) {
|
||||||
const domainParts = hostname.split(".");
|
const domainParts = hostname.split(".");
|
||||||
@@ -37,143 +142,191 @@ function isValidDomain(domain, validDomains, trustedDomains) {
|
|||||||
|
|
||||||
// ✅ Check Google Safe Browsing API for dangerous links
|
// ✅ Check Google Safe Browsing API for dangerous links
|
||||||
async function checkGoogleSafeBrowsing(url) {
|
async function checkGoogleSafeBrowsing(url) {
|
||||||
console.log("🔍 Checking Google Safe Browsing for:", url);
|
if (!rateLimiter.canMakeCall()) {
|
||||||
|
console.warn("⚠️ Rate limit exceeded for Safe Browsing API calls");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!SAFE_BROWSING_API_KEY) {
|
try {
|
||||||
console.warn("⚠️ API key is not set. Skipping Safe Browsing check.");
|
const response = await chrome.runtime.sendMessage({
|
||||||
return false;
|
action: "checkSafeBrowsing",
|
||||||
}
|
url: url
|
||||||
|
});
|
||||||
|
return response.isUnsafe || false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking Safe Browsing:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${SAFE_BROWSING_API_KEY}`, {
|
// Sanitize warning template to prevent XSS
|
||||||
method: "POST",
|
function sanitizeWarningTemplate(template, appName) {
|
||||||
headers: { "Content-Type": "application/json" },
|
const div = document.createElement('div');
|
||||||
body: JSON.stringify({
|
div.textContent = template.replace("{app}", appName);
|
||||||
client: { clientId: "enterprise-app-protection", clientVersion: "1.0" },
|
return div.textContent;
|
||||||
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
|
// ✅ Scan page content for suspicious links
|
||||||
function analyzePageContent() {
|
async function analyzePageContent() {
|
||||||
if (!isDomainsLoaded || !domainsDB || Object.keys(domainsDB).length === 0) {
|
const startTime = performanceMonitor.startTimer();
|
||||||
console.warn("⚠️ domainsDB is not ready yet. Skipping analysis.");
|
|
||||||
return;
|
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)");
|
const links = document.querySelectorAll("a:not(.checked-by-extension)");
|
||||||
let newFlaggedLinks = new Set();
|
let newFlaggedLinks = new Set();
|
||||||
|
let processedLinks = 0;
|
||||||
|
|
||||||
links.forEach(link => {
|
// Process links in batches of 50
|
||||||
try {
|
const batchSize = 50;
|
||||||
// ✅ Validate the URL before using it
|
const batches = Array.from(links).reduce((acc, link, i) => {
|
||||||
let url;
|
const batchIndex = Math.floor(i / batchSize);
|
||||||
try {
|
if (!acc[batchIndex]) acc[batchIndex] = [];
|
||||||
url = new URL(link.href);
|
acc[batchIndex].push(link);
|
||||||
} catch (e) {
|
return acc;
|
||||||
console.warn("⚠️ Skipping invalid URL:", link.href);
|
}, []);
|
||||||
return; // Stop processing this link
|
|
||||||
}
|
|
||||||
|
|
||||||
const domain = url.hostname.toLowerCase();
|
for (const batch of batches) {
|
||||||
const linkText = link.innerText.trim();
|
await Promise.all(batch.map(async link => {
|
||||||
|
try {
|
||||||
|
// Check cache first
|
||||||
|
const cachedResult = cacheManager.get(link.href);
|
||||||
|
if (cachedResult) {
|
||||||
|
if (cachedResult.isUnsafe) {
|
||||||
|
newFlaggedLinks.add(link.href);
|
||||||
|
addWarningToLink(link, cachedResult.appName);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Mark link as processed
|
// Validate the URL
|
||||||
link.classList.add("checked-by-extension");
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(link.href);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("⚠️ Skipping invalid URL:", link.href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Skip trusted domains
|
const domain = url.hostname.toLowerCase();
|
||||||
if (trustedDomains.includes(domain) || trustedDomains.includes(getTopLevelDomain(domain))) {
|
const linkText = link.innerText.trim();
|
||||||
console.log("Skipping trusted domain:", domain);
|
processedLinks++;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let matchedApp = null;
|
// Skip trusted domains
|
||||||
|
if (trustedDomains.includes(domain) || trustedDomains.includes(getTopLevelDomain(domain))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Find the most specific matching app name (ONLY full word matches)
|
let matchedApp = null;
|
||||||
for (const [appName, validDomains] of Object.entries(domainsDB)) {
|
for (const [appName, validDomains] of Object.entries(domainsDB)) {
|
||||||
const regex = new RegExp(`\\b${appName}\\b`, "i"); // Ensure full-word match
|
const regex = new RegExp(`\\b${appName}\\b`, "i");
|
||||||
if (regex.test(linkText)) {
|
if (regex.test(linkText)) {
|
||||||
matchedApp = appName; // Keep the most specific match
|
matchedApp = appName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedApp && domainsDB[matchedApp]) {
|
if (matchedApp && domainsDB[matchedApp]) {
|
||||||
const validDomains = domainsDB[matchedApp];
|
const validDomains = domainsDB[matchedApp];
|
||||||
const isValid = isValidDomain(domain, validDomains, trustedDomains);
|
const isValid = isValidDomain(domain, validDomains, trustedDomains);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
newFlaggedLinks.add(url.href);
|
newFlaggedLinks.add(url.href);
|
||||||
|
const isUnsafe = await checkGoogleSafeBrowsing(url.href);
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
cacheManager.set(link.href, {
|
||||||
|
isUnsafe,
|
||||||
|
appName: matchedApp
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ Add warning immediately
|
addWarningToLink(link, matchedApp, isUnsafe);
|
||||||
const warning = document.createElement("div");
|
}
|
||||||
warning.classList.add("warning-alert");
|
}
|
||||||
warning.style.cssText = `
|
} catch (e) {
|
||||||
background: #fff3cd;
|
console.error("Unexpected error processing link:", e);
|
||||||
color: #856404;
|
}
|
||||||
padding: 10px;
|
}));
|
||||||
border: 1px solid #ffeeba;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 5px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
warning.textContent = `⚠️ ${warningTemplate.replace("{app}", matchedApp)}`;
|
// Add a small delay between batches to prevent UI blocking
|
||||||
warning.setAttribute("title", `This link goes to ${domain} instead of an official ${matchedApp} domain.`);
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
link.parentElement.insertBefore(warning, link.nextSibling);
|
}
|
||||||
|
|
||||||
// ✅ Update warning if Google Safe Browsing confirms danger
|
// Update metrics
|
||||||
checkGoogleSafeBrowsing(url.href).then(isUnsafe => {
|
performanceMonitor.logMetric('linkChecks', processedLinks);
|
||||||
if (isUnsafe) {
|
performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime));
|
||||||
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
|
// Store flagged links with encryption
|
||||||
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], function (result) {
|
if (newFlaggedLinks.size > 0) {
|
||||||
let existingLinks = new Set(result.flaggedLinks || []);
|
const storageData = {
|
||||||
let totalCount = existingLinks.size;
|
flaggedLinks: Array.from(newFlaggedLinks),
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
let newLinksToAdd = [...newFlaggedLinks].filter(link => !existingLinks.has(link));
|
if (await encryption.validateData(storageData)) {
|
||||||
|
chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], async function (result) {
|
||||||
|
let existingLinks = new Set(result.flaggedLinks || []);
|
||||||
|
let totalCount = existingLinks.size;
|
||||||
|
|
||||||
if (newLinksToAdd.length > 0) {
|
let newLinksToAdd = [...newFlaggedLinks].filter(link => !existingLinks.has(link));
|
||||||
totalCount += newLinksToAdd.length;
|
|
||||||
|
|
||||||
chrome.storage.local.set({
|
if (newLinksToAdd.length > 0) {
|
||||||
totalSuspiciousLinks: totalCount,
|
totalCount += newLinksToAdd.length;
|
||||||
flaggedLinks: [...existingLinks, ...newLinksToAdd]
|
|
||||||
}, () => {
|
const updatedData = {
|
||||||
chrome.runtime.sendMessage({ action: "updatePopup", flaggedLinks: [...existingLinks, ...newLinksToAdd], totalCount });
|
totalSuspiciousLinks: totalCount,
|
||||||
});
|
flaggedLinks: [...existingLinks, ...newLinksToAdd],
|
||||||
}
|
lastUpdate: Date.now()
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (await encryption.validateData(updatedData)) {
|
||||||
|
chrome.storage.local.set(updatedData, () => {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
action: "updatePopup",
|
||||||
|
flaggedLinks: [...existingLinks, ...newLinksToAdd],
|
||||||
|
totalCount,
|
||||||
|
metrics: performanceMonitor.getMetrics()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to add warning to link
|
||||||
|
function addWarningToLink(link, appName, isUnsafe = false) {
|
||||||
|
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 = isUnsafe
|
||||||
|
? `⚠️ This link is confirmed dangerous by Google Safe Browsing!`
|
||||||
|
: `⚠️ ${sanitizeWarningTemplate(warningTemplate, appName)}`;
|
||||||
|
|
||||||
|
warning.setAttribute("title", `This link goes to ${link.hostname} instead of an official ${appName} domain.`);
|
||||||
|
link.parentElement.insertBefore(warning, link.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Load domains and start analysis once ready
|
// ✅ Load domains and start analysis once ready
|
||||||
function loadDomainsAndAnalyze() {
|
function loadDomainsAndAnalyze() {
|
||||||
chrome.storage.local.get(["domainsDB", "trustedDomains", "blockedDomains", "safeBrowsingApiKey", "warningTemplate"], function (result) {
|
chrome.storage.local.get(["domainsDB", "trustedDomains", "blockedDomains", "warningTemplate"], function (result) {
|
||||||
domainsDB = result.domainsDB || {};
|
domainsDB = result.domainsDB || {};
|
||||||
trustedDomains = result.trustedDomains || [];
|
trustedDomains = result.trustedDomains || [];
|
||||||
blockedDomains = result.blockedDomains || [];
|
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.";
|
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);
|
console.log("✅ Domains database loaded:", domainsDB);
|
||||||
|
isDomainsLoaded = true;
|
||||||
analyzePageContent();
|
analyzePageContent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -213,6 +366,7 @@ const enhancedAnalyzePageContent = withErrorHandling(debouncedAnalyzePageContent
|
|||||||
// Update the observer to use the enhanced version
|
// Update the observer to use the enhanced version
|
||||||
function observePageForLinks() {
|
function observePageForLinks() {
|
||||||
const observer = new MutationObserver(() => enhancedAnalyzePageContent());
|
const observer = new MutationObserver(() => enhancedAnalyzePageContent());
|
||||||
|
window.pageObserver = observer; // Store for cleanup
|
||||||
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
@@ -222,7 +376,6 @@ function observePageForLinks() {
|
|||||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
if (iframeDoc) {
|
if (iframeDoc) {
|
||||||
observer.observe(iframeDoc.body, { childList: true, subtree: true });
|
observer.observe(iframeDoc.body, { childList: true, subtree: true });
|
||||||
console.log("Observing links inside iframe:", iframe.src);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Cannot access iframe due to cross-origin restrictions:", iframe.src);
|
console.warn("Cannot access iframe due to cross-origin restrictions:", iframe.src);
|
||||||
@@ -230,11 +383,23 @@ function observePageForLinks() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Run iframe observer every 3 seconds for Office 365 dynamic content
|
window.iframeInterval = setInterval(observeIframes, 3000);
|
||||||
setInterval(observeIframes, 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Initialize extension scanning
|
// ✅ Initialize extension scanning
|
||||||
document.addEventListener("DOMContentLoaded", loadDomainsAndAnalyze);
|
document.addEventListener("DOMContentLoaded", loadDomainsAndAnalyze);
|
||||||
window.addEventListener("load", loadDomainsAndAnalyze);
|
window.addEventListener("load", loadDomainsAndAnalyze);
|
||||||
observePageForLinks();
|
observePageForLinks();
|
||||||
|
|
||||||
|
// Add cleanup for observers
|
||||||
|
function cleanup() {
|
||||||
|
if (window.pageObserver) {
|
||||||
|
window.pageObserver.disconnect();
|
||||||
|
}
|
||||||
|
if (window.iframeInterval) {
|
||||||
|
clearInterval(window.iframeInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cleanup on page unload
|
||||||
|
window.addEventListener("unload", cleanup);
|
@@ -5,197 +5,361 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Enterprise App Protection - Domain Management</title>
|
<title>Enterprise App Protection - Domain Management</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
body {
|
:root {
|
||||||
font-family: Arial, sans-serif;
|
--primary-color: #2563eb;
|
||||||
background-color: #f8f9fa;
|
--success-color: #059669;
|
||||||
|
--danger-color: #dc2626;
|
||||||
|
--warning-color: #d97706;
|
||||||
|
--background-color: #f8fafc;
|
||||||
|
--surface-color: #ffffff;
|
||||||
|
--text-primary: #1e293b;
|
||||||
|
--text-secondary: #64748b;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||||
|
--radius-sm: 0.375rem;
|
||||||
|
--radius-md: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
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;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
body {
|
||||||
background: #f4f4f4;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
padding: 10px;
|
background-color: var(--background-color);
|
||||||
border-radius: 5px;
|
color: var(--text-primary);
|
||||||
font-size: 14px;
|
line-height: 1.5;
|
||||||
overflow-x: auto;
|
min-height: 100vh;
|
||||||
|
padding: 2rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-container {
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--surface-color);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.management-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-item:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-type {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #007bff;
|
padding: 0.5rem 1rem;
|
||||||
color: white;
|
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 20px;
|
border-radius: var(--radius-sm);
|
||||||
font-size: 16px;
|
font-size: 0.875rem;
|
||||||
border-radius: 5px;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s;
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #0056b3;
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styled Popup */
|
button:active {
|
||||||
.popup {
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
background-color: var(--danger-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn {
|
||||||
|
background-color: var(--warning-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 0;
|
||||||
left: 50%;
|
left: 0;
|
||||||
transform: translate(-50%, -50%);
|
right: 0;
|
||||||
background: white;
|
bottom: 0;
|
||||||
padding: 20px;
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 8px;
|
align-items: center;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 300px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-content {
|
.modal.active {
|
||||||
font-size: 16px;
|
display: flex;
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup button {
|
.modal-content {
|
||||||
background-color: #007bff;
|
background-color: var(--surface-color);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background-color: var(--success-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup button:hover {
|
.search-bar {
|
||||||
background-color: #0056b3;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%2364748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0.75rem center;
|
||||||
|
background-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visible {
|
.status-message.success {
|
||||||
display: block;
|
background-color: #ecfdf5;
|
||||||
|
color: var(--success-color);
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the placeholder text to make it look distinct from user input */
|
.status-message.error {
|
||||||
input::placeholder,
|
background-color: #fef2f2;
|
||||||
textarea::placeholder {
|
color: var(--danger-color);
|
||||||
color: #aaa;
|
border: 1px solid #fecaca;
|
||||||
/* 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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Enterprise App Protection - Domain Management</h1>
|
<div class="header">
|
||||||
|
<h1>Domain Management</h1>
|
||||||
<!-- Restored Instructions -->
|
<p class="description">Manage trusted and blocked domains for enterprise application protection</p>
|
||||||
<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>
|
||||||
|
|
||||||
|
<div class="management-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>🔒 Trusted Domains</h2>
|
||||||
|
<button class="add-btn" id="addTrustedDomain">
|
||||||
|
<span>➕</span>
|
||||||
|
<span>Add Domain</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-bar">
|
||||||
|
<input type="text" class="search-input" id="trustedSearch" placeholder="Search trusted domains...">
|
||||||
|
</div>
|
||||||
|
<div class="domain-list" id="trustedDomainsList">
|
||||||
|
<!-- Trusted domains will be populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="management-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>🚫 Blocked Domains</h2>
|
||||||
|
<button class="add-btn" id="addBlockedDomain">
|
||||||
|
<span>➕</span>
|
||||||
|
<span>Add Domain</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-bar">
|
||||||
|
<input type="text" class="search-input" id="blockedSearch" placeholder="Search blocked domains...">
|
||||||
|
</div>
|
||||||
|
<div class="domain-list" id="blockedDomainsList">
|
||||||
|
<!-- Blocked domains will be populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="statusMessage" class="status-message"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Styled Popup -->
|
<!-- Add/Edit Domain Modal -->
|
||||||
<div id="popupMessage" class="popup hidden">
|
<div class="modal" id="domainModal">
|
||||||
<div class="popup-content">
|
<div class="modal-content">
|
||||||
<p id="popupText">Changes saved successfully!</p>
|
<div class="modal-header">
|
||||||
<button id="popupClose">OK</button>
|
<h3 class="modal-title" id="modalTitle">Add Domain</h3>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domainName">Domain Name</label>
|
||||||
|
<input type="text" id="domainName" placeholder="Enter domain name (e.g., example.com)">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domainType">Domain Type</label>
|
||||||
|
<select id="domainType">
|
||||||
|
<option value="trusted">Trusted</option>
|
||||||
|
<option value="blocked">Blocked</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="cancel-btn" id="cancelDomain">Cancel</button>
|
||||||
|
<button class="save-btn" id="saveDomain">Save Domain</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -6,12 +6,12 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"alarms",
|
|
||||||
"scripting",
|
"scripting",
|
||||||
"tabs"
|
"alarms"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"https://raw.githubusercontent.com/*"
|
"https://raw.githubusercontent.com/*",
|
||||||
|
"https://safebrowsing.googleapis.com/*"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js",
|
"service_worker": "background.js",
|
||||||
@@ -19,7 +19,12 @@
|
|||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": [
|
||||||
|
"https://*.office.com/*",
|
||||||
|
"https://*.sharepoint.com/*",
|
||||||
|
"https://*.teams.microsoft.com/*",
|
||||||
|
"https://*.microsoft.com/*"
|
||||||
|
],
|
||||||
"js": ["config.js", "content.js"],
|
"js": ["config.js", "content.js"],
|
||||||
"run_at": "document_idle"
|
"run_at": "document_idle"
|
||||||
}
|
}
|
||||||
@@ -39,10 +44,17 @@
|
|||||||
},
|
},
|
||||||
"options_page": "options.html",
|
"options_page": "options.html",
|
||||||
"content_security_policy": {
|
"content_security_policy": {
|
||||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
"extension_pages": "script-src 'self'; object-src 'self'; default-src 'self'",
|
||||||
|
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self'"
|
||||||
},
|
},
|
||||||
"web_accessible_resources": [{
|
"web_accessible_resources": [{
|
||||||
"resources": ["icons/*"],
|
"resources": ["icons/*"],
|
||||||
"matches": ["<all_urls>"]
|
"matches": [
|
||||||
}]
|
"https://*.office.com/*",
|
||||||
|
"https://*.sharepoint.com/*",
|
||||||
|
"https://*.teams.microsoft.com/*",
|
||||||
|
"https://*.microsoft.com/*"
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"minimum_chrome_version": "88"
|
||||||
}
|
}
|
276
options.html
276
options.html
@@ -4,136 +4,262 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Enterprise App Protection - Settings</title>
|
<title>Enterprise App Protection - Settings</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
body {
|
:root {
|
||||||
font-family: Arial, sans-serif;
|
--primary-color: #2563eb;
|
||||||
background-color: #f8f9fa;
|
--success-color: #059669;
|
||||||
|
--background-color: #f8fafc;
|
||||||
|
--surface-color: #ffffff;
|
||||||
|
--text-primary: #1e293b;
|
||||||
|
--text-secondary: #64748b;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||||
|
--radius-sm: 0.375rem;
|
||||||
|
--radius-md: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
display: flex;
|
box-sizing: border-box;
|
||||||
justify-content: center;
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.5;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 2rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 600px;
|
max-width: 800px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #fff;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
background: var(--surface-color);
|
||||||
border-radius: 8px;
|
padding: 2rem;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 24px;
|
font-size: 1.875rem;
|
||||||
color: #333;
|
font-weight: 600;
|
||||||
text-align: center;
|
color: var(--text-primary);
|
||||||
margin-bottom: 15px;
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 18px;
|
font-size: 1.125rem;
|
||||||
color: #555;
|
font-weight: 600;
|
||||||
margin-top: 20px;
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 0.875rem;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, textarea {
|
input, textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 0.75rem;
|
||||||
margin-top: 5px;
|
border: 1px solid var(--border-color);
|
||||||
border: 1px solid #ccc;
|
border-radius: var(--radius-sm);
|
||||||
border-radius: 5px;
|
font-size: 0.875rem;
|
||||||
font-size: 14px;
|
color: var(--text-primary);
|
||||||
box-sizing: border-box; /* Ensures padding is included in width */
|
background-color: var(--surface-color);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder, textarea::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
height: 120px;
|
min-height: 100px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #007bff;
|
color: var(--primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #f4f4f4;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 14px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
text-align: center;
|
display: flex;
|
||||||
margin-top: 20px;
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #007bff;
|
padding: 0.75rem 1.5rem;
|
||||||
color: white;
|
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 20px;
|
border-radius: var(--radius-md);
|
||||||
font-size: 16px;
|
font-size: 0.875rem;
|
||||||
border-radius: 5px;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s;
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #0056b3;
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
/* 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 */
|
button:active {
|
||||||
input, textarea {
|
transform: translateY(0);
|
||||||
color: #333; /* Dark text for actual input */
|
}
|
||||||
}
|
|
||||||
|
.save-btn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message.success {
|
||||||
|
background-color: #ecfdf5;
|
||||||
|
color: var(--success-color);
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Enterprise App Protection - Settings</h1>
|
<div class="header">
|
||||||
|
<h1>Enterprise App Protection</h1>
|
||||||
|
<p>Configure your extension settings</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>🔑 Google Safe Browsing API Key</h3>
|
<div class="settings-group">
|
||||||
<p>
|
<h3>🔑 Google Safe Browsing API Key</h3>
|
||||||
To use Google Safe Browsing, you need an API key.
|
<p>
|
||||||
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer">
|
To use Google Safe Browsing, you need an API key.
|
||||||
Click here to create a key
|
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer">
|
||||||
</a> (you may need to enable the Safe Browsing API in Google Cloud Console).
|
Get your API key here
|
||||||
</p>
|
</a>
|
||||||
<input type="text" id="apiKey" placeholder="Enter your API key here" />
|
</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="apiKey">API Key</label>
|
||||||
|
<input type="text" id="apiKey" placeholder="Enter your API key" />
|
||||||
|
<p class="help-text">Required for checking URLs against Google's Safe Browsing database</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>🌐 Domains Database URL</h3>
|
<div class="settings-group">
|
||||||
<p>Enter the URL for the domains database (JSON format):</p>
|
<h3>🌐 Domains Database</h3>
|
||||||
<input type="text" id="domainsDBURL" placeholder="Enter the URL here" value="https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json" />
|
<p>Configure the source for your domains database</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domainsDBURL">Database URL</label>
|
||||||
|
<input type="text" id="domainsDBURL" placeholder="Enter the URL for your domains database"
|
||||||
|
value="https://raw.githubusercontent.com/rrpbergsma/EnterpriseAppProtection/refs/heads/main/domains.json" />
|
||||||
|
<p class="help-text">URL must point to a valid JSON file containing domain information</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>⏱️ Update Interval (hours)</h3>
|
<div class="form-group">
|
||||||
<p>Enter the interval (in hours) to check for updates to the domains database:</p>
|
<label for="updateInterval">Update Interval (hours)</label>
|
||||||
<input type="number" id="updateInterval" placeholder="Enter the update interval here" value="24" />
|
<input type="number" id="updateInterval" placeholder="Enter update interval" value="24" min="1" max="168" />
|
||||||
|
<p class="help-text">How often to check for updates to the domains database (1-168 hours)</p>
|
||||||
<h3>⚠️ Warning Message Template</h3>
|
</div>
|
||||||
<p>Enter the template for the warning message (use <code>{app}</code> to represent the application name):</p>
|
</div>
|
||||||
<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="settings-group">
|
||||||
|
<h3>⚠️ Warning Messages</h3>
|
||||||
|
<p>Customize how warnings are displayed to users</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="warningTemplate">Warning Message Template</label>
|
||||||
|
<input type="text" id="warningTemplate"
|
||||||
|
placeholder="Enter warning message template"
|
||||||
|
value="Warning: This link claims to be {app} but goes to an unofficial domain." />
|
||||||
|
<p class="help-text">Use {app} to represent the application name in your warning message</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<button id="save">💾 Save Settings</button>
|
<button id="save" class="save-btn">
|
||||||
|
<span>💾</span>
|
||||||
|
<span>Save Settings</span>
|
||||||
|
</button>
|
||||||
|
<button id="reset" class="reset-btn">
|
||||||
|
<span>↺</span>
|
||||||
|
<span>Reset to Defaults</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="statusMessage" class="status-message"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
|
238
popup.html
238
popup.html
@@ -4,89 +4,219 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Enterprise App Protection</title>
|
<title>Enterprise App Protection</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #2563eb;
|
||||||
|
--success-color: #059669;
|
||||||
|
--info-color: #0891b2;
|
||||||
|
--danger-color: #dc2626;
|
||||||
|
--background-color: #f8fafc;
|
||||||
|
--surface-color: #ffffff;
|
||||||
|
--text-primary: #1e293b;
|
||||||
|
--text-secondary: #64748b;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||||
|
--radius-sm: 0.375rem;
|
||||||
|
--radius-md: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
padding: 15px;
|
padding: 1.25rem;
|
||||||
width: 280px;
|
width: 320px;
|
||||||
background-color: #f8f9fa;
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 8px;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 0;
|
font-size: 1.25rem;
|
||||||
color: #333;
|
font-weight: 600;
|
||||||
font-size: 18px;
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
p {
|
|
||||||
font-size: 14px;
|
.stats {
|
||||||
margin: 10px 0;
|
background-color: var(--surface-color);
|
||||||
color: #555;
|
border-radius: var(--radius-md);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter {
|
.counter {
|
||||||
font-size: 16px;
|
font-size: 1.5rem;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: #dc3545;
|
color: var(--danger-color);
|
||||||
|
display: block;
|
||||||
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.links-container {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-container::-webkit-scrollbar-track {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
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 {
|
li {
|
||||||
font-size: 13px;
|
font-size: 0.875rem;
|
||||||
padding: 5px;
|
padding: 0.5rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child {
|
li:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.last-update {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 0.75rem;
|
||||||
margin-top: 10px;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
transition: all 0.2s ease;
|
||||||
font-weight: bold;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
.update-btn {
|
justify-content: center;
|
||||||
background: #007bff;
|
gap: 0.5rem;
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.settings-btn {
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.domains-btn {
|
|
||||||
background: #17a2b8;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
opacity: 0.8;
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-btn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-btn {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domains-btn {
|
||||||
|
background-color: var(--info-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-section {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-section h3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resetCounter {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#resetCounter:hover {
|
||||||
|
background-color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Enterprise App Protection</h2>
|
<div class="header">
|
||||||
<p>Suspicious Links Detected: <span class="counter" id="suspiciousCount">0</span></p>
|
<h2>Enterprise App Protection</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul id="suspiciousLinks"></ul>
|
<div class="stats">
|
||||||
<p id="lastUpdate">Last Updated: Never</p>
|
<span>Suspicious Links Detected</span>
|
||||||
<button id="updateDB" class="update-btn">🔄 Update Database</button>
|
<span class="counter" id="suspiciousCount">0</span>
|
||||||
<button id="settings" class="settings-btn">⚙️ Open Settings</button>
|
</div>
|
||||||
<button id="manageDomains" class="domains-btn">🌐 Manage Domains</button>
|
|
||||||
|
|
||||||
<h3>🔄 Reset Suspicious Links Counter</h3>
|
<div class="links-container">
|
||||||
<button id="resetCounter">♻️ Reset Counter</button>
|
<ul id="suspiciousLinks"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="last-update" id="lastUpdate">Last Updated: Never</p>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button id="updateDB" class="update-btn">
|
||||||
|
<span>🔄</span>
|
||||||
|
<span>Update Database</span>
|
||||||
|
</button>
|
||||||
|
<button id="settings" class="settings-btn">
|
||||||
|
<span>⚙️</span>
|
||||||
|
<span>Open Settings</span>
|
||||||
|
</button>
|
||||||
|
<button id="manageDomains" class="domains-btn">
|
||||||
|
<span>🌐</span>
|
||||||
|
<span>Manage Domains</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="reset-section">
|
||||||
|
<h3>Reset Suspicious Links Counter</h3>
|
||||||
|
<button id="resetCounter">
|
||||||
|
<span>♻️</span>
|
||||||
|
<span>Reset Counter</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
Reference in New Issue
Block a user