Solana Vulnerability Scanner
Trail of BitsOfficialPublished by Trail of Bits
- Skill
- Review and audit
- Security
- CC-BY-SA-4.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Solana Vulnerability Scanner provides a structured framework for auditing Solana smart contracts. It focuses on six critical security patterns, including cross-program invocation (CPI) security, PDA validation, ownership checks, signer verification, sysvar integrity, and instruction introspection. The skill mandates a comprehensive reporting format that requires a coverage table for all six patterns, ensuring that every aspect of the program's security posture is explicitly evaluated, documented, and verified.
What it does
- Scans native Rust and Anchor-based Solana programs for 6 critical security vulnerabilities.
- Enforces a mandatory coverage table to ensure all security patterns are addressed.
- Provides specific remediation strategies and code examples for identified vulnerabilities.
- Guides users through a multi-step audit workflow covering CPI, PDA, and account validation.
- Integrates with standard Solana testing practices and linting tools.
- Requires explicit justification for any pattern marked as not applicable.
When to use it
- During the pre-launch security assessment of a Solana protocol.
- When reviewing cross-program invocation logic for potential security flaws.
- When validating program-derived address (PDA) implementations.
- During routine audits of Solana or Anchor program codebases.
- When verifying that account validation patterns are correctly implemented.
Advantages
- Prevents incomplete audits by requiring a full coverage table for all 6 patterns.
- Standardizes the reporting of security findings across different projects.
- Provides actionable, code-level recommendations for fixing identified issues.
- Reduces human error by enforcing a structured, step-by-step auditing workflow.
- Differentiates between 'clear' code and 'n/a' scenarios to ensure thoroughness.
Usage examples
- “Audit my Solana program for security vulnerabilities.”
- The agent will scan the codebase, generate a 6-row coverage table, and provide detailed findings or 'clear' status for each of the 6 critical vulnerability patterns.
- “Check my Anchor program's PDA implementation for security issues.”
- The agent will review the PDA derivation logic, verify the use of canonical bumps, and report whether the implementation is secure or requires remediation.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/trailofbits/solana-vulnerability-scanner/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip solana-vulnerability-scanner.zip -d .agents/skills
Documentation by Trail of Bits
Solana Vulnerability Scanner
1. Purpose
Systematically scan Solana programs (native and Anchor framework) for platform-specific security vulnerabilities related to cross-program invocations, account validation, and program-derived addresses. This skill encodes 6 critical vulnerability patterns unique to Solana's account model.
2. When to Use This Skill
Auditing Solana programs (native Rust or Anchor)
Reviewing cross-program invocation (CPI) logic
Validating program-derived address (PDA) implementations
Pre-launch security assessment of Solana protocols
Reviewing account validation patterns
Assessing instruction introspection logic
3. Platform Detection
File Extensions & Indicators
Rust files:
.rs
Language/Framework Markers
// Native Solana program indicators
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
program::invoke,
program::invoke_signed,
};
entrypoint!(process_instruction);
// Anchor framework indicators
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Program logic
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub authority: Signer<'info>,
}
// Common patterns
AccountInfo, Pubkey
invoke(), invoke_signed()
Signer<'info>, Account<'info>
#[account(...)] with constraints
seeds, bumpProject Structure
programs/*/src/lib.rs- Program implementationAnchor.toml- Anchor configurationCargo.tomlwithsolana-programoranchor-langtests/- Program tests
Tool Support
Trail of Bits Solana Lints: Rust linters for Solana
Installation: Add to Cargo.toml
anchor test: Built-in testing framework
Solana Test Validator: Local testing environment
4. How This Skill Works
When invoked, I will:
Search your codebase for Solana/Anchor programs
Analyze each program for the 6 vulnerability patterns
Report findings with file references and severity, above them a coverage table carrying a verdict for every pattern
Provide fixes for each identified issue
Check account validation and CPI security
5. Example Output
6. Vulnerability Patterns (6 Patterns)
I check for 6 critical vulnerability patterns unique to Solana. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
Pattern Summary:
Arbitrary CPI ⚠️ CRITICAL - User-controlled program IDs in CPI calls
Improper PDA Validation ⚠️ CRITICAL - Using create_program_address without canonical bump
Missing Ownership Check ⚠️ HIGH - Deserializing accounts without owner validation
Missing Signer Check ⚠️ CRITICAL - Authority operations without is_signer check
Sysvar Account Check ⚠️ HIGH - Spoofed sysvar accounts (pre-Solana 1.8.1)
Improper Instruction Introspection ⚠️ MEDIUM - Absolute indexes allowing reuse
For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
7. Scanning Workflow
Step 1: Platform Identification
Verify Solana program (native or Anchor)
Check Solana version (1.8.1+ for sysvar security)
Locate program source (
programs/*/src/lib.rs)Identify framework (native vs Anchor)
Step 2: CPI Security Review
# Find all CPI calls
rg "invoke\(|invoke_signed\(" programs/
# Check for program ID validation before each
# Should see program ID checks immediately before invokeFor each CPI:
[ ] Program ID validated before invocation
[ ] Cannot pass user-controlled program accounts
[ ] Anchor: Uses
Program<'info, T>type
Step 3: PDA Validation Check
# Find PDA usage
rg "find_program_address|create_program_address" programs/
rg "seeds.*bump" programs/
# Anchor: Check for seeds constraints
rg "#\[account.*seeds" programs/For each PDA:
[ ] Uses
find_program_address()or Anchorseedsconstraint[ ] Bump seed stored and reused
[ ] Not using user-provided bump
Step 4: Account Validation Sweep
# Find account deserialization
rg "try_from_slice|try_deserialize" programs/
# Should see owner checks before deserialization
rg "\.owner\s*==|\.owner\s*!=" programs/For each account used:
[ ] Owner validated before deserialization
[ ] Signer check for authority accounts
[ ] Anchor: Uses
Account<'info, T>andSigner<'info>
Step 5: Instruction Introspection Review
# Find instruction introspection usage
rg "load_instruction_at|load_current_index|get_instruction_relative" programs/
# Check for checked versions
rg "load_instruction_at_checked|load_current_index_checked" programs/[ ] Using checked functions (Solana 1.8.1+)
[ ] Using relative indexing
[ ] Proper correlation validation
Step 6: Trail of Bits Solana Lints
# Add to Cargo.toml
[dependencies]
solana-program = "1.17" # Use latest version
[lints.clippy]
# Enable Solana-specific lints
# (Trail of Bits solana-lints if available)8. Reporting Format
Coverage Table
Report on every pattern in §6, whether or not it turned anything up. Emit this table above the findings, with all 6 rows present:
| # | Pattern | Verdict | Evidence |
|---|---|---|---|
| 1 | Arbitrary CPI | n/a | this program makes no cross-program invocations |
| 2 | Improper PDA Validation | ||
| 3 | Missing Ownership Check | ||
| 4 | Missing Signer Check | ||
| 5 | Sysvar Account Check | ||
| 6 | Improper Instruction Introspection |
Each verdict is one of:
found— citefile:lineand write the finding up in full below.clear— the pattern applies to this program and the program handles it. Name the constraint, account type, or check you searched for, so a reader can repeat the search.n/a— the pattern cannot apply here. Give the reason in one clause ("this program makes no CPI calls"). Not having looked is notn/a. Pattern 5 is version-scoped (pre-Solana 1.8.1): cite thesolana-programversion the program targets rather than dropping the row, since "targets 1.17, fixed upstream" and "did not look" are otherwise the same answer.
A table with fewer than 6 rows is an incomplete scan and must be reported as one. A row whose Verdict cell is empty is incomplete in the same way: row 1 above is filled in to show the shape, and every row is filled in the same way before the report is done. Six clear verdicts is a
result a reader can act on. A report that covers two patterns and says nothing about the other four reads
exactly like a clean program, and that is the failure this table exists to prevent.
Finding Template
## [CRITICAL] Arbitrary CPI - Unchecked Program ID
**Location**: `programs/vault/src/lib.rs:145-160` (withdraw function)
**Description**:
The `withdraw` function performs a CPI to transfer SPL tokens without validating that the provided `token_program` account is actually the SPL Token program. An attacker can provide a malicious program that appears to perform a transfer but actually steals tokens or performs unauthorized actions.
**Vulnerable Code**:
```rust
// lib.rs, line 145
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let token_program = &ctx.accounts.token_program;
// WRONG: No validation of token_program.key()!
invoke(
&spl_token::instruction::transfer(...),
&[
ctx.accounts.vault.to_account_info(),
ctx.accounts.destination.to_account_info(),
ctx.accounts.authority.to_account_info(),
token_program.to_account_info(), // UNVALIDATED
],
)?;
Ok(())
}
```
**Attack Scenario**:
1. Attacker deploys malicious "token program" that logs transfer instruction but doesn't execute it
2. Attacker calls withdraw() providing malicious program as token_program
3. Vault's authority signs the transaction
4. Malicious program receives CPI with vault's signature
5. Malicious program can now impersonate vault and drain real tokens
**Recommendation**:
Use Anchor's `Program<'info, Token>` type:
```rust
use anchor_spl::token::{Token, Transfer};
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub vault: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>, // Validates program ID automatically
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
);
anchor_spl::token::transfer(cpi_ctx, amount)?;
Ok(())
}
```
**References**:
- building-secure-contracts/not-so-smart-contracts/solana/arbitrary_cpi
- Trail of Bits lint: `unchecked-cpi-program-id`9. Priority Guidelines
Critical (Immediate Fix Required)
Arbitrary CPI (attacker-controlled program execution)
Improper PDA validation (account spoofing)
Missing signer check (unauthorized access)
High (Fix Before Launch)
Missing ownership check (fake account data)
Sysvar account check (authentication bypass, pre-1.8.1)
Medium (Address in Audit)
Improper instruction introspection (logic bypass)
10. Testing Recommendations
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_rejects_wrong_program_id() {
// Provide wrong program ID, should fail
}
#[test]
#[should_panic]
fn test_rejects_non_canonical_pda() {
// Provide non-canonical bump, should fail
}
#[test]
#[should_panic]
fn test_requires_signer() {
// Call without signature, should fail
}
}Integration Tests (Anchor)
import * as anchor from "@coral-xyz/anchor";
describe("security tests", () => {
it("rejects arbitrary CPI", async () => {
const fakeTokenProgram = anchor.web3.Keypair.generate();
try {
await program.methods
.withdraw(amount)
.accounts({
tokenProgram: fakeTokenProgram.publicKey, // Wrong program
})
.rpc();
assert.fail("Should have rejected fake program");
} catch (err) {
// Expected to fail
}
});
});Solana Test Validator
# Run local validator for testing
solana-test-validator
# Deploy and test program
anchor test11. Additional Resources
Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/solana/Trail of Bits Solana Lints: https://github.com/trailofbits/solana-lints
Anchor Documentation: https://www.anchor-lang.com/
Solana Program Library: https://github.com/solana-labs/solana-program-library
Solana Cookbook: https://solanacookbook.com/
12. Quick Reference Checklist
Before completing Solana program audit:
CPI Security (CRITICAL):
[ ] ALL CPI calls validate program ID before
invoke()[ ] Cannot use user-provided program accounts
[ ] Anchor: Uses
Program<'info, T>type
PDA Security (CRITICAL):
[ ] PDAs use
find_program_address()or Anchorseedsconstraint[ ] Bump seed stored and reused (not user-provided)
[ ] PDA accounts validated against canonical address
Account Validation (HIGH):
[ ] ALL accounts check owner before deserialization
[ ] Native: Validates
account.owner == expected_program_id[ ] Anchor: Uses
Account<'info, T>type
Signer Validation (CRITICAL):
[ ] ALL authority accounts check
is_signer[ ] Native: Validates
account.is_signer == true[ ] Anchor: Uses
Signer<'info>type
Sysvar Security (HIGH):
[ ] Using Solana 1.8.1+
[ ] Using checked functions:
load_instruction_at_checked()[ ] Sysvar addresses validated
Instruction Introspection (MEDIUM):
[ ] Using relative indexes for correlation
[ ] Proper validation between related instructions
[ ] Cannot reuse same instruction across multiple calls
Testing:
[ ] Unit tests cover all account validation
[ ] Integration tests with malicious inputs
[ ] Local validator testing completed
[ ] Trail of Bits lints enabled and passing
[ ] Coverage table emitted with all 6 rows, each carrying a verdict of
found,clearorn/awith a reason
13. Rationalizations to Reject
"The program is small, so most patterns obviously don't apply." Obvious to whom? An
n/acosts one clause and makes the judgment reviewable. Silence records nothing, and a reader cannot tell it apart from not having checked."The Trail of Bits lints pass, so the program is clean." The lints cover a subset of these 6 patterns. A clean lint run is one row of evidence, not a verdict on the patterns it never examined. Say which patterns it covered.
"I checked the patterns that matter for this program." Deciding which patterns matter is the scan, not a precondition for starting it. Rank by severity after the table is complete, not by leaving rows out.
"No findings, so there is nothing to report." A zero-finding scan still emits the full coverage table. That table is the deliverable: it is what distinguishes a program that was examined from one that was glanced at.
"Anchor handles account validation." Name the constraint. Anchor validates what the account struct declares and nothing more:
#[account(mut)]is not an ownership check, andUncheckedAccountopts out entirely. Cite the attribute, not the framework."The PDA derivation makes it safe." Derivation is not validation.
create_program_addresswithout the canonical bump admits multiple valid addresses, which is pattern 2 in full.
Files
- ATTRIBUTION.mdAdded by AutoGineer975 B
- LICENSELicence file added by AutoGineer19.7 KiB
- SKILL.md14.1 KiB
- agents/openai.yaml258 B
- assets/trail-of-bits-mark.svg3.0 KiB
- resources/VULNERABILITY_PATTERNS.md19.9 KiB
agents/openai.yaml
interface: display_name: "Solana Vulnerability Scanner" short_description: "Find security vulnerabilities in Solana and Anchor programs" icon_small: "assets/trail-of-bits-mark.svg" icon_large: "assets/trail-of-bits-mark.svg" brand_color: "#D83A34"
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Trail of Bits
- Publisher
- Trail of Bits (Official)
- Source
- trailofbits/skills · plugins/building-secure-contracts/skills/solana-vulnerability-scanner · commit e4dd0bb
- Licence
- Creative Commons Attribution-ShareAlike 4.0 International (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Trail of Bits.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download solana-vulnerability-scanner.zip