Skip to main content
Sigvex

SPL Token Close Authority Bypass Remediation

How to fix missing close_authority validation when closing token accounts.

SPL Token Close Authority Bypass Remediation

Overview

Related Detector: SPL Token Close Authority Bypass

Missing close_authority validation allows unauthorized account closure and rent theft. The fix requires reading the token account’s close_authority field and verifying the signer matches before executing the close operation.

Before (Vulnerable)

// No close_authority check -- any signer could close
invoke(&spl_token::instruction::close_account(
    &spl_token::id(), token_account.key, destination.key, authority.key, &[],
)?, accounts)?;

After (Fixed)

let token_data = Account::unpack(&token_account.data.borrow())?;

// Check close_authority field
match token_data.close_authority {
    COption::Some(close_auth) => {
        require!(*authority.key == close_auth, InvalidCloseAuthority);
    }
    COption::None => {
        // No close_authority set -- owner can close
        require!(*authority.key == token_data.owner, InvalidOwner);
    }
}

require!(authority.is_signer, MissingSignature);

invoke(&spl_token::instruction::close_account(
    &spl_token::id(), token_account.key, destination.key, authority.key, &[],
)?, accounts)?;

Alternative Mitigations

1. Anchor has_one constraint

#[derive(Accounts)]
pub struct CloseToken<'info> {
    #[account(
        mut,
        close = destination,
        has_one = close_authority @ ErrorCode::InvalidCloseAuthority
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub close_authority: Signer<'info>,
    /// CHECK: rent destination
    pub destination: AccountInfo<'info>,
}

2. Set close_authority to PDA

Set the close_authority to a program-controlled PDA so only your program can close accounts:

spl_token::instruction::set_authority(
    &spl_token::id(), token_account.key,
    Some(&pda_authority), AuthorityType::CloseAccount,
    current_authority.key, &[],
)?;

Common Mistakes

Mistake 1: Checking owner instead of close_authority

// WRONG: if close_authority is set, owner may NOT be able to close
if *authority.key != token_data.owner { return Err(...); }
// CORRECT: check close_authority first, fall back to owner

Mistake 2: Not handling COption::None

// WRONG: unwrapping close_authority without checking if set
let close_auth = token_data.close_authority.unwrap(); // Panics if None

References