Tools: Update: Building a Zero-to-Production Solana Security Pipeline in 2026: Trident Fuzzing + Sec3 X-ray + AI Audit Agents in One GitHub Action
Building a Zero-to-Production Solana Security Pipeline in 2026: Trident Fuzzing + Sec3 X-ray + AI Audit Agents in One GitHub Action
Why Three Layers? The Detection Gap Problem
Layer 1: Trident Stateful Fuzzing
Writing an Effective Fuzz Harness
Key MGF Patterns to Fuzz
Running
Layer 2: Sec3 X-ray Static Analysis
GitHub Integration
What X-ray Catches That Fuzzing Misses
Layer 3: AI Audit Agent (RAG-Powered Review)
Option A: SOLSEC (Hosted)
Option B: Build Your Own with Claude + Solana Context
The Unified Pipeline
Real-World Results
Cost and Performance
Getting Started Checklist
Conclusion After reviewing over 40 Solana program exploits from Q1 2026 — including the $27.3M Step Finance key compromise, the $25M Resolv Labs mint logic abuse, and the $2.7M Solv Protocol reentrancy double-mint — one pattern keeps recurring: the exploit would have been caught by at least one automated tool, but the project had zero continuous security integration. More than 70% of exploited contracts in the past year had at least one professional audit. The audits weren't wrong — they were stale. Code changed. Assumptions drifted. Nobody re-checked. This article builds a complete, copy-paste-ready CI/CD security pipeline for Solana programs that layers three complementary tools: By the end, every git push to your Anchor project triggers all three — and blocks merges on any High/Critical finding. No single tool catches everything. Here's empirical data from auditing 12 Anchor programs across our team in 2025–2026: No single column is all-green. That's why you layer them. Trident is the most mature Solana fuzzer in 2026. Its Manually Guided Fuzzing (MGF) feature lets you define attack scenarios — not just random inputs — that systematically probe common exploit paths. This creates trident-tests/ with scaffolded fuzz harnesses. Most developers write minimal harnesses. Here's a pattern that actually catches real bugs — targeting the stale-account-after-CPI class that static analyzers miss: Sec3's X-ray scanner covers 50+ vulnerability classes through static analysis. 1. Missing has_one constraint on authority: 2. init_if_needed without re-validation: 3. Unchecked program ID in CPI: AI agents catch contextual bugs that neither fuzzing nor static analysis can reason about. We ran this pipeline against 8 production Anchor programs: 6 out of 8 findings were caught by only one layer. Without the full pipeline, you'd miss 75% of the bugs. The average Solana exploit in Q1 2026 cost $12.5M. A $30/month CI pipeline is the best insurance in crypto. The Solana security tooling landscape in 2026 has matured enough that there's no excuse for not running automated security checks on every commit. Trident catches the runtime logic bugs. Sec3 catches the structural constraints. AI agents catch the contextual anti-patterns. Together, they form a defense-in-depth pipeline that costs less than a team lunch. The projects getting hacked aren't the ones with bad auditors — they're the ones where security is a checkbox instead of a pipeline. Build the pipeline. Run it on every push. Sleep better. Previously in this series: Arbitrary External Calls, Solana MEV Defense, CrossCurve bridge exploit Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
# Install Trident CLI
cargo -weight: 500;">install trident-cli # Initialize in your Anchor project
cd my-anchor-program
trident init
# Install Trident CLI
cargo -weight: 500;">install trident-cli # Initialize in your Anchor project
cd my-anchor-program
trident init
# Install Trident CLI
cargo -weight: 500;">install trident-cli # Initialize in your Anchor project
cd my-anchor-program
trident init
// trident-tests/fuzz_tests/fuzz_0/test_fuzz.rs
use trident_client::fuzzing::*;
use my_program::state::*;
use my_program::instructions::*; #[derive(Arbitrary, Debug)]
pub struct DepositAndBorrowData { pub deposit_amount: u64, pub borrow_amount: u64, pub intermediate_cpi: bool,
} impl FuzzInstruction for DepositAndBorrowData { fn check( &self, pre_state: &AccountSnapshot, post_state: &AccountSnapshot, _accounts: &FuzzAccounts, ) -> Result<()> { let collateral = post_state.get_account::<VaultAccount>("vault")?; let debt = post_state.get_account::<DebtAccount>("debt")?; assert!( debt.borrowed <= collateral.deposited * COLLATERAL_RATIO / 100, "INVARIANT VIOLATED: borrowed {} exceeds collateral limit {}", debt.borrowed, collateral.deposited * COLLATERAL_RATIO / 100 ); Ok(()) }
}
// trident-tests/fuzz_tests/fuzz_0/test_fuzz.rs
use trident_client::fuzzing::*;
use my_program::state::*;
use my_program::instructions::*; #[derive(Arbitrary, Debug)]
pub struct DepositAndBorrowData { pub deposit_amount: u64, pub borrow_amount: u64, pub intermediate_cpi: bool,
} impl FuzzInstruction for DepositAndBorrowData { fn check( &self, pre_state: &AccountSnapshot, post_state: &AccountSnapshot, _accounts: &FuzzAccounts, ) -> Result<()> { let collateral = post_state.get_account::<VaultAccount>("vault")?; let debt = post_state.get_account::<DebtAccount>("debt")?; assert!( debt.borrowed <= collateral.deposited * COLLATERAL_RATIO / 100, "INVARIANT VIOLATED: borrowed {} exceeds collateral limit {}", debt.borrowed, collateral.deposited * COLLATERAL_RATIO / 100 ); Ok(()) }
}
// trident-tests/fuzz_tests/fuzz_0/test_fuzz.rs
use trident_client::fuzzing::*;
use my_program::state::*;
use my_program::instructions::*; #[derive(Arbitrary, Debug)]
pub struct DepositAndBorrowData { pub deposit_amount: u64, pub borrow_amount: u64, pub intermediate_cpi: bool,
} impl FuzzInstruction for DepositAndBorrowData { fn check( &self, pre_state: &AccountSnapshot, post_state: &AccountSnapshot, _accounts: &FuzzAccounts, ) -> Result<()> { let collateral = post_state.get_account::<VaultAccount>("vault")?; let debt = post_state.get_account::<DebtAccount>("debt")?; assert!( debt.borrowed <= collateral.deposited * COLLATERAL_RATIO / 100, "INVARIANT VIOLATED: borrowed {} exceeds collateral limit {}", debt.borrowed, collateral.deposited * COLLATERAL_RATIO / 100 ); Ok(()) }
}
fn fuzz_iteration(fuzz_data: &mut FuzzData) -> Result<()> { // Attack Pattern 1: Deposit → CPI to external program → Borrow fuzz_data.execute_sequence(&[ FuzzAction::Deposit(deposit_data), FuzzAction::ExternalCPI(cpi_data), FuzzAction::Borrow(borrow_data), ])?; // Attack Pattern 2: Initialize → Close → Reinitialize fuzz_data.execute_sequence(&[ FuzzAction::Initialize(init_data), FuzzAction::CloseAccount(close_data), FuzzAction::Initialize(reinit_data), ])?; // Attack Pattern 3: Rapid deposit/withdraw cycles for _ in 0..100 { fuzz_data.execute_sequence(&[ FuzzAction::Deposit(small_amount), FuzzAction::Withdraw(small_amount), ])?; } Ok(())
}
fn fuzz_iteration(fuzz_data: &mut FuzzData) -> Result<()> { // Attack Pattern 1: Deposit → CPI to external program → Borrow fuzz_data.execute_sequence(&[ FuzzAction::Deposit(deposit_data), FuzzAction::ExternalCPI(cpi_data), FuzzAction::Borrow(borrow_data), ])?; // Attack Pattern 2: Initialize → Close → Reinitialize fuzz_data.execute_sequence(&[ FuzzAction::Initialize(init_data), FuzzAction::CloseAccount(close_data), FuzzAction::Initialize(reinit_data), ])?; // Attack Pattern 3: Rapid deposit/withdraw cycles for _ in 0..100 { fuzz_data.execute_sequence(&[ FuzzAction::Deposit(small_amount), FuzzAction::Withdraw(small_amount), ])?; } Ok(())
}
fn fuzz_iteration(fuzz_data: &mut FuzzData) -> Result<()> { // Attack Pattern 1: Deposit → CPI to external program → Borrow fuzz_data.execute_sequence(&[ FuzzAction::Deposit(deposit_data), FuzzAction::ExternalCPI(cpi_data), FuzzAction::Borrow(borrow_data), ])?; // Attack Pattern 2: Initialize → Close → Reinitialize fuzz_data.execute_sequence(&[ FuzzAction::Initialize(init_data), FuzzAction::CloseAccount(close_data), FuzzAction::Initialize(reinit_data), ])?; // Attack Pattern 3: Rapid deposit/withdraw cycles for _ in 0..100 { fuzz_data.execute_sequence(&[ FuzzAction::Deposit(small_amount), FuzzAction::Withdraw(small_amount), ])?; } Ok(())
}
trident fuzz run-hfuzz fuzz_0 -- -n 1000000 --timeout 10
trident fuzz coverage fuzz_0
trident fuzz run-hfuzz fuzz_0 -- -n 1000000 --timeout 10
trident fuzz coverage fuzz_0
trident fuzz run-hfuzz fuzz_0 -- -n 1000000 --timeout 10
trident fuzz coverage fuzz_0
name: Sec3 X-ray Scan
on: [push, pull_request] jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Sec3 X-ray Scan uses: sec3dev/x-ray-action@v2 with: project-path: programs/my-program severity-threshold: high api-key: ${{ secrets.SEC3_API_KEY }} - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: sec3-results.sarif
name: Sec3 X-ray Scan
on: [push, pull_request] jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Sec3 X-ray Scan uses: sec3dev/x-ray-action@v2 with: project-path: programs/my-program severity-threshold: high api-key: ${{ secrets.SEC3_API_KEY }} - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: sec3-results.sarif
name: Sec3 X-ray Scan
on: [push, pull_request] jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Sec3 X-ray Scan uses: sec3dev/x-ray-action@v2 with: project-path: programs/my-program severity-threshold: high api-key: ${{ secrets.SEC3_API_KEY }} - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: sec3-results.sarif
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
} // ✅ FIXED
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut, has_one = authority)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
}
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
} // ✅ FIXED
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut, has_one = authority)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
}
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
} // ✅ FIXED
#[derive(Accounts)]
pub struct UpdateConfig<'info> { #[account(mut, has_one = authority)] pub config: Account<'info, Config>, pub authority: Signer<'info>,
}
// ❌ VULNERABLE — attacker can re-initialize with different authority
#[account(init_if_needed, payer = user, space = 8 + UserAccount::INIT_SPACE)]
pub user_account: Account<'info, UserAccount>,
// ❌ VULNERABLE — attacker can re-initialize with different authority
#[account(init_if_needed, payer = user, space = 8 + UserAccount::INIT_SPACE)]
pub user_account: Account<'info, UserAccount>,
// ❌ VULNERABLE — attacker can re-initialize with different authority
#[account(init_if_needed, payer = user, space = 8 + UserAccount::INIT_SPACE)]
pub user_account: Account<'info, UserAccount>,
// ❌ VULNERABLE
/// CHECK: Program to CPI into
pub target_program: UncheckedAccount<'info>, // ✅ FIXED
#[account(address = target_program::ID)]
pub target_program: Program<'info, TargetProgram>,
// ❌ VULNERABLE
/// CHECK: Program to CPI into
pub target_program: UncheckedAccount<'info>, // ✅ FIXED
#[account(address = target_program::ID)]
pub target_program: Program<'info, TargetProgram>,
// ❌ VULNERABLE
/// CHECK: Program to CPI into
pub target_program: UncheckedAccount<'info>, // ✅ FIXED
#[account(address = target_program::ID)]
pub target_program: Program<'info, TargetProgram>,
solsec audit ./programs/my-program/src/lib.rs \ --output report.json \ --severity-threshold medium
solsec audit ./programs/my-program/src/lib.rs \ --output report.json \ --severity-threshold medium
solsec audit ./programs/my-program/src/lib.rs \ --output report.json \ --severity-threshold medium
import anthropic
import json
from pathlib import Path SOLANA_AUDIT_CONTEXT = """
You are a Solana smart contract security auditor. Check for:
1. Missing signer checks on authority accounts
2. PDA seed collisions (same seeds for different logical entities)
3. Stale account reads after CPI (reload accounts after invoke())
4. Token-2022 transfer hook validation gaps
5. Incorrect remaining_accounts iteration
6. Rent-exempt assumptions on closeable accounts
7. Missing realloc checks on dynamic-size accounts
8. Integer overflow in fee calculations (even with checked_math)
9. Flash loan attack vectors on price-dependent operations
10. Cross-program invocation to unvalidated program IDs For each finding, provide:
- Severity (Critical/High/Medium/Low/Info)
- Exact line reference
- Exploit scenario
- Recommended fix with code
""" def audit_file(file_path: str) -> dict: client = anthropic.Anthropic() code = Path(file_path).read_text() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=SOLANA_AUDIT_CONTEXT, messages=[{"role": "user", "content": f"Audit this Solana program:\n\n```
{% endraw %}
rust\n{code}\n
{% raw %}
```"}] ) return parse_findings(response.content[0].text) if __name__ == "__main__": import sys results = audit_file(sys.argv[1]) print(json.dumps(results, indent=2)) critical = [f for f in results["findings"] if f["severity"] in ["Critical", "High"]] sys.exit(1 if critical else 0)
import anthropic
import json
from pathlib import Path SOLANA_AUDIT_CONTEXT = """
You are a Solana smart contract security auditor. Check for:
1. Missing signer checks on authority accounts
2. PDA seed collisions (same seeds for different logical entities)
3. Stale account reads after CPI (reload accounts after invoke())
4. Token-2022 transfer hook validation gaps
5. Incorrect remaining_accounts iteration
6. Rent-exempt assumptions on closeable accounts
7. Missing realloc checks on dynamic-size accounts
8. Integer overflow in fee calculations (even with checked_math)
9. Flash loan attack vectors on price-dependent operations
10. Cross-program invocation to unvalidated program IDs For each finding, provide:
- Severity (Critical/High/Medium/Low/Info)
- Exact line reference
- Exploit scenario
- Recommended fix with code
""" def audit_file(file_path: str) -> dict: client = anthropic.Anthropic() code = Path(file_path).read_text() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=SOLANA_AUDIT_CONTEXT, messages=[{"role": "user", "content": f"Audit this Solana program:\n\n```
{% endraw %}
rust\n{code}\n
{% raw %}
```"}] ) return parse_findings(response.content[0].text) if __name__ == "__main__": import sys results = audit_file(sys.argv[1]) print(json.dumps(results, indent=2)) critical = [f for f in results["findings"] if f["severity"] in ["Critical", "High"]] sys.exit(1 if critical else 0)
import anthropic
import json
from pathlib import Path SOLANA_AUDIT_CONTEXT = """
You are a Solana smart contract security auditor. Check for:
1. Missing signer checks on authority accounts
2. PDA seed collisions (same seeds for different logical entities)
3. Stale account reads after CPI (reload accounts after invoke())
4. Token-2022 transfer hook validation gaps
5. Incorrect remaining_accounts iteration
6. Rent-exempt assumptions on closeable accounts
7. Missing realloc checks on dynamic-size accounts
8. Integer overflow in fee calculations (even with checked_math)
9. Flash loan attack vectors on price-dependent operations
10. Cross-program invocation to unvalidated program IDs For each finding, provide:
- Severity (Critical/High/Medium/Low/Info)
- Exact line reference
- Exploit scenario
- Recommended fix with code
""" def audit_file(file_path: str) -> dict: client = anthropic.Anthropic() code = Path(file_path).read_text() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=SOLANA_AUDIT_CONTEXT, messages=[{"role": "user", "content": f"Audit this Solana program:\n\n```
{% endraw %}
rust\n{code}\n
{% raw %}
```"}] ) return parse_findings(response.content[0].text) if __name__ == "__main__": import sys results = audit_file(sys.argv[1]) print(json.dumps(results, indent=2)) critical = [f for f in results["findings"] if f["severity"] in ["Critical", "High"]] sys.exit(1 if critical else 0)
name: Solana Security Pipeline
on: push: branches: [main, develop] pull_request: branches: [main] jobs: static-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Cargo Clippy run: cargo clippy -- -D warnings - name: Cargo Audit run: cargo audit - name: Sec3 X-ray uses: sec3dev/x-ray-action@v2 with: project-path: programs/ severity-threshold: high fuzz-testing: runs-on: ubuntu-latest needs: static-analysis steps: - uses: actions/checkout@v4 - name: Install Solana + Anchor run: | sh -c "$(-weight: 500;">curl -sSfL https://release.anza.xyz/stable/-weight: 500;">install)" cargo -weight: 500;">install ---weight: 500;">git https://github.com/coral-xyz/anchor anchor-cli - name: Install Trident run: cargo -weight: 500;">install trident-cli - name: Run Fuzz Tests run: trident fuzz run-hfuzz fuzz_0 -- -n 500000 --timeout 10 timeout-minutes: 10 ai-review: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: AI Security Audit env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: python scripts/ai_audit.py programs/my-program/src/lib.rs security-gate: needs: [static-analysis, fuzz-testing, ai-review] if: always() runs-on: ubuntu-latest steps: - name: Check Results run: | if [ "${{ needs.static-analysis.result }}" != "success" ] || \ [ "${{ needs.fuzz-testing.result }}" != "success" ]; then echo "::error::Security pipeline failed" exit 1 fi
name: Solana Security Pipeline
on: push: branches: [main, develop] pull_request: branches: [main] jobs: static-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Cargo Clippy run: cargo clippy -- -D warnings - name: Cargo Audit run: cargo audit - name: Sec3 X-ray uses: sec3dev/x-ray-action@v2 with: project-path: programs/ severity-threshold: high fuzz-testing: runs-on: ubuntu-latest needs: static-analysis steps: - uses: actions/checkout@v4 - name: Install Solana + Anchor run: | sh -c "$(-weight: 500;">curl -sSfL https://release.anza.xyz/stable/-weight: 500;">install)" cargo -weight: 500;">install ---weight: 500;">git https://github.com/coral-xyz/anchor anchor-cli - name: Install Trident run: cargo -weight: 500;">install trident-cli - name: Run Fuzz Tests run: trident fuzz run-hfuzz fuzz_0 -- -n 500000 --timeout 10 timeout-minutes: 10 ai-review: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: AI Security Audit env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: python scripts/ai_audit.py programs/my-program/src/lib.rs security-gate: needs: [static-analysis, fuzz-testing, ai-review] if: always() runs-on: ubuntu-latest steps: - name: Check Results run: | if [ "${{ needs.static-analysis.result }}" != "success" ] || \ [ "${{ needs.fuzz-testing.result }}" != "success" ]; then echo "::error::Security pipeline failed" exit 1 fi
name: Solana Security Pipeline
on: push: branches: [main, develop] pull_request: branches: [main] jobs: static-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Cargo Clippy run: cargo clippy -- -D warnings - name: Cargo Audit run: cargo audit - name: Sec3 X-ray uses: sec3dev/x-ray-action@v2 with: project-path: programs/ severity-threshold: high fuzz-testing: runs-on: ubuntu-latest needs: static-analysis steps: - uses: actions/checkout@v4 - name: Install Solana + Anchor run: | sh -c "$(-weight: 500;">curl -sSfL https://release.anza.xyz/stable/-weight: 500;">install)" cargo -weight: 500;">install ---weight: 500;">git https://github.com/coral-xyz/anchor anchor-cli - name: Install Trident run: cargo -weight: 500;">install trident-cli - name: Run Fuzz Tests run: trident fuzz run-hfuzz fuzz_0 -- -n 500000 --timeout 10 timeout-minutes: 10 ai-review: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: AI Security Audit env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: python scripts/ai_audit.py programs/my-program/src/lib.rs security-gate: needs: [static-analysis, fuzz-testing, ai-review] if: always() runs-on: ubuntu-latest steps: - name: Check Results run: | if [ "${{ needs.static-analysis.result }}" != "success" ] || \ [ "${{ needs.fuzz-testing.result }}" != "success" ]; then echo "::error::Security pipeline failed" exit 1 fi - Trident — stateful fuzzing that catches logic bugs and arithmetic edge cases
- Sec3 X-ray — static analysis covering 50+ vulnerability classes
- AI audit agents (SOLSEC / Claude-based scanners) — RAG-powered review for Solana-specific anti-patterns - [ ] Install Trident: cargo -weight: 500;">install trident-cli && trident init
- [ ] Write MGF harnesses for your top 3 invariants
- [ ] Configure Sec3 X-ray GitHub Action
- [ ] Set up AI audit script with your preferred provider
- [ ] Add the unified pipeline YAML
- [ ] Set severity-threshold: high for merge blocking
- [ ] Run your first full scan and triage the findings