feat(entries): encrypt template at rest, guided tour, import fixes, cleanup

Batched session work sharing app.js / index.html / Entries.pas, so it can't
split cleanly without interactive hunk staging.

- feat: encrypt `template` metadata at rest (template_enc/iv, added to
  ENCRYPTED_META_FIELDS). withEncryptedMeta skips an absent template key so
  partial re-ships (add-tag, move-to-folder) don't wipe it via LHasTemplate.
  Cleartext column kept as migration fallback. +3 unit tests.
- feat: first-run guided tour ("How it works") — spotlight + bubble, no GIFs,
  re-launchable from Settings, seen-flag in DPAPI prefs.
- fix(import): preserve original created_at on restore (was stamped to import
  time); restore entry icons on overwrite (PUT ignores icon_b64).
- fix(settings): correct clipboard-privacy copy (already excluded from Win+V);
  PIN text 4-6 -> 4-12; reorder Set-PIN above unlock-method; move tray/startup
  toggles to General; dedicated backup-password button + warning status; tab icons.
- chore: remove dead legacy monolith (app-legacy.js, index-legacy.html,
  style-legacy.css) + unused passkeyBtn stub.
- docs: full-source review (CODE_AUDIT 6b), template + favorite/pinned notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-11 18:32:37 +01:00
parent 92ed153bc0
commit a42e4b205d
15 changed files with 381 additions and 2173 deletions
+58 -3
View File
@@ -86,8 +86,10 @@ 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`, `site`, `title`, `tags`~~ **chiffrés
(2026-07-09)** ; `folder`, `kind`, `template` encore en clair.
- `vault_entries` : ~~`username`, `site`, `title`, `tags`, `template`~~ **chiffrés
(username/site/title/tags 2026-07-09 ; template 2026-07-11)** ; `folder`,
`kind` restent en clair (folder = requête serveur de réassignation ;
kind = 2 valeurs, ~0 entropie).
**`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 :
@@ -102,7 +104,15 @@ champs (username/site/title/tags) pour ce compte, `_enc` remplies, affichage/
recherche/favicons OK. La migration est **par utilisateur** (tourne au unlock
sur les entries du compte connecté) — un compte non connecté garde son clair
jusqu'à sa prochaine connexion (comportement normal, pas une régression).
Résiduel : `folder`, `kind`, `template`, métadonnées d'attachments, nombre de
**`template` chiffré au repos (✅ 2026-07-11)** : même pattern
(`template_enc`/`template_iv`, ajouté à `ENCRYPTED_META_FIELDS`). Colonne clair
`template` gardée comme fallback de migration (`decryptEntryMeta` préfère `_enc`,
retombe sur le clair). Guard dans `withEncryptedMeta` : les re-ships partiels
(add-tag, move-to-folder) omettent `template` → le serveur le préserve via
`LHasTemplate` (clé absente), pas de wipe. 3 tests unitaires.
**Pièces jointes : le contenu est déjà chiffré** (AES-GCM, `encryptBlobBytes`) ;
seuls `filename`/`mime`/`size_bytes` restent en clair (pour lister sans tout
déchiffrer). Résiduel : `folder`, `kind`, métadonnées d'attachments, nombre de
lignes, timestamps.
### 1.4 ✅ Sync password faible — corrigé (2026-07-09)
@@ -353,6 +363,51 @@ cf. la checklist "Entry payload" de CLAUDE.md).
---
## 6b. Revue source complète (2026-07-11) — noté, NON corrigé
Passe manuelle JS + Delphi + HTML + CSS à la recherche d'illogismes, bugs
invisibles, perf et code mort. Rien de bloquant ; classé par type.
### Code mort / nettoyage
- ~~**`js/app-legacy.js` (1549 lignes)** + `index-legacy.html` + `css/style-legacy.css`~~
— ✅ **supprimés (2026-07-11)**. Ancien monolithe pré-découpage §3.1 (le trio
HTML+JS+CSS). Ni embarqués (absents du whitelist `BuildAssets.ps1`) ni
référencés sauf entre eux. 68/68 tests OK après suppression.
- **`#passkeyBtn` (index.html:134)** — bouton `display:none` jamais câblé en JS.
Stub passkey/WebAuthn abandonné. → supprimer le markup (ou implémenter).
- ~~**`#passkeyBtn`** (index.html:134)~~ — ✅ **supprimé (2026-07-11)**.
Bouton `display:none` jamais câblé (stub passkey/WebAuthn abandonné).
- **IDs inutilisés** `#reauthTitle`, `#historyTitle`, `#reauthConfirm`
**laissés volontairement** : un `id` non référencé ne coûte rien et pourrait
servir à un titre dynamique plus tard. Les retirer = churn à bénéfice nul.
### Performance
- **Search sans debounce** (app.js:8515) — **won't-do (YAGNI, 2026-07-11)**.
Pour < 500 entries le render est instantané ; un debounce ajouterait de la
latence au retour de recherche (régression du feel) pour un gros vault
hypothétique. À revisiter seulement si un profiler montre du lag réel.
- **N+1 attachments séquentiel** (buildSyncSnapshot, auto-backup, rotation) —
**déféré (2026-07-11)** : paralléliser touche le chemin sync/rotation (zone
la plus délicate) pour un gain spéculatif ; < 1 s sur loopback à < 500 entries.
Trigger pour revisiter : un backup/sync perceptiblement lent.
### Design / cohérence (tranché)
- **`favorite` / `pinned` device-local** — ✅ **décidé device-local (2026-07-11)**,
comme l'avatar. Absents de `buildSyncSnapshot` → leurs toggles serveur
(Entries.pas:804 favorite, 851 pin) ne bumpent volontairement PAS `updated_at`.
Asymétrie assumée (favorite est dans backup/export mais pas sync). Documenté
dans CLAUDE.md « Settings sync ».
### Vérifié sain (pour ne pas re-chasser)
- `DB.Lock`/`DB.Unlock` **équilibrés** (les écarts au grep étaient des mentions
en commentaire, pas des appels).
- Aucun `except … end` **muet** côté Delphi (pas de swallow silencieux serveur).
- Aucun `==` laxiste suspect en JS ; `setInterval`/`clearInterval` appariés.
- Restore d'entry (Entries.pas:759) et set-icône (965) bumpent bien `updated_at`.
- Soft-delete (712) ne bump pas mais sort du snapshot live → OK (trash non synced).
---
## 7. Verdict
Pour un usage **personnel, local, mono-utilisateur**, le produit est