arrow_backBACK TO SECURITY ENGINEERING FOR BACKEND ENGINEERS — THREATS, AUTH, CRYPTO AND SHIPPING SECURELY
Lesson 09Security Engineering for Backend Engineers — Threats, Auth, Crypto and Shipping Securely6 min read

Supply Chain and CI/CD Security

October 28, 2026

TL;DR

A modern app is maybe 10% your code and 90% dependencies — each of which pulls in its own, often hundreds deep — so you inherit the security of everything you import. Supply-chain attacks enter through typosquatting (a malicious package named like a real one), compromised maintainers (a popular package ships a bad update), dependency confusion (a public package shadows your internal private one), and build-system compromise (the CI that builds you is attacked). Defend by hardening the pipeline: pin and lock dependencies to exact versions with hashes, scan dependencies for known CVEs (Dependabot/Trivy/Snyk) and your own code for bad patterns (Semgrep/CodeQL) on every PR, generate an SBOM so you know exactly what you ship and can respond fast to the next big CVE, run CI with least privilege (scoped short-lived tokens, protected branches, no secrets in logs), and sign your build artifacts so deployments verify what they run. Shift these checks left into CI so every merge is automatically verified.

Here’s a fact that reframes how you think about security: the code you wrote is a small fraction of the code running in your application. A typical modern service is maybe ten percent your logic and ninety percent dependencies — frameworks, libraries, utilities — each of which pulls in its own dependencies, often hundreds deep. Every one of those packages runs with your application’s privileges. Which means you inherit the security of everything you import, from code written by people you’ve never met. This is supply-chain security, and it has moved from a niche concern to one of the most actively exploited attack surfaces there is.

You Inherit the Security of Everything You Import

Let’s make the scale vivid. You npm install or pip install a handful of direct dependencies. Each of those has dependencies, which have dependencies — the transitive tree. It’s routine for a modest project to end up with hundreds or thousands of packages, the vast majority of which you’ve never heard of and will never read.

Your code is mostly other people's code: a modern app pulls in hundreds of dependencies, each is attack surface. Your app is 10% your code; it depends on react, express, a-util, left-pad, each of which pulls in its OWN dependencies (transitive), often hundreds deep. Where supply-chain attacks get in: Typosquatting — 'reqeusts' instead of 'requests', you install malware by a typo. Compromised maintainer — a popular package gets a malicious update (real, repeatedly). Dependency confusion — a public package shadows your internal private one. Build-system compromise — the CI that builds you is attacked, poisoning the artifact. You inherit the security of everything you import, so treat dependencies as part of your attack surface

Attackers know this is a soft spot, and they get in through a few well-worn routes:

  • Typosquatting — publishing a malicious package with a name one typo away from a popular one (reqeusts vs requests, python-sqlite vs the real thing). A single fat-fingered install pulls in malware. Package registries are full of these.
  • Compromised maintainer — a legitimate, widely-used package gets a malicious update, because the maintainer’s account was phished, or they handed the project to a bad actor, or a malicious “helpful” contributor got commit rights. This has happened repeatedly to very popular packages, and because you trust that package, the bad version flows straight into your build.
  • Dependency confusion — if your company uses an internal package name (acme-auth) that also exists (or an attacker creates it) on the public registry, your package manager may fetch the public one — the attacker’s — instead of your private one. This trick has breached major companies.
  • Build-system compromise — the attacker doesn’t touch your code or dependencies at all; they compromise the CI/CD pipeline that builds and ships you, injecting malicious code into the final artifact. The infamous large-scale supply-chain incidents often took this form, because the build system is trusted implicitly and sits upstream of everyone who deploys the result.

The common thread: trust flows downhill, and attackers inject themselves upstream of that trust. So your defense has to extend beyond your own code to everything you depend on and everything that builds you.

Hardening the Pipeline

The good news is that most supply-chain defenses are concrete, automatable steps that fit neatly into your CI/CD pipeline — the place where code becomes a running artifact. Put the checks there and every change is verified without anyone remembering to.

Hardening the build pipeline: defenses that fit into CI/CD, where code becomes a running artifact. Pin and lock dependencies: exact versions plus lockfile plus hashes, no surprise upgrades. Scan dependencies (SCA): Dependabot / Trivy / Snyk flag known CVEs on every PR. Scan your code (SAST): Semgrep / CodeQL catch injection, secrets, bad patterns. Generate an SBOM: a bill of materials, know exactly what you ship, for fast CVE response. Least-privilege CI: scoped short-lived tokens, protected branches, no secrets in logs. Sign your artifacts: sign builds/images (Sigstore) so deploys verify what they run. Shift security left into the pipeline: every merge is automatically checked, not hoped about
  • Pin and lock dependencies. Use a lockfile (package-lock.json, poetry.lock, go.sum) that records exact versions and hashes, so every build gets the identical, verified bytes — no silent upgrade can slip a malicious version in. Commit the lockfile. Avoid floating ranges (^1.2.0) for anything sensitive. Hash-pinning in particular defeats a registry serving you different content than you reviewed.
  • Scan dependencies for known vulnerabilities (SCA — Software Composition Analysis). Tools like Dependabot, Trivy, or Snyk check your dependency tree against vulnerability databases on every pull request and flag (or auto-PR) anything with a known CVE. This catches the “three-years-out-of-date library with a public exploit” problem, which is one of the most common real breach causes.
  • Scan your own code (SAST — Static Application Security Testing). Tools like Semgrep or CodeQL read your source and flag dangerous patterns — string-built SQL (Lesson 4), hardcoded secrets (Lesson 7), unsafe deserialization — before they merge. They’re not perfect (false positives happen), but they catch a lot cheaply.
  • Generate an SBOM (Software Bill of Materials). An SBOM is a complete inventory of everything in your build — every package and version. Its value becomes obvious the next time a big CVE drops (think Log4Shell): instead of a frantic manual hunt, you query your SBOMs and know in minutes exactly which services are affected. It turns “are we vulnerable?” from a day of panic into a quick lookup.
  • Run CI with least privilege. Your pipeline is powerful and a juicy target (it can deploy to production), so treat it like production. Use scoped, short-lived tokens rather than broad long-lived credentials, protect your main branches (require review, no force-push), keep secrets out of build logs, and don’t let untrusted pull requests run privileged jobs with access to your secrets.
  • Sign your artifacts. Cryptographically sign your build outputs (container images, binaries) — using something like Sigstore/cosign — so your deployment step can verify it’s running exactly what your pipeline produced, not something swapped in later. This closes the loop against build- and registry-tampering (and uses the signing from Lesson 6).

The Mindset: Dependencies Are a Standing Decision

Beyond the tools, a couple of habits of judgment matter:

  • Evaluate before you add. Each dependency is a long-term trust relationship. Before pulling in a package, glance at whether it’s maintained, widely used, and reasonably scoped. Do you need a whole library for something you could write in ten lines? Fewer dependencies is less attack surface.
  • Keep them updated — but not blindly. Out-of-date dependencies accumulate known vulnerabilities, so patch regularly (automated PRs help). At the same time, a brand-new release could be the compromised one, so pinning + review gives you a beat to notice. The balance is “stay current, but through a reviewed, locked process,” not “auto-upgrade everything instantly” and not “never touch it.”
  • Treat the pipeline as production. The CI system deploys your code; a compromise of it is a compromise of everything. It deserves the same least-privilege, auditing, and care as your most sensitive service.

Supply-chain security can feel daunting because the surface is so large, but the practical version is encouraging: turn on dependency scanning, commit your lockfiles, generate SBOMs, lock down your pipeline’s permissions, and sign your builds. Each is a one-time setup that then runs automatically on every change — exactly the “shift left” idea we’ll formalise in Lesson 12.

Pitfalls to Avoid

  1. Ignoring transitive dependencies. Most of your code is indirect dependencies. Scan the whole tree, not just your direct imports.
  2. Floating versions, no lockfile. Invites a silent malicious upgrade. Pin exact versions with hashes and commit the lockfile.
  3. No dependency scanning. Out-of-date libraries with public exploits are a top breach cause. Run SCA (Dependabot/Trivy/Snyk) on every PR.
  4. No SBOM. When the next big CVE lands you’ll be hunting manually. Generate SBOMs so you can answer “are we affected?” in minutes.
  5. An over-privileged pipeline. CI can deploy to prod; a broad, long-lived token there is catastrophic if leaked. Use scoped, short-lived credentials and protected branches.
  6. Unsigned artifacts. Without signing, you can’t prove you’re deploying what you built. Sign builds and verify at deploy.
  7. Adding dependencies carelessly. Each one is standing attack surface and a trust relationship. Evaluate necessity and maintenance before adding.

Quick Recap

  • Your app is mostly other people’s code; you inherit the security of every dependency, hundreds deep.
  • Attacks enter via typosquatting, compromised maintainers, dependency confusion, and build-system compromise — upstream of your trust.
  • Harden the CI/CD pipeline: pin + lock with hashes, scan dependencies (SCA) and code (SAST) on every PR, generate SBOMs, run CI with least privilege, and sign artifacts.
  • Treat dependencies as a standing decision (evaluate before adding, update through a reviewed locked process) and the pipeline as production.

Next lesson: zooming out to the infrastructure your code runs on — cloud IAM, the blast-radius decision behind the most common cloud mistake, and defense in depth.