Key Takeaways
- A package published one namespace segment away from Jackson served Cobalt Strike from Maven Central in December 2025, no CVE involved.
- Maven Central verifies control of a namespace’s domain or hosting account, not any connection to the project it resembles.
- Artifacts published through Maven Central carry PGP signatures, and Maven does not verify them by default.
- A compromised npm package reached Maven Central automatically through mvnpm mirroring in November 2025.
Most of the Java supply chain conversation since Log4Shell has been about one shape of problem: a vulnerability sitting in legitimate code. Then, in December 2025, something else showed up. A namespace impersonation attack put a Cobalt Strike loader on Maven Central under coordinates that swapped a single segment of Jackson’s real ones.
Nothing was exploited. No vulnerability was involved. Someone registered a namespace, published an artifact, and waited for the wrong coordinates to get copied.
Maven Central security isn’t only the signature verification gap most people assume. It’s also a namespace problem, and most Java tooling catches neither one.
What Actually Happened With the Fake Jackson Package
The mechanics are simple enough to explain in one sentence, which is exactly why they worked.
Jackson 2.x publishes under com.fasterxml.jackson.core:jackson-databind. Aikido Security, which spotted the package on December 25, documented the malicious artifact as org.fasterxml.jackson.core with the same jackson-databind artifactId, so the whole coordinate string was off by three characters. Reverse-domain naming is supposed to prevent this, and on its own terms it did what it promised. Maven Central confirmed the publisher controlled a domain matching the namespace. That domain was fasterxml.org, registered through GoDaddy on December 17, 2025, eight days before the analysis, and a near-mirror of the real fasterxml.com. Ownership verification worked correctly. It just verified the wrong thing.
The payload was not a quick smash-and-grab. Deobfuscated, the class was a fake Spring Boot auto-configuration, and researchers who examined it found a multi-stage design: AES-encrypted configuration strings unlocked with a hardcoded key, an @Configuration class annotated with @ConditionalOnClass(ApplicationRunner.class) so Spring would register it and run the payload once the application context loaded, a persistence marker disguised as an IntelliJ file, and platform fingerprinting before any command-and-control traffic went out. Windows hosts pulled down svchosts.exe, a typosquat of the real svchost.exe process. Linux and macOS hosts got binaries that nearly every scanning engine on VirusTotal identified as Cobalt Strike beacons, the commercial red-team framework whose leaked builds are widely used by ransomware operators and APT groups.
Once the package was reported, Maven Central pulled it in roughly ninety minutes, which is a fast response for a public registry. That speed doesn’t help the projects that resolved it while it was live, and it says nothing about how a quieter version of the same trick would fare. A loud Cobalt Strike beacon gets found. Something built to sit still and exfiltrate slowly could stay in place far longer.
How Namespace Impersonation Works on Maven Central
Maven identifies dependencies by three coordinates: groupId, artifactId, version.
The convention is that groupId is a reverse domain the publisher controls, and Maven Central enforces that at registration by requiring proof of control over the domain, or over the code-hosting account tied to it. That verification is real, and it works exactly as designed.
It just doesn’t do what people assume it does. Proving control of a domain says nothing about whether the publisher has any relationship to the project the namespace resembles. It confirms control of a domain, and a lookalike domain costs a few dollars.
Jackson’s real namespace next to three patterns anyone could register, none of them the one used here.
| Coordinate pattern | Looks legitimate because | What actually gets verified |
|---|---|---|
com.fasterxml.jackson.core |
It is the real namespace | Domain control by FasterXML |
io.fasterxml.jackson |
Reverse domain, .io is common for OSS | Domain control by whoever bought it |
com.fasterxml-jackson.core |
One hyphen, reads identically at a glance | Domain control by whoever bought it |
org.jackson.databind |
Matches the project name | Domain control by whoever bought it |
The io.fasterxml.jackson, com.fasterxml-jackson.core, and org.jackson.databind variants are all reachable legitimately, with nothing compromised anywhere. The Jackson incident used a narrower version of the same idea, not a hyphen or a different TLD but a single top-level segment swapped from com to org, paired with a domain registered specifically to make that swap pass verification.
Coordinates tend to get copied out of Stack Overflow answers, blog posts, and AI-generated snippets rather than typed from memory, which is one reason this is harder to spot in Java than it would be with npm packages. A developer who has installed axios fifty times notices “axois” immediately. Almost nobody has the Jackson groupId memorized, and fewer still would catch one swapped segment in a forty-character string they’re pasting rather than reading.
Copying coordinates off a search results page instead of a project’s own documentation creates another opening for namespace confusion. Worth breaking that habit regardless of how this particular incident played out.
The Maven Central Security Gap Nobody Checks
Here’s the part that surprises Java developers who haven’t looked closely, and it’s a separate problem from the one above.
Maven Central will not accept a release without a PGP signature. Every artifact published through Central ships with a .asc file next to it, plus checksums, and the publishing requirements are stricter than npm’s or PyPI’s. That is a real strength.
Maven does not verify those signatures by default.
Run mvn install on a project with two hundred transitive dependencies and Maven fetches two hundred JARs and compares the checksums sitting beside them. By default, a mismatch only warns; failing the build on one takes -C, or a checksumPolicy of fail on the repository. Either way, the .asc files next to those JARs are never touched unless a plugin asks for them.
Checksum validation and signature verification get treated as interchangeable, and they aren’t. A checksum confirms that the file which arrived matches the file the repository is serving. A valid signature proves the artifact was signed with a particular private key, and it takes a trusted key mapping to connect that key to a publisher worth trusting. Maven does the first by default and skips the second entirely.
Which matters for working out what would and wouldn’t have helped here. The attacker controlled fasterxml.org and could sign under their own key, so signature verification would have passed on the malicious package too. Signature checking proves an artifact hasn’t changed since it was signed by the holder of that key. It says nothing about whether that publisher is who the namespace implies. Two problems, two fixes.
Gradle is ahead of Maven here, and most teams still haven’t turned it on.
./gradlew --write-verification-metadata sha256,pgp help
That bootstraps a verification-metadata.xml file from whatever Gradle resolves, and the bootstrap is the part worth slowing down for. It is deliberately optimistic. Keys that verify successfully get added to the trusted list automatically, artifacts with no usable signature fall back to checksums, and anything already compromised in your tree gets blessed on the way through. Read the generated checksums and trusted keys before committing the file. Once that baseline has been reviewed, Gradle fails the build on anything that stops matching, which is a strong control and not a heavy lift on a straightforward project.
Maven has no equivalent in core. Verification means a plugin.
mvn org.simplify4u.plugins:pgpverify-maven-plugin:1.19.1:check
That verifies dependency signatures and can take an explicit key map, so only the signing keys you name count as trusted. It works, and it isn’t what anyone gets by default.
The artifact-versus-source problem sits underneath both of these controls, because a JAR is a build output and nothing forces it to match the source repository a project links to. A scanner reading the published binary sees whatever the publisher chose to put there, signed or not. Rebuilding from verified source is what gives you an independent account of what went into an artifact instead of trusting the publisher’s binary, and SecureChain for Java is built around exactly that premise.
MavenGate and the Problem With Abandoned Coordinates
MavenGate, published by researchers at Oversecured in January 2024, described a different route to a similar outcome, and it’s arguably the more troubling one because it requires no impersonation at all.
The technique targets abandoned dependencies. A library published years ago under com.oldcompany.util sits in thousands of dependency trees. The company folds, the domain lapses, and nobody notices, because the artifact still resolves perfectly from Maven Central.
What happens next is less automatic than it sounds. Where no account manages that groupId in a given repository, whoever now owns the domain can claim it with a DNS TXT record. Where the groupId is already registered, taking it over means persuading that repository’s support team to transfer access, and Oversecured said plainly that it had no information on how those transfers work. Much of the risk also comes from projects that resolve across several repositories, where a malicious version introduced elsewhere on the list still arrives under coordinates the build already trusts. Projects using version ranges, or upgrading to the attacker’s version, then pick it up without ever seeing an unfamiliar name.
The scale is easier to pin down than the mechanics. Oversecured converted Maven Central groupIds into domains and checked which ones could simply be bought, and 3,710 of 26,163 domains (14.18 percent) came back purchasable. Widen that to GitHub-backed namespaces and the private repositories the researchers could find, and 18.18 percent of a 33,938-identifier dataset looked exposed.
Sonatype pushed back on how feasible the attack was, given the automation in place, then disabled accounts tied to expired domains, tightened its namespace verification, and fixed a public-key validation regression that had let artifacts through signed with keys that were never published anywhere. Oversecured’s reply was that no single registry can close this, because the attack turns on projects resolving from several public repositories at once, and that the most any repository owner can do is refuse automatic claims on namespaces already flagged as vulnerable.
Maven Central’s normal policy keeps published releases immutable, so a version can’t be quietly modified or withdrawn by its publisher, and security takedowns like the Jackson one are the narrow exception. That is useful for reproducibility. It also means abandoned dependencies stay resolvable and keep sitting deep in dependency trees long after the domain or account behind the namespace stopped being maintained, looking identical to healthy ones from inside a pom.xml.
What happens to those libraries when a CVE lands is a question with no good answer. There’s no patched version to upgrade to, and vendoring a security-relevant Java library into your own repository means your team now owns its CVEs, whether or not anyone was staffed for that.
How a Compromised npm Package Reached Maven Central
The part most Java teams miss is that this can happen without anyone on the team going near npm.
In November 2025, the second wave of the Shai-Hulud npm worm reached Maven Central through automated mvnpm mirroring. mvnpm republishes npm packages as Maven artifacts so Java web projects can pull front-end libraries through their normal build without a separate JavaScript toolchain. The mirroring is automated, and new npm releases sync as they are published, which is the entire point of the tool and also exactly how the compromised package got through. A malicious release of the npm posthog-node package was rebuilt into Maven Central as org.mvnpm:posthog-node:4.18.1, carrying the same setup_bun.js loader and bun_environment.js payload as the npm-side Shai-Hulud campaign.
Socket reported the artifact, and Maven Central confirmed the compromised release had come out of the automated mvnpm process, and purged every mirrored copy the same day. PostHog doesn’t publish to Maven at all. There’s no evidence of the worm’s self-propagating behavior spreading inside the Maven ecosystem, only the one mirrored artifact. That distinction matters for how seriously to take this as a live Maven supply chain pattern rather than a one-off. It was real and it was automatic, and no Java maintainer had to republish anything for a compromised npm release to become available to Java builds, but it didn’t behave like a worm once it landed there.
What it does show is that automatic mirroring moves a trust boundary without anyone deciding to move it. A Java shop that has never touched npm can end up resolving a compromised npm package because a bridge service turned it into a JAR on its behalf.
| Path into a Java build | Requires the team to use npm | Visible in pom.xml |
|---|---|---|
| Direct npm dependency via a JS toolchain | Yes | No |
| Mirrored npm artifact from a bridge service | No | Yes, as a normal JAR |
| Transitive Java dependency pulling a mirrored artifact | No | Only under dependency:tree |
Check the transitive path directly rather than reasoning about what should be there. Find out what’s really in your tree.
mvn dependency:tree -DoutputFile=deps.txt
Then read it. A mid-sized Spring service can easily resolve several hundred artifacts from a couple of dozen declared dependencies. Mirrored packages do show up in that output, under org.mvnpm coordinates, which is a genuine clue as long as somebody on the team knows that namespace means npm code repackaged for Maven.
What to Change in Your Build This Month
Start with whichever build tool you’re already running, because the two paths aren’t equivalent effort. Gradle can bootstrap dependency verification quickly with --write-verification-metadata sha256,pgp, then it’s a matter of reviewing the generated hashes and keys before the file gets committed. Maven has no native equivalent, so it means adding a plugin and pinning versions in pluginManagement, which is more setup but still a week’s task, not a quarter’s.
Then look at the habit that actually let the Jackson package through. Coordinates get copied from wherever they’re found rather than checked against a project’s own docs, and that’s easier to fix with a rule than with a tool. Verify any groupId your team hasn’t used before against the coordinates in the project’s official repository or documentation before it goes anywhere near a pom.xml, especially anything lifted from a search result or an AI-generated snippet.
Abandoned dependencies need a different kind of judgment call, and MavenGate is the reason. An artifact with no recent releases and no active maintainer is a takeover candidate whether or not it has an open CVE right now, because the risk isn’t in the code, it’s in whether anyone still controls the domain or account behind that namespace.
A Gradle project started in the last couple of years can adopt verification in a day. A ten-year-old multi-module Maven build with a custom parent POM and forty plugins is a longer conversation, and the implementation effort really does differ that much between the two.
Where SecureChain Fits
Maven Central checks namespace ownership on the way in, and Maven compares repository checksums on the way down. Nothing in the default path confirms that the binary you resolve matches what the project actually built. SecureChain for Java replaces that resolution path, rebuilding every package from verified source, screening for malicious behavior before it reaches your build, and patching continuously, including after upstream support ends. Your existing Maven or Gradle workflow stays where it is – point it at TuxCare and keep building.
See what SecureChain for OSS covers, or subscribe to the newsletter to get incident breakdowns like this one as they happen.
FAQ
What is namespace impersonation on Maven Central?
Publishing an artifact under a groupId built to resemble a legitimate project’s namespace. Maven Central verifies that the publisher controls the matching domain or hosting account, which confirms control of that namespace and says nothing about any relationship to the project being imitated.
Does Maven verify PGP signatures?
Not by default. Maven Central requires PGP signatures on published artifacts, and Maven compares repository checksums but does not verify those signatures or their trusted-key mappings by default. Verification requires a plugin such as pgpverify-maven-plugin.
How do I enable dependency verification in Gradle?
Run ./gradlew --write-verification-metadata sha256,pgp help to bootstrap a verification-metadata.xml file, review the generated checksums and trusted keys, then commit that baseline. After that, Gradle fails the build on any artifact whose checksum or signature stops matching.
What is MavenGate?
A takeover technique, published by Oversecured researchers in January 2024, that targets abandoned dependencies. Whoever acquires the lapsed domain or account behind an old groupId may be able to claim that namespace in a repository where nothing manages it, or try to get access to an existing registration, and projects resolving across multiple repositories can then accept malicious artifacts under familiar coordinates.
Can a compromised npm package affect a Java project?
Yes. Bridge services that automatically republish npm packages as Maven artifacts can carry a compromised npm release into Maven Central, where it resolves into a Java build as an ordinary JAR under org.mvnpm coordinates. Those coordinates name the bridge, but a team that doesn’t recognize the namespace will treat the artifact like any other dependency. That is exactly what happened when Shai-Hulud 2.0 reached Maven Central through mvnpm in November 2025.
How do I know if my Java build resolved a malicious Java library?
Run mvn dependency:tree or ./gradlew dependencies and check every unfamiliar groupId against the upstream project’s own documentation rather than a search result. For anything abandoned, treat takeover risk as independent of CVE status. It still comes down to that kind of manual scrutiny, because Maven Central’s namespace verification establishes control of a domain or hosting account rather than affiliation with a similarly named project, and Maven doesn’t authenticate dependency signatures by default.
Does Java have an equivalent to npm postinstall scripts?
Not for dependency installation, though Java builds still execute untrusted code. Maven plugins run in the build lifecycle, annotation processors run inside the compiler, and Gradle build scripts are executable programs rather than configuration. The fake Jackson package used a related trick at runtime, letting Spring discover its configuration class and start the payload on its behalf.



