An Anchor security checklist covers what Anchor does not check for you: Signer and has_one on every authority, a reason for each UncheckedAccount, seeds and bump on PDAs, careful init_if_needed, the close constraint, mint and authority constraints on token accounts, typed program accounts for CPIs, checked math and hand-validated remaining_accounts. Run all 16 checks before your audit.
| Area | Checks | Most common miss |
|---|---|---|
| Accounts and signers | 4 | Authority not a Signer, or not tied to state with has_one |
| PDAs and initialization | 4 | PDA without seeds and bump, init_if_needed resetting state |
| Tokens and CPIs | 4 | Token account not constrained by mint and authority |
| Math, upgrades and testing | 4 | Unchecked arithmetic, unvalidated remaining_accounts |
- Anchor prevents many classic Solana bugs, but only for the checks you actually declare in account constraints.
- Most critical findings in Anchor programs are still missing signer, owner or authority checks.
- Treat every UncheckedAccount, AccountInfo and remaining_accounts entry as unvalidated input.
- Constrain token accounts by mint and authority, and call other programs through typed Program accounts.
- This checklist is preparation for an audit, not a replacement for one.
Anchor is the default way to write Solana programs because it turns account validation into declarations: types and constraints instead of hand-written checks. That removes whole classes of bugs, but it also creates a false sense of safety. Anchor only checks what you tell it to check. These are the sixteen checks we would run on any Anchor program before sending it to an auditor.
Accounts and signers
- Use Signer for every account that must authorize the instruction.
An authority passed as a plain account or UncheckedAccount is not checked for a signature. Missing signer checks are still one of the most common critical findings.
- Tie stored authorities to the signer with has_one or an explicit constraint.
Checking that someone signed is not enough; you must also check that the signer is the authority stored in the state account, for example #[account(mut, has_one = authority)].
- Justify every UncheckedAccount and AccountInfo.
Anchor requires a /// CHECK: comment for a reason. Treat each one as a place where Anchor does no validation, and validate owner, key or data yourself.
- Prevent the same account being passed twice.
Anchor does not stop the caller from passing one account into two mutable slots, such as from and to. Add a constraint like from.key() != to.key() where it matters.
PDAs and initialization
- Validate PDAs with seeds and bump in the account constraint.
seeds = [...] with bump makes Anchor re-derive the address and reject anything else. Store the canonical bump in the account and reuse it instead of recalculating.
- Design seeds so users cannot collide.
Include the user or the parent account in the seeds. Seeds built only from user-controlled strings or numbers can let one user occupy or overwrite another user’s account.
- Avoid init_if_needed unless you have a reason.
It is behind a feature flag for a reason: if the instruction can run on an already initialized account, check that it cannot reset state such as balances or owners.
- Close accounts with the close constraint.
close = receiver moves the lamports and marks the account closed. Manually draining lamports can leave an account that is reused or revived later in the same transaction.
Tokens and CPIs
- Constrain token accounts by mint and authority.
A TokenAccount type only proves it is a token account. Add token::mint and token::authority (or associated_token constraints) so a user cannot pass an account for a different mint.
- Use Program<> or Interface<> types for programs you call.
Typed program accounts check the program ID. Calling a program ID the user passed in, without a check, lets an attacker substitute their own program.
- Decide on Token-2022 support explicitly.
If you accept Token-2022 mints via InterfaceAccount, review extensions such as transfer fees and transfer hooks: received amounts can differ from sent amounts, and hooks run extra code.
- Scope PDA signer seeds tightly in CPIs.
A PDA that signs for many purposes can be tricked into signing for the wrong one. Use separate PDAs for vaults, authorities and configuration.
Math, upgrades and testing
- Use checked arithmetic and keep overflow-checks on.
Use checked_add, checked_mul and friends in financial logic, and make sure overflow-checks = true is set for the release profile. Watch rounding direction: round in the protocol’s favour.
- Validate remaining_accounts by hand.
Anchor does no validation at all on ctx.remaining_accounts. Check owner, key and type for every account you read from it.
- Plan account layout changes before upgrades.
Changing a struct changes how existing accounts deserialize. Version accounts or reserve space, and use realloc carefully when growing accounts.
- Test the failure paths and fuzz the rest.
Wrong signer, wrong mint, duplicated accounts, zero and maximum amounts. Fuzzing with a tool like Trident finds the edge cases nobody thought to write a test for.
What to do with the results
Fix what you find, then write down the invariants your program must always keep, and send both to your auditor with a frozen commit. A team that arrives with this work done gets more of the audit spent on deep issues instead of missing checks. Before mainnet, go through the Solana pre-launch security checklist for keys, upgrades and monitoring, which this list does not cover. Terms such as PDA, CPI and discriminator are explained in our glossary.
Frequently asked questions
Is Anchor secure by default?
Anchor removes whole classes of bugs, such as missing owner checks on typed accounts and type confusion through discriminators, but only for what you declare. Missing signer, authority and token constraints are still the most common critical findings in Anchor programs.
What is the most common vulnerability in Anchor programs?
Missing authorization: an authority that is not a Signer, or a signer that is never compared to the authority stored in the state account. has_one or an explicit constraint fixes it.
Is init_if_needed dangerous?
It can be. If the instruction can run on an account that already exists, make sure it cannot reset state such as balances or owners. Anchor puts it behind a feature flag for this reason.
Does Anchor validate remaining_accounts?
No. Accounts in ctx.remaining_accounts are not validated at all; check owner, key and type for each one you use.
What does Anchor check automatically?
For typed accounts, Anchor checks the owner program and the account discriminator, and it enforces the constraints you declare, such as Signer, has_one, seeds with bump, token mint and authority, and Program types for CPIs. Anything you do not declare, and every UncheckedAccount or remaining_accounts entry, is not checked.
Do I still need an audit if I use Anchor?
Yes, for any program that holds user funds. Anchor removes whole classes of bugs, but logic errors, missing constraints and economic issues remain. Use this checklist to prepare, so the audit time goes into your logic instead of basic findings.
Ready for an audit?
Estimate what auditing your Anchor programs costs and how long it takes, then get a hand-picked shortlist of Solana auditors. Free for teams.
Estimate your audit →Related reading
- Solana audit readiness checklist: 12 things to prepare before the audit startsWhat to prepare before a Solana audit: a frozen commit, a short spec, invariants, tests that run in one command, agreed scope and a developer on call.
- Where to find Solana auditors, developers and agenciesIs there a marketplace for Solana service providers? The main places teams look: ecosystem directories, bounty boards, audit contests, freelance sites and vetted marketplaces.
- How to compare Solana audit firms: a neutral frameworkCompare Solana smart contract audit firms on evidence you can check yourself: public Solana reports, Anchor or native Rust experience, engineer-days, fix review and timing.