Instruction Sender Validation
Detects missing validation that the transaction sender has authority for privileged operations.
Instruction Sender Validation
Overview
Remediation Guide: How to Fix Instruction Sender Validation Issues
The instruction sender validation detector identifies Solana programs where privileged operations execute without verifying that the transaction signer has proper authority. This is distinct from a simple is_signer check — the detector specifically flags cases where the signer’s public key is not compared against an expected authority address. A program may verify that an account signed the transaction but still allow any signer to perform admin operations if it does not validate which signer is authorized.
Why This Is an Issue
On Solana, is_signer only confirms that the account signed the transaction. It does not confirm identity. Without checking the signer’s key against an expected authority:
- Any wallet can execute privileged operations simply by signing the transaction
- An attacker can drain funds by signing a transfer instruction with their own keypair
- State modifications (configuration changes, parameter updates) are open to anyone
- The program offers no meaningful access control despite having a signer check
This is one of the most common Solana vulnerabilities (CWE-862: Missing Authorization) and has led to significant fund losses in production programs.
How to Resolve
Native Solana
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
pub fn process_transfer(
accounts: &[AccountInfo],
amount: u64,
) -> Result<(), ProgramError> {
let authority = &accounts[0];
let treasury = &accounts[1];
let config = &accounts[2];
// Step 1: Verify signer status
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Step 2: Verify signer is the expected authority
let config_data = config.try_borrow_data()?;
let expected_authority = Pubkey::try_from(&config_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
if *authority.key != expected_authority {
return Err(ProgramError::InvalidArgument);
}
// Safe: both signer and authority validated
**treasury.try_borrow_mut_lamports()? -= amount;
**authority.try_borrow_mut_lamports()? += amount;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct SecureTransfer<'info> {
// Signer<'info> validates is_signer automatically
// has_one validates the key matches config.authority
pub authority: Signer<'info>,
#[account(mut, has_one = authority @ ErrorCode::Unauthorized)]
pub config: Account<'info, Config>,
#[account(mut)]
pub treasury: SystemAccount<'info>,
}
Examples
Vulnerable Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let user = &accounts[0];
let treasury = &accounts[1];
// Checks signer but NOT authority
if !user.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// VULNERABLE: any signer can withdraw
**treasury.try_borrow_mut_lamports()? -= amount;
**user.try_borrow_mut_lamports()? += amount;
Ok(())
}
Fixed Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let authority = &accounts[0];
let treasury = &accounts[1];
let config = &accounts[2];
// Signer check
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Authority check
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);
}
**treasury.try_borrow_mut_lamports()? -= amount;
**authority.try_borrow_mut_lamports()? += amount;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "instruction-sender-validation",
"severity": "critical",
"confidence": 0.80,
"title": "Missing Instruction Sender Authority Validation",
"description": "Privileged operation (lamport transfer) on account v1 is performed without both signer verification and authority validation. An attacker could pass any account and execute unauthorized operations like draining funds or modifying state.",
"location": { "function": "withdraw", "block": 0, "statement": 0 },
"cwe": 862
}
Detection Methodology
The detector performs two-pass analysis over the function’s intermediate representation:
- First pass — validation tracking: Collects all
CheckSignerstatements (signer verification),CheckKeystatements (authority validation), and bytecode-derived property reads (is_signer,account.key,account.owner) that serve as implicit validation. - Privileged operation identification: Identifies
TransferLamportsandStoreAccountDataoperations as requiring both signer and authority validation. - Gap analysis: For each privileged operation, checks whether the operating account has both a signer check and an authority check. Missing both checks yields a critical finding. Having only a signer check but no authority check yields a medium finding with reduced confidence (0.55), since signer-only may be valid for user-initiated operations.
- Context adjustment: Confidence is reduced for Anchor programs, admin/initialization functions, and read-only functions.
Limitations
False positives:
- User-initiated operations where any signer is legitimately allowed (e.g., a user depositing into their own account). The detector reduces confidence for signer-only cases but may still flag them.
- Anchor programs where
Signer<'info>andhas_oneconstraints handle validation.
False negatives:
- Authority validation performed through CPI to a governance program.
- Complex key derivation patterns where authority is validated indirectly via PDA seeds.
Related Detectors
- Missing Signer Check — entirely missing
is_signervalidation - Signer Authority Role — signer present but no role-based authority validation
- Admin Key Management — hardcoded or missing admin key validation