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.
- 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
- 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
[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, bump
Project Structure programs/*/src/lib.rs - Program implementation Anchor.toml - Anchor configuration Cargo.toml with solana-program or anchor-lang tests/ - 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 Provide fixes for each identified issue Check account validation and CPI security 5. Example Output 5. 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.
- 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 invoke
For 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 Anchor seeds constraint 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> and Signer<'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)
- Reporting Format 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
// 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:
Attacker deploys malicious "token program" that logs transfer instruction but doesn't execute it Attacker calls withdraw() providing malicious program as token_program Vault's authority signs the transaction Malicious program receives CPI with vault's signature Malicious program can now impersonate vault and drain real tokens
Recommendation: Use Anchor's Program<'info, Token> type:
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
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
7. 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)
8. Testing Recommendations
Unit Tests
```rust
[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 test
- 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/
- 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 Anchor seeds constraint 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