Token Init Race Remediation
How to fix token account initialization race conditions.
Token Init Race Remediation
Overview
Detector Reference: Token Init Race
This guide explains how to prevent race conditions during SPL token account initialization that allow attackers to substitute malicious accounts.
Recommended Fix
Always validate token account ownership, mint, and authority immediately after initialization:
// After initializing the token account via CPI
invoke(&init_account_ix, &[payer.clone(), token_account.clone()])?;
// Immediate validation
require!(*token_account.owner == spl_token::ID, InvalidOwner);
let data = spl_token::state::Account::unpack(&token_account.data.borrow())?;
require!(data.mint == expected_mint, InvalidMint);
require!(data.owner == expected_authority, InvalidAuthority);
For Anchor, use init with token constraints:
#[account(init, payer = user, token::mint = mint, token::authority = user)]
pub token_account: Account<'info, TokenAccount>,
Alternative Mitigations
- Avoid
init_if_needed: preferinitwhich requires the account to not exist, eliminating the race window. - Use Associated Token Accounts (ATA): ATAs have deterministic addresses derived from the owner and mint, making substitution harder.
- Signer-gated initialization: require a trusted signer for initialization so attackers cannot front-run without the key.
Common Mistakes
- Validating only the owner: an attacker can create a token account owned by
spl_tokenbut with a different mint. Always check both owner and mint. - Using
init_if_neededwithout constraints: Anchor’sinit_if_neededdoes not validate the account’s contents if it already exists. Addconstraint = ...for ownership checks. - Delayed validation: checking ownership several instructions after init leaves a race window. Validate immediately.