# Replayable recovery requests allow attacker to permanently block account recovery

Swafe recovery requests lack version/nonce binding, letting an attacker replay an old signed Recovery update to reset rec.pke and permanently block account recovery.

- Payout: Contest reward
- Severity: medium
- Program: Swafe
- Credited: Rhaydden
- Disclosed: 2026-05-04
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-11-swafe#m-04
- Library page: https://bugbountylibrary.com/r/c4%3A2025-11-swafe%3AM-04
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:rust technique:source-review vuln:access-control vuln:business-logic vuln:signature-replay

**Target:** Account recovery protocol (AccountUpdate Recovery message) in lib/src/account/v0.rs
**Root cause:** RecoveryRequestMessage signs only account_id and recovery_pke, omitting account version/nonce/timestamp; verify_update enforces cnt+1 for normal Updates but performs no freshness check for Recovery messages, so any old signed Recovery update is accepted and overwrites rec.pke.
**Steps:**
1. User initiates recovery session A with ephemeral key PKE_A; the signed RecoveryRequestMessage{account_id, recovery_pke=PKE_A} is observed (mempool/logs).
2. User later initiates session B with fresh key PKE_B.
3. Attacker replays the old session-A AccountUpdate (Recovery type) via `update_account`; verify_update validates the signature against stored associations and sets rec.pke=PKE_A with no version check.
4. Guardians call check_for_recovery and encrypt shares to current rec.pke=PKE_A.
5. User holds only PKE_B private key, cannot decrypt shares to PKE_A, recovery fails; repeat replay each time B is attempted.
**How found:** Code reading of verify_update: noticed normal Update path strictly enforces st.cnt == old.cnt+1 while the Recovery branch clones old state and only checks the signature, with no version/nonce binding in RecoveryRequestMessage.
**Exploitation:** Attacker needs only a previously observed signed Recovery update (public mempool or tx logs). By front-running/replaying it whenever the victim attempts a new recovery, the attacker forces on-chain rec.pke back to the old key, causing guardians to encrypt shares to a key the victim cannot decrypt, denying recovery indefinitely.
**Insight:** When a protocol has two message types sharing a verify path, compare their freshness/version checks: a signed message lacking the account version or nonce is replayable even if signatures are valid.
