Skip to main content
Sigvex

Token-2022 Immutable Owner Remediation

How to protect protocol token accounts from owner reassignment by initializing the Token-2022 Immutable Owner extension and verifying it on deposit.

Token-2022 Immutable Owner Remediation

Overview

Related Detector: Token-2022 Immutable Owner

Legacy SPL Token allows a token account’s owner to be reassigned with SetAuthority. For custody programs, vaults, and escrows, that is a liability: an owner change on a vault token account redirects every future withdrawal. Token-2022’s ImmutableOwner extension removes the hazard by making the runtime reject any owner change — but only if the extension is initialized, and it can only be initialized before the token account itself. The remediation is twofold: initialize the extension when creating protocol-controlled accounts, and verify its presence on token accounts your program accepts from users.

Before (Vulnerable)

// Plain account creation — owner can later be reassigned
invoke(
    &spl_token_2022::instruction::initialize_account3(
        token_program.key, vault.key, mint.key, authority.key,
    )?,
    &[vault.clone(), mint.clone()],
)?;

After (Fixed)

// 1. Allocate with space for the extension
let space = ExtensionType::try_calculate_account_len::<Account>(
    &[ExtensionType::ImmutableOwner],
)?;
create_account(payer, vault, rent.minimum_balance(space), space as u64, token_program.key)?;

// 2. Initialize the extension FIRST — this must precede account init
invoke(
    &spl_token_2022::instruction::initialize_immutable_owner(token_program.key, vault.key)?,
    &[vault.clone()],
)?;

// 3. Then initialize the token account
invoke(
    &spl_token_2022::instruction::initialize_account3(
        token_program.key, vault.key, mint.key, authority.key,
    )?,
    &[vault.clone(), mint.clone()],
)?;

Ordering is enforced by the token program: extension initialization on an already-initialized account fails, so a wrong order cannot be patched after the fact — the account must be recreated.

Alternative Mitigations

  • Use Associated Token Accounts. ATAs created by the associated token program under Token-2022 include ImmutableOwner automatically, and their address is derived from the owner — either property alone prevents silent owner swaps.
  • Verify on acceptance. When users supply existing token accounts, check the extension before treating the account as long-lived collateral:
let state = StateWithExtensions::<Account>::unpack(&account_data)?;
require!(
    state.get_extension::<ImmutableOwner>().is_ok(),
    ErrorCode::OwnerNotImmutable
);

Common Mistakes

Mistake: Initializing the Extension After the Account

initialize_account3(...)?;              // account initialized
initialize_immutable_owner(...)?;       // WRONG: fails — too late

Mistake: Allocating Legacy-Sized Accounts

Allocating exactly 165 bytes (the legacy account size) leaves no room for extension data, so extension initialization fails. Always compute the size with try_calculate_account_len and the full extension list.

Mistake: Assuming Token-2022 Implies Immutability

The extension is opt-in. A token account owned by the Token-2022 program without ImmutableOwner is exactly as reassignable as a legacy account.

References