Safeguard
Software Supply Chain Security

Untracked Dependencies in the Software Supply Chain

Most teams can name their direct dependencies but not the hundreds of transitive packages actually running underneath. Here's why that gap is where real supply chain attacks live.

Vikram Iyer
Security Researcher
7 min read

Modern applications ship with a dependency tree most engineering teams have never actually seen. A typical npm project lists 15-20 direct dependencies in package.json, but pull the full tree and that number balloons to 700-1,000 packages once transitive dependencies are counted. Each of those packages was written by someone your security team has never vetted, is maintained (or abandoned) on a schedule you don't control, and can change its contents between the version you tested and the version your CI pipeline actually pulls at build time.

This is the untracked dependency problem: the gap between the software your SBOM says you're running and the software actually executing in production. It's not a hypothetical. The xz-utils backdoor, the event-stream compromise, and the ua-parser-js hijack all traveled through dependency layers that no one was actively monitoring. Below, we walk through what "untracked" actually means, how deep the problem runs, and what it takes to close the gap.

What counts as an "untracked" dependency?

An untracked dependency is any package your build actually pulls in that isn't explicitly declared, pinned, or reviewed by your team — most commonly a transitive dependency several layers removed from your package.json, requirements.txt, or go.mod. If you declare 20 direct dependencies and each of those pulls in 15 more, you're running 300+ packages you never chose by name. Sonatype's 2023 State of the Software Supply Chain report found that 96% of known-vulnerable dependency downloads had a fixed version available — meaning the vulnerable version wasn't a dead end teams were stuck with, it was simply invisible to them. Untracked also covers dependencies pulled via floating version ranges (^1.2.0, latest, master branch references), dependencies resolved differently across environments due to lockfile drift, and build-time tooling (compilers, bundlers, CI actions) that never appears in an application SBOM at all but still executes with build-system privileges.

How deep do transitive dependency trees actually go?

Deep enough that a single popular package can sit invisibly inside hundreds of thousands of downstream projects. The event-stream npm package had roughly 2 million weekly downloads in November 2018 when a new co-maintainer — who had gained commit access after volunteering to help the original author, who no longer had time for the project — added a dependency called flatmap-stream that contained obfuscated code designed to steal cryptocurrency wallet credentials from a specific downstream application, Copay. Nobody using event-stream had declared flatmap-stream directly; it arrived as someone else's transitive choice. Similarly, ua-parser-js, downloaded roughly 7-8 million times a week, was compromised in October 2021 after its maintainer's npm account credentials were hijacked, pushing versions that installed cryptominers and password stealers onto every project that resolved the package during that window — often without a single line of application code changing. Depth, not popularity, is what makes these packages dangerous: teams audit what they import directly far more often than what gets imported four or five levels down.

Why did the xz-utils backdoor go undetected for two years?

Because it was introduced gradually, by a trusted maintainer, inside build artifacts that standard code review never touched. "Jia Tan" began contributing to the xz-utils compression library in 2021, built credibility over roughly two years of legitimate patches, and was eventually granted co-maintainer access. The malicious payload, tracked as CVE-2024-3094, was hidden not in the readable source but inside test files and a modified build script that altered the compiled liblzma library to intercept SSH authentication via OpenSSH's dependency on it — giving an attacker potential remote code execution on affected systems. It was caught almost by accident on March 29, 2024, when a Microsoft engineer, Andres Freund, noticed unusual CPU usage and slightly slower SSH login times during unrelated performance debugging. Had that half-second latency gone unnoticed, the backdoor would have shipped into major Linux distributions' stable releases. Source-level code review had nothing to catch — the malice lived in the build pipeline, precisely the layer most SBOM and dependency-scanning tools don't inspect.

How do unpinned versions and floating tags create silent risk?

Unpinned versions mean the exact code running in production today can differ from what ran yesterday, without a single commit to your own repository. A dependency declared as ^4.1.0 will happily accept 4.1.1 through anything below 5.0.0 — and under semantic versioning rules that's supposed to be safe, but it assumes every maintainer correctly categorizes every change, which the ua-parser-js and event-stream incidents both disprove. GitHub Actions workflows referencing a tag like actions/checkout@v4 rather than a pinned commit SHA face the same exposure: tags are mutable pointers that a compromised maintainer account can silently repoint to malicious code, as happened with the tj-actions/changed-files GitHub Action in March 2025, where a compromised token was used to retroactively rewrite release tags across nearly all historical versions to point at a commit that dumped CI secrets into build logs — affecting more than 23,000 repositories that referenced the action by tag rather than SHA. Lockfiles (package-lock.json, yarn.lock, Pipfile.lock) reduce this risk within a single repo, but only if they're actually committed, actually enforced in CI, and regenerated deliberately rather than automatically on every install.

What does a real untracked-dependency incident cost?

The 2021 Log4Shell vulnerability (CVE-2021-44228) in Apache Log4j, disclosed on December 9, 2021, is the clearest case study: it carried a CVSS score of 10.0, and because log4j-core was buried as a transitive dependency inside thousands of Java frameworks and enterprise products, most organizations couldn't even produce an accurate list of where it lived. The Apache Software Foundation and CISA both described remediation efforts stretching well into 2022, and IBM's Cost of a Data Breach research consistently places breaches involving supply chain compromise among the costliest categories, averaging roughly $4.6 million per breach in years the metric was tracked. The cost isn't just the incident response — it's the weeks spent manually grepping build outputs and container images across every service just to answer "are we exposed?" when a tracked SBOM would have answered that in minutes.

How Safeguard Helps

Safeguard is built specifically to close the gap between the dependencies you think you're running and the ones actually executing in your pipelines and production environments. Instead of relying on a manually maintained manifest, Safeguard continuously generates and reconciles software bills of materials against real build and runtime artifacts — catching transitive packages, floating tags, and build-tool dependencies that traditional dependency-declaration files never surface. When a new package enters your tree at any depth, Safeguard flags it, attributes it to the direct dependency that pulled it in, and checks it against known-malicious package indicators, maintainer account changes, and anomalous version jumps — the exact pattern behind the event-stream, ua-parser-js, and tj-actions/changed-files compromises.

Safeguard also enforces pinning discipline across package managers and CI configurations, alerting when workflows or lockfiles rely on mutable references instead of immutable, hash-verified ones, and integrates build-provenance verification so that what gets compiled matches what was reviewed — directly addressing the class of attack the xz-utils backdoor represents. Combined with continuous CVE correlation against your live dependency graph rather than a point-in-time scan, teams get an answer to "are we exposed to this new CVE" in minutes instead of the multi-week scramble that followed Log4Shell. The goal isn't another dashboard of dependencies you already knew about — it's visibility into the ones you didn't.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.