Files
seedpgp-web/doc/MEMORY_STRATEGY.md
LC mac 3bcb343fe3 docs: update version to v1.4.7 and organize documentation
- Update package.json version to v1.4.7
- Update README.md header to v1.4.7
- Update GEMINI.md version references to v1.4.7
- Update RECOVERY_PLAYBOOK.md version to v1.4.7
- Update SECURITY_AUDIT_REPORT.md version to v1.4.7
- Move documentation files to doc/ directory for better organization
- Add new documentation files: LOCAL_TESTING_GUIDE.md, SERVE.md, TAILS_OFFLINE_PLAYBOOK.md
- Add Makefile and serve.ts for improved development workflow
2026-02-13 23:24:26 +08:00

15 KiB
Raw Blame History

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

<meta http-equiv="Content-Security-Policy" content="
  default-src 'none';
  script-src 'self' 'wasm-unsafe-eval';
  connect-src 'none';
  form-action 'none';
  frame-ancestors 'none';
  base-uri 'self';
  upgrade-insecure-requests;
  block-all-mixed-content
" />

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 blockAllNetworks()

Provides user-controlled network interception via JavaScript API patching:

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

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

Optional React hook for encrypting individual state variables:

// 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 copyToClipboard()

Automatic protection for sensitive clipboard operations:

 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

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

    // Recommended approach for new features:
    import { useEncryptedState } from '@/lib/useEncryptedState';
    
    const [secret, setSecret] = useEncryptedState('');
    
  2. Never Store Plaintext Keys

    // ❌ Bad - plaintext in memory:
    const [key, setKey] = useState('secret-key');
    
    // ✅ Good - encrypted:
    const [key, setKey] = useEncryptedState('');
    
  3. Clear Sensitive Data After Use

    // 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

    // ✅ Trust CSP header enforcement for security guarantees
    // ⚠️ JS-level network blocking is UX, not security
    

Testing & Validation

Integration Tests

File: 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:

bun test:integration

Manual Verification

  1. CSP Verification

    # Browser DevTools → Network tab
    # Attempt to load external resource → CSP violation shown
    
  2. Network Blocking Test

    // In browser console with network blocking enabled:
    fetch('https://example.com') // → Network blocked error
    
  3. Clipboard Test

    // Copy a seed → 10 seconds later → Clipboard contains garbage
    navigator.clipboard.readText().then(text => console.log(text));
    
  4. Session Key Rotation

    // 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

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 for contact information and support channels.