fix: PPI warning accept/ignore buttons — v2.0.14

Accept (+) button:
- Was reading raw storage (getStorageData) which returns encrypted
  blobs when encryption is enabled, silently failing to add mappings
- Fixed: adds directly to the local mappings array and persists via
  setStorageData (storage bridge handles encryption transparently)

Auto-detect false positives:
- Was only checking identity values, not explicit mappings — values
  already in the mappings table still got flagged as unconfigured PPI
- Fixed: now adds all mapping real/substitute values to the skip set

Ignore button:
- Changed from plain text link to grey pill button for better UX
- Still persists permanently via ss_ignored_ppi in storage

https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
This commit is contained in:
Claude
2026-03-27 20:53:53 +00:00
parent 5c863b084d
commit eec0d52681
6 changed files with 23 additions and 20 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Silent Send", "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.", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Silent Send", "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.", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
"permissions": [ "permissions": [
"storage", "storage",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "silent-send", "name": "silent-send",
"version": "2.0.13", "version": "2.0.14",
"private": true, "private": true,
"license": "BSL-1.1", "license": "BSL-1.1",
"description": "Browser extension that substitutes personal data before sending to AI services", "description": "Browser extension that substitutes personal data before sending to AI services",
+4 -3
View File
@@ -221,13 +221,14 @@
.ss-ps-add:disabled { border-color: #333; cursor: default; } .ss-ps-add:disabled { border-color: #333; cursor: default; }
.ss-ps-ignore { .ss-ps-ignore {
background: none; background: #4b5563;
border: none; border: none;
color: #6b7280; color: #d1d5db;
font-size: 9px; font-size: 9px;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
padding: 2px 4px; padding: 3px 6px;
border-radius: 3px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
+15 -13
View File
@@ -578,7 +578,7 @@
if (!text || text.length < 5) return []; if (!text || text.length < 5) return [];
const hasContext = CONTEXT_WORDS_RE.test(text); 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(); const configured = new Set();
if (ident) { if (ident) {
const addAll = (arr, key) => (arr || []).forEach(item => { const addAll = (arr, key) => (arr || []).forEach(item => {
@@ -588,6 +588,11 @@
addAll(ident.names); addAll(ident.emails); addAll(ident.names); addAll(ident.emails);
addAll(ident.usernames); addAll(ident.hostnames); addAll(ident.phones); 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 = []; const findings = [];
for (const pat of PPI_PATTERNS) { for (const pat of PPI_PATTERNS) {
@@ -1848,13 +1853,12 @@
const items = warnings.slice(0, 8).map((w, i) => { const items = warnings.slice(0, 8).map((w, i) => {
const fake = generateFake(w.name, w.value); const fake = generateFake(w.name, w.value);
const displayVal = w.value.length > 25 ? w.value.slice(0, 22) + '...' : 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 `<div class="ss-ps-item"> return `<div class="ss-ps-item">
<span class="ss-ps-type">${w.name}</span> <span class="ss-ps-type">${w.name}</span>
<code class="ss-ps-value">${displayVal}</code> <code class="ss-ps-value">${displayVal}</code>
<span class="ss-ps-hint">${w.hint}</span> <span class="ss-ps-hint">${w.hint}</span>
${settings.autoAddDetected !== false <button class="ss-ps-add" data-real="${encodeURIComponent(w.value)}" data-fake="${encodeURIComponent(fake)}" data-cat="${w.category}" title="Add mapping: ${displayVal}${displayFake}">+</button>
? `<button class="ss-ps-add" data-real="${encodeURIComponent(w.value)}" data-fake="${encodeURIComponent(fake)}" data-cat="${w.category}" title="Add mapping: ${displayVal}${fake}">+</button>`
: ''}
<button class="ss-ps-ignore" data-value="${encodeURIComponent(w.value)}" title="Never flag this value again">ignore</button> <button class="ss-ps-ignore" data-value="${encodeURIComponent(w.value)}" title="Never flag this value again">ignore</button>
</div>`; </div>`;
}).join(''); }).join('');
@@ -1881,28 +1885,26 @@
preSendWarningEl.classList.remove('visible'); preSendWarningEl.classList.remove('visible');
}); });
// Auto-add buttons // Auto-add buttons (+)
preSendWarningEl.querySelectorAll('.ss-ps-add').forEach(btn => { preSendWarningEl.querySelectorAll('.ss-ps-add').forEach(btn => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const real = decodeURIComponent(btn.dataset.real); const real = decodeURIComponent(btn.dataset.real);
const fake = decodeURIComponent(btn.dataset.fake); const fake = decodeURIComponent(btn.dataset.fake);
const cat = btn.dataset.cat || 'general'; const cat = btn.dataset.cat || 'general';
// Add to mappings via storage // Add to local mappings array (used by the fetch interceptor)
const result = await getStorageData('ss_mappings'); const newMapping = {
const currentMappings = result || [];
currentMappings.push({
id: crypto.randomUUID(), id: crypto.randomUUID(),
real, substitute: fake, real, substitute: fake,
category: cat, category: cat,
caseSensitive: false, caseSensitive: false,
enabled: true, enabled: true,
createdAt: Date.now(), createdAt: Date.now(),
}); };
await setStorageData('ss_mappings', currentMappings); mappings.push(newMapping);
// Update local mappings so the fetch interceptor uses them immediately // Persist via storage bridge (handles encryption transparently)
mappings = currentMappings; setStorageData('ss_mappings', mappings);
// Replace the PPI value in the current input right now // Replace the PPI value in the current input right now
if (inputEl) { if (inputEl) {
+1 -1
View File
@@ -591,7 +591,7 @@
</section> </section>
<footer> <footer>
<p>Silent Send v2.0.13</p> <p>Silent Send v2.0.14</p>
</footer> </footer>
</div> </div>