Defending Against CI/CD Dependency Supply Chain Attacks

Modern software applications are assemblies of thousands of open-source packages. A standard web application repository might contain 50,000 lines of proprietary code, but pull in more than 1,200 open-source dependencies totaling millions of lines of external code.
Attackers know this. Instead of trying to breach a hardened web application firewall, adversaries target the dependencies that run inside your build pipeline through package typosquatting, dependency confusion, and maintainer account takeovers.
Here is how senior engineering teams lock down their delivery pipelines against supply chain threats without crippling developer velocity.
Common Supply Chain Attack Vectors
Understanding how malicious code enters a pipeline is key to designing effective defenses:
- Typosquatting: An attacker publishes
cross-envvorloadash, hoping a developer mistypes a package name during development. - Lockfile Desynchronization: A pipeline running
npm installrather thannpm ci, which allows newer patch versions containing malicious code to be fetched dynamically during builds. - Malicious Lifecycle Scripts: An npm or PyPI package executing an arbitrary post-install bash script (
postinstall) that steals environment variables and CI credentials upon installation.
Supply Chain Defense Checklist
| Security Control | Implementation | Attack Vector Prevented | Friction Level |
|---|---|---|---|
| Deterministic CI Install | npm ci / pip --require-hashes | Unexpected floating patch updates | Zero |
| Ignore Install Scripts | npm ci --ignore-scripts | Arbitrary post-install shell execution | Low |
| Automated Dependency Auditing | npm audit / Grype in CI | Known CVE injection | Low |
| Internal Package Scoping | @company/package-name | Dependency confusion attacks | Low |
| Artifact Provenance Validation | SLSA / Sigstore cosign verification | Tampered third-party binaries | Medium |
Hardening Package Installation in GitHub Actions
The simplest and most effective improvement you can make to your CI workflow is disabling untrusted execution during dependency installation:
# .github/workflows/ci.yml
name: Secure Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies securely
# --ignore-scripts prevents malicious postinstall execution
run: npm ci --ignore-scripts
- name: Run security vulnerability scan
run: npm audit --audit-level=high
- name: Rebuild trusted native binaries if required
# Explicitly build trusted native modules only
run: npm rebuild sharp
Eliminating Dependency Confusion
If your organization uses internal packages (e.g. company-auth-token), configure your .npmrc to strictly resolve your private registry for your organizational scope:
# .npmrc
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
This prevents the public npm registry from being queried for proprietary internal package names, neutralizing dependency confusion attacks.
To build security into every stage of your delivery cycle, explore our DevOps engineering services and read our checklist on securing software delivery pipelines.


