Safeguard
Open Source Security

Reconstructing a Real-World Dependency Confusion Incident...

A step-by-step reconstruction of a real dependency confusion attack, from malicious package upload to remediation, and how to defend your pipeline.

Nayan Dey
Security Researcher
7 min read

In late December 2022, a real dependency confusion attack compromised the PyTorch nightly build pipeline for approximately five days, causing anyone who ran pip install torchtriton — a dependency automatically pulled in by PyTorch nightly builds — to execute attacker-controlled code on their machine. This wasn't a theoretical vulnerability disclosed in a lab; it was a live supply chain compromise against one of the most widely used machine learning frameworks in the world, and it remains one of the clearest, best-documented examples of how dependency confusion works in production. This case study reconstructs what happened, why it worked, and what the incident still teaches security and platform teams about build-time trust.

What dependency confusion actually is

Dependency confusion (sometimes called "namespace confusion" or "substitution attack") exploits a simple, structural problem in how package managers resolve names across multiple registries. Many organizations reference internal packages by name inside requirements.txt, package.json, or internal build manifests — packages that live only on a private index. If that internal name is never reserved on the corresponding public registry (PyPI, npm, RubyGems), an attacker can publish a package with the identical name — usually with an artificially high version number — to the public registry. Depending on how the build tooling is configured, the resolver may prefer the public package, either because it's checked first or because it appears to be the "newer" version. The victim's own build pipeline then pulls and executes attacker-supplied code, with no phishing, no credential theft, and no exploit of a memory-safety bug required.

The technique was popularized by security researcher Alex Birsan, who in February 2021 published research showing he had successfully published confusion packages that were pulled into internal builds at more than 30 major companies, including Apple, Microsoft, PayPal, Netflix, Tesla, Uber, and Shopify, earning over $130,000 in bug bounties in the process. That research was responsible, ethical, and non-destructive. The PyTorch incident, roughly 22 months later, showed what the same technique looks like when it is used maliciously against a live pipeline.

Affected versions and components

The compromise affected PyTorch nightly builds for Linux installed via pip during the window PyTorch's own security advisory identifies as December 25 to December 30, 2022. Stable, tagged PyTorch releases were not affected — the issue was specific to the nightly channel's dependency on a package named torchtriton, which provided GPU kernel compilation support (the Triton compiler backend) for nightly builds.

torchtriton was intended to be installed from PyTorch's own package index (download.pytorch.org), not from PyPI. Because the name had never been registered as a placeholder on PyPI, and because the nightly install instructions used pip's --extra-index-url flag (which causes pip to consider multiple indices for a single install rather than a single trusted source), an attacker was able to publish a malicious package named torchtriton directly to PyPI. Under that resolution behavior, pip could select the PyPI-hosted package over the legitimate one from PyTorch's own index.

CVSS, EPSS, and KEV context

There is no single CVE, CVSS score, or EPSS/KEV listing directly tied to this incident, and that absence is itself instructive. Dependency confusion is not a memory-corruption bug or a logic flaw in a specific piece of software that a scanner can fingerprint by version string — it's a systemic issue in registry trust and namespace ownership, closest in spirit to CWE-427 (Uncontrolled Search Path Element) and related "untrusted search path" weaknesses. Because it manifests as a configuration and namespace-hygiene gap rather than a patchable defect in a single product, it typically doesn't receive a per-incident CVE, and it won't show up in the CISA Known Exploited Vulnerabilities catalog the way a specific software CVE would. That makes it easy for teams that only track CVE feeds and CVSS thresholds to miss this entire class of risk — a vulnerability management program built solely around "patch high-CVSS CVEs" will never flag a namespace that a competitor or attacker could squat tomorrow.

Timeline

  • February 2021 — Alex Birsan publishes "Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies," establishing the technique and demonstrating it against dozens of real organizations via bug-bounty-authorized testing.
  • December 25, 2022 — A malicious package named torchtriton is published to PyPI, matching the name of the internal dependency pulled by PyTorch nightly builds.
  • December 25–30, 2022 — Users installing PyTorch nightly builds for Linux via pip during this window have the malicious PyPI package installed instead of (or in addition to) the legitimate binary from PyTorch's own index, resulting in code execution on affected machines. Reported behavior of the malicious package included collecting system and environment information and exfiltrating it, alongside the ability to read sensitive local files such as SSH keys.
  • December 30, 2022 — The PyTorch team identifies the compromise, removes the affected nightly packages, and publishes a security advisory disclosing the issue and the affected window.
  • Following days — PyTorch renames the dependency to pytorch-triton and proactively registers placeholder packages under the old and new names across PyPI and other registries to prevent future name-squatting, and advises anyone who installed nightly builds during the window to uninstall the compromised package and treat any credentials or SSH keys on the affected machine as potentially exposed.

Remediation steps

For teams responding to this class of incident, or auditing whether they're exposed to the same pattern, the practical steps are consistent regardless of ecosystem:

  1. Reserve internal package names on every public registry your ecosystem uses. If you have an internal package called acme-auth-utils, publish an empty placeholder under that exact name on PyPI, npm, and any other registry your build tooling can reach — before an attacker does.
  2. Pin exact versions and use lockfiles with hash verification. requirements.txt with pinned versions plus --require-hashes, or npm's lockfile with integrity hashes, prevents a resolver from silently substituting a higher-versioned malicious package.
  3. Avoid multi-index resolution ambiguity. Prefer pip's --index-url (a single trusted source) over --extra-index-url for internal packages, or configure scoped registries (npm scopes, a properly namespaced private PyPI mirror) so there is no ambiguity about which registry should serve which package.
  4. Audit exposed internal package names. Leaked package.json, requirements.txt, internal documentation, or build logs can reveal internal package names to an attacker. Treat these names as sensitive where feasible, and monitor public registries for newly published packages matching your internal naming conventions.
  5. Verify package provenance before trusting install-time code execution. Build provenance attestations (such as Sigstore-based npm provenance) and SBOM-based origin tracking let you confirm a package actually came from the source you expect, rather than trusting name-matching alone.
  6. If compromise is suspected, rotate credentials. Because dependency confusion payloads run with the privileges of the developer or CI system that triggered the install, treat any SSH keys, tokens, or secrets present on an affected machine as compromised and rotate them, exactly as PyTorch advised its affected users to do.

How Safeguard Helps

Dependency confusion is fundamentally a visibility problem: most teams don't know every internal package name their build systems reference, whether those names are reserved on public registries, or when a new package matching an internal name suddenly appears in the wild. Safeguard is built to close that gap before it becomes an incident.

Safeguard continuously generates and monitors SBOMs across your build pipelines, so you have an accurate, current inventory of every internal and third-party package your software actually resolves — not just what's declared in a manifest. It tracks package origin and registry provenance, flagging cases where a dependency is being resolved from an unexpected source or where multi-index resolution creates ambiguity between a private and public registry. Safeguard also monitors public registries for newly published packages that match your organization's internal naming patterns, giving you an early warning before a confusion package can be pulled into a build, and it enforces policy gates in CI/CD that can block installs lacking verified provenance or hash pinning. For teams that have already reserved namespace placeholders and adopted lockfiles, Safeguard provides the ongoing monitoring layer that catches drift and new exposure as your dependency graph evolves — turning a class of risk that CVE feeds can't see into something your security team can actually track and act on.

Never miss an update

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