Skip to main content
Sigvex

Token-2022 Permanent Delegate Remediation

How to validate the Token-2022 permanent delegate before trusting a mint, so an issuer cannot silently seize user balances.

Token-2022 Permanent Delegate Remediation

Overview

Related Detector: Token-2022 Permanent Delegate

The permanent delegate extension names an authority that can transfer or burn tokens from any account holding a mint, without the account owner’s consent. It is the most powerful authority a mint can carry, and a mint that holds an unexpected or untrusted permanent delegate is a standing drain risk for everyone who touches it. The fix is to inspect a mint for a permanent delegate before trusting it and require either that none is set or that the delegate is an address you explicitly trust.

Before (Vulnerable)

// A mint is used without checking for a permanent delegate. The mint's
// issuer may be able to move or burn user balances later.
fn deposit(ctx: Context<Deposit>) -> Result<()> {
    transfer_checked_via_cpi(&ctx)?;
    Ok(())
}

After (Fixed)

use spl_token_2022::extension::{
    permanent_delegate::PermanentDelegate, BaseStateWithExtensions, StateWithExtensions,
};
use spl_token_2022::state::Mint;
use solana_program::program_option::COption;

fn deposit(ctx: Context<Deposit>) -> Result<()> {
    let data = ctx.accounts.mint.try_borrow_data()?;
    let state = StateWithExtensions::<Mint>::unpack(&data)?;

    if let Ok(ext) = state.get_extension::<PermanentDelegate>() {
        let delegate: COption<Pubkey> = ext.delegate.into();
        match delegate {
            COption::None => {}
            COption::Some(addr) => require_keys_eq!(
                addr,
                TRUSTED_DELEGATE,
                VaultError::UntrustedPermanentDelegate
            ),
        }
    }
    drop(data);

    transfer_checked_via_cpi(&ctx)?;
    Ok(())
}

This works because the delegate’s power is conferred by the mint itself, so no balance or signer check at deposit time can defend against it. Reading the extension and gating on the delegate address is the only point where the program can decide whether the asset is safe to onboard.

Alternative Mitigations

  • Validate at registration, not per transfer. Check the delegate once when a mint is first admitted to the program and store the result, so every later operation relies on a single audited decision.
  • Guard authority changes. For a SetAuthority against a mint that may carry a permanent delegate, validate the new authority against a known address, prefer a two-step transfer pattern, and record the change for audit. Never let the change proceed against an unvalidated target.

Common Mistakes

  • Treating a missing extension as proof of safety. Confirm the extension is genuinely absent rather than assuming it; a present-but-unread delegate is the dangerous case.
  • Trusting the delegate because the mint looks standard. A permanent delegate is invisible at the token-account level; only the mint’s extension data reveals it.
  • Allowing irreversible authority handoff. Reassigning authority on a delegated mint can transfer standing control to an attacker and is frequently irreversible; validate the target before any change.

References