SPL Token Delegation Security
Detects unsafe token delegation patterns including unlimited approvals and missing revocation.
SPL Token Delegation Security
Overview
Remediation Guide: How to Fix SPL Token Delegation Security
The SPL Token delegation security detector identifies unsafe token delegation patterns where programs approve delegates with unlimited amounts, fail to validate delegate accounts, or omit revocation after delegated operations. In SPL Token, the Approve instruction grants a delegate the right to transfer tokens on behalf of the account owner. Misuse of this mechanism can allow attackers to drain entire token account balances.
Why This Is an Issue
Token delegation via Approve gives a third-party account the right to transfer up to a specified amount of tokens. Unsafe delegation patterns create severe risks:
- Unlimited approvals (
u64::MAX) allow the delegate to drain the entire account balance at any time - Unvalidated delegates let attackers pass their own address as the delegate, granting themselves transfer rights
- Missing revocation leaves delegation active indefinitely, meaning a compromised delegate can drain funds long after the intended operation
These patterns are analogous to ERC-20 unlimited approval vulnerabilities but with Solana-specific characteristics around account lifetime and CPI patterns.
CWE mapping: CWE-269 (Improper Privilege Management), CWE-863 (Incorrect Authorization).
How to Resolve
Native Rust
pub fn safe_delegation(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let token_account = &accounts[0];
let delegate = &accounts[1];
let authority = &accounts[2];
// Validate the delegate is the expected account
if delegate.key != &EXPECTED_DELEGATE {
return Err(ProgramError::InvalidArgument);
}
// Use exact amount, never u64::MAX
let approve_ix = spl_token::instruction::approve(
&spl_token::id(), token_account.key, delegate.key,
authority.key, &[], amount, // Exact amount needed
)?;
invoke(&approve_ix, &[token_account.clone(), delegate.clone(), authority.clone()])?;
// Perform the delegated operation...
// IMPORTANT: revoke delegation 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()])?;
Ok(())
}
Anchor
use anchor_spl::token::{self, Approve, Revoke};
pub fn safe_delegation(ctx: Context<SafeDelegation>, amount: u64) -> Result<()> {
// Approve exact amount
token::approve(ctx.accounts.into_approve_context(), amount)?;
// Perform operation...
// Revoke immediately
token::revoke(ctx.accounts.into_revoke_context())?;
Ok(())
}
Examples
Vulnerable Code
pub fn approve_delegate(accounts: &[AccountInfo]) -> ProgramResult {
let token_account = &accounts[0];
let delegate = &accounts[1]; // Not validated!
let authority = &accounts[2];
// 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, &[token_account.clone(), delegate.clone(), authority.clone()])?;
Ok(())
}
Fixed Code
pub fn approve_delegate(accounts: &[AccountInfo], exact_amount: u64) -> ProgramResult {
let delegate = &accounts[1];
// Validate delegate
if delegate.key != &EXPECTED_DELEGATE { return Err(ProgramError::InvalidArgument); }
// Approve exact amount
let ix = spl_token::instruction::approve(
&spl_token::id(), accounts[0].key, delegate.key,
accounts[2].key, &[], exact_amount,
)?;
invoke(&ix, &[accounts[0].clone(), delegate.clone(), accounts[2].clone()])?;
// ... perform operation, then revoke
let revoke_ix = spl_token::instruction::revoke(
&spl_token::id(), accounts[0].key, accounts[2].key, &[],
)?;
invoke(&revoke_ix, &[accounts[0].clone(), accounts[2].clone()])?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "spl-token-delegation-security",
"severity": "high",
"confidence": 0.80,
"description": "Token delegation to unvalidated account without subsequent revocation. Delegate retains transfer rights indefinitely.",
"location": { "function": "approve_delegate", "offset": 1 }
}
Detection Methodology
- Instruction classification: Identifies Approve (discriminator 4), ApproveChecked (13), and Revoke (5) CPI calls.
- Validation tracking: Tracks
CheckOwner,CheckSigner, andCheckKeystatements to determine which accounts are validated. - Amount analysis: Extracts approval amounts and flags unlimited values (u64::MAX or values exceeding 99% of u64::MAX).
- Revocation analysis: Checks whether approval operations have corresponding Revoke operations within the same function.
Limitations
- Simplified instruction data parsing may not extract approval amounts from complex instruction layouts.
- Revocation in a separate transaction or function is not tracked.
- PDA delegates (program-controlled) may be flagged even though they are inherently safe.
Related Detectors
- SPL Token Delegation Overflow — detects integer overflow in delegation amount calculations
- SPL Token Delegation Chain Depth — detects excessive delegation chains
- SPL Token Authority Confusion — detects confusion between authority types