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

474 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<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](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** - <https://w3c.github.io/webappsec-csp/>
- **Web Crypto API** - <https://www.w3.org/TR/WebCryptoAPI/>
- **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.