Signer Authority Role
Detects privileged operations with signer checks but no authority role validation, allowing any signer to execute admin actions.
Signer Authority Role
Overview
Remediation Guide: How to Fix Signer Authority Role Issues
The signer authority role detector identifies Solana programs where privileged operations verify that an account is a signer but do not validate that the signer holds a specific authority role (admin, owner, operator). This means any wallet that signs the transaction can perform operations intended only for authorized parties — including fund transfers, configuration changes, and program upgrades via CPI.
Why This Is an Issue
Checking is_signer confirms the account signed the transaction but says nothing about who the signer is. Without authority validation:
- Any signer can transfer funds (CWE-284): Lamport transfers protected only by
is_signerallow any wallet to drain the treasury. - Any signer can modify state: Configuration data, fee parameters, and account state can be tampered with by unauthorized users.
- Privilege escalation via CPI (CWE-269): Privileged CPI calls (program upgrades, admin operations on other programs) can be triggered by any signer, potentially compromising the program or downstream programs.
This is distinct from a missing signer check — the signer check exists but is insufficient. The program has the appearance of security without the substance.
How to Resolve
Native Solana
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
pub fn admin_transfer(
accounts: &[AccountInfo],
amount: u64,
) -> Result<(), ProgramError> {
let authority = &accounts[0];
let vault = &accounts[1];
let config = &accounts[2];
// Signer check (necessary but not sufficient)
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Authority role validation (the critical check)
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);
}
// Now safe -- caller is both signer AND admin
**vault.try_borrow_mut_lamports()? -= amount;
**authority.try_borrow_mut_lamports()? += amount;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct AdminTransfer<'info> {
// Signer<'info> validates is_signer
// has_one validates the key matches config.admin
pub admin: Signer<'info>,
#[account(has_one = admin @ ErrorCode::Unauthorized)]
pub config: Account<'info, ProgramConfig>,
#[account(mut)]
pub vault: Account<'info, Vault>,
}
#[account]
pub struct ProgramConfig {
pub admin: Pubkey,
}
Examples
Vulnerable Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let signer = &accounts[0];
let vault = &accounts[1];
// Checks signer -- but ANY signer can call
if !signer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// VULNERABLE: no authority check
**vault.try_borrow_mut_lamports()? -= amount;
**signer.try_borrow_mut_lamports()? += amount;
Ok(())
}
Fixed Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let authority = &accounts[0];
let vault = &accounts[1];
let config = &accounts[2];
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Validate authority role
let config_data = config.try_borrow_data()?;
let expected = Pubkey::try_from(&config_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
if *authority.key != expected {
return Err(ProgramError::InvalidArgument);
}
drop(config_data);
**vault.try_borrow_mut_lamports()? -= amount;
**authority.try_borrow_mut_lamports()? += amount;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "signer-authority-role",
"severity": "high",
"confidence": 0.75,
"title": "Lamport Transfer With Signer But No Authority Validation",
"description": "Lamport transfer requires signer but doesn't validate that the signer has authority (e.g., admin, owner). Any signer can transfer lamports, which could lead to unauthorized fund movements.",
"location": { "function": "withdraw", "block": 0, "statement": 1 },
"cwe": 284
}
Detection Methodology
The detector performs two-pass analysis over the function’s intermediate representation:
- Signer tracking: Records all accounts that have
CheckSignerstatements, building a set of confirmed signers. - Authority tracking: Records accounts validated by
CheckKeywith a constant (non-dynamic) expected value, indicating the signer’s key is compared against a known authority address. Dynamic expected values (loaded from variables) do not count as proper authority validation. - Privileged operation matching: For each
TransferLamports,StoreAccountData, orInvokeCpiinvolving a signer-checked account, verifies that the same account also has authority validation. - Severity grading: Lamport transfers and data modifications with signer-only produce high severity. Privileged CPI calls with signer-only produce critical severity (due to potential program upgrade or escalation).
- Context adjustment: Confidence is reduced for Anchor programs (where
Signer<'info>handles signer checks buthas_one/constraintmust handle authority), admin functions, and read-only functions.
Limitations
False positives:
- User-initiated operations where any signer is legitimately authorized (e.g., a user withdrawing from their own account using a PDA they own).
- Anchor programs where
has_oneconstraints validate authority before the handler.
False negatives:
- Authority validation performed via CPI to an external access control program.
- Complex authority derivation through PDA seeds or multi-step lookups.
Related Detectors
- Instruction Sender Validation — missing both signer and authority checks
- Admin Key Management — hardcoded or missing admin keys
- Role-Based Access Control — missing role checks for privileged operations
- Missing Signer Check — entirely missing signer validation