Security Basics
8 examples to get you started with program security - 5 basic and 3 intermediate. Covers the Solana threat model, account validation, signer checks, and fail-closed design on Anchor 0.32.1.
Search across all documentation pages
8 examples to get you started with program security - 5 basic and 3 intermediate. Covers the Solana threat model, account validation, signer checks, and fail-closed design on Anchor 0.32.1.
anchor-lang 0.32.1.anchor --version # 0.32.1Any account that moves funds or changes authority must sign.
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>,
}Signer<'info> fails if the account did not sign the transaction.Signer unless you use invoke_signed.Related: Signer & Owner Checks - deeper patterns
Only the owning program should interpret account data.
// Account<'info, T> already enforces owner == crate::ID - no constraint needed.
pub vault: Account<'info, Vault>,
/// CHECK: owner validated manually
pub raw_vault: UncheckedAccount<'info>,
// in the handler, for the unchecked account:
require_keys_eq!(*ctx.accounts.raw_vault.owner, crate::ID, ErrorCode::InvalidOwner);Account<'info, T> checks owner == crate::ID automatically for your types.constraint = vault.owner == ... on an Account<'info, T> - it Derefs to your state struct, so .owner means a field on your data, not the account's program owner.AccountInfo and UncheckedAccount skip the owner check - use *account.owner on the AccountInfo and validate manually.Related: Account Validation Attacks
PDAs must be derived with canonical seeds and bump.
#[account(
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump,
)]
pub vault: Account<'info, Vault>,Related: PDA & Seed Attacks
Token amounts and shares can overflow in release builds unless the workspace turns overflow checks on.
let new_balance = old_balance
.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;overflow-checks = true is set in [profile.release] (Anchor's default template does set it), plain +/-/* wrap silently - verify the flag is present in the audited repo.checked_*, saturating_*, or u128 intermediates: an overflow panic aborts the instruction with a generic error instead of your typed one.Related: Arithmetic & Overflow
Prevent reinitialization of already-initialized accounts.
#[account(
init,
payer = payer,
space = 8 + Vault::INIT_SPACE,
)]
pub vault: Account<'info, Vault>,init once, then mut on subsequent instructions.init_if_needed is dangerous without a is_initialized flag guard.Related: Reinitialization Attacks
Callees must be the program you expect, not an impostor.
let cpi_program = ctx.accounts.token_program.to_account_info();
require_keys_eq!(cpi_program.key(), anchor_spl::token::ID, ErrorCode::BadProgram);require_keys_eq!.Related: CPI & Reentrancy Risks
Drain lamports to destination and zero data to prevent revival.
#[account(
mut,
close = destination,
)]
pub temp: Account<'info, Temp>,
#[account(mut)]
pub destination: SystemAccount<'info>,close drains lamports to the destination, zeroes the data length, and reassigns the account to the System Program so it cannot be revived within the transaction.destination must be writable - mark it #[account(mut)] or close = destination fails.Price-dependent logic must reject stale feeds.
let price = feed
.get_price_no_older_than(clock.unix_timestamp, 60)
.ok_or(ErrorCode::StaleOracle)?;Related: Oracles - feed integration
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026