The missing signer check, Solana's most expensive one-line bug
How an Anchor instruction ends up with no authority verification at all: AccountInfo instead of Signer, a missing has_one constraint, and what the fixed account struct looks like.
Updated 2026-09-03
Anchor does an enormous amount of validation for you, which is exactly why this bug is so easy to ship: the framework only checks what you declare in the account struct, and an omission there is silent. There is no warning, no runtime error, and the happy path works perfectly.
The vulnerable program
A config program storing an authority and a fee rate. Only the authority should be able to change the fee.
pub fn update_fee_rate(ctx: Context<UpdateFeeRate>, new_fee_bps: u16) -> Result<()> {
require!(new_fee_bps <= 10_000, ConfigError::FeeTooHigh);
let config = &mut ctx.accounts.config;
config.fee_bps = new_fee_bps;
Ok(())
}
#[derive(Accounts)]
pub struct UpdateFeeRate<'info> {
#[account(mut)]
pub config: Account<'info, Config>,
/// CHECK: not constrained
pub authority: AccountInfo<'info>,
}Two things are missing, not one
authorityis anAccountInfo, not aSigner. Anchor therefore never checks that this account signed the transaction. Anyone can pass any public key here.- There is no
has_one = authorityconstraint onconfig. Even if the account did have to sign, nothing ties it to theconfig.authoritystored on-chain — so any signer would do.
Either omission alone is fatal. Together they mean update_fee_rate has no access control whatsoever: any wallet on Solana can call it and set the fee to anything up to 100%. The require! on new_fee_bps validates the value and creates a convincing impression that the instruction is guarded. It validates nothing about the caller.
The fix
#[derive(Accounts)]
pub struct UpdateFeeRate<'info> {
#[account(mut, has_one = authority)]
pub config: Account<'info, Config>,
pub authority: Signer<'info>,
}Signer makes Anchor verify the signature. has_one = authority makes it verify that the signing key equals config.authority. Two words, and the instruction is correctly gated — the handler body does not change at all.