Skip to main content
Sigvex

Token-2022 Permanent Delegate

Detects unsafe handling of the Token-2022 permanent delegate extension, which grants an authority unconditional power to move or burn tokens.

Token-2022 Permanent Delegate

Overview

The permanent delegate extension in Token-2022 (Token Extensions) names an authority that can transfer or burn tokens from any account holding that mint, without the account owner’s consent. It is the most powerful authority a mint can carry: a permanent delegate effectively has standing access to every holder’s balance. Legitimate uses exist (regulated assets, recovery mechanisms), but a mint that carries an unexpected or untrusted permanent delegate is a standing drain risk for anyone who touches it.

This detector flags programs that interact with Token-2022 mints without accounting for the permanent delegate. It reports mint initializations that do not validate the delegate configuration, transfers that proceed without checking whether the mint carries a permanent delegate, and authority changes that could hand permanent-delegate control to an unverified address.

Why This Is an Issue

A permanent delegate has god-mode access scoped to a mint. If a program initializes or accepts a mint without checking the delegate, it may onboard an asset whose issuer can unilaterally seize user balances at any later time. Users who deposit such a token into a vault or pool are exposed to drainage that no balance check or signer check will catch, because the delegate’s authority is granted by the mint itself.

The authority-change path is equally dangerous. Reassigning authority on a mint that has a permanent delegate can transfer that standing control to an attacker, and such changes are frequently irreversible. Because the power is conferred at the mint level and applies to every account, the blast radius is the entire token’s holder base rather than a single account.

How to Resolve

Inspect the mint for a permanent delegate before trusting it, and either require that none is set or that the delegate is an address you explicitly trust.

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

// Before: 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: reject mints that carry an unexpected permanent delegate.
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();
        // Accept only a trusted delegate, or require that none is set.
        match delegate {
            COption::None => {}
            COption::Some(addr) => require_keys_eq!(
                addr,
                TRUSTED_DELEGATE,
                VaultError::UntrustedPermanentDelegate
            ),
        }
    }
    drop(data);

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

For authority changes on 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 a SetAuthority proceed against an unvalidated target.

Sample Sigvex Output

{
  "detector_id": "token-2022-permanent-delegate",
  "severity": "critical",
  "confidence": 0.80,
  "description": "Initializing a Token-2022 mint without validating the permanent delegate configuration.",
  "location": {
    "function": "init_mint",
    "offset": 0
  }
}

Detection Methodology

The detector analyzes the reconstructed intermediate representation of each function recovered from the program binary in two passes.

  1. Validation pass. It records evidence that the program validates the relevant accounts: owner checks on mint accounts, reads of permanent-delegate extension data at the offset range where that extension lives, calls that resolve a delegate extension, and branch conditions that compare delegate-related values. Accounts and variables touched by these checks are marked as validated.

  2. Vulnerability pass. It then inspects cross-program invocations and reports three patterns when the relevant account was not validated in the first pass:

    • Mint initialization (InitializeMint or InitializeMint2) without delegate validation — reported as critical, since this onboards a mint whose delegate is unknown.
    • Transfers (TransferChecked) against a mint that was never validated for a permanent delegate.
    • Authority changes (SetAuthority) without validation of the new authority.

Confidence is reduced for administrative or initialization functions, which are typically gated by caller-level access control, and is reduced further for read-only contexts, where exploiting permanent-delegate power requires a state-mutating transfer or burn.

Limitations

The offset-based detection of the permanent delegate extension is a heuristic over the mint layout and can miss or misattribute reads when the layout differs from the expected range. Validation that is performed in a helper the analysis cannot follow, or applied to an account passed indirectly, may not be linked to the operation it protects, producing false positives. Conversely, instruction discriminators that are computed dynamically cannot be resolved, producing false negatives. Confidence reductions for administrative and read-only functions can suppress otherwise valid findings.

References