Smart Contract CI/CD Pipeline: Foundry, Slither and Fuzzing
Quick answer. Run a GitHub Actions workflow on a pinned Foundry version that formats, builds, tests, fuzzes, runs Slither and diffs gas and storage layout on every pull request. Deploy through a multisig proposal, never a key stored in CI.
Video transcript
Smart contract CI should test, fuzz and scan every pull request, then deploy through a multisig, never a key in CI.
Your pipeline is a target too. In March 2025, attackers repointed a GitHub action's tags to a commit that printed CI secrets into logs.
Run formatting, tests and static analysis on every pull request. Fuzz deeper nightly, check storage layout on proxy upgrades, and deploy only approved releases.
Pin every layer, or the pipeline cannot be audited: the Foundry release, actions by commit SHA, the Slither version, and a read-only token.
Foundry's CI example uses ten thousand fuzz runs, versus 256 in a default profile. Fix the seed on pull requests, and run longer campaigns nightly.
Slither fails on any finding by default. For code scanning alerts, set fail-on to none, upload the SARIF, and require results in a branch ruleset.
Never keep a deployer key in CI, where one compromised action could reach every secret. Let CI prepare transactions, and owners confirm them in a Safe.
GitHub keeps logs and artifacts 90 days by default, so archive release evidence yourself: versions, fuzz seeds, SARIF reports, storage layouts and approvals.
Guardian audits include invariant suites that can run in this pipeline, and our pentests test whether a compromised pipeline can sign for you. Read on, or talk to Guardian.
A smart contract repository needs a stricter pipeline than most software. Once code is live, a missed bug sits next to real funds.
The pipeline is attack surface too. In March 2025, attackers repointed version tags of the tj-actions/changed-files action to a malicious commit that printed CI secrets into workflow logs, GitHub's advisory says.
This template covers a Foundry project on GitHub Actions, with notes for Hardhat. Every command comes from the tool's own documentation.
Pipeline Stages
Split the work by what each stage protects and when it runs.
| Stage | When | Tools |
|---|---|---|
| Format, lint, build | Every pull request | forge fmt, forge lint, forge build |
| Tests and gas | Every pull request | forge test, forge snapshot, forge coverage |
| Static analysis | Every pull request | Slither, Aderyn |
| Deep fuzzing | Nightly and before release | forge test with a CI profile |
| Upgrade checks | Any change to a proxied contract | forge inspect, OpenZeppelin validate |
| Deployment | Approved releases only | forge script, GitHub environments, Safe |
Pin everything
A pipeline that runs different tools each day cannot be audited. Pin each layer.
- Foundry: foundry-toolchain installs
stableunlessversionnames a release. - Binaries:
gh attestation verifychecks a Foundry release's signed provenance. - Actions: GitHub recommends a workflow lockfile, or else full commit SHA pins.
- Slither: set
slither-version, or the action installs the latest release. - Token: give
GITHUB_TOKENthe "minimum required permissions," starting read-only.
Format, Build and Test
This core job adapts Foundry's CI guide and the foundry-toolchain README. Each action is pinned to the commit behind its release tag.
name: contracts-ci
on: pull_request
permissions:
contents: read
env:
FOUNDRY_PROFILE: ci
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
- uses: foundry-rs/foundry-toolchain@dba66c27052e63476d886f22216e119ba7e615bf # v1.9.2
with:
version: v1.9.0
- run: forge fmt --check
- run: forge lint --deny warnings
- run: forge build --sizes
- run: forge test -vvv
- run: forge snapshot --check
fmt --checkexits with 1 if any file needs formatting.lint --deny warningsfails on any warning from Forge's linter.snapshot --checkfails if a test uses more gas than recorded.
The ci profile lives in foundry.toml, as in Foundry's guide.
Coverage
Add forge coverage --report lcov for a coverage service. Foundry "does not enforce a minimum percentage," so set thresholds there.
Coverage builds disable the optimizer by default, so they differ from production. Our mutation testing guide shows full line coverage still missing planted bugs.
Hardhat projects
Hardhat 3 runs Solidity tests, including fuzz tests, without a plugin. Its hardhat test --coverage writes LCOV and HTML reports.
Slither, Aderyn and SARIF
Slither is crytic's Solidity and Vyper static analyzer. Its GitHub Action can write SARIF, which code scanning shows as alerts.
slither:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
- uses: crytic/slither-action@b52cc1cbfee9ca3e8722dd5224299d16c9a6b80f # v0.4.2
with:
slither-version: 0.11.6
sarif: results.sarif
fail-on: none
- uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
sarif_file: results.sarif
- Upload needs
fail-on: none, the README says. The default fails on any finding. - Gate merges with a ruleset that requires code scanning results, with thresholds per tool.
- Private repositories need GitHub Code Security, and the job needs
actions: read. - Foundry version: the action installs Foundry stable itself. To keep your pin, build first and set
ignore-compile.
Aderyn and forge lint
Aderyn is Cyfrin's Rust-based Solidity analyzer. It runs on Foundry and Hardhat projects without configuration and writes SARIF, among other formats.
Forge's own linter adds detectors such as unchecked calls. Record each waiver in the repository, such as a forge-lint: disable-next-line comment.
Fuzz and Invariant Jobs
Foundry's docs show 256 fuzz runs in a default profile. Their CI example raises that sharply:
[profile.ci.fuzz]
runs = 10000
seed = "0x1"
[profile.ci.invariant]
runs = 1000
depth = 1000
- Pull requests: a fixed seed keeps a failure reproducible.
- Nightly: run longer campaigns on a schedule, with more runs and depth.
- Corpus: setting
corpus_direnables coverage-guided fuzzing that builds on earlier campaigns. - Failures:
failure_persist_dirkeeps counterexamples. Turn each into a regression test. - Fork tests: foundry-toolchain's cache does not work with fuzzed fork tests unless you set a seed.
Upgrades, Dry Runs and Approvals
Check storage layout on every upgrade
A proxy keeps its state across upgrades, so the new layout must stay compatible. Foundry's guide exports each version with forge inspect <contract> storage-layout and diffs them.
For a stricter gate, OpenZeppelin's upgrades-core validate checks upgrade safety and layout compatibility, and exits non-zero on errors.
Give it a reference contract with @custom:oz-upgrades-from or --reference. Without one, storage incompatibilities go unreported.
Then rehearse the upgrade on a fork. Our change management guide covers the release process around it.
Dry-run the deployment
Without --broadcast, Foundry's forge script only simulates: it writes a dry-run file and sends nothing. Upload that file for reviewers.
Approvals and signing
Put deployment jobs behind a GitHub environment with required reviewers. Per GitHub's docs:
- Up to six users or teams can review; one approval releases the job.
- An optional setting stops whoever started a deployment from approving it.
- Environment secrets stay locked until a reviewer approves.
- On Free, Pro and Team plans, required reviewers work only in public repositories.
Keep deployer keys out of CI secrets. In GitHub's words, one compromised action "would have access to all secrets configured on your repository."
- Propose, don't sign. CI prepares the transactions; a signer proposes them to the Safe and the owners confirm.
- Hardware wallets on mainnet, as Foundry's best practices advise.
- If CI must sign, use a KMS key through OIDC, whose token is "only valid for a single job". Give it no admin role.
Verify on explorers
Foundry's forge script --verify verifies every contract in the script's receipts. Hardhat's hardhat verify covers Etherscan, Blockscout and Sourcify.
Then compare deployed bytecode with the release build, as our Foundry deployment guide shows.
Evidence Retention
After an incident, or in due diligence, someone will ask what ran before a release. Answer from records.
GitHub keeps artifacts and logs for 90 days by default. So copy release evidence to storage you control:
- Commit SHA, tool versions and Foundry profile.
- Test, fuzz and invariant results, with seeds and counterexamples.
- SARIF reports with triage notes, LCOV and the gas diff.
- Storage layouts for each upgraded contract.
- Broadcast files, which record the Git commit they ran from.
- Reviewer approvals and multisig transaction hashes.
NIST's Secure Software Development Framework (SP 800-218, version 1.1) recommends archiving each release with "integrity verification information" and "provenance data" (PS.3.1).
GitHub artifact attestations add signed provenance, so others can "verify where and how your software was built." For launch day, see our pre-mainnet security checklist.
How Guardian Helps
Guardian's smart contract audit includes invariant fuzzing: your core safety properties as executable invariants, "left runnable after the engagement." That suite can run in this pipeline.
An infrastructure pentest covers CI/CD runner permissions, artifact integrity "and whether a compromised pipeline can ship or sign on your behalf."
With Managed Security, Helix runs continuously across your stack. Only human-verified findings reach you, each with a fix PR attached.
Guardian's security operations team reviews every multisig transaction before it is signed, and every deployment and configuration change as it lands.
Frequently Asked Questions
How do I set up CI/CD for a Foundry project?
Install a pinned Foundry release with foundry-toolchain. On every pull request, check formatting, lint, build, test and compare gas. Add Slither with SARIF upload, a CI fuzz profile and a deployment job behind reviewers.
Should a deployer private key be stored in GitHub secrets?
No. GitHub warns that one compromised action can reach every repository secret. Let CI simulate and prepare transactions, and have signers approve them in a multisig from hardware wallets.
How many fuzz runs should CI use?
Foundry's CI example uses 10,000 fuzz runs, and 1,000 invariant runs at a depth of 1,000. Use a fixed seed on pull requests and run longer campaigns on a schedule.
Can Slither block a pull request?
Yes. The Slither action fails on any finding by default. To see findings as code scanning alerts, upload SARIF with fail-on set to none, then require code scanning results in a branch ruleset.
Sources
- Foundry Docs: Continuous Integration
- foundry-rs: foundry-toolchain GitHub Action
- Foundry Docs: Verifying Releases
- Foundry Docs: Testing configuration
- Foundry Docs: Linting
- Foundry Docs: Gas tracking
- Foundry Docs: Code Coverage
- Foundry Docs: Fuzz Corpus Workflow
- Foundry Docs: Upgrading Contracts
- Foundry Docs: forge fmt reference
- Foundry Docs: forge script reference
- Foundry Docs: Broadcast files
- Foundry Docs: Best Practices
- Hardhat: Writing unit tests in Solidity
- Hardhat: Computing code coverage of your tests
- Hardhat: Verifying smart contracts
- crytic: Slither, the smart contract static analyzer
- crytic: slither-action README (v0.4.2)
- crytic: slither-action entrypoint script (Foundry install)
- Cyfrin: Aderyn
- OpenZeppelin Docs: Upgrades Core and CLI
- Safe Docs: Propose and confirm transactions
- GitHub Docs: Secure use reference
- GitHub Docs: Workflow dependency lockfiles
- GitHub Docs: Deployments and environments
- GitHub Docs: OpenID Connect
- GitHub Docs: Uploading a SARIF file to GitHub
- GitHub Docs: Set code scanning merge protection
- GitHub Docs: Removing workflow artifacts
- GitHub Docs: Artifact attestations
- GitHub Advisory Database: tj-actions changed-files, CVE-2025-30066 (March 15, 2025)
- NIST: SP 800-218, Secure Software Development Framework version 1.1 (February 2022)
- Guardian: Managed Security
- Guardian: Cloud and infrastructure penetration testing
- Guardian: Security for onchain teams
Ship every release with Guardian watching
Guardian reviews every multisig transaction before it is signed and every deployment as it lands, so your pipeline's output gets a second check.