DevSecOps

Securing CI/CD Pipelines from Supply Chain Attacks

CI/CD pipelines are the new attack surface. From poisoned dependencies to compromised build tools, here's how to lock down your software delivery infrastructure.

Bob
DevSecOps Engineer
6 min read

Your Pipeline Is a Target

If you're an attacker trying to compromise a software company, where do you focus? Not on the finished product behind firewalls and WAFs. You go to the factory floor — the CI/CD pipeline where code is built, tested, and deployed.

CI/CD pipelines have everything an attacker wants: source code access, build credentials, deployment keys, artifact signing certificates, and a direct path to production. Compromise the pipeline, and you compromise everything downstream.

The attacks of 2021 have made this painfully clear. SolarWinds was a build system compromise. Codecov was a CI tool compromise. Kaseya leveraged the software distribution mechanism. The pattern is unmistakable — attackers are shifting left, targeting the development and delivery infrastructure itself.

The CI/CD Attack Surface

Build Dependencies

Every npm install, pip install, or maven dependency:resolve in your pipeline pulls code from external registries. That code runs in your build environment with whatever permissions the build process has. A single compromised dependency means attacker code execution inside your pipeline.

Third-Party Actions and Plugins

GitHub Actions, Jenkins plugins, CircleCI Orbs, GitLab CI templates — these are code that runs in your pipeline, typically with full access to your repository and secrets. How many of your GitHub Actions workflows reference third-party actions by tag rather than SHA? That tag can be moved to point to malicious code at any time.

Build Tool Infrastructure

CI runners, build agents, and the orchestration platform itself are software with their own vulnerabilities. A compromised Jenkins server gives an attacker access to every build that runs on it.

Secrets and Credentials

Pipelines need credentials: API keys for deployment, registry tokens for publishing, signing keys for artifacts, cloud provider access for infrastructure provisioning. These secrets are the crown jewels. If they're accessible to the build process, they're accessible to anything running in that process — including compromised dependencies.

Pipeline Configuration

Pipeline definitions (.github/workflows/*.yml, Jenkinsfile, .gitlab-ci.yml) are code that defines what runs, where, and with what permissions. If an attacker can modify pipeline configuration through a pull request, they can inject arbitrary commands into your build.

Practical Controls

1. Pin Everything

Don't reference dependencies, actions, or base images by mutable tags. Pin to exact versions and verify checksums.

Instead of:

- uses: actions/checkout@v2

Use:

- uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0

Pinning by SHA ensures the code you reference today is the code that runs tomorrow. Tags and versions can be mutated; commit hashes cannot.

2. Minimize Secret Exposure

  • Use CI/CD platform features to scope secrets to specific jobs and steps
  • Never expose production credentials to pull request builds
  • Rotate secrets regularly and after any suspected compromise
  • Use short-lived tokens where possible (OIDC federation instead of long-lived keys)
  • Audit which secrets each pipeline step can access

3. Isolate Build Environments

  • Run builds in ephemeral containers that are destroyed after each job
  • Don't share build agents across security boundaries (e.g., don't build open-source PRs on the same agents as production deployments)
  • Use separate build environments for different trust levels

4. Restrict Pull Request Builds

Pull requests from external contributors (or even internal developers in untrusted branches) can modify pipeline configuration. Controls include:

  • Require approval before running CI on external PRs
  • Use pull_request_target cautiously in GitHub Actions — it runs with the privileges of the base branch
  • Don't allow pipeline configuration changes in PRs without review

5. Verify Artifact Integrity

  • Sign build artifacts with keys that aren't accessible to the build process itself (use dedicated signing infrastructure)
  • Generate and store provenance attestations for every build
  • Verify artifact signatures before deployment

6. Implement Network Controls

Build environments should have restricted network access:

  • Allowlist outbound connections to known registries and services
  • Block access to internal networks from build agents
  • Monitor for unexpected network connections during builds

7. Audit and Monitor

  • Log every command executed in your pipeline
  • Monitor for deviations from expected build behavior (new network connections, unexpected file access, unusual resource consumption)
  • Alert on changes to pipeline configuration files
  • Review third-party action and plugin updates before adopting them

Build Provenance

The SLSA framework defines levels of build provenance assurance:

  • SLSA 1 — Documentation of the build process (basic provenance)
  • SLSA 2 — Tamper-resistant provenance generated by a hosted build service
  • SLSA 3 — Hardened build platform with additional controls against tampering
  • SLSA 4 — Hermetic, reproducible builds with two-party review

Most organizations today don't meet SLSA 1. Simply documenting what your build process does — what inputs it takes, what tools it uses, what outputs it produces — is a meaningful first step.

Common Mistakes

Trusting CI environment variables blindly. GitHub Actions exposes GITHUB_TOKEN in every workflow. Actions that use this token can make API calls on behalf of the repository. A compromised action can use this to modify code, create releases, or access private data.

Running curl | bash in pipelines. Downloading and executing scripts without integrity verification is common in setup steps. If the server is compromised (like Codecov), your pipeline executes attacker code.

Caching dependencies without verification. Pipeline caches speed up builds but can serve stale or tampered content. Always verify dependency integrity after cache restoration.

Using latest tags for base images. A Docker base image tagged latest can change at any time. Pin to digest hashes for reproducibility and security.

How Safeguard.sh Helps

Safeguard.sh integrates directly into your CI/CD pipeline to provide continuous supply chain security checks during every build. The platform scans your dependency tree, container base images, and build tool configurations for known vulnerabilities, malicious packages, and risky patterns — catching issues before they reach production.

The platform generates build-time SBOMs that capture the exact composition of every artifact, creating an auditable record of what went into each release. This provenance data is essential for incident response, compliance, and customer transparency — and it's generated automatically without pipeline modifications.

Safeguard.sh also monitors your pipeline configurations for security anti-patterns: unpinned dependencies, overly permissive secret access, mutable action references, and unrestricted network access. These checks ensure that your pipeline hardening doesn't degrade over time as teams add new steps and workflows.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.