-
Notifications
You must be signed in to change notification settings - Fork 4
security(desktop): encrypt project data at rest (text stores) #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: fix/desktop-api-key-encryption
Are you sure you want to change the base?
Changes from all commits
8717e94
8106b7a
9dd151c
aea6d69
c93a92f
bbd3820
b2245b6
54ec14d
21914e3
4bc0a6d
3cf8c5e
1b1c7fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -311,8 +311,8 @@ The current primary project, settings, snapshot, image, Codex, RAG, and binder-a | |
|
|
||
| - **AES-256-GCM** with a PBKDF2-derived key (600 000 iterations, SHA-256, 32-byte random salt). | ||
| - Gated behind `featureFlags.enableIdbAtRestEncryption`. When a library is configured but locked, protected reads and writes fail closed rather than falling back to plaintext. | ||
| - Disable and passphrase rotation are temporarily unavailable until a journaled, cross-store migration protocol can prove recovery after interruption. | ||
| - **Web/PWA build only.** The unlock screen (`IdbUnlockModal`) and session-scoped in-memory key protect the IndexedDB-backed storage path used by the browser/PWA build. On the **Tauri desktop build**, primary project, settings, snapshot, image, Codex, RAG, and binder-asset data are written by the filesystem-backed store (`services/fs/*`), which is plaintext (LZ-string compressed, not encrypted) regardless of this setting — enabling it on desktop still shows the same unlock screen (the passphrase sentinel lives in the WebView's IndexedDB) but does not encrypt the actual manuscript files on disk. No `tauri-plugin-stronghold` or equivalent OS-keychain integration ships today — see the API-key encryption note below for the desktop-specific mechanism that does exist. | ||
| - Disable and passphrase rotation are available from Settings. IndexedDB uses its journal-backed migration protocol; desktop filesystem data is migrated before the shared key transition. An interrupted desktop filesystem migration blocks hydration for recovery rather than being treated as an empty library, but it is not yet crash-resumable (tracked in #359). | ||
| - **Tauri desktop build.** The unlock screen (`IdbUnlockModal`) and session-scoped in-memory key are shared with the browser/PWA build, and now genuinely protect the filesystem-backed store (`services/fs/*`) too — project, settings, snapshot, Codex, RAG, and image data reuse the same passphrase-derived key. Binder-asset files (`.bin` binary blob and `.meta.json` metadata sidecar) are the one exception and remain plaintext — see the encryption-mechanism table below. No `tauri-plugin-stronghold` or equivalent OS-keychain integration ships today — see the API-key encryption note below for the desktop-specific mechanism that does exist. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence after the earlier documentation fix is that the immediately preceding README bullet still says disable and passphrase rotation are unavailable, while this new desktop paragraph and the implemented Settings flow say they are supported; Useful? React with 👍 / 👎. |
||
| - At-rest protection reduces disclosure from an extracted browser profile while the library is locked; it does not protect an unlocked renderer, a compromised device, or every persistence surface. | ||
|
|
||
| ### 🔐 Encrypted Library Backup | ||
|
|
@@ -326,14 +326,15 @@ One-click encrypted export of your entire project library from **Settings → Da | |
|
|
||
| ### 🔑 Encryption — which mechanism protects what | ||
|
|
||
| There is no single blanket "encrypted at rest" guarantee — four independent mechanisms protect | ||
| There is no single blanket "encrypted at rest" guarantee — five independent mechanisms protect | ||
| different data, with different key material: | ||
|
|
||
| | Data | Mechanism | Where | | ||
| |------|-----------|-------| | ||
| | **Browser BYOK API key** | Random, non-extractable AES-256-GCM key generated via `crypto.subtle.generateKey()` — no passphrase, nothing to derive | `services/storage/idbKeyStore.ts` | | ||
| | **Browser IDB-at-rest data** _(opt-in, B-1)_ | User passphrase → PBKDF2 (600 000 iterations, SHA-256, random 32-byte salt) → AES-256-GCM, non-extractable key | `services/storage/storageEncryptionService.ts` | | ||
| | **Desktop (Tauri) BYOK API key** | Install-scoped secret material → PBKDF2 (600 000 iterations, SHA-256, random 32-byte salt) → AES-256-GCM, non-extractable key | `services/fs/fsCore.ts`, `services/fs/settingsFsStore.ts` | | ||
| | **Desktop (Tauri) BYOK API key** | When at-rest encryption is configured and unlocked: user passphrase → PBKDF2 (600 000 iterations, SHA-256, random 32-byte salt) → AES-256-GCM over `{provider, apiKey}`. Without a configured at-rest passphrase, the key is deliberately stored as plaintext rather than under reconstructible pseudo-secret material. | `services/fs/fsCore.ts`, `services/fs/settingsFsStore.ts` | | ||
| | **Desktop (Tauri) project/settings/snapshot/Codex/RAG/image data** | User passphrase → PBKDF2 (600 000 iterations, SHA-256, random 32-byte salt) → AES-256-GCM, same key material as the browser IDB-at-rest row above. Lazy/opportunistic: existing plaintext files are protected on their next save; first-time setup and disable/rotate additionally migrate every already-existing file immediately, not just future writes. ⚠️ **Not covered**: binder-asset files — both the binary blob (`.bin`) and its metadata sidecar (`.meta.json`, which includes the original filename) remain plaintext | `services/fs/*Store.ts`, `services/fs/fsEncryptionMigration.ts` | | ||
| | **Library backup vault** | User passphrase → PBKDF2 (600 000 iterations, SHA-256) → AES-256-GCM | `services/libraryBackupService.ts` | | ||
|
|
||
| See [`docs/SECURITY-THREAT-MODEL.md`](docs/SECURITY-THREAT-MODEL.md) for the full threat-model mapping. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.