Supply Chain Attacks Are Real and Your Pipeline Is a Target
by Arif Ikhsanudin, Backend Developer
What Happened to Codecov and Why Your Pipeline Is Similar
In April 2021, attackers compromised Codecov's bash uploader script — a tool used by thousands of teams in their CI pipelines to upload coverage reports. The malicious version of the script exfiltrated CI environment variables (which contain credentials, tokens, and secrets) to an attacker-controlled server. Teams that used the script in their pipelines had their credentials harvested silently. Among the affected: Twilio, HashiCorp, Rapid7, and hundreds of other organizations.
The attack worked because the pipeline was trusted to execute code from third parties without scrutiny. The Codecov script ran with access to the same environment variables as every other pipeline step — including production credentials, cloud provider tokens, and private repository access tokens.
Your pipeline almost certainly does the same thing. Every GitHub Action you reference, every Docker base image you pull, every Maven dependency your build downloads is third-party code executing in your pipeline environment. The trust you're extending is often implicit and unexamined.
The Attack Surfaces
GitHub Actions (and equivalent plugins) are the most direct attack surface. When you reference uses: some-org/some-action@v2, you're executing code from that repository. If the action's repository is compromised, or if the tag v2 is moved to a different (malicious) commit, your pipeline runs the attacker's code.
# Dangerous: tags can be moved to different commits
- uses: actions/checkout@v4
# Safer: pinned to a specific commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Commit SHAs are immutable. A tag can be moved; a SHA cannot. Pinning to a SHA means that even if the action's repository is compromised and tags are moved, your pipeline continues using the audited version.
Docker base images carry the same risk. An attacker who gains write access to a Docker Hub account can push a malicious image to a popular tag — node:20-alpine or eclipse-temurin:21-jre. Your pipeline pulls the image by tag, not by digest, and executes the malicious version.
# Vulnerable to tag mutation:
FROM eclipse-temurin:21-jre
# Pinned to a specific image digest:
FROM eclipse-temurin:21-jre@sha256:a1b2c3d4e5f6... # Cannot be mutated
Dependencies from public registries are the broadest attack surface and the hardest to defend. Typosquatting attacks (a package named com.myorg.utlils instead of com.myorg.utils) have been used in real attacks against Maven, npm, and PyPI ecosystems. Compromised legitimate packages (the event-stream npm incident, the ua-parser-js compromise) execute malicious code when installed.
Mitigations That Are Practical at Team Scale
Pin your actions to commit SHAs. This is the most impactful change for GitHub Actions users. Tools like pin-github-action or Dependabot's action update management can automate keeping pinned SHAs current:
# .github/dependabot.yml
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Dependabot will open PRs to update pinned SHA references
Use a private artifact repository. Nexus, Artifactory, or AWS CodeArtifact act as a proxy between your build and the public registry. Dependencies are pulled through the proxy, which can be configured to block unknown packages, enforce allowlists, and scan incoming artifacts before caching them. This doesn't prevent compromise of allowed packages, but it prevents typosquatting and makes dependency changes visible.
Run SBOM (Software Bill of Materials) generation and scanning. Syft generates an SBOM from your container image or dependency tree; Grype scans it for known vulnerabilities:
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:${{ github.sha }}
- name: Scan for vulnerabilities
uses: anchore/scan-action@v3
with:
image: myapp:${{ github.sha }}
fail-build: true
severity-cutoff: high
Running SBOM generation on every build gives you a point-in-time inventory of what's in your images. Running it against a fixed baseline lets you detect unexpected additions.
Minimize pipeline permissions. Supply chain attacks succeed because the compromised code runs with broad permissions. If your pipeline's OIDC role has permission only to push to ECR and update a single ECS service, even a fully compromised action can only do those two things. Defense in depth: reduce trust in third-party code and reduce what compromised code can do.
Review action source code before first use. For actions from outside the major vendors (GitHub, HashiCorp, AWS, Docker), read the code before adding it. Most malicious actions are not subtle — they exfiltrate environment variables or call out to external URLs. A 15-minute code review before adding a new action is proportionate to the risk.
The supply chain is a real attack surface used in real attacks against real organizations. Treating it as such is not paranoia — it's appropriate threat modeling for a system that executes third-party code with production credentials on every commit.