mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-06 17:37:51 +08:00
- 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
474 lines
15 KiB
Markdown
474 lines
15 KiB
Markdown
# 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.
|