Skip to main content
Sigvex

SPL Token Account Validation Remediation

How to fix missing validation in SPL token operations.

Remediating SPL Token Account Validation Issues

Overview

Related Detector: SPL Token Account Validation

SPL token operations require full validation of every account passed in: ownership by the SPL Token program, correct mint, authorized signer, and proper ATA derivation. The fix is to validate each account according to its role before invoking any token CPI.

Before (Vulnerable)

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let user_token = &accounts[0];
    let pool_token = &accounts[1];
    let authority = &accounts[2];

    // No validation before CPI
    solana_program::program::invoke(
        &spl_token::instruction::transfer(
            &spl_token::id(), user_token.key, pool_token.key,
            authority.key, &[], amount,
        )?,
        &[user_token.clone(), pool_token.clone(), authority.clone()],
    )?;
    Ok(())
}

After (Fixed)

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let user_token = &accounts[0];
    let pool_token = &accounts[1];
    let authority = &accounts[2];

    // Validate ownership
    if user_token.owner != &spl_token::id() || pool_token.owner != &spl_token::id() {
        return Err(ProgramError::IllegalOwner);
    }

    // Validate signer
    if !authority.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Validate mint match
    let user_data = spl_token::state::Account::unpack(&user_token.data.borrow())?;
    let pool_data = spl_token::state::Account::unpack(&pool_token.data.borrow())?;
    if user_data.mint != pool_data.mint {
        return Err(ProgramError::InvalidArgument);
    }

    solana_program::program::invoke(
        &spl_token::instruction::transfer(
            &spl_token::id(), user_token.key, pool_token.key,
            authority.key, &[], amount,
        )?,
        &[user_token.clone(), pool_token.clone(), authority.clone()],
    )?;
    Ok(())
}

Alternative Mitigations

  1. Anchor typed accounts with token constraints: the account-validation framework’s token::mint and token::authority constraints validate ownership, mint, and authority in one declaration.
#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut, token::mint = pool_mint, token::authority = authority)]
    pub user_token: Account<'info, TokenAccount>,
    #[account(mut, token::mint = pool_mint)]
    pub pool_token: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
    pub pool_mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
}
  1. ATA validation: For user-facing accounts, validate they are the canonical Associated Token Account for the user and mint.
let expected_ata = spl_associated_token_account::get_associated_token_address(
    authority.key, &expected_mint,
);
if user_token.key != &expected_ata {
    return Err(ProgramError::InvalidArgument);
}
  1. Comprehensive validation function: Create a reusable validation function that checks ownership, mint, and authority for each token account.

Common Mistakes

  • Validating only one side of a transfer: Both source and destination token accounts must be validated. A fake destination can redirect funds.
  • Checking mint on source but not destination: Ensures you send the right token but does not prevent receiving into a wrong-mint account.
  • Forgetting to validate the token program itself: The token program account must be validated (key == spl_token::id()) to prevent a fake token program from approving invalid operations.
  • Not checking authority matches token account authority: Even with a signer check, the signer must be the designated authority for the specific token account being operated on.
  • Ignoring Token-2022 extensions: Transfer hooks, non-transferable tokens, and other extensions may require additional validation beyond basic ownership and mint checks.

References