feat: auto-redact detected PPI on send + standard fake values

Detected PPI is now auto-redacted in the fetch hook using
RFC/standard reserved values — not just warned about:

- IPs → 192.0.2.1 (RFC 5737 TEST-NET-1, never routed)
- MACs → 00:00:00:00:00:00
- Addresses → 123 Example Street, Anytown, ST 00000
- GPS → 0.000000,0.000000 (Gulf of Guinea)
- Dates → 01/01/1970 (Unix epoch)
- EINs → 00-0000000 (impossible prefix)
- Paths → /home/user
- Git → example org

These are obviously fake and guaranteed not to be real data,
unlike random values which could be confused with actual PPI.

New Options toggle: "Auto-redact detected PPI on send" (on by
default). When on, PPI is caught in the fetch hook even if the
user hits Enter immediately after pasting. Warning banner now
says "Auto-redacted with standard placeholders" instead of
"These were sent as-is."

https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
This commit is contained in:
Claude
2026-03-26 04:50:59 +00:00
parent 3cb06c6dea
commit 5495024be9
4 changed files with 51 additions and 23 deletions
+35 -23
View File
@@ -276,9 +276,27 @@
} }
// 4. Auto-detect: scan the FINAL text for unconfigured PPI // 4. Auto-detect: scan the FINAL text for unconfigured PPI
// Auto-redact if enabled, otherwise just warn
if (settings.autoDetect !== false) { if (settings.autoDetect !== false) {
const warnings = autoDetectPPI(finalText, identity); const warnings = autoDetectPPI(finalText, identity);
if (warnings.length > 0) { if (warnings.length > 0) {
// Auto-redact detected PPI in the outbound text
if (settings.autoRedactDetected !== false) {
for (let i = warnings.length - 1; i >= 0; i--) {
const w = warnings[i];
const fake = generateFake(w.name, w.value);
const escaped = esc(w.value);
const regex = new RegExp(escaped, 'g');
finalText = finalText.replace(regex, fake);
allReplacements.push({
original: w.value,
replaced: fake,
category: 'auto-detect',
pattern: w.name,
});
}
}
// Still show the warning so user knows what was caught
showAutoDetectWarning(warnings); showAutoDetectWarning(warnings);
} }
} }
@@ -1009,34 +1027,32 @@
// Pre-Send PPI Detection — scans as you type/paste (spellcheck style) // Pre-Send PPI Detection — scans as you type/paste (spellcheck style)
// ============================================================ // ============================================================
// Generate plausible fake values for detected PPI // Generate obviously-fake values using reserved/standard ranges
// These are recognizable as placeholders and guaranteed not to be real
function generateFake(type, value) { function generateFake(type, value) {
switch (type) { switch (type) {
case 'Private IP': case 'Private IP':
case 'Public IP': case 'Public IP':
return '10.' + rnd(1,254) + '.' + rnd(1,254) + '.' + rnd(1,254); // RFC 5737 — reserved for documentation, never routed
return '192.0.2.1';
case 'MAC Address': case 'MAC Address':
return Array.from({length:6}, () => rnd(0,255).toString(16).padStart(2,'0')).join(':'); return '00:00:00:00:00:00';
case 'Street Address': case 'Street Address':
const streets = ['Oak', 'Maple', 'Pine', 'Cedar', 'Elm', 'Main', 'Park', 'Lake']; return '123 Example Street, Anytown, ST 00000';
const types = ['St', 'Ave', 'Dr', 'Ln', 'Rd'];
return rnd(100,9999) + ' ' + streets[rnd(0,7)] + ' ' + types[rnd(0,4)];
case 'GPS Coordinates': case 'GPS Coordinates':
return (rnd(-90,90) + Math.random()).toFixed(6) + ',' + (rnd(-180,180) + Math.random()).toFixed(6); return '0.000000,0.000000';
case 'Date (possible DOB)': case 'Date (possible DOB)':
return (rnd(1,12) + '').padStart(2,'0') + '/' + (rnd(1,28) + '').padStart(2,'0') + '/' + rnd(1950,2005); return '01/01/1970';
case 'EIN / Tax ID': case 'EIN / Tax ID':
return rnd(10,99) + '-' + (rnd(1000000,9999999) + ''); return '00-0000000';
case 'Home Path': { case 'Home Path':
const fakeUser = 'user' + rnd(100,999); if (value.startsWith('C:\\')) return 'C:\\Users\\user';
if (value.startsWith('C:\\')) return 'C:\\Users\\' + fakeUser; if (value.startsWith('/Users/')) return '/Users/user';
if (value.startsWith('/Users/')) return '/Users/' + fakeUser; return '/home/user';
return '/home/' + fakeUser;
}
case 'Shell Prompt': case 'Shell Prompt':
return 'user@computer:$ '; return 'user@host:$ ';
case 'Git Remote': case 'Git Remote':
return value.replace(/[:/][^/\s]+\//, ':/anonymous/'); return value.replace(/[:/][^/\s]+\//, ':/example/');
case 'Env Variable': case 'Env Variable':
return value.split('=')[0] + '=REDACTED'; return value.split('=')[0] + '=REDACTED';
default: default:
@@ -1044,10 +1060,6 @@
} }
} }
function rnd(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Pre-send warning UI // Pre-send warning UI
let preSendWarningEl = null; let preSendWarningEl = null;
let preSendTimer = null; let preSendTimer = null;
@@ -1082,8 +1094,8 @@
${items} ${items}
${more} ${more}
<div class="ss-ad-footer"> <div class="ss-ad-footer">
${settings.autoAddDetected !== false ? 'Click + to auto-add a mapping.' : ''} ${settings.autoRedactDetected !== false ? 'Auto-redacted with standard placeholders.' : 'These were sent as-is.'}
Add to Identity or Mappings to protect this data. ${settings.autoAddDetected !== false ? ' Click + to add a permanent mapping.' : ''}
</div> </div>
`; `;
+1
View File
@@ -20,6 +20,7 @@ const DEFAULT_SETTINGS = {
revealMode: false, revealMode: false,
secretScanning: true, secretScanning: true,
autoDetect: true, autoDetect: true,
autoRedactDetected: true,
autoAddDetected: true, autoAddDetected: true,
maxLogEntries: 200, maxLogEntries: 200,
customDomains: [], customDomains: [],
+10
View File
@@ -67,6 +67,16 @@
<span class="toggle-slider"></span> <span class="toggle-slider"></span>
</label> </label>
</div> </div>
<div class="setting-row">
<div>
<label>Auto-redact detected PPI on send</label>
<p class="setting-desc">Automatically replace detected PPI with generic placeholders (192.0.2.1, 123 Example Street, etc.) when sending</p>
</div>
<label class="toggle">
<input type="checkbox" id="autoRedactDetected" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-row"> <div class="setting-row">
<div> <div>
<label>Offer to auto-add detected PPI</label> <label>Offer to auto-add detected PPI</label>
+5
View File
@@ -15,6 +15,7 @@ document.addEventListener('DOMContentLoaded', async () => {
$('#showHighlights').checked = settings.showHighlights || false; $('#showHighlights').checked = settings.showHighlights || false;
$('#secretScanning').checked = settings.secretScanning !== false; $('#secretScanning').checked = settings.secretScanning !== false;
$('#autoDetect').checked = settings.autoDetect !== false; $('#autoDetect').checked = settings.autoDetect !== false;
$('#autoRedactDetected').checked = settings.autoRedactDetected !== false;
$('#autoAddDetected').checked = settings.autoAddDetected !== false; $('#autoAddDetected').checked = settings.autoAddDetected !== false;
$('#maxLogEntries').value = settings.maxLogEntries || 200; $('#maxLogEntries').value = settings.maxLogEntries || 200;
@@ -47,6 +48,10 @@ document.addEventListener('DOMContentLoaded', async () => {
await Storage.saveSettings({ autoDetect: e.target.checked }); await Storage.saveSettings({ autoDetect: e.target.checked });
}); });
$('#autoRedactDetected').addEventListener('change', async (e) => {
await Storage.saveSettings({ autoRedactDetected: e.target.checked });
});
$('#autoAddDetected').addEventListener('change', async (e) => { $('#autoAddDetected').addEventListener('change', async (e) => {
await Storage.saveSettings({ autoAddDetected: e.target.checked }); await Storage.saveSettings({ autoAddDetected: e.target.checked });
}); });