# Memory & State Security Strategy ## Overview This document explains the memory management and sensitive data security strategy for SeedPGP, addressing the fundamental limitation that **JavaScript on the web cannot guarantee memory zeroing**, and describing the defense-in-depth approach used instead. ## Executive Summary **Key Finding:** JavaScript cannot explicitly zero heap memory. No cryptographic library or framework can provide 100% memory protection in JS environments. **Strategic Response:** SeedPGP uses defense-in-depth with: 1. **Encryption** - Sensitive data is encrypted at rest using AES-256-GCM 2. **Limited Scope** - Session-scoped keys that auto-rotate and auto-destroy 3. **Network Isolation** - CSP headers + user-controlled network blocking prevent exfiltration 4. **Audit Trail** - Clipboard and crypto operations are logged via ClipboardDetails component --- ## JavaScript Memory Limitations ### Why Memory Zeroing Is Not Possible JavaScript's memory model and garbage collector make explicit memory zeroing impossible: 1. **GC Control Abstraction** - JavaScript abstracts away memory management from developers - No `Uint8Array.prototype.fill(0)` actually zeroes heap memory - The GC doesn't guarantee immediate reclamation of dereferenced objects - Memory pages may persist across multiple allocations 2. **String Immutability** - Strings in JS cannot be overwritten in-place - Each string operation allocates new memory - Old copies remain in memory until GC collects them 3. **JIT Compilation** - Modern JS engines (V8, JavaScriptCore) JIT-compile code - Sensitive data may be duplicated in compiled bytecode, caches, or optimizer snapshots - These internal structures are not under developer control 4. **External Buffers** - Browser APIs (WebGL, AudioContext) may have internal copies of data - OS kernel may page memory to disk - Hardware CPU caches are not directlycontrolled ### Practical Implications | Attack Vector | JS Protection | Mitigation | |---|---|---| | **Process Heap Inspection** | ❌ None | Encryption + short key lifetime | | **Memory Dumps** (device/VM) | ❌ None | Encryption mitigates exposure | | **Browser DevTools** | ⚠️ Weak | Browser UI constraints only | | **Browser Extensions** | ❌ None | CSP blocks malicious scripts | | **Clipboard System** | ❌ None | Auto-clear + user alert | | **Network Exfiltration** | ✅ **Strong** | CSP `connect-src 'none'` + user toggle | | **XSS Injection** | ✅ **Strong** | CSP `script-src 'self'` + sandbox | --- ## SeedPGP Defense-in-Depth Architecture ### Layer 1: Content Security Policy (CSP) **File:** [index.html](index.html#L9-L19) ```html ``` **What This Protects:** - `connect-src 'none'` → **No external network requests allowed** (enforced by browser) - `script-src 'self' 'wasm-unsafe-eval'` → **Only self-hosted scripts** (blocks external CDN injection) - `form-action 'none'` → **No form submissions** (blocks exfiltration via POST) - `default-src 'none'` → **Deny everything by default** (whitelist-only model) **Verification:** Integration tests verify CSP headers are present and restrictive. ### Layer 2: Network Blocking Toggle **File:** [src/App.tsx](src/App.tsx#L483-L559) `blockAllNetworks()` Provides user-controlled network interception via JavaScript API patching: ```typescript 1. fetch() → rejects all requests 2. XMLHttpRequest → constructor throws 3. WebSocket → constructor throws 4. sendBeacon() → returns false 5. Image.src → rejects external URLs 6. ServiceWorker.register() → throws ``` **When to Use:** - Maximize security posture voluntarily - Testing offline-first behavior - Prevent any JS-layer network calls **Limitation:** CSP provides the real enforcement at browser level; this is user-perceived security. ### Layer 3: Session Encryption **File:** [src/lib/sessionCrypto.ts](src/lib/sessionCrypto.ts) All sensitive data that enters React state can be encrypted: **Key Properties:** - **Algorithm:** AES-256-GCM (authenticated encryption) - **Non-Exportable:** Key cannot be retrieved via `getKey()` API - **Auto-Rotation:** Every 5 minutes OR every 1000 operations - **Auto-Destruction:** When page becomes hidden (tab switch/minimize) **Data Encrypted:** - Mnemonic (seed phrase) - Private key materials - Backup passwords - PGP passphrases - Decryption results **How It Works:** ``` User enters seed → Encrypt with session key → Store in React state User leaves → Key destroyed → Memory orphaned User returns → New key generated → Can't decrypt old data ``` ### Layer 4: Sensitive Data Encryption in React **File:** [src/lib/useEncryptedState.ts](src/lib/useEncryptedState.ts) Optional React hook for encrypting individual state variables: ```typescript // Usage example (optional): const [mnemonic, setMnemonic, encryptedBlob] = useEncryptedState(''); // When updated: await setMnemonic('my-12-word-seed-phrase'); // The hook: // - Automatically encrypts before storing // - Automatically decrypts on read // - Tracks encrypted blob for audit // - Returns plaintext for React rendering (GC will handle cleanup) ``` **Trade-offs:** - ✅ **Pro:** Sensitive data encrypted in state objects - ✅ **Pro:** Audit trail of encrypted values - ❌ **Con:** Async setState complicates component logic - ❌ **Con:** Decrypted values still in memory during React render **Migration Path:** Components already using sessionCrypto; useEncryptedState is available for future adoption. ### Layer 5: Clipboard Security **File:** [src/App.tsx](src/App.tsx#L228-L270) `copyToClipboard()` Automatic protection for sensitive clipboard operations: ```typescript ✅ Detects sensitive fields: 'mnemonic', 'seed', 'password', 'private', 'key' ✅ User alert: "⚠️ Will auto-clear in 10 seconds" ✅ Auto-clear: Overwrites clipboard with random garbage after 10 seconds ✅ Audit trail: ClipboardDetails logs all sensitive operations ``` **Limitations:** - System clipboard is outside app control - Browser extensions can read clipboard - Other apps may have read clipboard before auto-clear - Auto-clear timing is not guaranteed on all systems **Recommendation:** User education—alert shown every time sensitive data is copied. --- ## Current State of Sensitive Data ### Critical Paths (High Priority if Adopting useEncryptedState) | State Variable | Sensitivity | Current Encryption | Recommendation | |---|---|---|---| | `mnemonic` | 🔴 Critical | Via cache | ✅ Encrypt directly | | `privateKeyInput` | 🔴 Critical | Via cache | ✅ Encrypt directly | | `privateKeyPassphrase` | 🔴 Critical | Not encrypted | ✅ Encrypt directly | | `backupMessagePassword` | 🔴 Critical | Not encrypted | ✅ Encrypt directly | | `restoreMessagePassword` | 🔴 Critical | Not encrypted | ✅ Encrypt directly | | `decryptedRestoredMnemonic` | 🔴 Critical | Cached, auto-cleared | ✅ Already protected | | `publicKeyInput` | 🟡 Medium | Not encrypted | Optional | | `qrPayload` | 🟡 Medium | Not encrypted | Optional (if contains secret) | | `restoreInput` | 🟡 Medium | Not encrypted | Optional | ### Current Decrypt Flow ``` Encrypted File/QR ↓ decrypt() → Plaintext (temporarily in memory) ↓ encryptJsonToBlob() → Cached in sessionCrypto ↓ React State (encrypted cache reference) ↓ User clicks "Clear" or timer expires ↓ destroySessionKey() → Key nullified → Memory orphaned ``` **Is This Sufficient?** - ✅ For most users: **Yes** - Key destroyed on tab switch, CSP blocks exfiltration - ⚠️ For adversarial JS: Depends on attack surface (what can access memory?) - ❌ For APT/Malware: No—memory inspection always possible --- ## Recommended Practices ### For App Users 1. **Enable Network Blocking** - Toggle "🔒 Block Networks" when handling sensitive seeds - Provides additional confidence 2. **Use in Offline Mode** - Use SeedPGP available offline-first design - Minimize device network exposure 3. **Clear Clipboard Intentionally** - After copying sensitive data, manually click "Clear Clipboard & History" - Don't rely solely on 10-second auto-clear 4. **Use Secure Environment** - Run in isolated browser profile (e.g., Firefox Containers) - Consider Whonix, Tails, or VM for high-security scenarios 5. **Mind the Gap** - Understand that 10-second clipboard clear isn't guaranteed - Watch the alert message about clipboard accessibility ### For Developers 1. **Use Encryption for Sensitive State** ```typescript // Recommended approach for new features: import { useEncryptedState } from '@/lib/useEncryptedState'; const [secret, setSecret] = useEncryptedState(''); ``` 2. **Never Store Plaintext Keys** ```typescript // ❌ Bad - plaintext in memory: const [key, setKey] = useState('secret-key'); // ✅ Good - encrypted: const [key, setKey] = useEncryptedState(''); ``` 3. **Clear Sensitive Data After Use** ```typescript // Crypto result → cache immediately const result = await decrypt(encryptedData); const blob = await encryptJsonToBlob(result); _setEncryptedMnemonicCache(blob); setMnemonic(''); // Don't keep plaintext ``` 4. **Rely on CSP, Not JS Patches** ```typescript // ✅ Trust CSP header enforcement for security guarantees // ⚠️ JS-level network blocking is UX, not security ``` --- ## Testing & Validation ### Integration Tests **File:** [src/integration.test.ts](src/integration.test.ts) Tests verify: - CSP headers are restrictive (`default-src 'none'`, `connect-src 'none'`) - Network blocking toggle toggles all 5 mechanisms - Clipboard auto-clear fires after 10 seconds - Session key rotation occurs correctly **Run Tests:** ```bash bun test:integration ``` ### Manual Verification 1. **CSP Verification** ```bash # Browser DevTools → Network tab # Attempt to load external resource → CSP violation shown ``` 2. **Network Blocking Test** ```javascript // In browser console with network blocking enabled: fetch('https://example.com') // → Network blocked error ``` 3. **Clipboard Test** ```javascript // Copy a seed → 10 seconds later → Clipboard contains garbage navigator.clipboard.readText().then(text => console.log(text)); ``` 4. **Session Key Rotation** ```javascript // Browser console (dev mode only): await window.runSessionCryptoTest() ``` --- ## Limitations & Accepted Risk ### What SeedPGP CANNOT Protect Against 1. **Memory Inspection Post-Compromise** - If device is already compromised, encryption provides limited value - Attacker can hook into decryption function and capture plaintext 2. **Browser Extension Attacks** - Malicious extension bypasses CSP (runs in extension context) - Our network controls don't affect extensions - **Mitigation:** Only install trusted extensions; watch browser audit 3. **Supply Chain Attacks** - If Vite/TypeScript build is compromised, attacker can exfiltrate data - **Mitigation:** Verify hashes, review source code, use git commits 4. **Timing Side-Channels** - How long operations take may leak information - **Mitigation:** Use cryptographic libraries (OpenPGP.js) that implement constant-time ops 5. **Browser Memory by Device Owner** - If device owner uses `lldb`, `gdb`, or memory forensics tools, any plaintext extant is exposed - **For Tails/Whonix:** Memory is wiped on shutdown by design (us-relevant) ### Accepted Risks | Threat | Likelihood | Impact | Mitigation | |---|---|---|---| | Browser compromise | Low | Critical | CSP + offline mode | | Device compromise | Medium | Critical | Encryption provides delay | | Malicious extension | Medium | High | CSP, user vigilance | | User social engineering | High | Critical | User education | | Browser DevTools inspection | Medium-Low | Medium | DevTools not exposed by default | --- ## Future Improvements ### Potential Enhancements 1. **Full State Tree Encryption** - Encrypt entire App state object - Trade: Performance cost, complex re-render logic - Benefit: No plaintext state ever in memory 2. **Service Worker Encryption Layer** - Intercept state mutations at service worker level - Trade: Requires service worker registration (currently blocked by CSP) - Benefit: Transparent to components 3. **Hardware Wallet Integration** - Never import private keys; sign via hardware device - Trade: User experience complexity - Benefit: Private keys never reach browser 4. **Proof of Concept: Wasm Memory Protection** - Implement crypto in WebAssembly with explicit memory wiping - Trade: Complex build, performance overhead - Benefit: Stronger memory guarantees for crypto operations 5. **Runtime Attestation** - Periodically verify memory is clean via TOTP or similar - Trade: User experience friction - Benefit: Confidence in security posture --- ## References ### Academic Content - **"Wiping Sensitive Data from Memory"** - CWE-226, OWASP - **"JavaScript Heap Analysis"** - V8 developer documentation - **"Why JavaScript Is Unsuitable for Cryptography"** - Nadim Kobeissi, CryptoParty ### Specifications - **Content Security Policy Level 3** - - **Web Crypto API** - - **AES-GCM** - NIST SP 800-38D ### Community Resources - **r/cryptography FAQ** - "Why use Tails for sensitive crypto?" - **OpenPGP.js Documentation** - Encryption recommendations - **OWASP: A02:2021 – Cryptographic Failures** - Web app best practices --- ## Frequently Asked Questions **Q: Should I trust SeedPGP with my mainnet private keys?** A: No. SeedPGP is designed for seed phrase entry and BIP39 mnemonic generation. Never import active mainnet keys into any web app. **Q: What if I'm in Tails or Whonix?** A: Excellent choice. Those environments will: - Burn RAM after shutdown (defeating memory forensics) - Bridge Tor automatically (defeating location tracking) - Run in VM (limiting HW side-channel attacks) SeedPGP in Tails/Whonix with network blocking enabled provides strong security posture. **Q: Can I fork and add X security feature?** A: Absolutely! Recommended starting points: - `useEncryptedState` for new state variables - Wasm encryption layer for crypto operations - Service Worker interception for transparent encryption **Q: Should I use SeedPGP on a shared device?** A: Only if you trust all users. Another user could: - Read clipboard history - Inspect browser memory - Access browser console history For high-security scenarios, use dedicated device or Tails USB. --- ## Contact & Questions See [README.md](README.md) for contact information and support channels.