DEV Community

ohmygod
ohmygod

Posted on

The Zero-Cost Solana Security Pipeline: 7 Free Tools That Catch 90% of Anchor Vulnerabilities Before Your Auditor Does

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Key configuration:

# Trident.toml
[fuzz]
iterations = 10000
exit_upon_crash = true

[fuzz.programs]
my_protocol = "programs/my-protocol"
Enter fullscreen mode Exit fullscreen mode

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>,
}
Enter fullscreen mode Exit fullscreen mode

The audit checklist:

  • ✅ Every PDA uses seeds + bump (no manual derivation)
  • ✅ Every authority account is Signer<'info>, never just AccountInfo
  • ✅ Every has_one constraint matches a stored pubkey
  • ✅ Every program account uses constraint for 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What This Pipeline Won't Catch

Let's be honest about the gaps:

  1. Economic model flaws — No tool can tell you your tokenomics are broken. That requires human review.
  2. Oracle manipulation — Price feed attacks require simulation against real market conditions.
  3. Governance attacks — Social engineering and vote buying are off-chain problems.
  4. Private key compromise — Step Finance lost $27.3M to compromised executive devices. No code audit prevents stolen keys.
  5. 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)