diff --git a/Authenticator/Authenticator.crx b/Authenticator/Authenticator.crx index 3d0307ce7..914ff7cb4 100644 Binary files a/Authenticator/Authenticator.crx and b/Authenticator/Authenticator.crx differ diff --git a/Authenticator/repo/Okta.png b/Authenticator/repo/Okta.png deleted file mode 100644 index ff54d6d34..000000000 Binary files a/Authenticator/repo/Okta.png and /dev/null differ diff --git a/Authenticator/repo/background.js b/Authenticator/repo/background.js index 2d2ed6ba1..cc00c1328 100644 --- a/Authenticator/repo/background.js +++ b/Authenticator/repo/background.js @@ -1,5 +1,18 @@ console.log("Background script starting..."); +// Import XRegExp and pattern files for real-time detection +importScripts( + 'public/libs/xregexp.js', + 'public/ptr/piiPatterns.js', + 'public/ptr/apiPatterns_new.js', + 'public/ptr/fiPatterns.js', + 'public/ptr/cryptoPatterns.js', + 'public/ptr/medPatterns.js', + 'public/ptr/networkPatterns.js' +); + +console.log("✅ Pattern files loaded successfully"); + // Clear all storage on extension startup to prevent reinstall issues chrome.storage.local.clear(function () { console.log("Background: Cleared all extension storage on startup"); @@ -25,6 +38,238 @@ const rateLimiter = new Map(); const analyzedUrls = new Map(); // Track analyzed URLs per tab let latestAnalysis = null; +// ====================== +// PATTERN-BASED REDACTION ENGINE +// ====================== + +class PatternRedactor { + /** + * Smart redaction function that shows first 2 digits and masks the rest + * Example: 1234567890123456 -> 12XXXXXXXXXXXXXX + */ + static smartRedactCardNumber(text) { + if (text.length <= 2) return text; + const visiblePart = text.substring(0, 2); + const maskedPart = 'X'.repeat(text.length - 2); + return visiblePart + maskedPart; + } + + /** + * Redact email showing first char + domain + * Example: john.doe@example.com -> j***@example.com + */ + static smartRedactEmail(email) { + const parts = email.split('@'); + if (parts.length !== 2) return email; + const username = parts[0]; + const domain = parts[1]; + return username[0] + '***@' + domain; + } + + /** + * Redact phone showing last 4 digits + * Example: (555) 123-4567 -> XXX-XXX-4567 + */ + static smartRedactPhone(phone) { + const digits = phone.replace(/\D/g, ''); + if (digits.length < 4) return phone; + const last4 = digits.slice(-4); + return 'XXX-XXX-' + last4; + } + + /** + * Complete masking for sensitive data + */ + static fullRedact(text) { + return 'X'.repeat(text.length); + } + + /** + * Main redaction function - applies patterns and redacts matches + */ + static redactText(text) { + if (!text || typeof text !== 'string') return text; + + let redactedText = text; + const detections = []; + + // Check Financial Patterns (Credit Cards) + if (typeof fiPatterns !== 'undefined') { + // Visa Card + if (fiPatterns.visaCardNumber) { + const pattern = fiPatterns.visaCardNumber.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const cleanMatch = match[0].replace(/[\s-]/g, ''); + const redacted = this.smartRedactCardNumber(cleanMatch); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'Visa Card', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + + // Mastercard + if (fiPatterns.mastercardNumber) { + const pattern = fiPatterns.mastercardNumber.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const cleanMatch = match[0].replace(/[\s-]/g, ''); + const redacted = this.smartRedactCardNumber(cleanMatch); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'Mastercard', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + + // AMEX + if (fiPatterns.amexNumber) { + const pattern = fiPatterns.amexNumber.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const cleanMatch = match[0].replace(/[\s-]/g, ''); + const redacted = this.smartRedactCardNumber(cleanMatch); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'AMEX Card', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + + // Bank Account Number + if (fiPatterns.bankAccountNumber) { + const pattern = fiPatterns.bankAccountNumber.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const accountNum = match[0].match(/\d{8,17}/); + if (accountNum) { + const redacted = this.smartRedactCardNumber(accountNum[0]); + redactedText = redactedText.replace(accountNum[0], redacted); + detections.push({ type: 'Bank Account', original: match[0], redacted }); + } + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + } + + // Check PII Patterns + if (typeof personalIdentificationPatterns !== 'undefined') { + // SSN + if (personalIdentificationPatterns.ssn) { + const pattern = personalIdentificationPatterns.ssn.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const redacted = 'XXX-XX-' + match[0].slice(-4); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'SSN', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + + // Email + if (personalIdentificationPatterns.email) { + const pattern = personalIdentificationPatterns.email.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const redacted = this.smartRedactEmail(match[0]); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'Email', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + + // Phone + if (personalIdentificationPatterns.phoneNumber) { + const pattern = personalIdentificationPatterns.phoneNumber.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const redacted = this.smartRedactPhone(match[0]); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: 'Phone', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + } + + // Check API Patterns + if (typeof apiPatterns !== 'undefined') { + Object.entries(apiPatterns).forEach(([key, patternInfo]) => { + if (patternInfo && patternInfo.pattern) { + const pattern = patternInfo.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const redacted = this.smartRedactCardNumber(match[0]); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: patternInfo.type || 'API Key', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + }); + } + + // Check Crypto Patterns + if (typeof cryptoPatterns !== 'undefined') { + Object.entries(cryptoPatterns).forEach(([key, patternInfo]) => { + if (patternInfo && patternInfo.pattern) { + const pattern = patternInfo.pattern; + let match; + let lastIndex = 0; + while ((match = pattern.exec(redactedText)) !== null) { + const redacted = this.smartRedactCardNumber(match[0]); + redactedText = redactedText.replace(match[0], redacted); + detections.push({ type: patternInfo.type || 'Crypto', original: match[0], redacted }); + + // Prevent infinite loop + if (pattern.lastIndex === lastIndex) break; + lastIndex = pattern.lastIndex; + } + } + }); + } + + // Log detections if any + if (detections.length > 0) { + console.log('🔒 Pattern Redaction Applied:', detections); + } + + return { + redactedText, + detections, + hasRedactions: detections.length > 0 + }; + } +} + // Prompt Generator for Gemini API class PromptGenerator { static generateSensitiveContentPrompt(domData) { @@ -731,6 +976,38 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { sendResponse({ success: true }); return true; } + + // Handle real-time text redaction (NEW - Pattern-based masking) + if (message.type === "REDACT_TEXT") { + console.log("🔒 Redaction request received for text of length:", message.text?.length); + + try { + const result = PatternRedactor.redactText(message.text); + + if (result.hasRedactions) { + console.log(`✅ Redacted ${result.detections.length} sensitive item(s)`); + result.detections.forEach(det => { + console.log(` - ${det.type}: ${det.original} → ${det.redacted}`); + }); + } + + sendResponse({ + success: true, + redactedText: result.redactedText, + detections: result.detections, + hasRedactions: result.hasRedactions + }); + } catch (error) { + console.error("❌ Redaction error:", error); + sendResponse({ + success: false, + error: error.message, + redactedText: message.text + }); + } + + return true; + } }); // Clean up analyzed URLs when tabs are closed diff --git a/Authenticator/repo/content.js b/Authenticator/repo/content.js index 60a08a080..9eb4c33db 100644 --- a/Authenticator/repo/content.js +++ b/Authenticator/repo/content.js @@ -6,8 +6,43 @@ // Configuration const CONFIG = { ANALYSIS_DELAY: 3000, // Wait 3 seconds after page load for dynamic content + AI_WEBSITES: [ + 'chatgpt.com', + 'openai.com', + 'gemini.google.com', + 'bard.google.com', + 'claude.ai', + 'anthropic.com', + 'perplexity.ai', + 'poe.com', + 'deepseek.com', + 'you.com', + 'character.ai', + 'replika.ai', + 'jasper.ai', + 'copy.ai', + 'writesonic.com', + 'chat.mistral.ai', + 'huggingface.co' + ] }; + /** + * Check if current website is an AI website + */ + function isAIWebsite() { + const currentHostname = window.location.hostname.toLowerCase(); + + const isAI = CONFIG.AI_WEBSITES.some(aiDomain => { + return currentHostname === aiDomain || + currentHostname.endsWith('.' + aiDomain) || + currentHostname.includes(aiDomain); + }); + + console.log(`🌐 Domain check: ${currentHostname} - AI Website: ${isAI ? '✅ YES' : '❌ NO'}`); + return isAI; + } + // Track analysis state let analysisPerformed = false; let currentUrl = window.location.href; @@ -206,21 +241,515 @@ } // ====================== + // REAL-TIME INPUT REDACTION (NEW) + // ====================== + + /** + * Apply real-time pattern-based redaction to input fields + */ + function attachInputRedaction(element) { + if (element.dataset.redactionAttached) return; + element.dataset.redactionAttached = 'true'; + + console.log("🔒 Attached redaction to:", element.tagName, element.id || element.className); + + let isRedacting = false; + let lastValue = ''; + + element.addEventListener('input', function (e) { + if (isRedacting) return; + + const currentValue = e.target.value; + const cursorPosition = e.target.selectionStart; + + console.log("⌨️ Input event detected:", { + length: currentValue.length, + elementType: e.target.tagName + }); + + // Send to background for redaction + chrome.runtime.sendMessage({ + type: "REDACT_TEXT", + text: currentValue + }, (response) => { + if (chrome.runtime.lastError) { + console.error("❌ Redaction request failed:", chrome.runtime.lastError); + return; + } + + if (response && response.success && response.hasRedactions) { + isRedacting = true; + + // Apply redacted text + e.target.value = response.redactedText; + + // Restore cursor position + const newCursorPos = Math.min(cursorPosition, response.redactedText.length); + e.target.setSelectionRange(newCursorPos, newCursorPos); + + console.log("✅ Applied redaction:", { + detections: response.detections.length, + types: response.detections.map(d => d.type) + }); + + lastValue = response.redactedText; + + // Reset flag after a tick + setTimeout(() => { + isRedacting = false; + }, 10); + } + }); + }); + + // Also handle paste events + element.addEventListener('paste', function (e) { + setTimeout(() => { + const event = new Event('input', { bubbles: true }); + e.target.dispatchEvent(event); + }, 10); + }); + } + + /** + * Apply real-time pattern-based redaction to contentEditable elements (ChatGPT, Claude) + */ + function attachContentEditableRedaction(element) { + if (element.dataset.redactionAttached) return; + element.dataset.redactionAttached = 'true'; + + console.log("🔒 Attached contentEditable redaction to:", element.tagName, element.id || element.className); + + let isRedacting = false; + + // Monitor input event on contentEditable + element.addEventListener('input', function (e) { + if (isRedacting) return; + + const currentText = element.innerText || element.textContent; + + console.log("⌨️ ContentEditable input detected:", { + length: currentText.length, + elementType: 'CONTENTEDITABLE' + }); + + // Send to background for redaction + chrome.runtime.sendMessage({ + type: "REDACT_TEXT", + text: currentText + }, (response) => { + if (chrome.runtime.lastError) { + console.error("❌ Redaction request failed:", chrome.runtime.lastError); + return; + } + + if (response && response.success && response.hasRedactions) { + isRedacting = true; + + // Save current selection + const selection = window.getSelection(); + const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null; + const cursorOffset = range ? range.startOffset : 0; + + // Apply redacted text + element.innerText = response.redactedText; + + // Try to restore cursor position + try { + const textNode = element.firstChild; + if (textNode && textNode.nodeType === 3) { // Text node + const newRange = document.createRange(); + const newOffset = Math.min(cursorOffset, response.redactedText.length); + newRange.setStart(textNode, newOffset); + newRange.collapse(true); + selection.removeAllRanges(); + selection.addRange(newRange); + } + } catch (err) { + console.warn("Could not restore cursor position:", err); + } + + console.log("✅ Applied contentEditable redaction:", { + detections: response.detections.length, + types: response.detections.map(d => d.type) + }); + + // Reset flag after a tick + setTimeout(() => { + isRedacting = false; + }, 10); + } + }); + }); + + // Also handle paste events + element.addEventListener('paste', function (e) { + setTimeout(() => { + const event = new Event('input', { bubbles: true }); + e.target.dispatchEvent(event); + }, 10); + }); + } + + /** + * Find and attach redaction to all input fields + */ + function initializeInputRedaction() { + console.log("🔧 Initializing real-time input redaction..."); + + // Target textareas (ChatGPT, Claude, etc.) + const textareas = document.querySelectorAll('textarea'); + textareas.forEach(textarea => { + attachInputRedaction(textarea); + }); + + // Target input fields + const inputs = document.querySelectorAll('input[type="text"], input[type="email"], input[type="tel"], input:not([type])'); + inputs.forEach(input => { + attachInputRedaction(input); + }); + + // Target contentEditable elements (ChatGPT uses these!) + const editableDivs = document.querySelectorAll('[contenteditable="true"]'); + editableDivs.forEach(div => { + attachContentEditableRedaction(div); + }); + + console.log(`✅ Attached redaction to ${textareas.length} textareas, ${inputs.length} inputs, and ${editableDivs.length} contentEditable divs`); + } + + /** + * Monitor for dynamically added input fields + */ + function monitorDynamicInputs() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { // Element node + if (node.tagName === 'TEXTAREA' || node.tagName === 'INPUT') { + attachInputRedaction(node); + } + // Check for contentEditable + if (node.getAttribute && node.getAttribute('contenteditable') === 'true') { + attachContentEditableRedaction(node); + } + // Check children + const textareas = node.querySelectorAll?.('textarea'); + const inputs = node.querySelectorAll?.('input[type="text"], input[type="email"], input[type="tel"], input:not([type])'); + const editableDivs = node.querySelectorAll?.('[contenteditable="true"]'); + + textareas?.forEach(textarea => attachInputRedaction(textarea)); + inputs?.forEach(input => attachInputRedaction(input)); + editableDivs?.forEach(div => attachContentEditableRedaction(div)); + } + }); + }); + }); + + if (document.body) { + observer.observe(document.body, { + childList: true, + subtree: true + }); + console.log("👁️ Monitoring for dynamic input fields and contentEditable elements..."); + } + } + + // ====================== + // SEND BUTTON INTERCEPTION FOR AI WEBSITES + // ====================== + + const AI_SEND_BUTTON_SELECTORS = { + 'chatgpt.com': [ + 'button[data-testid="send-button"]', + 'button[data-testid="fruitjuice-send-button"]', + '[data-testid="send-button"]' + ], + 'openai.com': [ + 'button[data-testid="send-button"]', + '[data-testid="send-button"]' + ], + 'claude.ai': [ + 'button[aria-label*="Send"]', + 'button[data-testid="send-button"]' + ], + 'gemini.google.com': [ + 'button[aria-label*="Send"]', + 'button.send-button' + ], + 'perplexity.ai': [ + 'button[aria-label*="Submit"]' + ], + 'poe.com': [ + 'button[class*="ChatMessageSendButton"]' + ] + }; + + /** + * Get send button selectors for current AI website + */ + function getSendButtonSelectors() { + const hostname = window.location.hostname.toLowerCase(); + + for (const [domain, selectors] of Object.entries(AI_SEND_BUTTON_SELECTORS)) { + if (hostname.includes(domain)) { + return selectors; + } + } + + return null; + } + + /** + * Find input element (textarea or contentEditable) on page + */ + function findInputElement() { + // Try contentEditable first (ChatGPT uses this) + const contentEditableInputs = document.querySelectorAll('[contenteditable="true"]'); + for (const el of contentEditableInputs) { + const text = (el.innerText || el.textContent || '').trim(); + if (text.length > 0) { + console.log("📝 Found contentEditable with text:", text.substring(0, 50)); + return { element: el, type: 'contenteditable' }; + } + } + + // Try textarea + const textareas = document.querySelectorAll('textarea'); + for (const el of textareas) { + const text = (el.value || '').trim(); + if (text.length > 0) { + console.log("📝 Found textarea with text:", text.substring(0, 50)); + return { element: el, type: 'textarea' }; + } + } + + // Try text inputs + const inputs = document.querySelectorAll('input[type="text"]'); + for (const el of inputs) { + const text = (el.value || '').trim(); + if (text.length > 0) { + console.log("📝 Found input with text:", text.substring(0, 50)); + return { element: el, type: 'input' }; + } + } + + return null; + } + + /** + * Intercept send button clicks and apply masking before sending + */ + function setupSendButtonInterception() { + const selectors = getSendButtonSelectors(); + if (!selectors) { + console.log("⏭️ No send button selectors for this AI website"); + return; + } + + console.log("🎯 Setting up send button interception for:", window.location.hostname); + console.log("🎯 Using selectors:", selectors); + + let interceptedButtons = 0; + + selectors.forEach(selector => { + const buttons = document.querySelectorAll(selector); + buttons.forEach(button => { + if (button.dataset.intercepted) return; + + button.dataset.intercepted = 'true'; + interceptedButtons++; + + console.log("🔗 Intercepting send button:", selector); + + // Capture the original click handler + button.addEventListener('click', async function interceptClickHandler(e) { + // Skip if this is our programmatic click (bypass flag) + if (button.dataset.bypassInterception === 'true') { + console.log("� Bypassing interception (programmatic click)"); + button.dataset.bypassInterception = ''; + return; // Allow the click to proceed + } + + console.log("�🚨 ===== SEND BUTTON CLICKED ====="); + + // Prevent immediate send + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + // Find input with text + const inputData = findInputElement(); + if (!inputData) { + console.log("⚠️ No input element found with text"); + // Allow original send if no input found + button.dataset.bypassInterception = 'true'; + button.click(); + return; + } + + const { element, type } = inputData; + const originalText = type === 'contenteditable' + ? (element.innerText || element.textContent || '') + : element.value; + + console.log("📤 Original text to analyze:", originalText); + + // Send to background for analysis and masking + chrome.runtime.sendMessage({ + type: "REDACT_TEXT", + text: originalText + }, (response) => { + console.log("📥 Redaction response:", response); + + if (chrome.runtime.lastError) { + console.error("❌ Analysis failed:", chrome.runtime.lastError); + // Allow original send on error + button.dataset.bypassInterception = 'true'; + button.click(); + return; + } + + if (response && response.success) { + if (response.hasRedactions) { + console.log("🚨 SENSITIVE DATA DETECTED!"); + console.log("🚨 Detections:", response.detections); + console.log("🚨 Original:", originalText); + console.log("🚨 Redacted:", response.redactedText); + + // Replace text in input with masked version + if (type === 'contenteditable') { + element.innerText = response.redactedText; + } else { + element.value = response.redactedText; + } + + // Trigger input event to update UI + const inputEvent = new Event('input', { bubbles: true }); + element.dispatchEvent(inputEvent); + + console.log("✅ Text replaced with masked version"); + } else { + console.log("✅ No sensitive data detected"); + } + + // Now allow the original send to proceed + setTimeout(() => { + console.log("🚀 Triggering original send action..."); + button.dataset.bypassInterception = 'true'; + button.click(); + }, 100); + } else { + console.log("⚠️ Analysis response invalid, allowing send"); + button.dataset.bypassInterception = 'true'; + button.click(); + } + }); + }, { capture: true }); // Use capture phase to intercept early + }); + }); + + if (interceptedButtons > 0) { + console.log(`✅ Intercepted ${interceptedButtons} send buttons`); + } else { + console.log("⚠️ No send buttons found yet (will monitor for dynamic buttons)"); + } + } + + /** + * Monitor for dynamically added send buttons + */ + function monitorSendButtons() { + const selectors = getSendButtonSelectors(); + if (!selectors) return; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + selectors.forEach(selector => { + try { + // Check if node itself matches + if (node.matches && node.matches(selector) && !node.dataset.intercepted) { + console.log("🆕 New send button detected:", selector); + setupSendButtonInterception(); + } + + // Check children + const buttons = node.querySelectorAll?.(selector); + if (buttons && buttons.length > 0) { + console.log(`🆕 Found ${buttons.length} new send buttons in added node`); + setupSendButtonInterception(); + } + } catch (err) { + // Selector might not be valid for this node + } + }); + } + }); + }); + }); + + if (document.body) { + observer.observe(document.body, { + childList: true, + subtree: true + }); + console.log("👁️ Monitoring for dynamic send buttons..."); + } + } // ====================== // INITIALIZATION // ====================== // Initialize content analysis - auto-trigger once per page console.log("🔧 Initializing content analysis..."); - // Trigger analysis automatically after page load + // Check if this is an AI website for pattern-based redaction + const enablePatternRedaction = isAIWebsite(); + + if (enablePatternRedaction) { + console.log("🤖 AI Website detected - Pattern-based redaction ENABLED"); + } else { + console.log("🌐 Regular website - Pattern-based redaction DISABLED"); + } + + // Initialize real-time input redaction ONLY on AI websites if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { console.log("📄 DOM loaded, starting analysis..."); performAnalysis(); + + // Initialize input redaction ONLY on AI websites + if (enablePatternRedaction) { + setTimeout(() => { + initializeInputRedaction(); + monitorDynamicInputs(); + + // Setup send button interception + setupSendButtonInterception(); + monitorSendButtons(); + }, 1000); + } else { + console.log("⏭️ Skipping input redaction (not an AI website)"); + } }); } else { console.log("📄 DOM already loaded, starting analysis..."); performAnalysis(); + + // Initialize input redaction ONLY on AI websites + if (enablePatternRedaction) { + setTimeout(() => { + initializeInputRedaction(); + monitorDynamicInputs(); + + // Setup send button interception + setupSendButtonInterception(); + monitorSendButtons(); + }, 1000); + } else { + console.log("⏭️ Skipping input redaction (not an AI website)"); + } } // Monitor for DOM changes (content analysis only) diff --git a/Authenticator/repo/manifest.json b/Authenticator/repo/manifest.json index 9599629d6..43bf2327a 100644 --- a/Authenticator/repo/manifest.json +++ b/Authenticator/repo/manifest.json @@ -37,7 +37,14 @@ "resources": [ "auth-success.html", "auth-success.js", - "popup.html" + "popup.html", + "public/libs/xregexp.js", + "public/ptr/apiPatterns_new.js", + "public/ptr/piiPatterns.js", + "public/ptr/fiPatterns.js", + "public/ptr/cryptoPatterns.js", + "public/ptr/medPatterns.js", + "public/ptr/networkPatterns.js" ], "matches": [ "" diff --git a/Authenticator/repo/popup.html b/Authenticator/repo/popup.html index 5c90afb8e..8bb1e31bd 100644 --- a/Authenticator/repo/popup.html +++ b/Authenticator/repo/popup.html @@ -7,7 +7,7 @@ diff --git a/Authenticator/repo/popup.js b/Authenticator/repo/popup.js deleted file mode 100644 index a60ec1c6d..000000000 --- a/Authenticator/repo/popup.js +++ /dev/null @@ -1,157 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - const authBtn = document.getElementById("authBtn"); - const statusDiv = document.getElementById("status"); - const progressBar = document.getElementById("progressBar"); - const progressFill = document.getElementById("progressFill"); - - const OKTA_URL = - "https://integrator-2373294.okta.com/home/integrator-2373294_wootzapp_1/0oatpx2h4ye8vjezl697/alntpxch84VdDzIUW697"; - - function updateStep(stepNumber, completed = false) { - const step = document.getElementById(`step${stepNumber}`); - if (step) { - if (completed) { - step.classList.add("completed"); - } else { - step.classList.remove("completed"); - } - } - } - - // Initialize popup as ready - setButtonState("ready"); - updateStatus("Ready to authenticate. Click \"Begin Authentication\" to start the secure login process.", "info"); - - function updateStatus(message, type = "info", showProgress = false) { - // Remove existing status classes - statusDiv.className = ""; - - // Set message with appropriate icon and styling - let icon = "�"; - if (type === "success") { - statusDiv.className = "success"; - icon = "✅"; - } else if (type === "error") { - statusDiv.className = "error"; - icon = "❌"; - } else if (type === "loading") { - statusDiv.className = "loading"; - icon = "⏳"; - } - - statusDiv.innerHTML = ` -
- ${icon} - ${message} -
-
-
-
- `; - - if (showProgress) { - setTimeout(() => { - const fill = document.getElementById("progressFill"); - if (fill) { - fill.classList.add("animate"); - } - }, 100); - } - - console.log("Status:", message); - } - - function setButtonState(state) { - const buttonContent = authBtn.querySelector(".button-content"); - - switch (state) { - case "loading": - authBtn.disabled = true; - authBtn.classList.add("button-loading"); - buttonContent.innerHTML = - '
'; - break; - case "disabled": - authBtn.disabled = true; - buttonContent.innerHTML = "Processing..."; - break; - case "ready": - default: - authBtn.disabled = false; - authBtn.classList.remove("button-loading"); - buttonContent.innerHTML = ` - - - - Begin Authentication - `; - break; - } - } - - authBtn.addEventListener("click", function () { - console.log("Auth button clicked - navigating to Okta"); - - // Update UI to show progress - setButtonState("loading"); - updateStep(2, true); - updateStatus("Connecting to authentication server...", "loading", true); - - // Clear any existing SAML response and auth state to start fresh - chrome.storage.local.remove(['pendingSamlResponse', 'authResult'], function() { - console.log("Cleared existing SAML response and auth state"); - }); - - // Store that we're starting authentication - chrome.storage.local.set({ - authInProgress: true, - extensionUrl: chrome.runtime.getURL("popup.html"), - }); - - // Simulate connection delay for better UX - setTimeout(() => { - updateStatus("Redirecting to secure login portal...", "loading", true); - - setTimeout(function () { - window.location.href = OKTA_URL; - }, 800); - }, 1200); - }); - - // Check if we're returning from authentication - chrome.storage.local.get(["authResult"], function (result) { - if (result.authResult) { - if (result.authResult.success) { - updateStep(3, true); - setButtonState("disabled"); - updateStatus( - "Authentication completed successfully! You are now signed in to your organization.", - "success" - ); - - // Show a completion message after a delay - setTimeout(() => { - updateStatus( - "Welcome! Your secure session is now active. You can close this window.", - "success" - ); - }, 3000); - } else { - updateStep(2, false); - updateStep(3, false); - setButtonState("ready"); - updateStatus( - "Authentication failed. Please try again or contact your IT administrator if the problem persists.", - "error" - ); - } - - // Clear the result - chrome.storage.local.remove(["authResult"]); - } - }); - - console.log("Popup script loaded"); -}); diff --git a/Authenticator/repo/public/icons/Authenticator.png b/Authenticator/repo/public/icons/Authenticator.png new file mode 100644 index 000000000..cdd6ee122 Binary files /dev/null and b/Authenticator/repo/public/icons/Authenticator.png differ diff --git a/Authenticator/repo/public/icons/Enterprise.png b/Authenticator/repo/public/icons/Enterprise.png new file mode 100644 index 000000000..cdd6ee122 Binary files /dev/null and b/Authenticator/repo/public/icons/Enterprise.png differ diff --git a/Authenticator/repo/public/icons/icon128.png b/Authenticator/repo/public/icons/icon128.png index ff54d6d34..cdd6ee122 100644 Binary files a/Authenticator/repo/public/icons/icon128.png and b/Authenticator/repo/public/icons/icon128.png differ diff --git a/Authenticator/repo/public/libs/xregexp.js b/Authenticator/repo/public/libs/xregexp.js new file mode 100644 index 000000000..d3b5d4bb4 --- /dev/null +++ b/Authenticator/repo/public/libs/xregexp.js @@ -0,0 +1,24 @@ +// Minimal XRegExp replacement for the extension +function XRegExp(pattern, flags) { + // If pattern is already a string, create a regular expression + if (typeof pattern === 'string') { + return new RegExp(pattern, flags); + } + + // If pattern is already a RegExp, return it as is + if (pattern instanceof RegExp) { + return pattern; + } + + // Fallback + return new RegExp(pattern, flags); +} + +// Make XRegExp available globally +if (typeof window !== 'undefined') { + window.XRegExp = XRegExp; +} else if (typeof global !== 'undefined') { + global.XRegExp = XRegExp; +} else if (typeof self !== 'undefined') { + self.XRegExp = XRegExp; +} \ No newline at end of file diff --git a/Authenticator/repo/public/ptr/apiPatterns_new.js b/Authenticator/repo/public/ptr/apiPatterns_new.js new file mode 100644 index 000000000..e69de29bb diff --git a/Authenticator/repo/public/ptr/apipatterns.js b/Authenticator/repo/public/ptr/apipatterns.js new file mode 100644 index 000000000..f5f265391 --- /dev/null +++ b/Authenticator/repo/public/ptr/apipatterns.js @@ -0,0 +1,1257 @@ +const apiPatterns = { + + + +googleAPIKey: { + type: "Google API Key", + pattern: XRegExp('\\b(?:AIZA|aiza|AiZA|aIZA|Aiza|aiZA|AIza|AiZa)[0-9A-Za-z\\-_]{25,43}\\b'), + description: "Matches Google API keys prefixed with 'AIza'.", + tags: ["Google", "API Key"] +}, + + +adobeSignAPIKey: { + type: "Adobe Sign API Key", + pattern: XRegExp('^SIGN-[A-Za-z0-9]{20,25}$'), + description: "Matches Adobe Sign API keys starting with 'SIGN-'.", + tags: ["Adobe", "API Key"] +}, + + +adobeAnalyticsAPIKey: { + type: "Adobe Analytics API Key", + pattern: XRegExp('^AKEY-[A-Za-z0-9]{20,25}$'), + description: "Matches Adobe Analytics API keys starting with 'AKEY-'.", + tags: ["Adobe", "API Key"] +}, + + +adobeClientID: { + type: "Adobe API Key (Client ID)", + pattern: XRegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'), + description: "Matches Adobe Client ID in UUID format.", + tags: ["Adobe", "API Key"] +}, + + +ablyAPIKey: { + type: "Ably API Key", + pattern: XRegExp('^ably_[a-zA-Z0-9]{32}$'), + description: "Matches Ably API keys prefixed with 'ably_'.", + tags: ["Ably", "API Key"] +}, + + +accuWeatherAPIKey: { + type: "AccuWeather API Key", + pattern: XRegExp('^[0-9A-Za-z]{32}$'), + description: "Matches 32-character AccuWeather API keys.", + tags: ["AccuWeather", "API Key"] +}, + + +airtableAPIKey: { + type: "Airtable API Key", + pattern: XRegExp('^key[A-Za-z0-9]{14}$'), + description: "Matches Airtable API keys prefixed with 'key'.", + tags: ["Airtable", "API Key"] +}, + + +algoliaAPIKey: { + type: "Algolia API Key", + pattern: XRegExp('^[a-f0-9]{32}$'), + description: "Matches Algolia API keys in hexadecimal format.", + tags: ["Algolia", "API Key"] +}, + + +agoraAPIKey: { + type: "Agora API Key", + pattern: XRegExp('^[A-Za-z0-9]{32}$'), + description: "Matches 32-character Agora API keys.", + tags: ["Agora", "API Key"] +}, + + +awsAccessID: { + type: "AWS Access ID Key", + pattern: XRegExp('\\bAKIA[0-9A-Z]{16}\\b'), + description: "Matches AWS Access ID Keys starting with 'AKIA'.", + tags: ["AWS", "Access Key"] +}, + + +awsSecretKey: { + type: "AWS Secret Key", + pattern: XRegExp('\\b[0-9a-zA-Z/+]{40}\\b'), + description: "Matches 40-character AWS Secret Keys.", + tags: ["AWS", "Secret Key"] +}, + + +amazonAuthToken: { + type: "Amazon Auth Token", + pattern: XRegExp('^amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-10-[0-9a-f]{4}-[0-9a-f]{12}$'), + description: "Matches Amazon Auth Tokens in a specific pattern.", + tags: ["Amazon", "Auth Token"] +}, + + +amplitudeAPIKey: { + type: "Amplitude API Key", + pattern: XRegExp('^amp_[A-Za-z0-9]{20,32}$'), + description: "Matches Amplitude API keys prefixed with 'amp_'.", + tags: ["Amplitude", "API Key"] +}, + + +applePrivateKey: { + type: "Apple Private Key", + pattern: XRegExp('^-----BEGIN PRIVATE KEY-----[A-Za-z0-9+/=\\s]+-----END PRIVATE KEY-----$'), + description: "Matches Apple Private Keys in PEM format.", + tags: ["Apple", "Private Key"] +}, + + +atlassianAPIKey: { + type: "Atlassian API Key", + pattern: XRegExp('^atl_[a-zA-Z0-9]{24,40}$'), + description: "Matches Atlassian API keys prefixed with 'atl_'.", + tags: ["Atlassian", "API Key"] +}, + + +autodeskAPIKey: { + type: "Autodesk API Key", + pattern: XRegExp('^ads[A-Za-z0-9]{20,32}$'), + description: "Matches Autodesk API keys prefixed with 'ads'.", + tags: ["Autodesk", "API Key"] +}, + + +autopilotAPIKey: { + type: "Autopilot API Key", + pattern: XRegExp('^AP-[A-Za-z0-9]{30}$'), + description: "Matches Autopilot API keys prefixed with 'AP-'.", + tags: ["Autopilot", "API Key"] +}, + + +basecampAPIKey: { + type: "Basecamp API Key", + pattern: XRegExp('^BC[A-Za-z0-9]{24}$'), + description: "Matches Basecamp API keys prefixed with 'BC'.", + tags: ["Basecamp", "API Key"] +}, + + +benchlingAPIKey: { + type: "Benchling API Key", + pattern: XRegExp('^bench_[A-Za-z0-9]{32}$'), + description: "Matches Benchling API keys prefixed with 'bench_'.", + tags: ["Benchling", "API Key"] +}, + + +bitbucketAPIKey: { + type: "Bitbucket API Key", + pattern: XRegExp('^bitbucket_[A-Za-z0-9]{20,30}$'), + description: "Matches Bitbucket API keys prefixed with 'bitbucket_'.", + tags: ["Bitbucket", "API Key"] +}, + + +boxDeveloperToken: { + type: "Box Developer Token", + pattern: XRegExp('^[A-Za-z0-9_-]{64}$'), + description: "Matches 64-character Box Developer Tokens.", + tags: ["Box", "Developer Token"] +}, + + +calendlyAPIKey: { + type: "Calendly API Key", + pattern: XRegExp('^api_key-[a-zA-Z0-9]{40}$'), + description: "Matches Calendly API keys prefixed with 'api_key-'.", + tags: ["Calendly", "API Key"] +}, + + +ciscoWebexAPIKey: { + type: "Cisco Webex API Key", + pattern: XRegExp('^MC[A-Za-z0-9_-]{20,40}$'), + description: "Matches Cisco Webex API keys prefixed with 'MC'.", + tags: ["Cisco", "Webex", "API Key"] +}, + + +clarityAIKey: { + type: "Clarity AI API Key", + pattern: XRegExp('^[a-f0-9]{36}$'), + description: "Matches 36-character Clarity AI API keys.", + tags: ["Clarity AI", "API Key"] +}, + + +clickupAPIKey: { + type: "ClickUp API Key", + pattern: XRegExp('^cl_[A-Za-z0-9]{32}$'), + description: "Matches ClickUp API keys prefixed with 'cl_'.", + tags: ["ClickUp", "API Key"] +}, + + +cloudflareAPIToken: { + type: "Cloudflare API Token", + pattern: XRegExp('^[A-Fa-f0-9]{32}$'), + description: "Matches 32-character Cloudflare API tokens.", + tags: ["Cloudflare", "API Token"] +}, + + +cloudinaryAPIKey: { + type: "Cloudinary API Key", + pattern: XRegExp('^CLOUDK-[A-Za-z0-9_-]{20}$'), + description: "Matches Cloudinary API keys prefixed with 'CLOUDK-'.", + tags: ["Cloudinary", "API Key"] +}, + + +cockroachDBAPIKey: { + type: "CockroachDB API Key", + pattern: XRegExp('^cockroach_[A-Za-z0-9]{24,40}$'), + description: "Matches CockroachDB API keys prefixed with 'cockroach_'.", + tags: ["CockroachDB", "API Key"] +}, + + +codaAPIToken: { + type: "Coda API Token", + pattern: XRegExp('^[A-Za-z0-9]{40}$'), + description: "Matches 40-character Coda API tokens.", + tags: ["Coda", "API Token"] +}, + + +coinbaseAPIKey: { + type: "Coinbase API Key", + pattern: XRegExp('^[a-zA-Z0-9]{32}$'), + description: "Matches 32-character Coinbase API keys.", + tags: ["Coinbase", "API Key"] +}, + + +contentfulDeliveryAPIKey: { + type: "Contentful Delivery API Key", + pattern: XRegExp('^[a-zA-Z0-9_-]{43}$'), + description: "Matches 43-character Contentful API keys.", + tags: ["Contentful", "Delivery API Key"] +}, + + +courierAPIKey: { + type: "Courier API Key", + pattern: XRegExp('^courier_[a-zA-Z0-9]{50}$'), + description: "Matches Courier API keys prefixed with 'courier_'.", + tags: ["Courier", "API Key"] +}, + + +dashlaneAPIKey: { + type: "Dashlane API Key", + pattern: XRegExp('^dashlane_[A-Za-z0-9]{24,}$'), + description: "Matches Dashlane API keys prefixed with 'dashlane_'.", + tags: ["Dashlane", "API Key"] +}, + + +datadogAPIKey: { + type: "Datadog API Key", + pattern: XRegExp('^DDOG[a-fA-F0-9]{28}$'), + description: "Matches Datadog API keys prefixed with 'DDOG'.", + tags: ["Datadog", "API Key"] +}, + + +dailymotionAPIKey: { + type: "Dailymotion API Key", + pattern: XRegExp('^[A-Za-z0-9]{40}$'), + description: "Matches 40-character Dailymotion API keys.", + tags: ["Dailymotion", "API Key"] +}, + + +deezerAPIKey: { + type: "Deezer API Key", + pattern: XRegExp('^[A-Za-z0-9]{32}$'), + description: "Matches 32-character Deezer API keys.", + tags: ["Deezer", "API Key"] +}, + + +dockerAPIToken: { + type: "Docker API Token", + pattern: XRegExp('^docker_[A-Za-z0-9]{32}$'), + description: "Matches Docker API tokens prefixed with 'docker_'.", + tags: ["Docker", "API Token"] +}, + + +dockerHubAPIToken: { + type: "Docker Hub API Token", + pattern: XRegExp('^[0-9A-Za-z_-]{32}$'), + description: "Matches 32-character Docker Hub API tokens.", + tags: ["Docker Hub", "API Token"] +}, + + +docusignAPIKey: { + type: "Docusign API Key", + pattern: XRegExp('^DS[A-Za-z0-9]{20}$'), + description: "Matches Docusign API keys prefixed with 'DS'.", + tags: ["Docusign", "API Key"] +}, + + +driftAPIToken: { + type: "Drift API Token", + pattern: XRegExp('^drift_[A-Za-z0-9]{40}$'), + description: "Matches Drift API tokens prefixed with 'drift_'.", + tags: ["Drift", "API Token"] +}, + + +dropboxAPIKey: { + type: "Dropbox API Key", + pattern: XRegExp('^[a-z0-9]{40,50}$'), + description: "Matches Dropbox API keys, typically 40-50 characters.", + tags: ["Dropbox", "API Key"] +}, + + +duckduckgoAPIKey: { + type: "DuckDuckGo API Key", + pattern: XRegExp('^duck[A-Za-z0-9]{20}$'), + description: "Matches DuckDuckGo API keys prefixed with 'duck'.", + tags: ["DuckDuckGo", "API Key"] +}, + + +ebayAPIKey: { + type: "eBay API Key", + pattern: XRegExp('^[0-9a-zA-Z]{24}$'), + description: "Matches 24-character eBay API keys.", + tags: ["eBay", "API Key"] +}, + + +elasticCloudAPIKey: { + type: "Elastic Cloud API Key", + pattern: XRegExp('^[a-zA-Z0-9]{32}$'), + description: "Matches Elastic Cloud API keys, typically 32 characters.", + tags: ["Elastic Cloud", "API Key"] +}, + + +envoyAPIKey: { + type: "Envoy API Key", + pattern: XRegExp('^env_[A-Za-z0-9]{40}$'), + description: "Matches Envoy API keys prefixed with 'env_'.", + tags: ["Envoy", "API Key"] +}, + + +etsyAPIKey: { + type: "Etsy API Key", + pattern: XRegExp('^key_[A-Za-z0-9]{32}$'), + description: "Matches Etsy API keys prefixed with 'key_'.", + tags: ["Etsy", "API Key"] +}, + + +eventbriteAPIKey: { + type: "Eventbrite API Key", + pattern: XRegExp('^[A-Za-z0-9]{32}$'), + description: "Matches Eventbrite API keys, typically 32 characters.", + tags: ["Eventbrite", "API Key"] +}, + + +expensifyAPIKey: { + type: "Expensify API Key", + pattern: XRegExp('^exp_[A-Za-z0-9]{40}$'), + description: "Matches Expensify API keys prefixed with 'exp_'.", + tags: ["Expensify", "API Key"] +}, + + +facebookGraphAPIToken: { + type: "Facebook Graph API Token", + pattern: XRegExp('^EAAG[a-zA-Z0-9]{30,60}$'), + description: "Matches Facebook Graph API tokens prefixed with 'EAAG'.", + tags: ["Facebook", "Graph API", "Token"] +}, + + +facebookAccessToken: { + type: "Facebook Access Token", + pattern: XRegExp('^EAACEdEose0cBA[0-9A-Za-z]+$'), + description: "Matches Facebook Access Tokens with common prefix.", + tags: ["Facebook", "Access Token"] +}, + + +figmaAPIToken: { + type: "Figma API Token", + pattern: XRegExp('^figd_[a-f0-9]{32,64}$'), + description: "Matches Figma API tokens prefixed with 'figd_'.", + tags: ["Figma", "API Token"] +}, + + +firebaseWebAPIKey: { + type: "Firebase Web API Key", + pattern: XRegExp('^AAAA[A-Za-z0-9_-]{20,50}$'), + description: "Matches Firebase Web API keys prefixed with 'AAAA'.", + tags: ["Firebase", "Web API Key"] +}, + + +flexportAPIKey: { + type: "Flexport API Key", + pattern: XRegExp('(?