All articles
OpSecTutorial

CI/CD Pipeline Hardening for Web3: Stop Deploying Malicious Contracts Through Your Own Workflows

July 1, 2026·14 min read

Your CI/CD pipeline is a privileged execution environment with access to deployer keys, RPC endpoints, and production infrastructure. An attacker who compromises a workflow doesn't need to hack your wallet — they just need to submit a PR. Here's how to lock it down.

Intel source: The Red Guild · DevSecOops HandbookView original →

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_target with secrets access, it's game over.
  • Dependency poisoning. A compromised npm/pip/cargo dependency runs code during npm install or pip install inside 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 printenv or echo \$SECRET | base64 in 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_actions

Top 5 Checkov findings in Web3 repos

  1. CKV_GHA_1: Suspicious use of pull_request_target with secrets. This is the most dangerous pattern. pull_request_target runs in the context of the base repository — meaning it has access to all secrets. Never use it with untrusted PR code checkout.
  2. CKV_GHA_2: Workflow does not pin action versions to a SHA. Using @v3 or @main means an attacker who compromises the action repo can inject code into your pipeline. Pin everything to commit SHAs.
  3. CKV_GHA_3: GitHub Actions runner uses excessive permissions. Default GITHUB_TOKEN has write access. Set permissions: read-all as default and escalate only where needed.
  4. 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.
  5. 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 more

Pattern 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 available

Pattern 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  # v1

Pattern 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' environment

Phase 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 persistence

The Web3 CI/CD Security Checklist

Every CI/CD pipeline handling smart contract deployments should pass these checks:

#CheckToolSeverity
1No pull_request_target with checkout of PR codeCheckov, OctoscanCritical
2Deployer keys scoped to environment with approvalManualCritical
3All actions pinned to SHA, not tagsCheckovCritical
4GITHUB_TOKEN set to read-all by defaultCheckov, OctoscanHigh
5No script injection via ${{ github.event.* }} in shellSemgrep, OctoscanHigh
6Secrets scoped to specific steps, not entire jobManualHigh
7No self-hosted runners for public reposCheckovHigh
8Build provenance (SLSA) enabled for releasesCheckovMedium
9semgrep secrets scan in CI for Web3 key patternsSemgrepMedium
10Dependency review enabled for PRsGitHub nativeMedium

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.

Delta V Intel pipelineGenerated and verified through the Delta V intelligence system.

Explore IntelHub →

Want high-signal intel like this in your inbox?

Get in touch