From 263799adcd84afceef5a2a4d76c83a88a3e1b979 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Thu, 9 Jul 2026 05:15:34 +0100 Subject: [PATCH] =?UTF-8?q?fix(crypto):=20migrate=20trashed=20entries'=20u?= =?UTF-8?q?sernames=20too=20(=C2=A71.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit migrateUsernamesAtRest only swept state.entries (live rows), so a soft-deleted entry kept its cleartext username in the DB until purge. Now it also fetches + decrypts the trash (GET /entries?deleted=1) and includes those rows in the sweep. The PUT updates the row's fields without touching `deleted`, so the entry stays in the trash; trashed rows aren't in the sync snapshot, so no churn. Surfaced by a lingering cleartext username on a trashed test entry. Co-Authored-By: Claude Opus 4.8 --- js/app.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 08baa44..04836aa 100644 --- a/js/app.js +++ b/js/app.js @@ -1509,7 +1509,20 @@ async function withEncryptedUsername(obj) { // so other devices converge to the same value). async function migrateUsernamesAtRest() { if (!state.cryptoKey) return; - const todo = state.entries.filter(e => e && !e.username_enc && (e.username || '') !== ''); + let pool = (state.entries || []).slice(); + // Trashed rows live in state.trashed (loaded on demand), not state.entries, + // so the live-only sweep would leave a soft-deleted entry's username in + // cleartext until purge. Fetch + decrypt the trash so it's covered too — + // the PUT updates the row's fields without touching `deleted`, so it stays + // in the trash. Trashed rows aren't in the sync snapshot, so no churn. + try { + const trash = await api('/entries?deleted=1', { headers: authHeaders() }); + if (Array.isArray(trash)) { + await decryptEntryUsernames(trash); + pool = pool.concat(trash); + } + } catch (_) {} + const todo = pool.filter(e => e && !e.username_enc && (e.username || '') !== ''); if (todo.length === 0) return; let migrated = 0; for (const e of todo) {