feat(crypto): encrypt site/title/tags at rest too (CODE_AUDIT §1.3)
Extends the username-at-rest scheme to site, title and tags — the last searchable metadata still stored cleartext. Same design: dedicated <f>_enc/<f>_iv columns (AES-GCM under the vault key), decrypted at load into e.<f>, so client-side search/sort/render/favicon/autofill-match are unchanged. Full-strength random-IV AES-GCM (no searchable encryption) because search is client-side. Generalized the helpers over ENCRYPTED_META_FIELDS = [username, site, title, tags]: - withEncryptedUsername → withEncryptedMeta (encrypts all four, blanks cleartext) — wraps every POST/PUT body. - decryptEntryUsernames → decryptEntryMeta (decrypts all four at load). - migrateUsernamesAtRest → migrateMetadataAtRest (sweeps any field still cleartext, live + trash). - doChangeMasterPassword re-encrypts all four under the new key. Server (Entries + Auth + Database): - Columns site_enc/iv, title_enc/iv, tags_enc/iv; GET emits them (new AddNullableField helper); POST/PUT/bulk read+persist (BindNullable helper); rotation UPDATE re-encrypts them. - Removed the server "Site required" validation (site='' when encrypted — the client enforces it) at POST/PUT/bulk. - ?q= server search neutralized (site+username ciphertext → LIKE useless; the frontend never sends ?search=). Tests: merge assertions updated to decrypt site (encrypted on import). 65/65. username was runtime-validated earlier; site/title/tags NOT yet compiled/ runtime-tested (Delphi) — large multi-handler change. Rebuild BuildAssets + PMServer, then create/edit/dup/move/tag/import/rotate and verify the DB shows no cleartext site/title/tags (and the app still renders/searches). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-11
@@ -85,20 +85,21 @@ 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` : ~~`username`~~ **chiffré (2026-07-08)** ; `site`, `title`,
|
||||
`folder`, `tags`, `kind`, `template` encore en clair.
|
||||
- `vault_entries` : ~~`username`, `site`, `title`, `tags`~~ **chiffrés
|
||||
(2026-07-09)** ; `folder`, `kind`, `template` encore en clair.
|
||||
|
||||
**`username` chiffré au repos (✅ 2026-07-08)** : colonnes
|
||||
`username_enc/username_iv` (AES-GCM sous la clé du vault). Clé de l'approche :
|
||||
**`username` + `site` + `title` + `tags` chiffrés au repos (✅ 2026-07-09)** :
|
||||
colonnes `<f>_enc/<f>_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. **✅ Validé runtime (2026-07-09)** : après rebuild + unlock, la
|
||||
base montre 0 username en clair (54 entries, 43 `username_enc`, migration
|
||||
`migrateUsernamesAtRest` complétée) et l'affichage/recherche marchent.
|
||||
`withEncryptedMeta` sur tous les writes ; `decryptEntryMeta` au load ; migration
|
||||
`migrateMetadataAtRest` (live + corbeille) ; rotation re-chiffre les 4.
|
||||
Validation serveur « Site required » retirée + `?q=` neutralisé (LIKE inutile
|
||||
sur ciphertext). Détails CLAUDE.md « Entry payload ». **`username` validé
|
||||
runtime le 2026-07-09** (0 en clair après migration). **`site`/`title`/`tags`
|
||||
NON encore compilés/testés runtime** — même patron, gros changement
|
||||
multi-handlers, rebuild + test soigneux requis. Résiduel : `folder`, `kind`,
|
||||
`template`, métadonnées d'attachments, nombre de lignes, timestamps.
|
||||
|
||||
### 1.4 🟡 Snapshot de sync = tout le vault en clair sous le sync password
|
||||
|
||||
|
||||
Reference in New Issue
Block a user