Account Ownership & Permissions
The owner-writes rule is Solana's core security invariant: only an account's owner program may modify its data (and certain metadata). Signers authorize lamport transfers; owners authorize data mutations.
Recipe
#[derive(Accounts)]
pub struct SecureUpdate<'info> {
#[account(
mut,
has_one = authority @ MyError::Unauthorized,
constraint = data.is_active @ MyError::Inactive,
)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>,
}When to reach for this:
- Every instruction that mutates program-owned accounts
- Auditing programs for missing signer or owner checks
- Designing CPI flows where callee must trust passed accounts
- Understanding why
UncheckedAccountis dangerous by default
Working Example
use anchor_lang::prelude::*;
#[account]
pub struct MyData {
pub authority: Pubkey,
pub is_active: bool,
pub balance: u64,
}
#[program]
pub mod permissions {
use super::*;
pub fn update_balance(ctx: Context<UpdateBalance>, new_balance: u64) -> Result<()> {
ctx.accounts.data.balance = new_balance;
Ok(())
}
pub fn steal_attempt(ctx: Context<StealAttempt>) -> Result<()> {
// This FAILS at runtime - caller is not owner of victim account
Ok(())
}
}
#[derive(Accounts)]
pub struct UpdateBalance<'info> {
#[account(mut, has_one = authority)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>,
}
#[derive(Accounts)]
pub struct StealAttempt<'info> {
#[account(mut)]
pub data: Account<'info, MyData>, // Anchor checks owner == program
pub attacker: Signer<'info>,
}What this demonstrates:
Account<'info, MyData>verifies owner == this programhas_one = authorityties mutation rights to a stored pubkey- Attacker cannot pass another user's account unless they are the authority
Deep Dive
Permission Layers
| Check | Enforced By | Purpose |
|---|---|---|
| Owner program | Runtime | Only owner writes data |
| Signer | Runtime | Ed25519 signature present |
has_one / constraint | Anchor | Application-level auth |
| Account meta flags | Runtime | Writable vs read-only |
CPI Permissions
- Caller passes accounts to callee; callee re-validates
- Owner program can CPI to delegate token transfers (SPL)
invoke_signedlets programs sign for PDAs
Gotchas
- UncheckedAccount without constraints - arbitrary account substitution. Fix: add owner, seeds, or
has_onechecks. - Signer != authority - user signs but isn't the stored authority. Fix:
has_one = authorityonSigner. - Writable flag on unauthorized accounts - runtime may reject or owner program must allow. Fix: only mark writable accounts that your program owns and intends to mutate.
- Missing mut on accounts that change - silent no-op or compile error. Fix:
#[account(mut)]on every changed account. - Confusing token owner with account authority - SPL has separate owner and delegate. Fix: use SPL constraints or CPI correctly.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
has_one | Single authority pubkey in data | Multi-sig (use custom constraint) |
seeds + bump | PDA authority | User wallet is authority |
constraint expressions | Complex boolean rules | Simple pubkey match (use has_one) |
Manual require! | Dynamic checks | Static layout checks (prefer constraints) |
FAQs
What is the owner-writes rule?
Only the program listed in account.owner can modify the account's data buffer.
Can a signer mutate any account?
No. Signers authorize transactions; data writes still require owner program logic in the instruction.
What does has_one check?
A pubkey field inside the account data matches the pubkey of another account in the struct.
Can programs change account owner?
Owner program may call System Program assign to transfer ownership - rare and dangerous if misused.
Why use Signer vs UncheckedAccount for authority?
Signer proves they signed the tx. UncheckedAccount does not - must add manual signer check.
What is a common drain vulnerability?
Missing owner/authority check lets attacker pass victim's account with writable flag.
Do read-only accounts need signer?
No, unless your logic requires proof of identity for read paths (uncommon).
How do PDAs fit permissions?
Program signs via invoke_signed with seeds; no human signer needed for PDA-owned logic.
Can two signers co-authorize?
Yes. Pass multiple Signer accounts and validate both in constraints.
What is address lookup table impact?
ALTs change how addresses are referenced, not ownership rules.
Does Anchor verify token account owner?
InterfaceAccount / token constraints verify SPL Token program ownership.
How do I audit permissions?
List every mut account; trace required signer and owner checks per instruction.
Related
- System Accounts vs. Program Accounts - who owns what
- Program-Owned State - state account design
- Account Model Basics - constraint examples
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.