All Case Studies
Supply ChainAvailable45 min · Apr 8, 2026

Supply Chain Compromise via Dependency Confusion in CI/CD

Reconstruction of an internal npm package substitution: the build resolved to a malicious public version and executed attacker-controlled code during CI.

Background

This is a reconstruction of a documented dependency-confusion class compromise against a CI/CD pipeline. The technique is well-known; the value of this writeup is in the operational detail - what made it succeed, where it should have been caught, and what the detection patterns look like.

How it worked

The target organisation used a private npm registry for internal packages, scoped without the namespace prefix that npm requires for proper resolution. An attacker noticed the package name in publicly-leaked code samples, registered the same name on the public registry under a higher version number, and added a benign-looking preinstall script.

When the build pipeline ran, npm resolved the package name to the public registry - because the public version number was higher than the internal one - and executed the preinstall script. The script had network access during build and ran as the build user.

Dwell time

The malicious package was published 14 days before detection. Three production builds executed it in that window. Detection came from an outbound connection alert flagged by the cloud network team - not from the build pipeline itself.

What detection should look like

Three complementary controls would have flagged this earlier:

  1. Registry pinning per package name. The build configuration should hard-pin internal packages to the internal registry only. This is a config change, not a tool change.
  2. Preinstall script monitoring. The CI runner should log every preinstall and lifecycle script execution. This data is often discarded.
  3. Outbound network policy on builders. Build runners should have a strict allow-list for outbound connections. A new destination domain should produce an alert.

None of these are novel - they are well-known controls. The lesson here is that not having one of them is fine; not having any of them is the failure mode.