Skip to main content
Sigvex

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.

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.

References