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.
Recommended Fix
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)