feat(crypto): encrypt username at rest (CODE_AUDIT §1.3)
username is no longer stored cleartext. New columns username_enc/username_iv (AES-GCM under the vault key, same as encrypted_password). Search/sort/render stay client-side, so the field is decrypted at loadEntries into e.username in memory — everything downstream is unchanged. Full-strength random-IV AES-GCM (no searchable/deterministic encryption) precisely because search is client-side. Server (PM.Handler.Entries / .Auth / PM.Database): - Schema: vault_entries.username_enc, username_iv. - GET returns them; POST/PUT/bulk-import read + persist them; master-pw rotation re-encrypts them under the new key (UPDATE + loop). - ?q= server search drops `username LIKE` (ciphertext won't match; frontend searches client-side anyway). Client (app.js / app.import.js): - loadEntries/loadTrash decrypt username_enc → e.username (fallback to cleartext for un-migrated rows). - withEncryptedUsername(obj): write choke point — encrypts obj.username into username_enc/username_iv and blanks the cleartext. Wraps every POST/PUT body: saveEntry, soSave, duplicateEntry, moveEntryToFolder, addTagToEntry, batchMove/AddTag, encryptImportEntry (import + sync-apply). - doChangeMasterPassword re-encrypts username under the new key. - migrateUsernamesAtRest(): one-time sweep at enterApp, PUT-re-ships rows that still carry cleartext username so the DB gets scrubbed (bumps updated_at once; plaintext unchanged so devices converge). site/title/tags stay cleartext (same pattern later — see memory note). +1 merge test (username encrypted on import). 65/65. NOT compiled/tested at runtime (Delphi) — large multi-handler change; rebuild BuildAssets + PMServer and test create/edit/rotate/import/sync + verify the DB shows no cleartext username. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-5
@@ -85,12 +85,19 @@ Documenté mais à rappeler pour un futur modèle de menace :
|
||||
|
||||
- `entry_attachments` : `filename`, `mime`, `size_bytes` **non chiffrés**
|
||||
- `users.avatar_b64` : image **non chiffrée** (cosmétique, assumé)
|
||||
- `vault_entries` : `site`, `title`, `username`, `folder`, `tags`, `kind`,
|
||||
`template` **non chiffrés** (nécessaire pour recherche/tri sans déchiffrer)
|
||||
- `vault_entries` : ~~`username`~~ **chiffré (2026-07-08)** ; `site`, `title`,
|
||||
`folder`, `tags`, `kind`, `template` encore en clair.
|
||||
|
||||
Un attaquant avec accès disque voit la liste des sites et usernames. Pour
|
||||
un vault perso c'est un compromis acceptable (recherche instantanée), mais
|
||||
à documenter clairement pour l'utilisateur.
|
||||
**`username` chiffré au repos (✅ 2026-07-08)** : colonnes
|
||||
`username_enc/username_iv` (AES-GCM sous la clé du vault). Clé de l'approche :
|
||||
recherche/tri sont **côté client** → on déchiffre au `loadEntries` en mémoire,
|
||||
donc AES-GCM plein (IV aléatoire), pas de searchable-encryption. Choke-point
|
||||
`withEncryptedUsername` sur tous les writes ; migration `migrateUsernamesAtRest`
|
||||
au unlock pour les vieilles lignes ; rotation re-chiffre. Détails dans
|
||||
CLAUDE.md « Entry payload ». **Reste** : `site`/`title`/`tags` (même patron,
|
||||
[[encrypt-metadata-plan]]). Résiduel : nombre de lignes, timestamps, métadonnées
|
||||
d'attachments. **Non compilé/testé runtime Delphi cette session** —
|
||||
gros changement multi-handlers, rebuild + test soigneux requis.
|
||||
|
||||
### 1.4 🟡 Snapshot de sync = tout le vault en clair sous le sync password
|
||||
|
||||
|
||||
Reference in New Issue
Block a user