From 8e7ffe3e33e36396fa7d2c17486e1707a2da4f43 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 7 Aug 2026 23:00:25 -0700 Subject: users: sum sub-account balances in the deleteSelf guard (S12) The "withdraw your balance before deleting" guard summed only the main account, but the deletion loop wipes every sub-account's balance:/pending: keys unconditionally. A user with dust in the main account and savings in a sub-account passed the guard and silently lost the sub-account funds. Sum balance+pending across all ${id}:accounts before refusing. Co-Authored-By: Claude Opus 4.8 --- routes/users.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/routes/users.ts b/routes/users.ts index ed2d3861..0a3fa415 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -693,10 +693,19 @@ export default { fail("Type your username to confirm account deletion"); // Allow deletion through dust (often too small to withdraw); only block - // a meaningful balance so nobody destroys real funds by accident. - const balance = Number(await db.get(`balance:${id}`)) || 0; - const pending = Number(await db.get(`pending:${id}`)) || 0; - if (balance + pending > 10000) + // a meaningful balance so nobody destroys real funds by accident. Sum + // EVERY account (main + sub-accounts) — the deletion loop below wipes each + // sub-account's balance:/pending: keys unconditionally, so a guard that + // only checked the main account let a user with funds parked in a + // sub-account pass and silently destroy them. + const aids = await db.lRange(`${id}:accounts`, 0, -1); + if (!aids.includes(id)) aids.push(id); + let total = 0; + for (const aid of aids) { + total += Number(await db.get(`balance:${aid}`)) || 0; + total += Number(await db.get(`pending:${aid}`)) || 0; + } + if (total > 10000) fail("Withdraw your balance before deleting your account"); const keys = [ -- cgit v1.2.3