Skip to main content
Sigvex

Token Account Spoofing Remediation

How to fix token account spoofing and aliasing vulnerabilities.

Token Account Spoofing Remediation

Overview

Related Detector: Token Account Spoofing

Token account spoofing allows attackers to substitute fake token accounts with wrong mints or attacker-controlled owners. The fix requires validating mint, owner, and key for every token account, plus anti-aliasing checks when multiple accounts are involved.

Before (Vulnerable)

pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let token_a = &accounts[0]; // No validation!
    let token_b = &accounts[1]; // No validation!
    transfer_tokens(token_a, token_b, amount)?;
    Ok(())
}

After (Fixed)

pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let token_a = &accounts[0];
    let token_b = &accounts[1];

    // Validate mint
    let data_a = Account::unpack(&token_a.data.borrow())?;
    let data_b = Account::unpack(&token_b.data.borrow())?;
    if data_a.mint != EXPECTED_MINT_A || data_b.mint != EXPECTED_MINT_B {
        return Err(ProgramError::InvalidAccountData);
    }

    // Validate owner
    if data_a.owner != *accounts[2].key {
        return Err(ProgramError::IllegalOwner);
    }

    // Prevent aliasing
    if token_a.key == token_b.key {
        return Err(ProgramError::InvalidArgument);
    }

    transfer_tokens(token_a, token_b, amount)?;
    Ok(())
}

Alternative Mitigations

1. Anchor token constraints

#[derive(Accounts)]
pub struct Swap<'info> {
    #[account(mut, token::mint = mint_a, token::authority = user)]
    pub token_a: Account<'info, TokenAccount>,
    #[account(mut, token::mint = mint_b,
        constraint = token_a.key() != token_b.key() @ ErrorCode::Aliasing)]
    pub token_b: Account<'info, TokenAccount>,
}

2. Associated Token Account derivation

Use ATAs to ensure the correct token account is used for a given owner and mint:

let expected_ata = get_associated_token_address(user.key, &mint);
require!(token_account.key() == expected_ata, ErrorCode::InvalidTokenAccount);

Common Mistakes

Mistake 1: Checking owner but not mint

// INCOMPLETE: correct owner but wrong mint allows worthless token spoofing
if data.owner != *user.key { return Err(...); }
// Missing: if data.mint != EXPECTED_MINT { return Err(...); }

Mistake 2: No anti-aliasing check

// WRONG: if source == destination, accounting breaks
// Must check: require!(source.key() != destination.key())

Mistake 3: Validating only one of multiple token accounts

// INCOMPLETE: token_a validated but token_b is not

References