SPL Token Authority Confusion
Detects confusion between token account authorities (owner, delegate, close authority, mint authority, freeze authority).
SPL Token Authority Confusion
Overview
Remediation Guide: How to Fix SPL Token Authority Confusion
The SPL Token authority confusion detector identifies programs that misuse token account authorities — confusing owner with delegate, close authority with owner, or mint authority with freeze authority. SPL Token accounts have multiple distinct authorities, and using the wrong one for a given operation can lead to unauthorized transfers, account closures, or token minting.
Why This Is an Issue
SPL Token accounts maintain several authority fields: owner (controls transfers), delegate (can transfer up to delegated_amount), close_authority (can close the account), and at the mint level, mint_authority and freeze_authority. Confusing these leads to:
- Unauthorized transfers: Checking owner but using delegate’s allowance, or vice versa
- Unauthorized account closure: Using the wrong authority for
CloseAccountoperations - Unauthorized minting: Confusing
mint_authoritywithfreeze_authority, allowing the freeze authority holder to mint tokens - Unchecked variants: Using
Transferinstead ofTransferCheckedskips decimal validation
CWE mapping: CWE-863 (Incorrect Authorization), CWE-682 (Incorrect Calculation).
How to Resolve
Native Rust
pub fn transfer_tokens(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
let authority = &accounts[2];
let mint = &accounts[3];
// Verify authority is a signer
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Use TransferChecked to validate decimals
let ix = spl_token::instruction::transfer_checked(
&spl_token::id(), source.key, mint.key, destination.key,
authority.key, &[], amount, 6, // explicit decimals
)?;
invoke(&ix, &[source.clone(), mint.clone(), destination.clone(), authority.clone()])?;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut, token::authority = authority)]
pub source: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
}
Examples
Vulnerable Code
pub fn unsafe_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let source = &accounts[0];
let dest = &accounts[1];
let authority = &accounts[2]; // Not validated as signer!
// VULNERABLE: Using Transfer instead of TransferChecked -- no decimal validation
// VULNERABLE: authority not verified as signer
let ix = spl_token::instruction::transfer(
&spl_token::id(), source.key, dest.key, authority.key, &[], amount,
)?;
invoke(&ix, &[source.clone(), dest.clone(), authority.clone()])?;
Ok(())
}
Fixed Code
pub fn safe_transfer(accounts: &[AccountInfo], amount: u64, decimals: u8) -> ProgramResult {
let authority = &accounts[2];
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Use TransferChecked for decimal validation
let ix = spl_token::instruction::transfer_checked(
&spl_token::id(), accounts[0].key, accounts[3].key,
accounts[1].key, authority.key, &[], amount, decimals,
)?;
invoke(&ix, accounts)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "spl-token-authority-confusion",
"severity": "high",
"confidence": 0.78,
"description": "Token transfer uses authority without validating it as a signer. The authority could be the token owner or a delegate -- ensure the correct one is checked.",
"location": { "function": "unsafe_transfer", "offset": 1 }
}
Detection Methodology
- Validation tracking: Collects signer and owner checks across all basic blocks.
- Operation classification: Identifies Transfer, TransferChecked, CloseAccount, Approve, MintTo, FreezeAccount, and ThawAccount operations from CPI instruction discriminators.
- Authority analysis: For each operation, extracts the authority account and verifies it has been validated appropriately for that specific operation type.
- Checked variant detection: Flags use of
Transferinstead ofTransferChecked, which skips decimal validation.
Limitations
- Simplified account index extraction treats the entire accounts expression as a single variable; multi-account arrays require richer HIR support.
- Authority validation via helper functions called before the CPI may not be tracked.
- Anchor programs with
token::authorityconstraints provide implicit validation that may not be detected.
Related Detectors
- SPL Token Delegation Security — detects unsafe delegation patterns
- SPL Token Mint Authority — detects mint authority vulnerabilities
- SPL Token Freeze Burn — detects freeze/burn authority issues