diff --git a/README.md b/README.md index 6f0db22..42ed7c8 100644 --- a/README.md +++ b/README.md @@ -99,20 +99,20 @@ Go to **Options** → **Transfer Data** → **Import CSV / Password Export**. ### Document scanning -When you upload files to an AI service, Silent Send extracts and scans the text for PPI before the file is sent: +When you upload files to an AI service, Silent Send extracts and scans the text for PII before the file is sent: | Format | How it works | |---|---| -| PDF | Text extracted from content streams, PPI substituted, uploaded as clean plaintext | -| DOCX, XLSX, PPTX | XML text extracted from ZIP structure, PPI substituted, uploaded as plaintext | -| ODT, ODS, ODP | OpenDocument XML text extracted, PPI substituted | +| PDF | Text extracted from content streams, PII substituted, uploaded as clean plaintext | +| DOCX, XLSX, PPTX | XML text extracted from ZIP structure, PII substituted, uploaded as plaintext | +| ODT, ODS, ODP | OpenDocument XML text extracted, PII substituted | | DOC, XLS | Legacy binary format — readable text runs extracted | | RTF | Formatting stripped, text extracted | | TXT, CSV, JSON, code files | Direct string substitution | | Images (PNG, JPG, etc.) | Not scanned — no text to extract | | Scanned PDFs (image-only) | Not scanned — no text layer | -For PDF/DOCX/XLSX uploads, a preview panel shows what PPI was found before uploading. You can choose to substitute and upload, or upload the original. Text files are substituted silently. The original file on your disk is never modified — substitution only happens to the in-flight upload. +For PDF/DOCX/XLSX uploads, a preview panel shows what PII was found before uploading. You can choose to substitute and upload, or upload the original. Text files are substituted silently. The original file on your disk is never modified — substitution only happens to the in-flight upload. ## First-time setup @@ -390,7 +390,7 @@ src/ crypto.js — AES-256-GCM encryption, PBKDF2 key derivation, TOTP (RFC 6238), WebAuthn, key caching sync.js — Cross-browser sync with encryption (browser sync, Gist, folder, URL, sync codes) storage.js — Browser storage wrapper with transparent at-rest encryption - auto-detect.js — PPI pattern detection (IPs, addresses, paths, proper nouns) + auto-detect.js — PII pattern detection (IPs, addresses, paths, proper nouns) document-scanner.js — PDF/DOCX/XLSX/ODT/RTF text extraction and scanning import-parser.js — Bulk import from CSV, password managers, autofill exports version-history.js — Sync version snapshots + rollback @@ -427,7 +427,7 @@ For teams that want to enforce privacy rules across all members: 1. **Admin** creates a JSON policy file hosted at any URL (static file, S3, cloud function) 2. **Team members** join by entering the policy URL or an invite code in Options → Organization 3. **Org rules merge** with personal rules — required mappings are always active and cannot be disabled -4. **Compliance dashboard** shows which required fields are configured (without revealing actual PPI) +4. **Compliance dashboard** shows which required fields are configured (without revealing actual PII) 5. **Policy updates** are polled automatically (hourly) ### Org policy format @@ -479,7 +479,7 @@ When you enable **sync encryption** (Options → Sync Between Browsers → Sync | TOTP secret | Authenticator app shared secret | | All sync data | Everything sent to Gist, sync folders, custom URLs, browser sync | -**Only two things remain plaintext** — the encryption salt (needed to derive the key) and a verification blob (needed to check the password). Neither contains PPI. +**Only two things remain plaintext** — the encryption salt (needed to derive the key) and a verification blob (needed to check the password). Neither contains PII. Without at-rest encryption, data is stored in plaintext in the browser's local storage (similar to cookies and localStorage). Anyone with file system access to your browser profile directory could read it. @@ -561,3 +561,16 @@ The source code is available for inspection — you don't have to take our word [Business Source License 1.1](LICENSE) — free for personal, non-commercial use only. Commercial use is not permitted. The code converts to MIT on March 26, 2030. Contributions welcome. If you find a bug, especially a privacy-related one, please [open an issue](https://github.com/outis1one/silent-send/issues). + +## Support + +Silent Send is a weekend project — built because the developer wanted it and didn't look to see if something else already existed before building it with Claude. No donations needed. If you find it useful, great — enjoy. If it helps you keep your personally identifiable information private, that's thanks enough. More people having access to privacy is the goal. + +That said, Claude access isn't free. If you have a desire to give a coffee back, it would be appreciated — but don't feel any obligation to tip. + +**[Buy me a coffee on Ko-fi](https://ko-fi.com/YOUR_KOFI_USERNAME)** + +A few things to know: +- Donations do not influence updates. Silent Send may or may not get updates based on the developer's usage and available time. +- Donating does not create any warranty, support obligation, or expectation of future development. +- This is and always will be a free, non-commercial project. diff --git a/src/content/content.css b/src/content/content.css index 8f287d9..3eab79a 100644 --- a/src/content/content.css +++ b/src/content/content.css @@ -35,7 +35,7 @@ border-radius: 2px; } -/* Auto-detect PPI warning banner */ +/* Auto-detect PII warning banner */ .ss-autodetect-warning { position: fixed; top: 16px; @@ -136,7 +136,7 @@ font-style: italic; } -/* Pre-send PPI warning (spellcheck-style, appears while typing) */ +/* Pre-send PII warning (spellcheck-style, appears while typing) */ .ss-presend-warning { position: fixed; top: 16px; diff --git a/src/content/content.js b/src/content/content.js index 0d907e4..226b391 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -288,7 +288,7 @@ // ============================================================ // Combined substitution: smart patterns + explicit + auto-redact - // + auto-detect warning for unconfigured PPI + // + auto-detect warning for unconfigured PII // ============================================================ function substituteAll(text) { const allReplacements = []; @@ -309,12 +309,12 @@ finalText = redacted.text; } - // 4. Auto-detect: scan the FINAL text for unconfigured PPI + // 4. Auto-detect: scan the FINAL text for unconfigured PII // Auto-redact if enabled, otherwise just warn if (settings.autoDetect !== false) { - const warnings = autoDetectPPI(finalText, identity); + const warnings = autoDetectPII(finalText, identity); if (warnings.length > 0) { - // Auto-redact detected PPI in the outbound text + // Auto-redact detected PII in the outbound text if (settings.autoRedactDetected !== false) { for (let i = warnings.length - 1; i >= 0; i--) { const w = warnings[i]; @@ -343,9 +343,9 @@ } // ============================================================ - // Auto-Detect PPI Scanner (inline for page world) + // Auto-Detect PII Scanner (inline for page world) // ============================================================ - const PPI_PATTERNS = [ + const PII_PATTERNS = [ // Network { name: 'Private IP', re: /\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b/g, hint: 'Private IP address', cat: 'network' }, @@ -574,7 +574,7 @@ }); } - function autoDetectPPI(text, ident) { + function autoDetectPII(text, ident) { if (!text || text.length < 5) return []; const hasContext = CONTEXT_WORDS_RE.test(text); @@ -590,7 +590,7 @@ } const findings = []; - for (const pat of PPI_PATTERNS) { + for (const pat of PII_PATTERNS) { if (pat.contextRequired && !hasContext) continue; pat.re.lastIndex = 0; let m; @@ -642,7 +642,7 @@ safeHTML(warningEl, `
- Silent Send detected potential PPI that may not be substituted: + Silent Send detected potential PII that may not be substituted:
${items} @@ -978,7 +978,7 @@ // ============================================================ // Document Upload Processing // - // Scans files in FormData uploads for PPI. Supports PDF, DOCX, + // Scans files in FormData uploads for PII. Supports PDF, DOCX, // XLSX, and text files. Shows preview for binary formats. // ============================================================ @@ -1050,8 +1050,8 @@ } /** - * Scan a document file for PPI. Strategy: extract text from any - * format, substitute PPI, upload as plaintext. The AI extracts text + * Scan a document file for PII. Strategy: extract text from any + * format, substitute PII, upload as plaintext. The AI extracts text * from files anyway — no need to preserve formatting in a file * the user never gets back. Original stays untouched on disk. * @@ -1308,7 +1308,7 @@ safeHTML(docPreviewEl, `
- PPI found in ${esc(filename)} + PII found in ${esc(filename)} ${preview.replacementCount} item(s)
${preview.note || ''}
@@ -1745,7 +1745,7 @@ } // ============================================================ - // Pre-Send PPI Detection — scans as you type/paste (spellcheck style) + // Pre-Send PII Detection — scans as you type/paste (spellcheck style) // ============================================================ // Generate obviously-fake values using reserved/standard ranges @@ -1810,7 +1810,7 @@ safeHTML(preSendWarningEl, `
- Potential PPI detected — not yet configured: + Potential PII detected — not yet configured:
${items} @@ -1851,12 +1851,12 @@ // Update local mappings so the fetch interceptor uses them immediately mappings = currentMappings; - // Replace the PPI value in the current input right now + // Replace the PII value in the current input right now if (inputEl) { replaceInInput(inputEl, real, fake); - // Re-scan — will dismiss warning if no more PPI remains + // Re-scan — will dismiss warning if no more PII remains if (inputScanTimer) clearTimeout(inputScanTimer); - inputScanTimer = setTimeout(() => scanInputForPPI(inputEl), 150); + inputScanTimer = setTimeout(() => scanInputForPII(inputEl), 150); } // Visual feedback @@ -1879,7 +1879,7 @@ // Re-scan to update warning if (inputEl) { if (inputScanTimer) clearTimeout(inputScanTimer); - inputScanTimer = setTimeout(() => scanInputForPPI(inputEl), 150); + inputScanTimer = setTimeout(() => scanInputForPII(inputEl), 150); } }); }); @@ -1930,14 +1930,14 @@ // Scan input on type and paste let inputScanTimer = null; - function scanInputForPPI(target) { + function scanInputForPII(target) { const text = target.textContent || target.value || ''; if (!text || text.length < 5) { if (preSendWarningEl) preSendWarningEl.classList.remove('visible'); return; } - const warnings = autoDetectPPI(text, identity); + const warnings = autoDetectPII(text, identity); if (warnings.length > 0) { showPreSendWarning(warnings, target); } else if (preSendWarningEl) { @@ -1951,7 +1951,7 @@ if (target.matches?.('[contenteditable], textarea, input[type="text"]')) { // Debounce — don't scan on every keystroke if (inputScanTimer) clearTimeout(inputScanTimer); - inputScanTimer = setTimeout(() => scanInputForPPI(target), 800); + inputScanTimer = setTimeout(() => scanInputForPII(target), 800); } }, true); @@ -1961,7 +1961,7 @@ if (target.matches?.('[contenteditable], textarea, input[type="text"]') || target.closest?.('[contenteditable]')) { // Scan shortly after paste completes - setTimeout(() => scanInputForPPI(target.closest?.('[contenteditable]') || target), 200); + setTimeout(() => scanInputForPII(target.closest?.('[contenteditable]') || target), 200); } }, true); diff --git a/src/lib/auto-detect.js b/src/lib/auto-detect.js index f06f336..a6309d2 100644 --- a/src/lib/auto-detect.js +++ b/src/lib/auto-detect.js @@ -1,14 +1,14 @@ /** * Silent Send - Auto-Detect * - * Scans text for potential PPI that the user hasn't configured. + * Scans text for potential PII that the user hasn't configured. * This catches things the identity and auto-redact scanner can't — * because the user forgot or didn't know to configure them. * * Returns warnings (not auto-redactions) so the user can decide. */ -const PPI_PATTERNS = [ +const PII_PATTERNS = [ // --- Network --- { name: 'Private IP Address', @@ -21,7 +21,7 @@ const PPI_PATTERNS = [ regex: /\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b/g, category: 'network', hint: 'IP address — could identify your network', - // Exclude common non-PPI IPs + // Exclude common non-PII IPs exclude: /^(?:127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.\d+|8\.8\.[84]\.[84]|1\.1\.1\.1|1\.0\.0\.1)$/, }, { @@ -126,12 +126,12 @@ const PPI_PATTERNS = [ }, ]; -// Context words that make ambiguous patterns more likely to be PPI +// Context words that make ambiguous patterns more likely to be PII const CONTEXT_WORDS = /\b(?:born|birthday|dob|birth|passport|license|driver|ssn|social\s*security|address|home|live|lives|reside|zip|postal)\b/i; const AutoDetect = { /** - * Scan text for potential unconfigured PPI. + * Scan text for potential unconfigured PII. * Pass in identity so we can skip values the user already configured. * * Returns array of { name, value, hint, category, index } @@ -167,7 +167,7 @@ const AutoDetect = { } } - for (const pattern of PPI_PATTERNS) { + for (const pattern of PII_PATTERNS) { // Skip context-dependent patterns if no context words present if (pattern.contextRequired && !hasContext) continue; diff --git a/src/lib/document-scanner.js b/src/lib/document-scanner.js index 666a068..fa7aaee 100644 --- a/src/lib/document-scanner.js +++ b/src/lib/document-scanner.js @@ -1,11 +1,11 @@ /** * Silent Send - Document Scanner * - * Scans uploaded documents for PPI and substitutes/redacts before + * Scans uploaded documents for PII and substitutes/redacts before * the file reaches the AI service. * * Supported formats: - * - PDF: Extract text, scan for PPI, create sanitized plaintext version + * - PDF: Extract text, scan for PII, create sanitized plaintext version * (PDFs can't be reliably edited in-place without breaking layout) * - DOCX: Parse XML, find-replace text, repackage ZIP (layout preserved) * - XLSX: Parse cells, find-replace values, repackage (formatting preserved) @@ -13,7 +13,7 @@ * * Modes: * - Silent: substitute and upload (default for text files) - * - Preview: show PPI findings, let user confirm before upload (default for PDF/DOCX/XLSX) + * - Preview: show PII findings, let user confirm before upload (default for PDF/DOCX/XLSX) * * Integration: * - The fetch interceptor in content.js detects multipart/form-data uploads @@ -70,9 +70,9 @@ const DocumentScanner = { }, /** - * PDF: extract text, scan for PPI, create sanitized text file. + * PDF: extract text, scan for PII, create sanitized text file. * PDFs can't be reliably edited in-place, so we extract text, - * substitute PPI, and send the clean text instead. + * substitute PII, and send the clean text instead. */ async _processPDF(file, filename, substituteAll, options) { const text = await this._extractPDFText(file); diff --git a/src/lib/org-policy.js b/src/lib/org-policy.js index 8a6808f..ce50bea 100644 --- a/src/lib/org-policy.js +++ b/src/lib/org-policy.js @@ -12,7 +12,7 @@ * - Policy updates are applied automatically * * Privacy: the org admin can check compliance (are required fields - * configured?) but CANNOT see individual PPI values. + * configured?) but CANNOT see individual PII values. */ import api from './browser-polyfill.js'; @@ -220,7 +220,7 @@ const OrgPolicy = { /** * Check if the user's configuration meets org policy requirements. - * Returns compliance status WITHOUT revealing actual PPI values. + * Returns compliance status WITHOUT revealing actual PII values. * * @returns {{ compliant: boolean, missing: string[], configured: string[] }} */ diff --git a/src/lib/storage.js b/src/lib/storage.js index 3927ccc..4926a08 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -23,7 +23,7 @@ const KEYS = { SETTINGS: 'ss_settings', }; -// Keys that contain sensitive PPI and should be encrypted at rest +// Keys that contain sensitive PII and should be encrypted at rest // All user data keys are encrypted at rest — settings included since // custom domains and configuration can reveal what services the user // accesses. Only ss_sync_encryption (salt, verification blob) and diff --git a/src/options/options.html b/src/options/options.html index a925ca3..226bada 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -90,8 +90,8 @@
- -

Automatically replace detected PPI with generic placeholders (192.0.2.1, 123 Example Street, etc.) when sending

+ +

Automatically replace detected PII with generic placeholders (192.0.2.1, 123 Example Street, etc.) when sending

- -

Show a + button on detected PPI to instantly create a mapping with a suggested fake value

+ +

Show a + button on detected PII to instantly create a mapping with a suggested fake value

diff --git a/src/popup/popup.html b/src/popup/popup.html index 7deb185..c896408 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -206,7 +206,7 @@
- Auto-detect PPI + Auto-detect PII Warn about unconfigured personal data (IPs, addresses, paths)
@@ -214,8 +214,8 @@
- Auto-redact detected PPI - Replace detected PPI with placeholders on send + Auto-redact detected PII + Replace detected PII with placeholders on send
@@ -231,7 +231,7 @@
Document scan preview - Show PPI findings before uploading documents + Show PII findings before uploading documents
@@ -264,7 +264,7 @@
Silent Send is a convenience tool, not a security guarantee. It reduces - but cannot eliminate the risk of sharing personal data. It may miss PPI + but cannot eliminate the risk of sharing personal data. It may miss PII in images, unusual formats, or data you haven't configured. Third-party sites may change how they send data at any time, which can cause missed substitutions without warning. Always verify sensitive messages before diff --git a/src/popup/popup.js b/src/popup/popup.js index f22223c..b318f05 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -758,20 +758,20 @@ function renderTestDiff() { if (redactCount > 0) parts.push(`${redactCount} auto-redacted`); if (warnCount > 0) parts.push(`${warnCount} warnings`); - // Auto-detect unconfigured PPI in the final text - const ppiWarnings = AutoDetect.scan(finalText, identity); - if (ppiWarnings.length > 0) parts.push(`${ppiWarnings.length} PPI detected`); + // Auto-detect unconfigured PII in the final text + const piiWarnings = AutoDetect.scan(finalText, identity); + if (piiWarnings.length > 0) parts.push(`${piiWarnings.length} PII detected`); stats.textContent = `${allReplacements.length} substitution${allReplacements.length !== 1 ? 's' : ''} (${parts.join(', ')})`; - // Show PPI warnings below stats - if (ppiWarnings.length > 0) { - const ppiDiv = document.createElement('div'); - safeHTML(ppiDiv, `
- Unconfigured PPI detected: - ${ppiWarnings.map(w => `
${escapeHtml(w.value)} — ${w.hint}
`).join('')} + // Show PII warnings below stats + if (piiWarnings.length > 0) { + const piiDiv = document.createElement('div'); + safeHTML(piiDiv, `
+ Unconfigured PII detected: + ${piiWarnings.map(w => `
${escapeHtml(w.value)} — ${w.hint}
`).join('')}
`); - stats.appendChild(ppiDiv); + stats.appendChild(piiDiv); } }