diff --git a/README.md b/README.md index 6cc9d0f..ee444d4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A browser extension (Chrome, Firefox, and Safari) that intercepts personal infor | Service | Domains | Status | |---------|---------|--------| | Claude | claude.ai, claude.ai/code | Tested | -| ChatGPT | chatgpt.com, chat.openai.com | Untested | +| ChatGPT | chatgpt.com, chat.openai.com | Tested | | Grok | grok.x.ai, x.com/i/grok | Untested | | Gemini | gemini.google.com | Untested | | OpenWebUI | localhost, 127.0.0.1, or custom domain | Untested | diff --git a/manifest.firefox.json b/manifest.firefox.json index 0f9077d..880e480 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "2.0.13", + "version": "2.0.14", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "browser_specific_settings": { "gecko": { diff --git a/manifest.json b/manifest.json index 9800648..8ff33ee 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "2.0.13", + "version": "2.0.14", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "permissions": [ "storage", diff --git a/package.json b/package.json index 0a2ff37..2dcb4a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silent-send", - "version": "2.0.13", + "version": "2.0.14", "private": true, "license": "BSL-1.1", "description": "Browser extension that substitutes personal data before sending to AI services", diff --git a/src/content/content.css b/src/content/content.css index 8f287d9..d6d9fd3 100644 --- a/src/content/content.css +++ b/src/content/content.css @@ -221,13 +221,14 @@ .ss-ps-add:disabled { border-color: #333; cursor: default; } .ss-ps-ignore { - background: none; + background: #4b5563; border: none; - color: #6b7280; + color: #d1d5db; font-size: 9px; cursor: pointer; flex-shrink: 0; - padding: 2px 4px; + padding: 3px 6px; + border-radius: 3px; text-transform: uppercase; letter-spacing: 0.5px; } diff --git a/src/content/content.js b/src/content/content.js index c7e5031..b70789c 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -578,7 +578,7 @@ if (!text || text.length < 5) return []; const hasContext = CONTEXT_WORDS_RE.test(text); - // Build skip set from configured values + // Build skip set from configured values (identity + explicit mappings) const configured = new Set(); if (ident) { const addAll = (arr, key) => (arr || []).forEach(item => { @@ -588,6 +588,11 @@ addAll(ident.names); addAll(ident.emails); addAll(ident.usernames); addAll(ident.hostnames); addAll(ident.phones); } + // Also skip values that are already in explicit mappings + for (const m of mappings) { + if (m.real) configured.add(m.real.toLowerCase()); + if (m.substitute) configured.add(m.substitute.toLowerCase()); + } const findings = []; for (const pat of PPI_PATTERNS) { @@ -1548,8 +1553,8 @@ function revealInElement(el) { if (SKIP_REVEAL_TAGS.has(el.tagName)) return; if (el.classList?.contains('ss-reveal-badge')) return; - // Never touch contenteditable elements (chat input boxes) if (el.isContentEditable) return; + if (isInNonChatArea(el)) return; const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode(node) { @@ -1557,6 +1562,7 @@ if (parent && SKIP_REVEAL_TAGS.has(parent.tagName)) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge')) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; + if (isInNonChatArea(parent)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); @@ -1576,11 +1582,13 @@ function unrevealInElement(el) { if (SKIP_REVEAL_TAGS.has(el.tagName)) return; if (el.isContentEditable) return; + if (isInNonChatArea(el)) return; const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode(node) { const parent = node.parentElement; if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; + if (isInNonChatArea(parent)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); @@ -1615,6 +1623,7 @@ if (parent && SKIP_REVEAL_TAGS.has(parent.tagName)) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge')) return NodeFilter.FILTER_REJECT; if (parent?.closest?.('[contenteditable="true"]')) return NodeFilter.FILTER_REJECT; + if (isInNonChatArea(parent)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); @@ -1653,11 +1662,18 @@ } } - // Elements to skip when revealing (inputs, scripts, styles, extension UI) + // Elements to skip when revealing (inputs, scripts, styles, extension UI, navigation) const SKIP_REVEAL_TAGS = new Set([ 'SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME', 'INPUT', 'TEXTAREA', 'SELECT', + 'NAV', 'ASIDE', 'HEADER', 'FOOTER', ]); + // Skip reveal in navigation, sidebars, headers, and other non-chat UI + function isInNonChatArea(el) { + if (!el) return false; + return !!el.closest('nav, aside, header, footer, [role="navigation"], [role="banner"], [role="complementary"], [data-sidebar], [class*="sidebar"], [class*="Sidebar"], [class*="nav-"], [class*="Nav"], [class*="menu"], [class*="Menu"], [class*="header"], [class*="Header"]'); + } + // Reveal ALL text on the page + apply highlights function revealAllResponses() { revealInElement(document.body); @@ -1695,10 +1711,11 @@ // Only do text replacement in reveal mode if (settings.revealMode) { if (node.nodeType === Node.ELEMENT_NODE) { - // Skip contenteditable (chat input) and skipped tags + // Skip non-chat areas, contenteditable, and skipped tags if (!SKIP_REVEAL_TAGS.has(node.tagName) && !node.isContentEditable && - !node.closest?.('[contenteditable="true"]')) { + !node.closest?.('[contenteditable="true"]') && + !isInNonChatArea(node)) { revealInElement(node); } } else if (node.nodeType === Node.TEXT_NODE) { @@ -1848,13 +1865,12 @@ const items = warnings.slice(0, 8).map((w, i) => { const fake = generateFake(w.name, w.value); const displayVal = w.value.length > 25 ? w.value.slice(0, 22) + '...' : w.value; + const displayFake = fake.length > 20 ? fake.slice(0, 17) + '...' : fake; return `
${displayVal}
${w.hint}
- ${settings.autoAddDetected !== false
- ? ``
- : ''}
+