Skip to main content
Sigvex

SPL Token Delegation Security Remediation

How to fix unsafe token delegation patterns in Solana programs.

SPL Token Delegation Security Remediation

Overview

Related Detector: SPL Token Delegation Security

Unsafe token delegation allows delegates to transfer more tokens than intended, or retains delegation rights indefinitely. The fix involves limiting approval amounts, validating delegate accounts, and revoking delegation immediately after use.

Before (Vulnerable)

// Unlimited approval to unvalidated delegate, no revocation
let ix = spl_token::instruction::approve(
    &spl_token::id(), token_account.key, delegate.key,
    authority.key, &[], u64::MAX,
)?;
invoke(&ix, accounts)?;

After (Fixed)

// Validate delegate
if delegate.key != &expected_delegate {
    return Err(ProgramError::InvalidArgument);
}

// Approve exact amount needed
let ix = spl_token::instruction::approve(
    &spl_token::id(), token_account.key, delegate.key,
    authority.key, &[], exact_amount,
)?;
invoke(&ix, accounts)?;

// Perform the delegated operation
// ...

// Revoke immediately after use
let revoke_ix = spl_token::instruction::revoke(
    &spl_token::id(), token_account.key, authority.key, &[],
)?;
invoke(&revoke_ix, &[token_account.clone(), authority.clone()])?;

Alternative Mitigations

1. Anchor token CPI wrappers

use anchor_spl::token::{self, Approve, Revoke};

// Approve with exact amount
token::approve(
    CpiContext::new(token_program, Approve { to: token_account, delegate, authority }),
    exact_amount,
)?;

// Revoke after operation
token::revoke(
    CpiContext::new(token_program, Revoke { source: token_account, authority }),
)?;

2. PDA-based delegation

Use a PDA as the delegate so that only your program can use the delegation via invoke_signed.

3. Temporary token accounts

Create a temporary token account, transfer exact amounts into it, perform the operation, and close the account. This avoids delegation entirely.

Common Mistakes

Mistake 1: Using u64::MAX as approval amount

// WRONG: gives delegate unlimited access
spl_token::instruction::approve(..., u64::MAX)?;
// CORRECT: use the exact amount needed
spl_token::instruction::approve(..., transfer_amount)?;

Mistake 2: Forgetting to revoke after use

// INCOMPLETE: delegation stays active forever
token::approve(ctx, amount)?;
token::transfer(ctx, amount)?;
// Missing: token::revoke(ctx)?;

Mistake 3: Not validating the delegate address

// WRONG: accepts any account as delegate from instruction input
let delegate = next_account_info(accounts_iter)?;
// Should validate: require!(delegate.key == &KNOWN_DELEGATE)

References