CI/CD for Smart Contracts: Automated Security in Your Development Pipeline

The typical smart contract security workflow looks like this: develop for months, audit once, deploy, then hope nothing breaks. Every change after the audit, every new collateral listing, every parameter tweak, every governance proposal, lands on-chain without the same scrutiny the original code received.
This is not how the rest of software engineering works. Web applications run security scanners on every PR. Infrastructure changes go through automated policy checks. Database migrations are tested against staging environments before production.
Smart contracts should work the same way. Every PR should be scanned. Every deployment script should be tested. Every governance proposal should be simulated. Automatically, before it reaches mainnet, with no manual intervention required.
Here is how to build that pipeline.
The Minimum Viable Security Pipeline
A basic CI/CD security pipeline for smart contracts has four stages:
PR Created -> Compile + Lint -> Test Suite -> Security Scan -> Merge
If any stage fails, the PR does not merge. This catches the majority of common vulnerabilities before a human reviewer even looks at the code.
Stage 1: Compile with Strict Settings
Compiler warnings are the first line of defense. Treat them as errors.
Solidity (Foundry):
# .github/workflows/security.yml
- name: Build
run: forge build --force --deny-warnings
Rust (Anchor/CosmWasm):
- name: Build
run: cargo build --release --target wasm32-unknown-unknown
- name: Clippy
run: cargo clippy -- -D warnings
Clippy alone catches dozens of bug patterns: unused variables, redundant clones, incorrect error handling, and more. It is free. There is no reason not to run it.
Stage 2: Run the Full Test Suite
Tests should run on every PR. If they do not, you will not know when a change breaks existing behavior.
- name: Test
run: forge test -vvv --gas-report
- name: Coverage
run: forge coverage --report lcov
Set a minimum coverage threshold. 80% line coverage is a reasonable starting point for smart contracts. Functions below the threshold should be flagged for review.
For fork tests (tests that run against mainnet state):
- name: Fork Tests
env:
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
run: forge test --fork-url $ETH_RPC_URL -vvv --match-path "test/fork/*"
Stage 3: Static Analysis
Run static analysis tools that catch known vulnerability patterns.
Slither (Solidity):
- name: Slither Analysis
uses: crytic/slither-action@v0.4.0
with:
target: "src/"
slither-args: "--filter-paths 'test|script|lib' --exclude naming-convention"
fail-on: "high"
cargo-audit (Rust):
- name: Dependency Audit
run: cargo audit
These tools catch known patterns: reentrancy, unchecked return values, dangerous delegatecall usage, known vulnerable dependency versions. They are fast (seconds, not minutes) and free.
Stage 4: AI-Powered Security Scanning
Static analysis catches known patterns. It does not understand business logic, oracle composition, or protocol-specific invariants. AI-powered scanning fills that gap.
Odin Scan GitHub Action:
- name: Odin Scan
uses: odin-scan/github-action@v1
with:
api-key: ${{ secrets.ODIN_SCAN_API_KEY }}
severity-threshold: "high"
scan-path: "src/"
Odin Scan uses multiple AI models that cross-validate findings. This dramatically reduces false positives while catching semantic vulnerabilities that pattern-matching tools miss: oracle misconfiguration, access control logic gaps, unsafe arithmetic in complex calculations, and cross-function state inconsistencies.
The scan runs on every PR and posts findings as inline comments, pointing to the exact file and line with a description of the vulnerability and suggested fix.
Advanced: Governance Proposal Simulation
For protocols with governance, add a simulation step that runs before any proposal is submitted on-chain.
# Triggered when proposal scripts are modified
on:
push:
paths:
- "proposals/**"
- "scripts/governance/**"
jobs:
simulate-proposal:
runs-on: ubuntu-latest
steps:
- name: Fork and Simulate
run: |
forge script proposals/MIP-X44.sol \
--fork-url $ETH_RPC_URL \
--broadcast \
-vvv
- name: Post-Simulation Validation
run: |
forge test \
--fork-url $ETH_RPC_URL \
--match-path "test/governance/post-proposal-checks.t.sol" \
-vvv
The post-simulation validation tests should check:
- All oracle prices are within expected ranges
- All collateral factors are within safe bounds
- All role assignments are correct
- No unexpected state changes occurred
This would have caught the Moonwell oracle misconfiguration. The simulation would have shown cbETH priced at $1.12, and the post-simulation test would have failed on the oracle sanity check.
Advanced: Deployment Script Verification
Deployment scripts should be tested against forked state before mainnet execution.
deploy-verification:
runs-on: ubuntu-latest
steps:
- name: Deploy to Fork
run: |
forge script script/Deploy.s.sol \
--fork-url $ETH_RPC_URL \
-vvv
- name: Verify Deployment
run: |
forge test \
--fork-url $ETH_RPC_URL \
--match-path "test/deployment/verify.t.sol" \
-vvv
Verification tests should confirm:
- Contract addresses are correct
- Initialization parameters are set correctly
- Roles are assigned to the right addresses
- External dependencies (oracles, pools, routers) are reachable and returning expected values
Advanced: Dependency Monitoring
Dependencies change. A new version of OpenZeppelin might fix a vulnerability your contracts depend on. A Chainlink feed might be deprecated. Monitor these continuously.
# Run weekly to check for new vulnerabilities in dependencies
on:
schedule:
- cron: "0 0 * * 1" # Every Monday
jobs:
dependency-check:
runs-on: ubuntu-latest
steps:
- name: Check Solidity Dependencies
run: |
npm audit --audit-level=high
- name: Check Rust Dependencies
run: cargo audit
- name: Notify on Findings
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Dependency vulnerability detected',
body: 'Weekly dependency audit found vulnerabilities. See workflow run for details.',
labels: ['security', 'dependencies']
})
The Complete Pipeline
Putting it all together:
name: Smart Contract Security Pipeline
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Build
run: forge build --force --deny-warnings
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Unit Tests
run: forge test -vvv
- name: Fork Tests
env:
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
run: forge test --fork-url $ETH_RPC_URL --match-path "test/fork/*" -vvv
static-analysis:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Slither
uses: crytic/slither-action@v0.4.0
with:
fail-on: "high"
ai-scan:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Odin Scan
uses: odin-scan/github-action@v1
with:
api-key: ${{ secrets.ODIN_SCAN_API_KEY }}
severity-threshold: "high"
Four parallel jobs after build. If any fail, the PR is blocked. Total pipeline time: under 10 minutes for most projects.
The ROI
The cost of running this pipeline is minimal: CI/CD minutes and an Odin Scan subscription. The cost of not running it is measured in millions.
Every protocol that got hacked in 2025 had at least one stage of this pipeline missing. The Moonwell oracle misconfiguration would have been caught by the AI scan. The access control bugs that drained smaller protocols would have been caught by static analysis. The integer overflows would have been caught by strict compiler settings and Clippy.
Point-in-time audits are necessary. They are not sufficient. Continuous automated scanning is what fills the gap between audits.
Odin Scan integrates into your CI/CD pipeline in under five minutes. GitHub Actions, GitLab CI, or any CI system that supports webhooks. Every PR scanned, every finding posted as an inline comment, every vulnerability caught before it reaches mainnet. Start your free trial.