Files
silent-send/src/lib/storage.js
T
Claude 3a266d7aa7 feat: secret scanner — auto-detects and redacts API keys, tokens, credentials
Adds a secret scanning layer that runs after identity/explicit
substitutions. Catches secrets the user didn't configure:

- API keys: OpenAI (sk-), Anthropic (sk-ant-), Google (AIza),
  AWS (AKIA), GitHub (ghp_), GitLab (glpat-), Slack (xox),
  Stripe (sk_live/test), SendGrid (SG.)
- Auth: Bearer tokens, key=value assignments (password=, secret=,
  api_key=, token=), private key blocks (-----BEGIN PRIVATE KEY-----)
- Connection strings: mongodb://, postgres://, mysql:// with creds
- PII: SSN (xxx-xx-xxxx), credit card numbers (Visa/MC/Amex/Discover)

Redacted values shown in red in the Test tab. Secrets are truncated
in the activity log (first 8 chars + "...") to avoid logging the
full secret. Enabled by default, toggle in Options.

https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
2026-03-26 01:24:16 +00:00

138 lines
3.3 KiB
JavaScript

/**
* Silent Send - Storage Manager
*
* Wraps browser/api.storage.local with typed helpers for mappings,
* activity log, and settings.
*/
import api from './browser-polyfill.js';
const KEYS = {
MAPPINGS: 'ss_mappings',
IDENTITY: 'ss_identity',
LOG: 'ss_activity_log',
SETTINGS: 'ss_settings',
};
const DEFAULT_SETTINGS = {
enabled: true,
showHighlights: false,
revealMode: false,
secretScanning: true,
maxLogEntries: 200,
customDomains: [],
categories: ['name', 'email', 'phone', 'address', 'ssn', 'dob', 'general'],
};
const Storage = {
// --- Mappings ---
async getMappings() {
const result = await api.storage.local.get(KEYS.MAPPINGS);
return result[KEYS.MAPPINGS] || [];
},
async saveMappings(mappings) {
await api.storage.local.set({ [KEYS.MAPPINGS]: mappings });
},
async addMapping(mapping) {
const mappings = await this.getMappings();
const newMapping = {
id: crypto.randomUUID(),
real: mapping.real || '',
substitute: mapping.substitute || '',
category: mapping.category || 'general',
caseSensitive: mapping.caseSensitive ?? false,
enabled: mapping.enabled ?? true,
createdAt: Date.now(),
};
mappings.push(newMapping);
await this.saveMappings(mappings);
return newMapping;
},
async updateMapping(id, updates) {
const mappings = await this.getMappings();
const idx = mappings.findIndex((m) => m.id === id);
if (idx === -1) return null;
mappings[idx] = { ...mappings[idx], ...updates };
await this.saveMappings(mappings);
return mappings[idx];
},
async deleteMapping(id) {
const mappings = await this.getMappings();
const filtered = mappings.filter((m) => m.id !== id);
await this.saveMappings(filtered);
},
// --- Identity (Smart Patterns) ---
async getIdentity() {
const result = await api.storage.local.get(KEYS.IDENTITY);
return result[KEYS.IDENTITY] || {
emails: [],
names: [],
usernames: [],
phones: [],
catchAllEmail: '',
emailDomains: [],
enabled: { emails: true, names: true, usernames: true, phones: true, paths: true },
};
},
async saveIdentity(identity) {
await api.storage.local.set({ [KEYS.IDENTITY]: identity });
},
// --- Activity Log ---
async getLog() {
const result = await api.storage.local.get(KEYS.LOG);
return result[KEYS.LOG] || [];
},
async addLogEntry(entry) {
const settings = await this.getSettings();
const log = await this.getLog();
log.unshift({
...entry,
id: crypto.randomUUID(),
timestamp: Date.now(),
});
// Trim to max entries
if (log.length > settings.maxLogEntries) {
log.length = settings.maxLogEntries;
}
await api.storage.local.set({ [KEYS.LOG]: log });
},
async clearLog() {
await api.storage.local.set({ [KEYS.LOG]: [] });
},
// --- Settings ---
async getSettings() {
const result = await api.storage.local.get(KEYS.SETTINGS);
return { ...DEFAULT_SETTINGS, ...(result[KEYS.SETTINGS] || {}) };
},
async saveSettings(settings) {
const current = await this.getSettings();
await api.storage.local.set({
[KEYS.SETTINGS]: { ...current, ...settings },
});
},
};
if (typeof globalThis !== 'undefined') {
globalThis.SilentSendStorage = Storage;
}
export default Storage;