Web3 teams run CI/CD pipelines that can deploy smart contracts, push to package registries, update frontends, and execute database migrations — all unattended, all with elevated privileges. A single compromised .github/workflows/deploy.yml is a signed blank cheque. This tutorial walks through hardening your CI/CD pipeline using Checkov, workflow auditing, and opsec patterns that actually work.
The Threat Model
Before we touch tools, understand what you're defending against:
- Malicious PR injection. An attacker opens a PR that modifies a workflow file to echo secrets or execute arbitrary code. If your pipeline runs on
pull_request_targetwith secrets access, it's game over. - Dependency poisoning. A compromised npm/pip/cargo dependency runs code during
npm installorpip installinside your CI environment, exfiltrating environment variables. - Workflow hijacking. An attacker with write access to any repo in your org modifies a shared workflow or action that downstream repos consume.
- Secret exfiltration via logs. A
printenvorecho \$SECRET | base64in a workflow dumps your deployer key into publicly visible CI logs. - Runner compromise. Self-hosted GitHub Actions runners persist state between jobs. An attacker who compromises one job can carry access to the next.
Phase 1: Infrastructure-as-Code Scanning with Checkov
Checkov scans your IaC files — Terraform, CloudFormation, Kubernetes manifests, Dockerfiles, and GitHub Actions workflows — for misconfigurations. For Web3 teams, the immediate priority is GitHub Actions workflow scanning.
Install and run against your workflows
# Install via pip
pip install checkov
# Scan all GitHub Actions workflows in a repo
checkov --directory . --framework github_actions
# Scan and output as SARIF for CI integration
checkov --directory . --framework github_actions --output sarif
# Scan specific workflow files only
checkov --file .github/workflows/deploy.yml --framework github_actionsTop 5 Checkov findings in Web3 repos
- CKV_GHA_1: Suspicious use of pull_request_target with secrets. This is the most dangerous pattern.
pull_request_targetruns in the context of the base repository — meaning it has access to all secrets. Never use it with untrusted PR code checkout. - CKV_GHA_2: Workflow does not pin action versions to a SHA. Using
@v3or@mainmeans an attacker who compromises the action repo can inject code into your pipeline. Pin everything to commit SHAs. - CKV_GHA_3: GitHub Actions runner uses excessive permissions. Default
GITHUB_TOKENhas write access. Setpermissions: read-allas default and escalate only where needed. - CKV_GHA_4: Secret is passed as an environment variable globally. Every step in the job can read it. Scoping secrets to specific steps reduces blast radius.
- CKV_GHA_7: Build does not use SLSA provenance. Without build provenance, you can't verify that the deployed artifact matches the source code. Critical for Web3 where frontend integrity is user-facing security.
Phase 2: Workflow Hardening Patterns
Pattern 1: Lock down GITHUB_TOKEN
# ALWAYS start with minimal permissions
# Then escalate per-job only where needed
permissions: read-all # top-level default
jobs:
test:
runs-on: ubuntu-latest
# inherits read-all — good
deploy:
runs-on: ubuntu-latest
permissions:
contents: write # only this job gets write
id-token: write # for OIDC-based cloud auth
# now deploy has the access it needs, nothing morePattern 2: Never run untrusted code with secrets
# ❌ DANGEROUS: pull_request_target runs PR code in base context
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # checks out PR code!
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install && npm test # running untrusted code with secrets
# ✅ SAFE: use pull_request instead — runs in fork context, no secrets
on:
pull_request:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # checks out PR code in isolated context
- run: npm install && npm test # no secrets availablePattern 3: Pin all actions to commit SHAs
# ❌ Vulnerable to tag/branch hijacking
- uses: actions/checkout@v4
- uses: foundry-rs/foundry-toolchain@v1
# ✅ Pinned to immutable commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: foundry-rs/foundry-toolchain@8f1998e8558b88ef01ada84a3bd786e85e27ca15 # v1Pattern 4: Environment-based secret scoping
# Deployer keys should require explicit environment approval
# and NEVER be available to PR-triggered workflows
jobs:
deploy-mainnet:
runs-on: ubuntu-latest
environment: mainnet # requires approval + protection rules
environment:
name: mainnet
url: https://app.yourprotocol.xyz
steps:
- run: forge script Deploy.s.sol --rpc-url ${{ secrets.MAINNET_RPC }}
# MAINNET_RPC is ONLY available in the 'mainnet' environmentPhase 3: CI/CD Secret Scanning with Semgrep
Your CI/CD configuration files themselves can leak secrets — hardcoded RPC URLs in workflow files, base64-encoded deployer keys in shell scripts, Terraform backends with embedded credentials. Semgrep catches these at the source code level.
# Scan for Web3-specific secret patterns
# Create a custom Semgrep rule for your project:
# rules/web3-secrets.yaml
rules:
- id: hardcoded-rpc-url-with-key
pattern-regex: "https://.*\.alchemy\.com/v2/[a-zA-Z0-9_-]+"
message: "Hardcoded Alchemy API key detected"
severity: ERROR
languages: [generic]
- id: hardcoded-private-key
pattern-regex: "0x[a-fA-F0-9]{64}"
message: "Hardcoded Ethereum private key detected"
severity: ERROR
languages: [generic]
- id: hardcoded-mnemonic
pattern-regex: "(?:[a-z]+ ){11,23}[a-z]+"
message: "Potential BIP39 mnemonic detected"
severity: ERROR
languages: [generic]
# Run Semgrep with your custom rules
semgrep --config rules/web3-secrets.yaml .github/ scripts/Phase 4: Workflow Auditing with Octoscan (CI/CD Focus)
Octoscan's GitHub Actions analysis module examines your workflow files for insecure patterns that Checkov might miss — overly permissive if: conditions, script injection via ${{ }} expressions, and dangerous third-party action usage.
# Audit workflow security across your org
octoscan --org your-org --token $GITHUB_TOKEN --focus workflows
# Specific checks Octoscan performs:
# - Expression injection: github.event.issue.title in shell commands
# - Unpinned actions: docker:// and non-SHA action refs
# - Secrets in artifacts: workflow artifacts containing env files
# - Write access analysis: which jobs can modify the repo
# - Self-hosted runner usage: labels and persistenceThe Web3 CI/CD Security Checklist
Every CI/CD pipeline handling smart contract deployments should pass these checks:
| # | Check | Tool | Severity |
|---|---|---|---|
| 1 | No pull_request_target with checkout of PR code | Checkov, Octoscan | Critical |
| 2 | Deployer keys scoped to environment with approval | Manual | Critical |
| 3 | All actions pinned to SHA, not tags | Checkov | Critical |
| 4 | GITHUB_TOKEN set to read-all by default | Checkov, Octoscan | High |
| 5 | No script injection via ${{ github.event.* }} in shell | Semgrep, Octoscan | High |
| 6 | Secrets scoped to specific steps, not entire job | Manual | High |
| 7 | No self-hosted runners for public repos | Checkov | High |
| 8 | Build provenance (SLSA) enabled for releases | Checkov | Medium |
| 9 | semgrep secrets scan in CI for Web3 key patterns | Semgrep | Medium |
| 10 | Dependency review enabled for PRs | GitHub native | Medium |
Delta V Hardening Addendum
Beyond the checklist, these are patterns we enforce for high-stakes Web3 deployments:
- No deployer key in CI/CD, period. Use a hardware wallet with a human in the loop for mainnet deployments. CI/CD can prepare the calldata, run tests, and generate the deployment artifact — but the final signature happens off-CI.
- Ephemeral build environments. Every CI run gets a fresh container. No build cache persistence that an attacker can poison across runs.
- Dual approval for mainnet deploys. GitHub Environment protection rules require two reviewers to approve before the deploy job runs. This makes a single compromised account insufficient.
- Audit the auditors. The tools in this tutorial (Checkov, Semgrep, Trufflehog) should themselves run in CI on every PR — so a PR that disables the security scanner gets flagged before merge.
Sources: The Red Guild · CI/CD Security • DevSecOps-toolkit on GitHub • Checkov • Semgrep