Security and production
Common Sealevel attack patterns, footguns to avoid, and how to ship verifiable Solana programs.
Sealevel attacks
Footguns
Verifiable builds
Sealevel attacks
A curated list of common Solana security exploits and Anchor-idiomatic fixes from the Sealevel attacks repo.
Anchor eliminates many common footguns, but anything shipped to mainnet should be understood end to end. The Sealevel attacks repository catalogs common exploit patterns. Each entry comes in three variants: an insecure version showing the flawed code, a secure version with a fix, and a recommended version that uses idiomatic Anchor.
Important (Not a substitute for review)
These examples are not exhaustive nor guaranteed to be secure on their own. Each one isolates a single issue and its fix for study. Real programs combine multiple checks, and a clean entry here does not mean a program built around that pattern is automatically safe.
Footguns
Common footguns in Anchor that can lead to security or correctness issues.
Zeroed discriminators#
Anchor supports overriding discriminators with custom values for accounts, events, and instructions. All-zero discriminators are only supported on events and instructions. The following forms are rejected for accounts:
#[account(discriminator = [0, 0, 0])]#[account(discriminator = 0)]#[account(discriminator = ZERO_CONSTANT)]Danger (Why all-zero account discriminators are rejected)
An all-zero account discriminator is indistinguishable from a freshly allocated, uninitialized account. A program-owned account whose data is still zero (for example, an AccountInfo that has been allocated but not yet written, or one prepared for #[zero] initialization) would pass the discriminator check, exposing the program to takeover via IDL instructions and other security and usability issues.
Generic account types#
Different instantiations of the same generic #[account] type share a default discriminator:
generic arguments are not part of its derivation.
#[account]pub struct State<T: AnchorSerialize + AnchorDeserialize + Clone> { pub value: T,}
// State<A> and State<B> share a discriminator despite being distinct Rust types.// State<u64>// State<u128>An account serialized as State<u128> can deserialize as State<u64>: the discriminator
matches, and deserialization reads the first eight bytes of the value, leaving the remaining
bytes unchecked. The reverse requires enough account data for a u128; a minimally sized
State<u64> account is too short.
Danger (Generic arguments do not distinguish accounts)
If the serialized data for one instantiation can be deserialized as another, an account can pass the discriminator check and be interpreted as the wrong type. Do not rely on generic arguments to prevent this type confusion. Use distinct account structs with distinct discriminators, or explicitly validate a stored type tag before using the account data.
Borrowed data in account types#
Account types used with Context should own their values rather than hold references borrowed
from other accounts or handler-local data. Do not replace an AccountInfo’s lamports or data
backing reference with a reference to a field in ctx.accounts. Mutate the account contents
instead of their backing references.
Danger (Avoid borrowed account data)
For example, avoid account types such as
struct Borrowed<'info> { value: Cell<Option<&'info u64>> }. Anchor 1.0 uses simplified
Context lifetimes, which can make a reference retained through Cell, RefCell,
or similar interior mutability outlive the value it points to.
Verifiable builds
Use Anchor's Docker-backed CLI to produce reproducible Solana program binaries that can be verified against a deployed program.
Building Solana programs with the Solana CLI can embed machine-specific code into the resulting binary, so the same source on different machines may produce different executables. Anchor solves this by building inside a Docker image with pinned dependencies, producing a verifiable build.
Install Docker first, then use the Anchor CLI commands below.
Building#
Produce a verifiable build:
anchor build --verifiableThe binary is written to target/verifiable. Deploy it instead of the regular build in target/deploy:
anchor program deploy --verifiableVerifying#
Verify a build against a program deployed on mainnet:
anchor verify -p <lib-name> <program-id>The <lib-name> is the library name defined in the program’s Cargo.toml. If the program has an IDL, the IDL deployed on chain is also checked against the local IDL.
Images#
The Docker image lives in a different registry depending on the Anchor version.
Anchor v1.0.2 and later (OtterSec releases) are published on Quay under tags of the form quay.io/ottersec/anchor:<version>. To pull the image for Anchor v1.2.0:
docker pull quay.io/ottersec/anchor:v1.2.0Earlier versions have not yet been mirrored to Quay and remain on Docker Hub under solanafoundation/anchor (and backpackapp/build before v0.31.1). For example, to pull Anchor v1.0.1:
docker pull solanafoundation/anchor:v1.0.1Removing an image#
If a CLI verifiable build exits prematurely, the underlying Docker container may still be running in the background. Remove it with:
docker rm -f anchor-programanchor-program is the default image name the Anchor CLI uses.