Back to Blog

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

|Odin Scan Team
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.