In February 2021, security researcher Alex Birsan showed that simply guessing the internal package names used by companies like Apple, Microsoft, Netflix, and PayPal — then publishing same-named packages to public registries — was enough to get his code auto-installed inside their internal build pipelines. He collected more than $130,000 in bug bounties from over 35 organizations. The technique, which came to be called dependency confusion, exploited a quirk shared by npm, PyPI, and Maven: package managers resolve whichever version of a given coordinate looks "newest," regardless of whether it came from a private feed or the public internet. Four years later, Maven dependency confusion remains a live risk for Java and Kotlin shops, because Maven Central's groupId system was designed to establish namespace ownership, not to stop a public artifact from impersonating an internal one. If your build files still reference short, unclaimed, or inconsistently scoped groupIds, an attacker who publishes to Central under that same coordinate can end up compiled into your JAR before anyone notices.
What Is Maven Dependency Confusion, Exactly?
Maven dependency confusion is an attack in which a malicious actor publishes a public artifact that shares the same groupId, artifactId, and a higher version number as a package your organization only intends to resolve from an internal repository. Maven, Gradle, and other JVM build tools resolve dependencies by checking every repository listed in your pom.xml or settings.xml and, absent explicit repository pinning, taking the highest version number found across all of them. If your internal artifact com.acmecorp:auth-utils:1.4.0 only exists in your private Nexus or Artifactory instance, and an attacker publishes com.acmecorp:auth-utils:99.0.0 to Maven Central, a misconfigured build will happily pull the attacker's version — and execute whatever code sits in its Maven plugin lifecycle hooks, annotation processors, or static initializers during the build itself, not just at runtime. This is what separates dependency confusion from typosquatting: the name isn't misspelled, it's identical, and the attack wins on version arithmetic rather than on tricking a developer into a typo.
Why Does Maven Central's Namespace Model Create This Risk?
Maven Central's namespace model creates this risk because groupId ownership is verified against a domain or a public code-hosting account, but that verification only covers what gets published publicly — it says nothing about names your company uses exclusively behind a firewall. Since Sonatype's OSSRH policy changes around 2011, publishers who want to release under a groupId like com.acmecorp must prove they control the acmecorp.com domain (via a DNS TXT record) or publish under a reversed GitHub-style namespace like io.github.username. That process is good Maven Central security hygiene for legitimate open source releases, but it does nothing to stop someone else from claiming com.acmecorp if your company has never published anything publicly and therefore never registered the namespace. Sonatype has reported Central serving tens of billions of download requests a month across well over half a million distinct artifacts; at that scale, an unclaimed internal-sounding groupId is simply inventory waiting for a squatter, and nothing in the resolution protocol itself checks whether the party requesting an internal-looking artifact is the same party who is entitled to publish it.
What Does a Java Package Namespace Collision Look Like in Practice?
A java package namespace collision looks like this in practice: your platform team names an internal shared library com.internal.commons:string-utils, references it in forty microservices' POM files, and never publishes it externally because it was never meant to leave the building. An attacker who has seen the artifact name — leaked in a public GitHub repo, a Stack Overflow question, a job posting describing your tech stack, or a conference talk slide — registers a matching or adjacent groupId on Central and ships a version numbered higher than anything your team has cut internally. Security researchers demonstrated exactly this pattern against JFrog, Alibaba, and other large Java shops in 2021 and 2022 disclosures, proving that generic or internal-sounding groupIds (com.internal, com.company, org.utils) are collision magnets precisely because so many organizations independently pick the same low-effort names for their in-house tooling.
How Is Internal vs Public Maven Repo Configuration the Real Vulnerability?
The real vulnerability sits in how build tools are configured to search internal vs public Maven repo sources, not in Maven Central itself. Most CI pipelines list an internal Nexus or Artifactory proxy alongside repo.maven.apache.org in the same <repositories> block or settings.xml mirror list, with no explicit rule dictating that a given groupId must only ever resolve from one source. Maven's default behavior is to query repositories in listing order and accept the first (or highest-versioned) match, which means a public repository placed before, or merely alongside, your internal one can win the resolution race for names it was never supposed to touch. Gradle has the same exposure through its repository-search-order semantics unless you explicitly declare exclusiveContent blocks. The fix isn't avoiding Maven Central — it's routing traffic so that internal groupIds are never even allowed to fall through to a public source, a configuration most teams don't realize is missing until it's tested.
How Can Teams Tell If They're Exposed?
Teams can tell they're exposed by auditing three things: which internal groupIds exist, whether those exact coordinates are already registered (by anyone) on Maven Central, and whether their build and repository-manager configuration enforces repository-to-namespace pinning. Start by pulling every distinct groupId referenced across your organization's POM and Gradle build files, then check each one against Central's search API — a hit you didn't publish is a live collision waiting to happen. In parallel, configure your repository manager (Nexus, Artifactory, or GitHub Packages) with routing rules that map specific internal groupIds exclusively to internal-only repositories, so a public upstream can never satisfy that request even if it offers a newer version. Sonatype and other repository vendors have shipped "namespace protection" or "exclusive content" features specifically in response to this attack class since 2021, but they are opt-in, not default, in most deployments — which is exactly why so many organizations remain unaware of their exposure until an external researcher or an automated scan flags it.
How Safeguard Helps
Safeguard treats Maven dependency confusion as a build-time supply chain control, not a one-time audit. We continuously inventory every groupId and artifactId your services actually resolve, cross-reference each one against what's publicly registered on Maven Central, and flag any internal-sounding namespace that is unclaimed, already squatted, or resolvable from more than one repository source in your CI configuration. Where a collision risk exists, Safeguard verifies your repository manager's routing rules enforce a strict internal vs public Maven repo boundary — confirming that internal groupIds are pinned to internal-only sources rather than left to whichever repository responds first or offers the highest version number. We also monitor for newly published artifacts on Central that match your organization's internal namespaces, so a squatting attempt gets caught within hours of publication rather than surfacing after a compromised build has already shipped. For teams that have never formally claimed their public namespace, we help prioritize and execute that registration so the door closes for good. The goal is straightforward: your builds should only ever pull the code you actually intended to pull, from the source you actually intended to pull it from.