| # π‘οΈ SolVox Security Model |
|
|
| ## Threat Model |
|
|
| SolVox operates under the assumption that: |
| 1. The **renderer process is untrusted** β it could be compromised via XSS |
| 2. The **main process is the trust boundary** β all key operations happen here |
| 3. The **local filesystem is semi-trusted** β OS-level encryption protects at-rest data |
| 4. The **network is adversarial** β only Solana RPC calls leave the device |
|
|
| ## Security Controls |
|
|
| ### 1. Process Isolation |
|
|
| | Control | Setting | Purpose | |
| |---|---|---| |
| | `contextIsolation` | `true` | Renderer cannot access Node.js globals | |
| | `nodeIntegration` | `false` | No `require()` in renderer | |
| | `sandbox` | `true` | OS-level process sandbox (Chromium) | |
| | `webSecurity` | `true` | Same-origin policy enforced | |
| | `allowRunningInsecureContent` | `false` | No HTTP content in HTTPS context | |
| | `navigateOnDragDrop` | `false` | Prevents file drag-and-drop navigation | |
|
|
| ### 2. IPC Channel Allowlist |
|
|
| The preload script exposes ONLY these namespaced channels via `contextBridge`: |
|
|
| ``` |
| wallet:create, wallet:import, wallet:getPublicKey, wallet:getBalance, |
| wallet:sendSOL, wallet:sendUSDT, wallet:getHistory, wallet:isUnlocked, |
| wallet:lock, wallet:exists |
| |
| auth:biometric, auth:unlock, auth:setPin, auth:biometricAvailable |
| |
| security:getSettings, security:updateSettings, security:addWhitelist, |
| security:removeWhitelist, security:getWhitelist, security:getAnomalies |
| |
| ai:initialize, ai:processVoice, ai:chat, ai:parseIntent, ai:speak, |
| ai:translate, ai:embed, ai:ocr, ai:getStatus |
| |
| rag:search, rag:addDocument |
| ``` |
|
|
| **No raw `ipcRenderer` is ever exposed.** The renderer cannot invoke arbitrary IPC channels. |
|
|
| ### 3. Key Storage |
|
|
| ``` |
| Mnemonic/Private Key |
| β |
| βΌ |
| PIN-based AES-256-GCM (PBKDF2, 600K iterations, SHA-512) |
| β |
| βΌ |
| Electron safeStorage (OS keychain: macOS Keychain / Windows DPAPI / Linux libsecret) |
| β |
| βΌ |
| File on disk (0600 permissions, owner-only) |
| ``` |
|
|
| The mnemonic is **double-encrypted**: first with the user's PIN (AES-256-GCM with PBKDF2-derived key), then with OS-level `safeStorage`. An attacker who obtains the vault file still needs: |
| 1. The user's OS login credentials (to decrypt safeStorage) |
| 2. The user's PIN (to decrypt the inner AES layer) |
|
|
| ### 4. In-Memory Key Handling |
|
|
| - The `Keypair` object lives exclusively in the main process's `_sessionKeypair` variable |
| - On lock: `secretKey.fill(0)` zeroes the key buffer (best-effort; V8 GC is non-deterministic) |
| - Auto-lock triggers after 5 minutes of inactivity |
| - Key material is NEVER sent over IPC β only the public key (base58 string) and signed transaction bytes |
|
|
| ### 5. Transaction Guards |
|
|
| | Guard | Protection | |
| |---|---| |
| | **Input validation** | Regex-validated addresses, bounds-checked amounts | |
| | **Per-tx limits** | Configurable maximum per transaction | |
| | **Daily volume** | Cumulative daily spend limit | |
| | **Velocity** | Max transactions per hour | |
| | **Cooldown** | Minimum time between transactions | |
| | **Whitelist** | Optional β only send to pre-approved addresses | |
| | **Anomaly detection** | AI-powered pattern analysis (high amounts, odd hours, rapid sequences) | |
| | **Confirmation** | Every transaction requires explicit user confirmation | |
|
|
| ### 6. Content Security Policy |
|
|
| ``` |
| default-src 'self'; |
| script-src 'self'; |
| style-src 'self' 'unsafe-inline'; |
| img-src 'self' data: https:; |
| connect-src 'self' https://api.mainnet-beta.solana.com https://api.devnet.solana.com; |
| object-src 'none'; |
| base-uri 'self'; |
| form-action 'self'; |
| frame-ancestors 'none'; |
| ``` |
|
|
| - No `unsafe-eval` β prevents `eval()` and `new Function()` attacks |
| - No external scripts β blocks CDN-based script injection |
| - Limited connect-src β only Solana RPC endpoints allowed |
| - No frames β prevents clickjacking |
|
|
| ### 7. Navigation Protection |
|
|
| - All navigation to non-local URLs is blocked |
| - New window creation is denied (`setWindowOpenHandler(() => ({ action: 'deny' }))`) |
| - DevTools are disabled in production builds |
|
|
| ## Data Flow |
|
|
| ``` |
| Voice Input βββ QVAC (local) βββ Intent βββ Main Process validates βββ Wallet signs βββ Solana RPC |
| β |
| βΌ |
| TransactionGuard checks: |
| βββ Address format valid? |
| βββ Amount within limits? |
| βββ Daily volume OK? |
| βββ Velocity limit OK? |
| βββ Address whitelisted? |
| βββ Anomaly detected? |
| βββ User confirmed? |
| ``` |
|
|
| **Only the signed transaction bytes leave the device** β via Solana RPC. All AI processing, intent parsing, and validation happen locally. |
|
|
| ## Known Limitations |
|
|
| 1. **V8 garbage collection** means `secretKey.fill(0)` doesn't guarantee the key is erased from all memory pages |
| 2. **Electron renderer** could theoretically be compromised via a 0-day in Chromium β contextIsolation + sandbox mitigate this |
| 3. **Transaction privacy** is limited by Solana's public blockchain β recipients and amounts are on-chain |
| 4. **PIN brute-force** is mitigated by PBKDF2 (600K iterations) + lockout, but a sufficiently weak PIN (e.g., `123456`) can still be guessed |
|
|
| ## Recommendations for Production |
|
|
| - Use a hardware security module (HSM) or secure enclave for key storage |
| - Implement Shamir's Secret Sharing for mnemonic backup |
| - Add a dead man's switch (auto-transfer if inactive for N days) |
| - Integrate with hardware wallets (Ledger/Trezor) for signing |
| - Add transaction simulation (Solana `simulateTransaction`) before signing |
|
|