Account Verification Chain
Detects incomplete account verification chains where accounts undergo partial validation, allowing attackers to bypass security checks.
Account Verification Chain
Overview
Remediation Guide: How to Fix Incomplete Account Verification Chains
The account verification chain detector identifies Solana programs where accounts are only partially validated — for example, checking the signer status but not the owner, or checking the key but not the owner. A complete verification chain for critical accounts should include signer validation, owner validation, and key validation. Partial verification allows attackers to provide accounts that satisfy some requirements but not others.
Sigvex tracks three categories of validation checks (CheckSigner, CheckOwner, CheckKey) for each account variable, and identifies accounts used in sensitive operations (state modifications, lamport transfers). Accounts with only one validation check out of three possible checks are flagged, with elevated severity when the account is used in sensitive operations. CWE mapping: CWE-863 (Incorrect Authorization).
Why This Is an Issue
Each validation check serves a distinct purpose:
- CheckSigner: Verifies WHO authorized the transaction (the private key holder signed it).
- CheckOwner: Verifies WHAT program owns the account (prevents cross-program account confusion).
- CheckKey: Verifies WHICH specific account is being used (prevents account substitution).
Common vulnerability patterns from incomplete chains:
- Key without owner: An attacker creates a fake account with the correct key but owned by a malicious program, injecting crafted data.
- Signer without owner: A signer can control any account they own, but without an owner check, the account could be from a different program with incompatible state.
- Owner without signer/key: Any account owned by the expected program can be passed, but without authorization, an attacker uses someone else’s account.
How to Resolve
Native Rust
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const EXPECTED_ACCOUNT: Pubkey = /* specific account */;
const EXPECTED_OWNER: Pubkey = /* owning program */;
pub fn sensitive_operation(accounts: &[AccountInfo]) -> ProgramResult {
let authority = &accounts[0];
let target = &accounts[1];
// Complete verification chain for authority
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Complete verification chain for target
if target.key != &EXPECTED_ACCOUNT {
return Err(ProgramError::InvalidArgument);
}
if target.owner != &EXPECTED_OWNER {
return Err(ProgramError::IllegalOwner);
}
// Safe to modify
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct SensitiveOperation<'info> {
// Signer type + constraint = complete chain for authority
#[account(constraint = authority.key() == ADMIN @ ErrorCode::Unauthorized)]
pub authority: Signer<'info>,
// Owner + key validation via Account type + constraint
#[account(
mut,
constraint = target.key() == EXPECTED_ACCOUNT @ ErrorCode::WrongAccount
)]
pub target: Account<'info, MyState>,
}
Examples
Vulnerable Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn update_config(accounts: &[AccountInfo], new_value: u64) -> ProgramResult {
let admin = &accounts[0];
let config = &accounts[1];
// Only checks signer -- no owner or key check!
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Writes to config without verifying ownership
let mut data = config.data.borrow_mut();
data[0..8].copy_from_slice(&new_value.to_le_bytes());
Ok(())
}
Fixed Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const ADMIN_KEY: Pubkey = /* admin address */;
pub fn update_config(accounts: &[AccountInfo], new_value: u64) -> ProgramResult {
let admin = &accounts[0];
let config = &accounts[1];
// Signer check
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Key check
if admin.key != &ADMIN_KEY {
return Err(ProgramError::InvalidArgument);
}
// Owner check on config
if config.owner != &MY_PROGRAM_ID {
return Err(ProgramError::IllegalOwner);
}
let mut data = config.data.borrow_mut();
data[0..8].copy_from_slice(&new_value.to_le_bytes());
Ok(())
}
Sample Sigvex Output
{
"detector_id": "account-verification-chain",
"severity": "high",
"confidence": 0.80,
"description": "Account variable 1 has incomplete verification. Present checks: CheckSigner. Missing checks: CheckOwner, CheckKey. This account is used in sensitive operations (state modification or transfers).",
"location": { "function": "update_config", "offset": 0 }
}
Detection Methodology
The detector performs four targeted checks:
- Incomplete chain: Accounts with only one of three possible validation checks (signer, owner, key) are flagged. Severity is elevated when the account is used in sensitive operations.
- Key without owner: Accounts validated by key but not by owner are flagged as high severity, since a fake account with the correct key but wrong owner can bypass key-based checks.
- Signer without owner for sensitive ops: Accounts used in state modifications that are signer-checked but not owner-checked are flagged as high severity.
- Owner without authorization: Accounts with only an owner check and no signer or key check are flagged as medium severity.
Context modifiers:
- Anchor programs: confidence reduced by 60% (Anchor’s
#[account]constraints enforce full validation) - Admin/initialization functions: confidence reduced by 40%
- Read-only/view functions: confidence reduced by 60%
Limitations
False positives:
- PDA accounts that are validated by derivation (seeds + bump) rather than by explicit key/owner checks may be flagged, even though PDA derivation provides equivalent security.
- Read-only reference accounts (e.g., price feeds, oracles) that only need key validation may be flagged for missing signer checks.
False negatives:
- Accounts validated through variable expressions (e.g.,
HirExpr::Param) rather than simple variables are not tracked. - Validation performed in helper functions called from the main instruction handler is not recognized.
Related Detectors
- Secondary Signer Validation — specifically detects key check without signer check
- Missing Owner Check — detects missing owner validation
- Missing Signer Check — detects missing signer validation