Missing Idempotency
Detects critical state-changing operations that lack idempotency guards, allowing replay or double-execution.
Missing Idempotency
Overview
The missing idempotency detector identifies critical state-changing operations (lamport transfers, token program CPIs) that lack replay protection. Without nonce validation, sequence numbers, or slot-based expiry, these operations can potentially be double-executed. For remediation steps, see the Missing Idempotency Remediation.
Why This Is an Issue
Idempotency ensures that executing an operation multiple times produces the same result as executing it once. On Solana, while the runtime prevents exact transaction replay through blockhash expiry, programs that accept the same logical operation with different blockhashes can be exploited. Critical operations that lack idempotency guards include:
- Token minting: Without a nonce, the same mint instruction can be submitted in multiple transactions.
- Lamport transfers: Repeated transfers from the same source drain the account.
- Authority changes: Without one-time-use patterns, authority transfer instructions can be replayed.
- Airdrop claims: Without tracking claimed addresses, users can claim multiple times.
The risk is highest for operations triggered by off-chain coordination where the same instruction data could be bundled into multiple valid transactions.
How to Resolve
// Before: Vulnerable -- no idempotency guard
pub fn transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
**accounts[0].try_borrow_mut_lamports()? -= amount;
**accounts[1].try_borrow_mut_lamports()? += amount;
Ok(())
}
// After: Fixed -- nonce-based idempotency
pub fn transfer(accounts: &[AccountInfo], amount: u64, nonce: u64) -> ProgramResult {
let state = &mut accounts[2];
let current_nonce = u64::from_le_bytes(
state.try_borrow_data()?[..8].try_into().unwrap()
);
if nonce != current_nonce {
return Err(ProgramError::InvalidArgument);
}
**accounts[0].try_borrow_mut_lamports()? -= amount;
**accounts[1].try_borrow_mut_lamports()? += amount;
// Advance nonce
let new_nonce = current_nonce.checked_add(1)
.ok_or(ProgramError::ArithmeticOverflow)?;
state.try_borrow_mut_data()?[..8].copy_from_slice(&new_nonce.to_le_bytes());
Ok(())
}
Examples
Vulnerable Code
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
// No nonce, sequence number, or processed-ID tracking
token::mint_to(
ctx.accounts.into_mint_context(),
amount,
)?;
Ok(())
}
Fixed Code
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64, request_id: u64) -> Result<()> {
let state = &mut ctx.accounts.mint_state;
// Validate request has not been processed
require!(request_id == state.next_request_id, ErrorCode::InvalidRequestId);
token::mint_to(
ctx.accounts.into_mint_context(),
amount,
)?;
// Advance request counter
state.next_request_id = state.next_request_id
.checked_add(1)
.ok_or(ErrorCode::Overflow)?;
Ok(())
}
Example JSON Finding
{
"detector": "missing-idempotency",
"severity": "medium",
"confidence": 0.70,
"title": "Token Operation Without Idempotency Guard",
"description": "Token program CPI lacks idempotency validation. Critical token operations should have unique identifiers to prevent double-execution.",
"cwe_ids": [841]
}
Detection Methodology
- Critical operation identification: Scans for lamport transfers and token program CPIs (mint, burn, transfer).
- Nonce pattern search: Looks for variables that follow an increment-and-store pattern (
var = var + 1followed byStoreAccountData), indicating nonce tracking. - Temporal guard detection: Identifies slot or timestamp reads used in branch conditions, which provide time-based replay prevention.
- Gap analysis: Reports critical operations that lack both nonce guards and temporal guards.
Limitations
False positives: Programs where idempotency is enforced at the transaction level (e.g., via durable nonces) or by external orchestration will be flagged. Anchor programs using init constraints receive significantly reduced confidence since init is naturally idempotent-safe. False negatives: Custom idempotency patterns that do not follow increment-and-store or slot-check patterns may not be recognized.
Related Detectors
- Durable Nonce Manipulation — detects unsafe nonce account handling
- Signature Replay — detects missing replay protection for signatures