How a Solana program gets compromised
Solana programs are stateless. The code lives at one address. The state lives in accounts the program reads and writes. The same program can be called against any combination of accounts, and the program is responsible for verifying that the account combination is the one it expects.
Most Solana exploits come from this verification gap. Either the program does not check that an account is what it thinks it is, or the program checks the wrong property, or the program trusts a derived address without verifying the derivation. The pattern catalog below names where the verification goes wrong.
The audit methodology applies to Solana the same way it applies to EVM. The pattern catalog is different.
What we look at first
The Anchor IDL, if present. Most production Solana programs are written with Anchor. The IDL declares the account constraints, the instruction signatures, and the relationships between them. The IDL is also the spec the audit holds the program against.
The instruction surface. Every instruction the program exposes, the accounts each instruction requires, and the access constraints (signer requirements, owner checks, PDA derivations) the program declares for each. Solana programs rarely fail "inside" an instruction; they fail at the boundary between instructions.
The CPI graph. Every program the program calls into (cross-program invocation). Solana programs compose; a program that CPIs into a token program, into a swap program, into a governance program inherits the trust model of each.
The pattern catalog
Missing signer checks. An instruction that mutates state on behalf of an account must verify the account is is_signer. Anchor's Signer<'info> type enforces this at the constraint layer; programs that bypass Anchor (or use AccountInfo and forget the check) ship the vulnerability. We hunt every state-mutating instruction for the explicit signer assertion.
Missing owner checks. An account that the program treats as one of its own data accounts must have owner == program_id. Without the check, an attacker can pass a malicious account with the same data layout that the program will happily write to. Anchor's Account<'info, T> type checks this; raw AccountInfo does not.
PDA confusion. A Program Derived Address is a deterministic address computed from a seed set, a bump, and the program ID. The program is supposed to verify that the PDA passed to it derives from the seeds the program expects. Programs that skip the derivation check, or that derive with attacker-controlled seeds, accept arbitrary accounts as PDAs. The pattern caught Wormhole in February 2022: the contract trusted a passed account as the guardian-set authority without re-deriving its address.
Sysvar account validation. Sysvars (system variables: clock, rent, recent blockhashes, instruction sysvar) are special accounts at known addresses. A program that reads a sysvar must verify the passed account is in fact the expected sysvar. Wormhole's 2022 ~$325M loss was a sysvar validation gap: the contract accepted a fake Instructions sysvar account that the attacker controlled, which let the attacker forge guardian signatures.
Discriminator confusion in Anchor. Anchor prepends an 8-byte discriminator to every data account to identify its type. A program that reads an account without checking the discriminator can be passed an account of the wrong type that happens to deserialize. Anchor's typed accessors check this; raw deserialization does not.
Cross-program invocation (CPI) trust. When program A calls program B, A is implicitly trusting B with the accounts it passes. If B is malicious or compromised, B can drain or corrupt anything in those accounts. We trace every CPI to the target program's authority and verify the trust assumption is defensible.
Reentrancy through CPI. Solana does not have built-in reentrancy guards. A program that CPIs into another program that CPIs back can re-enter the original program's state. The Solana runtime's account locking provides some protection (you cannot mutate the same account from two simultaneous CPIs) but does not protect against state-dependent decisions made on stale reads.
Rent-exempt assumptions. Solana accounts must hold a minimum balance proportional to their data size (rent exemption). Periodic rent collection on non-exempt accounts has been deprecated in favor of requiring rent exemption at account creation, but legacy programs still ship with assumptions about rent-collection behavior. A program that assumes its accounts will always be rent-exempt can be broken by an attacker draining the lamports to just under the threshold, causing the account to be garbage-collected mid-operation.
Account ordering and aliasing. The order in which accounts are passed to an instruction is part of the call signature. An attacker who passes the same account twice (account aliasing) can cause logic that assumes two distinct accounts to behave incorrectly. Anchor's seeds constraint helps; the audit-time check is whether the constraint catches the aliasing case.
Lamport handling. Direct lamport transfers (mutating **account.try_borrow_mut_lamports()?) bypass the SOL transfer paths. Programs that mutate lamports directly must maintain the invariant that pre-transfer balance equals post-transfer balance plus any intended outflow.
Token-2022 (Token Extensions) interactions. Solana's newer token program supports extensions (transfer fees, confidential transfers, non-transferable tokens, default account state, interest-bearing tokens, permanent delegate authority). A program written against the original SPL Token program that accepts Token-2022 mints inherits the extensions' semantics. A transfer-fee extension changes the amount received from a transfer. A non-transferable extension causes transfers to fail. A permanent delegate extension means the issuer can move any account's tokens. We audit every Token-2022 acceptance path against the extension surface the program is willing to honor.
What we audit on a Solana program
We audit:
- every instruction's account constraints against the operations it performs
- every signer requirement against the operation's authority model
- every owner check on every account the program treats as its own
- every PDA derivation, including seeds and bumps, against what the program expects
- every sysvar read against the expected sysvar address
- every CPI target against the trust assumption that allows the call
- rent-exemption invariants for every account the program creates or holds
- lamport flows for conservation invariants
The deliverable is the audit report described in our methodology.
Tools that earn their place
Anchor's constraint system does the largest share of the verification work. Programs that use Anchor's typed account constraints inherit signer, owner, and discriminator checks for free. Anchor's init_if_needed constraint is a known foot-gun: it conditionally initializes an account, and programs that use it without further constraints can be tricked into re-initializing accounts that already exist.
Sec3 (formerly the Soteria static analyzer) is the Solana-specific static-analysis tool we use. It catches the patterns Anchor's constraint system does not (CPI reentrancy, rent-exemption assumptions, lamport accounting).
Property-based fuzzing on Solana has improved as the ecosystem matured. We use program-specific harnesses written against solana-program-test to generate account combinations and instruction sequences, and assert protocol-level invariants.
solana-program-test is the canonical testing harness for unit and integration tests.
LLVM-level analysis adapted to Solana BPF catches deeper compiler-level issues that source-level review and constraint checking miss. The tooling is more specialized than the Solidity ecosystem's; auditors with BPF familiarity are not interchangeable with auditors who only know the source language.
security.txt is a Solana-specific convention for embedding security-contact metadata in the program binary. A program that ships without security.txt lacks the contact path responsible disclosure relies on; we flag the omission in the audit report.
What clean means
A clean Solana audit means the account-constraint and CPI-trust assumptions hold under the threat model the program states. It does not mean a future upgrade to the Solana runtime, or a future change in the program's CPI targets, cannot break those assumptions. We say so explicitly.