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.
Search across all documentation pages
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.
#[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:
UncheckedAccount is dangerous by defaultuse 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 = authority ties mutation rights to a stored pubkey| 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 |
invoke_signed lets programs sign for PDAshas_one checks.has_one = authority on Signer.#[account(mut)] on every changed account.| 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) |
Only the program listed in account.owner can modify the account's data buffer.
No. Signers authorize transactions; data writes still require owner program logic in the instruction.
A pubkey field inside the account data matches the pubkey of another account in the struct.
Owner program may call System Program assign to transfer ownership - rare and dangerous if misused.
Signer proves they signed the tx. UncheckedAccount does not - must add manual signer check.
Missing owner/authority check lets attacker pass victim's account with writable flag.
No, unless your logic requires proof of identity for read paths (uncommon).
Program signs via invoke_signed with seeds; no human signer needed for PDA-owned logic.
Yes. Pass multiple Signer accounts and validate both in constraints.
ALTs change how addresses are referenced, not ownership rules.
InterfaceAccount / token constraints verify SPL Token program ownership.
List every mut account; trace required signer and owner checks per instruction.
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