fix(security): freeze fund-exit for blocked/under-review wallets (block gate only guarded /attest)#8014
Open
Vyacheslav-Tomashevskiy wants to merge 1 commit into
Conversation
The wallet block/review gate (wallet_review_gate_response, backed by wallet_review_holds + legacy blocked_wallets) was wired to only one endpoint — /attest/submit. Every fund-EXIT path ignored it, so a wallet an operator had just frozen for fraud/compromise could still drain its whole balance via /withdraw/request or /wallet/transfer/signed before a maintainer released it. Blocking earning while leaving spending open defeats the purpose of a review hold. - Consult the gate on the acting wallet (miner_pk / from_address) before any state mutation in /withdraw/request and /wallet/transfer/signed. - ensure_wallet_review_tables() now also creates the legacy blocked_wallets table it reads, so the gate is safe to call from any endpoint instead of raising 'no such table: blocked_wallets' on DBs where startup init didn't create it. - Mutation-checked regression test drives both real endpoints with a genuinely blocked wallet: asserts 403 wallet_blocked and untouched balance; fails on pre-fix code, passes after.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security bug: wallet block/review gate is enforced only on
/attest/submit, not on any fund-exit pathwallet_review_gate_response()(backed bywallet_review_holdsand the legacyblocked_walletstable) is the control an operator uses to freeze a compromised / fraud / sanctioned wallet. It is wired to exactly one call site — the attestation handler (review_gate = wallet_review_gate_response(miner)), which literally comments "Check wallet review / block registry".Every path that lets value leave the ledger ignores it:
POST /withdraw/request— verifies nonce, balance, encumbrance, daily limit and signature, then debits — but never consults the block registry.POST /wallet/transfer/signed— same, on the signed sender.Impact
A wallet an admin has just marked
blocked/needs_review/held/escalated(via/admin/wallet-review-holds, resolve actionblock, or a legacyblocked_walletsrow) can still drain its entire balance to any address before a maintainer releases it. Freezing the ability to earn (attest) while leaving the ability to spend wide open defeats the whole purpose of a review hold — draining funds is exactly the action a flagged wallet is most likely to take.Fix
wallet_review_gate_response()on the acting wallet (miner_pk/from_address) before any state mutation in/withdraw/requestand/wallet/transfer/signed. The gate already returns a ready(json, 403|409)response, so blocked →403 wallet_blocked, under-review →409 wallet_under_review. Checked before theBEGIN IMMEDIATEwrite txn so the gate's own read connection can't contend with the reserved write lock.ensure_wallet_review_tables()now also creates the legacyblocked_walletstable thatget_wallet_review_entry()reads. Previously the gate raisedno such table: blocked_walletson any DB where the main startup init hadn't created it — which is why it had never been safe to call outside the one attest path.Test
node/tests/test_blocked_wallet_fund_exit_freeze.pydrives both real endpoints with a genuinely blocked wallet holding a balance, and asserts403 wallet_blockedwith the balance untouched. Mutation-checked: neutralizing the gate makes both tests fail (the request proceeds past where it should stop); with the fix both pass. Existing withdraw/transfer/attest test suites remain green.