Skip to main content
Sigvex

Admin Key Management Remediation

How to fix unsafe admin key handling, hardcoded keys, and single points of failure.

Admin Key Management Remediation

Overview

Related Detector: Admin Key Management

Unsafe admin key management occurs when programs hardcode admin keys in bytecode, skip admin validation before privileged operations, or rely on a single key with no multi-sig protection. The fix involves storing admin keys in on-chain accounts, validating the caller before every privileged operation, and using multi-signature schemes for critical actions.

Before (Vulnerable)

const ADMIN: Pubkey = solana_program::pubkey!("HardCodedAdminXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");

pub fn admin_withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let vault = &accounts[0];
    let dest = &accounts[1];
    // No validation -- anyone can call
    **vault.try_borrow_mut_lamports()? -= amount;
    **dest.try_borrow_mut_lamports()? += amount;
    Ok(())
}

After (Fixed)

pub fn admin_withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let admin = &accounts[0];
    let vault = &accounts[1];
    let dest = &accounts[2];
    let config = &accounts[3];

    // Verify signer
    if !admin.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Load admin from on-chain config account
    let config_data = config.try_borrow_data()?;
    let stored_admin = Pubkey::try_from(&config_data[0..32])
        .map_err(|_| ProgramError::InvalidAccountData)?;
    if *admin.key != stored_admin {
        return Err(ProgramError::InvalidArgument);
    }

    **vault.try_borrow_mut_lamports()? -= amount;
    **dest.try_borrow_mut_lamports()? += amount;
    Ok(())
}

Alternative Mitigations

1. PDA-based admin authority

Derive the admin authority from a PDA so it cannot be hardcoded and is tied to the program:

let (admin_pda, _bump) = Pubkey::find_program_address(&[b"admin"], program_id);
if *admin_account.key != admin_pda {
    return Err(ProgramError::InvalidArgument);
}

2. Multi-sig admin using Squads or SPL Governance

Require multiple signers for critical admin operations:

#[derive(Accounts)]
pub struct AdminAction<'info> {
    #[account(
        constraint = multisig.is_signer @ ErrorCode::Unauthorized,
        constraint = multisig.key() == config.multisig_authority @ ErrorCode::Unauthorized
    )]
    pub multisig: Signer<'info>,
    pub config: Account<'info, ProgramConfig>,
}

3. Anchor has_one constraint

#[derive(Accounts)]
pub struct UpdateConfig<'info> {
    pub admin: Signer<'info>,
    #[account(mut, has_one = admin @ ErrorCode::Unauthorized)]
    pub config: Account<'info, ProgramConfig>,
}

Common Mistakes

Mistake 1: Hardcoding the admin key

Hardcoded keys cannot be rotated after deployment. Always store admin keys in mutable on-chain accounts.

Mistake 2: Checking signer but not authority

// WRONG: any signer can call this
if !caller.is_signer {
    return Err(ProgramError::MissingRequiredSignature);
}
// Missing: if *caller.key != expected_admin { return Err(...); }

Verifying is_signer alone only confirms the caller signed the transaction. You must also verify the signer’s public key matches the expected admin.

Mistake 3: No admin key rotation mechanism

Deploy an update_admin instruction that allows the current admin to transfer authority to a new key, with proper validation that the current admin is the caller.

References