Your Anchor program has vulnerabilities. Find them before mainnet.
SAST, fuzzing, and manual review for Solana programs. From quick scans to full engagements with fix PRs. Browse all packages ↓
Trace. Vector. Anchor.
Submit your repo
Share your repo (public or private) and we'll review the code structure and scope the engagement.
We scan, fuzz, and review
Semgrep SAST for common pitfalls, Trident fuzz tests for runtime vulnerabilities, and manual review by experienced auditors.
Get your report + fix PRs
Professional PDF report with severity ratings and remediation steps. Anchor tier includes fix PRs submitted directly to your repo.
Common Anchor vulnerabilities
These are real vulnerability patterns we look for in every Solana audit. Ranked by severity and exploitability.
Description
Instruction handler accepts an account parameter without verifying that the account actually signed the transaction. Any account can be passed by an attacker.
Impact
An attacker can impersonate any account in the system, potentially draining funds, modifying protocol state, or taking administrative actions without authorization.
Recommendation
Add explicit signer check: require!(ctx.accounts.authority.is_signer, ErrorCode::MissingSigner). Verify all privileged accounts are signers before performing sensitive operations.
Description
Initialize instruction does not check if the account has already been initialized. An attacker can re-run init on an existing account to reset state.
Impact
Attacker can reset account state after initialization, potentially wiping ownership records, resetting balances to initial values, or breaking protocol invariants. Can lead to total loss of funds.
Recommendation
Add discriminator check at the start of init handler: require!(account.discriminator == 0, ErrorCode::AlreadyInitialized). Use Anchor's #[account(init)] constraint which includes this check automatically.
Description
Program performs Cross-Program Invocation (CPI) and passes signer seeds to an untrusted program. The called program inherits PDA signing authority.
Impact
Malicious program can use the inherited PDA authority to perform unauthorized actions on behalf of the calling program, including transferring tokens or modifying accounts owned by the PDA.
Recommendation
Never pass signer seeds to untrusted CPI targets. Only use invoke_signed when calling known, audited programs. For third-party integrations, use invoke() without seeds and require explicit user authorization.
Description
Token amount calculations use unchecked integer arithmetic. Rust does not panic on overflow in release builds, values silently wrap around.
Impact
An attacker can craft inputs that cause overflow, resulting in unexpectedly small values. For example, depositing u64::MAX + 1 tokens wraps to 0, allowing free withdrawals or breaking accounting invariants.
Recommendation
Use checked arithmetic for all token amounts: amount.checked_add(deposit).ok_or(ErrorCode::Overflow)?. Consider using the @solana/spl-math library for safe math operations.
Description
Instruction accepts a PDA account but does not validate the seeds used to derive it. An attacker can pass a PDA derived with different seeds than expected.
Impact
Attacker can bypass access controls or account ownership checks by providing a validly-derived PDA that uses unexpected seeds. Can lead to unauthorized state modifications or fund access.
Recommendation
Always validate PDA derivation in the instruction: let (expected_pda, bump) = Pubkey::find_program_address(&[b"vault", user.key().as_ref()], program_id); require!(expected_pda == *vault.key, ErrorCode::InvalidPDA);
Description
Account closing logic drains lamports to recipient but does not zero out account data. The account data remains on-chain and can be reused.
Impact
If the account is re-initialized later, stale data may be interpreted as valid state. This can lead to replay attacks, privilege escalation, or unexpected behavior depending on how the data is used.
Recommendation
Zero account data before closing: account.data.borrow_mut().fill(0); **account.to_account_info().assign(&system_program::ID); Use Anchor's #[account(close = recipient)] constraint which handles this safely.
Description
Account data is deserialized without checking the discriminator field. Any account with sufficient size can be passed, leading to type confusion.
Impact
Attacker can pass an account of the wrong type but with matching layout, causing the program to misinterpret field values. Can lead to logic errors, incorrect calculations, or privilege escalation.
Recommendation
Always verify discriminator before deserializing: let account_data = account.try_borrow_data()?; require!(account_data[0..8] == EXPECTED_DISCRIMINATOR, ErrorCode::InvalidAccountType); Anchor does this automatically with #[account] constraints.
Description
Instruction iterates over ctx.remaining_accounts without validating ownership or signer status. Attacker-controlled accounts can be passed in the remaining accounts list.
Impact
Depending on how remaining_accounts are used, this can lead to unauthorized access, CPI to malicious programs, or incorrect state updates. Impact varies based on the specific logic.
Recommendation
Validate all remaining accounts before use. Check ownership with require!(account.owner == expected_program_id), signer status with require!(account.is_signer), and any other invariants specific to your use case.
Description
Account is used without verifying that account.owner matches the expected program ID. For token accounts, should verify owner is the Token Program.
Impact
Low risk in most cases, but can allow attacker to pass accounts from unrelated programs. If the program logic assumes specific account structure, this may cause unexpected behavior or panics.
Recommendation
Add ownership validation: require!(token_account.owner == &spl_token::ID, ErrorCode::InvalidOwner). Use Anchor's #[account(owner = token_program)] constraint for automatic validation.
Fixed-price audits. No surprises.
Tracer
Static analysis and manual review of your Anchor program. Findings report with severity ratings and remediation steps.
- Semgrep SAST scan (Anchor/Rust rules)
- Architecture + account model review
- Common Anchor pitfalls check
- Security Snapshot PDF
- Findings prioritized by severity
Vector
Deep manual review tracing every attack vector through your program. Full PDF report with PoC exploit notes.
- Everything in Tracer
- Full manual code review
- Custom Trident fuzz suite (1000+ iterations)
- Professional PDF report
- Severity ratings + remediation steps
Anchor
Complete security engagement. Every vulnerability found, documented, and fixed. Fix PRs submitted directly to your repo.
- Everything in Vector
- Fix PRs submitted to your repo
- Re-audit after fixes merge
- Architecture recommendations doc
- Direct Slack/Telegram access during engagement
Pricing based on typical program size (under 5k LOC). Larger programs quoted separately. All tiers include NDA and final report ownership.