Signer Authority Role Remediation
How to fix privileged operations that check signer status but not authority role.
Signer Authority Role Remediation
Overview
Related Detector: Signer Authority Role
Signer authority role issues occur when a program verifies is_signer but does not check the signer’s public key against an expected authority address, allowing any wallet to execute privileged operations. The fix requires adding a key comparison that validates the signer is the specific admin, owner, or operator authorized for the operation.
Recommended Fix
Before (Vulnerable)
pub fn admin_withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let signer = &accounts[0];
let vault = &accounts[1];
// Signer check only -- any signer can call
if !signer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
**vault.try_borrow_mut_lamports()? -= amount;
**signer.try_borrow_mut_lamports()? += amount;
Ok(())
}
After (Fixed)
pub fn admin_withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let authority = &accounts[0];
let vault = &accounts[1];
let config = &accounts[2];
// Step 1: signer check
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Step 2: authority role validation
let config_data = config.try_borrow_data()?;
let admin_key = Pubkey::try_from(&config_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
if *authority.key != admin_key {
return Err(ProgramError::InvalidArgument);
}
drop(config_data);
**vault.try_borrow_mut_lamports()? -= amount;
**authority.try_borrow_mut_lamports()? += amount;
Ok(())
}
Alternative Mitigations
1. Anchor Signer + has_one
Anchor provides declarative authority validation:
#[derive(Accounts)]
pub struct AdminWithdraw<'info> {
pub admin: Signer<'info>,
#[account(has_one = admin @ ErrorCode::Unauthorized)]
pub config: Account<'info, ProgramConfig>,
#[account(mut)]
pub vault: Account<'info, Vault>,
}
2. PDA-derived authority
When the authority is a PDA, the seeds themselves enforce the role:
let (expected_authority, bump) = Pubkey::find_program_address(
&[b"admin", program_state.key.as_ref()],
program_id,
);
if *authority.key != expected_authority {
return Err(ProgramError::InvalidArgument);
}
3. Role account validation
For multi-role programs, validate against a role assignment account:
pub fn operator_action(accounts: &[AccountInfo]) -> ProgramResult {
let caller = &accounts[0];
let role_account = &accounts[1];
if !caller.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let role_data = role_account.try_borrow_data()?;
let role_holder = Pubkey::try_from(&role_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
let role_type = role_data[32];
if *caller.key != role_holder || role_type > 1 /* Admin or Operator */ {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
Common Mistakes
Mistake 1: Assuming is_signer implies authorization
is_signer only proves the account’s private key signed the transaction. It does not prove identity or authorization. Always compare the signer’s key against a stored authority.
Mistake 2: Validating against a dynamic (user-supplied) expected key
// WRONG: attacker supplies their own key as expected_authority
let expected = Pubkey::try_from(&instruction_data[0..32])?;
if *signer.key != expected { return Err(...); }
The expected authority must come from a trusted on-chain source (config account, PDA).
Mistake 3: Checking authority on the wrong account
Ensure you verify is_signer and compare the key on the same account. Checking signer on account A and comparing key on account B leaves a gap where account A is not validated.