Skip to main content
Sigvex

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.

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

  1. Avoid init_if_needed: prefer init which requires the account to not exist, eliminating the race window.
  2. Use Associated Token Accounts (ATA): ATAs have deterministic addresses derived from the owner and mint, making substitution harder.
  3. 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_token but with a different mint. Always check both owner and mint.
  • Using init_if_needed without constraints: the account-validation framework’s init_if_needed does not validate the account’s contents if it already exists. Add constraint = ... for ownership checks.
  • Delayed validation: checking ownership several instructions after init leaves a race window. Validate immediately.

References