this write-up covers a dependency confusion vulnerability i found against microsoft teams infrastructure and reported to msrc (microsoft security response center). a single internal npm package that was missing from the public registry was enough to get a package i controlled pulled and executed on microsoft-owned systems.
in line with responsible disclosure, the internal hostname and all source ip addresses are redacted throughout this post.
while enumerating teams-related infrastructure, i found a publicly reachable package.json exposed on one of the teams sub-services:
https://<redacted>.teams.microsoft.com/package.json
an exposed manifest like this is a goldmine for supply-chain testing — it lists every direct dependency a service pulls in, including internal-only packages that were never meant to be visible to the outside world.
i ran the dependency list through dustilock — a tool that flags npm/pypi packages susceptible to dependency confusion — and cross-checked the results manually against the public npm registry.
one dependency stood out: hydra-player-sdk. it was referenced in the manifest, but it did not exist on the public npmjs.com registry. that gap is the exact precondition for dependency confusion — an internal package name with no public owner, free for anyone to claim.
at that point the idea to test for dependency confusion was obvious.
dependency confusion (a.k.a. namespace / substitution attack, first detailed by alex birsan) abuses how package managers resolve names. if a build resolves hydra-player-sdk and can reach the public registry, it will happily fetch a public package of that name — and most resolvers prefer the highest available version when the source isn't explicitly pinned.
how dependency confusion works, in general:
because the build can also reach the public registry, the attacker's same-named package at a higher version wins over the private one — unless internal scopes are pinned to the private registry.
so i claimed the name. i registered hydra-player-sdk on npmjs.com under my own account and published it with version 2.2.x — deliberately higher than any plausible internal version.
the package's index.js was harmless by design — it only phoned home. i embedded two independent confirmation channels:
this proved code execution without touching any data or running a single destructive command.
a couple of hours after publishing, the callbacks started landing. i received dns interactions from two microsoft-owned azure ip addresses (verified via ipinfo.io):
104.208.•••.••• → microsoft / azure (redacted)
40.70.•••.••• → microsoft / azure (redacted)
in parallel, the canary token tied to the package fired. collaborator dns hits and a canary trigger, both originating from microsoft-owned infrastructure — that combination confirmed my public hydra-player-sdk had been pulled and executed by microsoft systems.
the payload was intentionally limited to beaconing. in a real attack, the same index.js could have carried os-command execution and data exfiltration — which is exactly what turns dependency confusion from a curiosity into a supply-chain remote-code-execution primitive.
hydra-player-sdk)..npmrc scoping, private-registry routing, or lockfile pinning isn't enforced, the public package wins over the intended internal one.@org/pkg) and pin the registry per scope in .npmrc.package.json / package-lock.json on public endpoints.i reported this to msrc with the canary and collaborator evidence plus the microsoft-owned source ips. i flagged that if it was already known or considered acceptable risk, it could be closed as informative — but the out-of-band execution from microsoft infrastructure made the impact clear.
package.json on teams infrastructure; confirmed hydra-player-sdk missing from public npmmsrc accepted the report — classified as remote code execution, marked complete, and a bounty was awarded.