From 41a5819f5d7c662a5a2884952e49a14464e83c41 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 29 Sep 2026 08:37:39 -0700 Subject: payments: refuse on-chain sends to blocked addresses before debiting debit() checks blocked_addresses against the payment hash, which for an on-chain send is the txid, so a blocked destination address was never matched. Check each output address of the built transaction in sendOnchain and fail before any debit. Co-Authored-By: Claude Fable 5.1 --- lib/payments.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/payments.ts b/lib/payments.ts index f2a05d50..84d2f25a 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -709,6 +709,25 @@ export const sendOnchain = async (params) => { const r = await node.testMempoolAccept([hex]); if (!r[0].allowed) fail("transaction rejected"); + // Known drain destinations. debit() checks `blocked_addresses` against + // its hash, which for an on-chain send is the txid, so the address itself + // was never matched here. Refuse before the debit: a drain operator can + // fund fresh throwaway accounts and sweep each one on-chain to the same + // address, so freezing accounts is a treadmill while the destination is + // constant. + for (const { + scriptPubKey: { address }, + } of tx.vout) { + if (address && (await db.sIsMember("blocked_addresses", address))) { + const n = quiet(`blockedaddr:${user.id}`, 5 * 60 * 1000); + if (n !== null) + err( + `SECURITY: blocked on-chain send to ${address} by ${user.username} ${suppressed(n)}`, + ); + fail("address blocked"); + } + } + let total = 0; let fee = 0; let change = 0; -- cgit v1.2.3