Skip to main content
Dark green background, "Weak Application Security Can Cost You Millions," 3 slanted images of fingers pointing to digital locks, and a "Learn the Basics" button
5 CI/CD Mistakes That Let Supply Chain Attacks InSoftware Supply Chain Security
4 min readFor Supply Chain Risk Managers

5 CI/CD Mistakes That Let Supply Chain Attacks In

Your GitHub Actions workflows look clean. Dependencies are listed. Secrets are stored in the vault. So why are supply chain attacks still slipping through?

Most teams secure the obvious attack vectors but miss the structural weaknesses that attackers exploit. Recent compromises targeting axios, Trivy, LiteLLM, checkmarx KICS, and the Bitwarden CLI didn't succeed because organizations ignored security. They succeeded because teams made predictable mistakes in configuring their build pipelines.

Here's what keeps going wrong.

Why These Mistakes Persist

CI/CD security doesn't fail from ignorance. It fails due to convenience defaults, copy-pasted workflow examples, and a false sense of safety from using "official" tools. When package manager documentation shows org/action-name@v2 in every example, pinning to version tags feels like the right approach. When GitHub lets you store API keys as secrets, rotating them quarterly seems sufficient.

The problem: attackers now target the trust mechanisms themselves. They compromise action maintainers, overwrite version tags, and steal long-lived tokens because these patterns are predictable across thousands of repositories.

Mistake 1: Pinning Actions to Version Tags

Why it happens: Every GitHub Actions tutorial shows version tags. actions/checkout@v3 looks stable. Your security scanner doesn't flag it.

The consequence: Repository owners can overwrite tags to point to different commits. When the tj-actions/changed-files and aquasecurity/trivy-action repositories were compromised, attackers simply moved the version tags to malicious commits. Every workflow using those tags pulled the poisoned code automatically on the next run.

The fix: Pin every third-party action to its commit hash, not its version tag. Replace org/[email protected] with org/action-name@a1b2c3d4 (the full SHA-256 hash at that version). Yes, this makes updates more manual. That friction is the point. You want to review what changed before pulling new code into your release pipeline.

Mistake 2: Storing Package Repository Keys as Long-Lived Secrets

Why it happens: Package repositories issue API keys. GitHub provides a secrets vault. Storing the key as PYPI_TOKEN and referencing it in your publish job feels secure.

The consequence: If an attacker compromises your workflow (through a vulnerable action, for example), they can exfiltrate that secret and publish malicious packages directly to your official repository, even outside GitHub Actions. The LiteLLM attack followed this pattern: attackers stole the static PyPI API key and pushed malicious packages to the legitimate LiteLLM account.

The fix: Use OIDC-based Trusted Publishing wherever your package ecosystem supports it. Register your GitHub repository as a trusted publisher with npm or PyPI during initial setup. At publish time, the package repository issues a short-lived token tied to that specific workflow run. No long-lived secrets in your repository, no persistent access if a workflow is compromised. Both npm and PyPI provide official tooling that makes this straightforward.

Mistake 3: Treating Third-Party Actions as Vetted Code

Why it happens: Popular actions have thousands of stars, official-looking names, and clean documentation. If 10,000 repositories use an action, it must be safe.

The consequence: Stars can be artificially inflated. Maintainer accounts get compromised. Even well-intentioned actions introduce dependencies you haven't audited. Each third-party action is code running in your build environment with access to your source tree and secrets.

The fix: Replace third-party actions with built-in tools wherever possible. Creating a GitHub release? Use the GitHub CLI instead of a third-party action. Opening a PR? Same. For actions that provide non-trivial functionality, audit the source code before pinning to a commit hash. If you can't justify the audit effort, you probably don't need the action.

Mistake 4: Letting Dependencies Float on Latest Versions

Why it happens: Automated dependency updates feel like good hygiene. Staying current reduces technical debt. Letting your package manager pull the latest compatible versions keeps you patched.

The consequence: Malicious releases land in your build within hours of publication. Maintainer accounts get compromised, malicious versions get published, and your next build pulls them automatically before the community notices and the package gets yanked.

The fix: Pin dependencies to specific versions using deterministic lock files. In Go, commit go.mod and go.sum. In JavaScript, commit package-lock.json. In Python, use a package manager like uv that produces uv.lock files. Then enable dependency cooldowns in package managers that support them. A three-day cooldown gives upstream maintainers time to yank malicious releases before they reach your repository.

Mistake 5: Scoping Secrets at the Repository Level

Why it happens: GitHub lets you define secrets once for the entire repository. Every workflow can reference them. It's convenient.

The consequence: A vulnerability in any workflow, even a low-risk test job, can exfiltrate secrets intended only for production releases. If your documentation build job gets compromised, it shouldn't be able to access your package publishing token.

The fix: Scope secrets to specific environments and jobs. Define a production environment in GitHub and restrict your PYPI_TOKEN to workflows running in that environment. Within workflows, use job-level permissions to ensure only the publish job sees the secret. This limits blast radius: compromising a test workflow no longer grants access to production credentials.

Prevention Checklist

Before your next release:

  • All third-party actions pinned to commit hashes, not version tags
  • Package publishing uses OIDC-based Trusted Publishing (no long-lived API keys)
  • Third-party actions audited or replaced with built-in GitHub CLI commands
  • Dependencies pinned in lock files; cooldown period enabled where supported
  • Secrets scoped to specific environments and jobs, not repository-wide
  • 2FA required on all accounts with repository or package registry access
  • Static analysis tool (like Zizmor) running against workflow files
  • Vulnerability scanner (govulncheck, pip-audit, npm audit) running on nightly schedule

These aren't theoretical hardening steps. They're countermeasures that address the specific techniques used in the supply chain attacks we're seeing now. Implement them before the next wave.

Application Security Isn’t Optional Anymore.

You Might Also Like