Account abstraction is enabling smart wallets to replace seed phrase custody with more flexible recovery methods. Among them, social recovery allows users to restore access to their wallet using a set of predefined guardians. These guardians approve recovery actions if the original key is lost. While the user experience improves, this design introduces new attack surfaces that need to be modeled with the same precision as core protocol logic.
This article breaks down the actual risks observed in smart wallet deployments that support social recovery, especially those aligned with ERC-4337. It focuses on how guardian systems are targeted and how developers can build defensible recovery flows.
Known Vulnerabilities Observed in Recovery Modules
Security audits have surfaced several recurring flaws:
- Contracts allowing recovery execution with incomplete or unchecked thresholds
- Guardian approvals lacking nonces or session identifiers, making them replayable
- Guardian list mutations during active recovery attempts
- Unverified addresses accepted as guardians without user-facing validation
- Iterative loops over dynamic guardian lists without gas safety
- Recovery initiation or execution happening without alerting affected parties
Takeaway: Fixes exist, but they must be embedded into wallet architecture—not treated as post-deployment patchwork.
Guardian Execution Paths
Many recovery systems use quorum-based approval. A set of guardians must approve a proposed recovery to change ownership. In some wallets, core functions like executeRecovery() have been exposed without checks on replay protection, threshold validation, or timing constraints. In these cases, a single compromised guardian or stale state may be enough to hijack control.
A proper design enforces replay protection, restricts when recovery can be finalized, and logs each recovery-related action clearly. Snapshots of the guardian list should be taken when recovery is initiated to prevent quorum changes mid-process.
Guardian Reassignment Vectors
Some smart wallet contracts allow guardians to be updated by the owner. If these updates are not guarded by timelocks, meta-approvals, or transparency requirements, attackers can trick users into rotating in malicious addresses.
Attackers often embed malicious guardian update calls into transactions that appear safe. A successful reassignment followed by recovery execution gives full wallet access to the attacker.
Guardian Identity Binding
Guardian lists defined as raw addresses create spoofing risk. Attackers have succeeded by convincing users to add addresses controlled by the attacker, under the guise of a known contact. Without identity verification or resolution, the wallet cannot distinguish between a trusted guardian and a malicious clone.
Implementations should integrate name resolution (e.g., ENS), validate guardian identity off-chain where possible, and present clear guardian info in the interface.
Circular Delegation Risks
In some cases, wallets use contracts such as multisigs as guardians. If the wallet being recovered is also a signer in the multisig, circular control paths can emerge. An attacker may be able to meet recovery thresholds indirectly through cross-signing arrangements that bypass intended quorum logic.
This type of logic flaw requires both static and runtime checks. Circular delegation should be disallowed, and guardian contract eligibility should be scoped tightly.
List Bloat and Gas Exhaustion
Some smart wallet implementations use loops to check each guardian’s approval. If an attacker can bloat the guardian list, they may cause recovery attempts to run out of gas. In effect, this creates a denial-of-service condition that locks the user out of recovery.
Caps on guardian list size and the use of mappings or fixed-size quorum structures help avoid these failure modes.
Recovery Session Design with Security Constraints
This model assumes a wallet that uses a guardian-based social recovery process under ERC-4337 architecture or similar account abstraction logic.
Core Idea
Recovery is not a single action. It’s a multi-phase process:
- Initiation: A user or guardian proposes a new owner.
- Voting: Guardians submit approvals within a defined window.
- Execution: The new owner is finalized if the threshold is met and the delay period has passed.
struct RecoverySession {
address proposedNewOwner;
address[] guardianSnapshot;
mapping(address => bool) hasVoted;
uint voteCount;
uint initiatedAt;
bool executed;
}
mapping(uint => RecoverySession) public recoverySessions;
uint public latestSessionId;
uint public constant VOTING_WINDOW = 3 days;
uint public constant MIN_APPROVALS = 3;
Recovery Flow
function initiateRecovery(address _newOwner) external onlyGuardian {
latestSessionId++;
RecoverySession storage session = recoverySessions[latestSessionId];
session.proposedNewOwner = _newOwner;
session.initiatedAt = block.timestamp;
session.guardianSnapshot = guardians; // assumes guardians[] is a contract-level variable
// Automatically record initiating guardian’s vote
session.hasVoted[msg.sender] = true;
session.voteCount++;
}
function approveRecovery(uint sessionId) external onlyGuardian {
RecoverySession storage session = recoverySessions[sessionId];
require(
block.timestamp <= session.initiatedAt + VOTING_WINDOW,
"Voting window expired"
);
require(!session.hasVoted[msg.sender], "Already voted");
for (uint i = 0; i < session.guardianSnapshot.length; i++) {
if (session.guardianSnapshot[i] == msg.sender) {
session.hasVoted[msg.sender] = true;
session.voteCount++;
return;
}
}
revert("Not in snapshot");
}
function executeRecovery(uint sessionId) external {
RecoverySession storage session = recoverySessions[sessionId];
require(!session.executed, "Already executed");
require(block.timestamp > session.initiatedAt + VOTING_WINDOW, "Voting still active");
require(session.voteCount >= MIN_APPROVALS, "Insufficient approvals");
owner = session.proposedNewOwner;
session.executed = true;
}
Optional: Owner Veto
To defend against guardian collusion or compromise:
function cancelRecovery(uint sessionId) external onlyOwner {
RecoverySession storage session = recoverySessions[sessionId];
require(!session.executed, "Already executed");
session.executed = true;
}
Security Benefits in This Design
- Immutable guardian snapshot: Protects against quorum manipulation after initiation.
- Explicit voting window: Prevents indefinite recovery attempts or stale execution.
- Vote replay prevention: hasVoted mapping prevents duplicate votes or reused approvals.
- Eligibility check against snapshot: Disallows newly added guardians from hijacking an ongoing recovery.
- Single-use execution flag: Prevents double execution or race conditions.
- Owner override option: Allows vetoing recovery attempts initiated by colluding guardians.
Common Bugs Prevented
- Recovery front-runs or list swaps: Because the snapshot is stored at initiation.
- Out-of-bounds quorum modification: Quorum is based only on the snapshot, not current state.
- Silent takeovers: Requires visible, signed voting with a forced delay before execution.
- Malicious quorum override: Owner can cancel an active recovery attempt if still in control.
Conclusion
Social recovery is an essential piece of smart wallet adoption. But it creates new categories of protocol risk that must be addressed directly in design. Guardian systems are not external helpers; they are onchain actors with control paths equivalent to private keys.
Wallets with social recovery need formal threat modeling, runtime visibility, and restrictive logic to prevent guardian-based abuse. Spearbit helps teams validate recovery designs through code review, adversarial modeling, and integration testing.
If you’re building or upgrading wallet recovery systems, start with a threat model that treats guardians as first-class attack surfaces.
