Skip to content

fix(recovery): roll back confirm record to 'confirmed' on failure#306

Open
ety001 wants to merge 1 commit into
nextfrom
fix/recovery-confirm-processing-deadlock
Open

fix(recovery): roll back confirm record to 'confirmed' on failure#306
ety001 wants to merge 1 commit into
nextfrom
fix/recovery-confirm-processing-deadlock

Conversation

@ety001

@ety001 ety001 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Fixes audit V2 finding #1 (High): the recovery/confirm route's CAS claim sets status = 'processing', but only the success path transitions to 'closed'. Every failure path left the record permanently stuck in 'processing' — the user could never retry, and the subsequent recover-account broadcast (which requires status = 'closed') was wedged. A single transient Steem RPC error would permanently brick a recovery request.

Changes

  • src/app/api/recovery/confirm/route.ts: add rollbackToConfirmed() helper that resets 'processing' → 'confirmed' (CAS-guarded on status = 'processing' to avoid clobbering a concurrent success). Call it on every failure path after the CAS claim:
    • owner-key mismatch (400)
    • conveyor config missing (503)
    • requestAccountRecovery RPC error / any catch (500; best-effort)
  • tests/unit/recovery-confirm-route.test.ts: add rollback regression tests for the RPC-error and conveyor-missing paths; extend setupUpdateMocks to support the third update chain (rollback).

Root cause

The CAS at the top of the try-block atomically claims the record (confirmed → processing) to prevent TOCTOU races. But the original code's comment even admitted: "If this fails, the record stays in 'processing' and won't be re-processed." — with no rollback or janitor to reset it.

Test plan

  • pnpm type-check — clean
  • pnpm test — 467 passed (includes 2 new rollback regression tests)

The CAS in recovery/confirm sets status to 'processing' but only the success
path transitions to 'closed'. Every failure after the claim — owner-key
mismatch, conveyor config missing (503), requestAccountRecovery RPC error —
left the record permanently stuck in 'processing', so the user could never
retry and the subsequent recover-account broadcast (which requires
status='closed') was wedged. A single transient RPC error would brick a
recovery request.

Add a rollbackToConfirmed() helper that resets 'processing' → 'confirmed'
(CAS-guarded on status='processing' to avoid clobbering concurrent success)
and call it on every failure path: owner-key mismatch, conveyor preflight,
and the catch block. The catch-block rollback is best-effort.

Tests: add rollback assertions for the RPC-error and conveyor-missing paths;
extend setupUpdateMocks to chain a third update (rollback).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant