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.
Recommended Fix
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.