Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXV · Software Supply Chain SecurityDependencyTrust

Dependency trust and the attack surface — what is pulled in, what could be malicious

Advanced⏱ ~28 mingit

What you'll learn

  • Recognise the dependency tree as the largest attack surface in the supply chain
  • Identify the controls that establish dependency trust - lock files, SBOM, signed packages, dependency pinning
  • Generate an SBOM from a source directory and interpret its contents
  • Map known dependency attacks to the controls that would have caught them

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A build pulls in libraries, base images, and tools from external sources. Every pull is a trust decision. The dependency tree — the transitive closure of everything the build consumes — is the largest attack surface in the supply chain and the one most often left unverified. The attacker at the dependency boundary does not need to compromise the developer, the repository, or the runner; the attacker only needs to publish a package the build pulls in, or to compromise a package the build already pulls in.

The dependency tree is the attack surface

A typical application has hundreds of transitive dependencies. A typical container image has thousands. The transitive closure of those dependencies is the set of all the code that runs in production under the application’s name. The attacker does not need to compromise the application; the attacker only needs to compromise a dependency the application pulls in — directly or transitively.

flowchart LR
    A["Application"] --> B["Direct deps"]
    B --> C["Transitive deps"]
    C --> D["Transitive deps"]
    B --> E["Base image"]
    E --> F["Base image libs"]
    F --> G["Base image libs"]
    C --> H["Build tool"]
    H --> I["Build tool plugin"]

The tree is wide. Each node is a trust decision. A node that is unverified is a node the attacker can substitute. The attacker at the dependency boundary chooses the cheapest path through the tree — typically a transitive dependency that is not pinned and not reviewed.

The known incidents at this boundary are striking:

  • event-stream (2018): a popular npm package added a dependency on a malicious package that exfiltrated cryptocurrency wallets. The malicious package was added through a maintainer handoff, not through a compromise.
  • ua-parser-js (2021): a popular npm package was compromised through a maintainer account takeover and shipped a cryptominer and a credential stealer.
  • node-ipc (2022): a maintainer shipped a protest payload that wiped files on Russian-language systems. The maintainer was the publisher, but the package’s reach was global.
  • xz-utils (2024): a two-year social-engineering campaign added a backdoor that activated only at build time. The backdoor was in a transitive dependency of openssh, which is a transitive dependency of many distros.

The pattern is the same: the attacker succeeded at the dependency boundary because the defender trusted the dependency tree without verifying it.

The controls: lock files, SBOM, signed packages

The controls that establish dependency trust are:

  • Lock file pinning. A lock file records the exact version (and often the digest) of every direct and transitive dependency. A build that uses the lock file is reproducible and pinned. A build that does not use the lock file is a build that can pull a different version every time.
  • Software Bill of Materials (SBOM). An SBOM is a machine-readable document that lists every dependency in the build, the version, the license, and the supplier. The SBOM is what makes the dependency tree visible. The SBOM is generated from the source tree or from the artifact.
  • Signed packages. A package that is signed by its publisher carries a verifiable identity. The build can verify the signature before pulling the package. The signature is what distinguishes “this package came from the maintainer” from “this package is a typo-squat”.
  • Dependency review. A review of every new dependency (and every new version of an existing dependency) before the change is merged. The review is the human link at the dependency boundary.

The four controls together establish dependency trust. A team that has lock files but no SBOM has pinned dependencies but cannot audit them. A team that has SBOMs but no signed packages has visible dependencies but cannot verify them. A team that has signed packages but no review has verified dependencies but cannot catch a malicious one. The chain is the four together.

Generating an SBOM

The syft tool generates an SBOM from a source directory or from an image. The SBOM is produced in SPDX or CycloneDX format. The production discipline is to generate the SBOM at build time, sign it, attach it to the artifact, and verify it at deployment.

syft dir:. -o spdx-json > sbom.spdx.json

The command reads the source directory, enumerates the dependencies (by package manifest, lock file, and filesystem heuristics), and emits an SPDX JSON document. The document lists every package, its version, its license, and its supplier. The document is what the build attaches to the artifact and what the deployment verifies.

syft dir:. -o spdx-json | cosign sign --key $SIG_KEY sbom.spdx.json

The second command signs the SBOM with a key. The signature is what makes the SBOM verifiable. An SBOM that is generated and signed is a verifiable record of what the build pulled in. An SBOM that is generated but not signed is a document anyone could write.

Production discipline

  1. Pin every dependency by digest in the lock file. A version range is a contract that resolves to whatever the registry returns today, not what it returned yesterday.
  2. Generate an SBOM at build time and sign it. The SBOM is the visible record of the dependency tree. The signature is what makes it verifiable.
  3. Verify the SBOM at deployment. The deployment step fails if the SBOM is missing, unsigned, or lists a dependency that has not been reviewed.
  4. Treat every transitive dependency as a dependency you ship. The transitive layer is the attacker’s cheapest path; make it visible and review it.

Cross-course references

  • Git, CI/CD & GitOps — Part LXV-01 (Trust Boundaries) maps the dependency boundary this lesson covers.
  • Git, CI/CD & GitOps — Part XXXVII (CI Foundations) covers the build environment that consumes the dependencies.
  • Linux for Production Sysadmins — Part XII (RepoSecurity) covers the OS-level analogue: apt/dnf repository trust.
  • Terraform for Production Sysadmins — Part XX (Module Trust) covers the same model applied to Terraform modules.

Quiz

Knowledge check · 4 questions

  1. Q1. Which control makes the dependency tree visible to a verifier outside the build?

  2. Q2. An SBOM becomes an effective deployment control only when policy actually verifies it before admitting the artifact.

  3. Q3. Name the four controls that, together, establish dependency trust at the dependency boundary.

  4. Q4. Identify the gap in the team's dependency trust posture and the rule that closes it.

    Team T builds a container image. The Dockerfile uses a base image pinned by tag (`ubuntu:22.04`). The build uses a package manager that resolves dependencies at build time and does not pin transitive dependencies in a lock file. The team does not generate an SBOM. An attacker compromises a transitive dependency of a common library used by the team's application. The malicious version is published to the upstream registry. The next build pulls the malicious version because the lock file does not pin transitive deps. The image is built, signed, and deployed. The malicious code runs in production.

Passing score: 75%. Answers are checked in this browser.