The Zero-Cost Solana Security Pipeline: 7 Free Tools That Catch 90% of Anchor Vulnerabilities Before Your Auditor Does
Most Solana teams treat security as a pre-launch event: hire an auditor, fix the findings, ship. That's like only visiting the dentist when your teeth fall out.
The brutal truth from Q1 2026: $137M was lost across 15 DeFi protocols, and the majority of exploits targeted vulnerability classes that free, automated tools would have flagged. Step Finance ($27.3M), Truebit ($26.2M), Resolv ($25M) — none of these required novel zero-day techniques. They exploited well-known patterns.
Here's how to build a security pipeline that catches these patterns continuously, for $0 in tooling costs.
The 7-Layer Pipeline
Layer 1: cargo-clippy — The Rust Linter You're Ignoring
What it catches: Type confusion, unused variables, redundant clones, unsafe patterns
Most Solana developers run clippy once during setup, then ignore the warnings. That's a mistake.
# Don't just run clippy — make it strict
cargo clippy --all-targets --all-features -- \
-D warnings \
-D clippy::cast_possible_truncation \
-D clippy::integer_arithmetic \
-W clippy::unwrap_used
The -D clippy::integer_arithmetic flag is critical. It forces explicit handling of every arithmetic operation — the same class of bug that cost Truebit $26.2M when an unchecked overflow in their legacy EVM contracts went undetected.
In Anchor programs, add to your Cargo.toml:
[lints.clippy]
integer_arithmetic = "deny"
cast_possible_truncation = "deny"
CI integration: Add as the first step in your GitHub Actions workflow. If clippy fails, nothing else runs.
Layer 2: cargo-audit — Dependency Vulnerability Scanner
What it catches: Known CVEs in your dependency tree
cargo install cargo-audit
cargo audit
Solana programs pull in dozens of transitive dependencies through anchor-lang, solana-program, and spl-token. A single vulnerable crate in your tree can compromise everything.
Pro tip: Run cargo audit --deny warnings in CI to fail on any advisory, not just errors. Pair with cargo-geiger to quantify your unsafe exposure:
cargo install cargo-geiger
cargo geiger --all-features
If your unsafe count exceeds your program's actual FFI needs, investigate.
Layer 3: Sec3 X-Ray — Solana-Specific Static Analysis
What it catches: Missing signer checks, account validation gaps, PDA seed collisions, CPI privilege escalation
Sec3 X-Ray is the closest thing Solana has to Slither for EVM. It scans for 50+ vulnerability patterns specific to Solana's account model.
# .github/workflows/security.yml
- name: Sec3 X-Ray Scan
uses: sec3dev/x-ray-action@v1
with:
program-path: programs/my-protocol
severity-threshold: medium
What makes X-Ray valuable: It understands Anchor's #[account] constraints and can detect when your has_one, constraint, or seeds annotations don't match your actual security requirements.
Real-world catch: X-Ray would have flagged the ACPRoute exploit ($58K) where a memory vs storage keyword mismatch in state management allowed double-claiming. The missing state persistence check is exactly the pattern X-Ray's "stale account data" detector targets.
Layer 4: Trident — Solana Fuzzing Framework
What it catches: Instruction ordering bugs, state machine violations, edge-case panics
Trident by Ackee Blockchain is a property-based fuzzing framework built specifically for Solana programs.
// trident-tests/fuzz_tests/fuzz_0/test_fuzz.rs
use my_protocol::instructions::*;
#[derive(Arbitrary)]
pub struct FuzzInstruction {
pub instruction: MyProtocolInstruction,
pub accounts: FuzzAccounts,
}
impl FuzzTestExecutor for FuzzInstruction {
fn run_instruction(
&self,
program: &mut ProgramTestContext,
) -> Result<()> {
// Trident generates random instruction sequences
// and checks for panics, overflows, and invariant violations
execute_instruction(program, &self.instruction, &self.accounts)
}
}
Key configuration:
# Trident.toml
[fuzz]
iterations = 10000
exit_upon_crash = true
[fuzz.programs]
my_protocol = "programs/my-protocol"
What to fuzz: Focus on instruction sequences that involve:
- Deposit → Withdraw (reentrancy, rounding)
- Initialize → Update → Close (use-after-close)
- Multiple users interacting with shared state (race conditions)
Layer 5: Anchor's Built-in Constraints — Use Them All
What it catches: Access control, account validation, type safety
This isn't a tool — it's a checklist. Most Anchor exploits happen because developers use half the constraint system.
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump, // ← verify bump
has_one = authority, // ← verify ownership
constraint = vault.balance >= amount @ ErrorCode::InsufficientBalance,
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub authority: Signer<'info>, // ← Signer, not AccountInfo
/// CHECK: Validated in CPI
#[account(
constraint = token_program.key() == spl_token::ID @ ErrorCode::InvalidProgram
)]
pub token_program: AccountInfo<'info>,
}
The audit checklist:
- ✅ Every PDA uses
seeds+bump(no manual derivation) - ✅ Every authority account is
Signer<'info>, never justAccountInfo - ✅ Every
has_oneconstraint matches a stored pubkey - ✅ Every program account uses
constraintfor business logic validation - ✅ Every external program is verified against a known program ID
Layer 6: Custom Invariant Tests
What it catches: Economic exploits, state corruption, business logic flaws
Beyond fuzzing, write explicit invariant tests that verify your protocol's economic properties:
#[cfg(test)]
mod invariant_tests {
use super::*;
/// Total deposits must always equal total shares * exchange_rate
#[test]
fn invariant_deposits_match_shares() {
let mut ctx = setup_test_context();
for _ in 0..1000 {
let action = random_action(); // deposit, withdraw, or accrue
execute_action(&mut ctx, action);
let total_deposits = ctx.vault.total_deposits;
let implied_deposits = ctx.vault.total_shares
.checked_mul(ctx.vault.exchange_rate)
.unwrap()
.checked_div(PRECISION)
.unwrap();
// Allow 1 lamport rounding tolerance
assert!(
total_deposits.abs_diff(implied_deposits) <= 1,
"Invariant violated: deposits={}, implied={}",
total_deposits, implied_deposits
);
}
}
/// No single withdrawal can exceed deposited amount
#[test]
fn invariant_no_withdrawal_exceeds_deposit() {
// ... property-based test
}
}
The invariants that would have prevented Q1 2026 exploits:
- "Total minted tokens ≤ total collateral value" → Resolv ($25M)
- "Exchange rate is monotonically non-decreasing between harvests" → Venus ($3.7M donation attack)
- "Cumulative withdrawals per account ≤ cumulative deposits" → ACPRoute ($58K double-claim)
Layer 7: Runtime Monitoring with Helius Webhooks
What it catches: Live exploit attempts, anomalous transactions
Free tier covers most projects. Set up transaction webhooks that alert on suspicious patterns:
// monitor.js — Helius webhook handler
const ALERT_THRESHOLDS = {
maxSingleWithdraw: 100_000 * 1e9, // 100K SOL
maxTxPerMinute: 50,
suspiciousPrograms: ['11111111111111111111111111111111'], // System program in unexpected CPI
};
app.post('/webhook', (req, res) => {
const tx = req.body[0];
// Check for flash loan patterns: borrow + exploit + repay in single tx
if (tx.instructions.length > 10 && hasLargeTransfer(tx)) {
alert(`⚠️ Possible flash loan attack: ${tx.signature}`);
}
// Check for new program authority changes
if (hasAuthorityChange(tx)) {
alert(`🚨 Program authority changed: ${tx.signature}`);
}
res.sendStatus(200);
});
The GitHub Actions Workflow
Here's the complete pipeline that ties all seven layers together:
name: Security Pipeline
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Layer 1: Rust linting
- name: Clippy (strict)
run: cargo clippy --all-targets -- -D warnings -D clippy::integer_arithmetic
# Layer 2: Dependency audit
- name: Cargo Audit
run: |
cargo install cargo-audit
cargo audit --deny warnings
# Layer 3: Solana-specific static analysis
- name: Sec3 X-Ray
uses: sec3dev/x-ray-action@v1
with:
program-path: programs/
severity-threshold: medium
# Layer 4: Fuzz testing (nightly only)
- name: Trident Fuzz
if: github.event_name == 'schedule'
run: |
cargo install trident-cli
trident fuzz run-hfuzz fuzz_0 -- -n 10000
# Layers 5-6: Unit + invariant tests
- name: Test Suite
run: cargo test --all-features
# Layer 7: Deploy monitoring webhook (on main merge)
- name: Update Monitoring
if: github.ref == 'refs/heads/main'
run: ./scripts/update-helius-webhooks.sh
What This Pipeline Won't Catch
Let's be honest about the gaps:
- Economic model flaws — No tool can tell you your tokenomics are broken. That requires human review.
- Oracle manipulation — Price feed attacks require simulation against real market conditions.
- Governance attacks — Social engineering and vote buying are off-chain problems.
- Private key compromise — Step Finance lost $27.3M to compromised executive devices. No code audit prevents stolen keys.
- Novel zero-days — By definition, these evade pattern-matching tools.
This is why you still need a professional audit. But this pipeline ensures your auditor spends time on the hard problems, not the obvious ones.
The ROI Math
- Average Solana audit cost: $50K-$150K
- Average time saved by pre-audit pipeline: 30-40% of audit hours
- Cost of this pipeline: $0 (all tools are free/open-source)
- Q1 2026 losses from preventable bugs: ~$80M+ (conservative estimate)
Run the pipeline. Catch the easy bugs. Let your auditor focus on what actually requires human intelligence.
This is part of an ongoing series on DeFi security research. Previous entries covered Aave's CAPO oracle incident, the SagaEVM precompile exploit, and custom detector development.
Top comments (0)