Safeguard
Security Guides

Maven Dependency Security: Pinning, Verification, and Scanning

How to secure a Maven build in 2026 — pin versions, enforce convergence, verify artifacts, and scan the transitive tree that pom.xml never shows you.

Marcus Chen
AppSec Engineer
5 min read

The pom.xml in your repository is a promising fiction. It lists the dependencies you asked for, but the JARs that actually land on your classpath are decided by Maven's transitive resolution, and there are usually five times as many of them as you declared. That gap is where supply-chain risk hides: the Log4j that gave you Log4Shell (CVE-2021-44228) was, for most victims, a transitive dependency pulled in by something else, sitting several layers deep where nobody was looking. Securing a Maven build means controlling not just what you declare, but what resolves, what's verified, and what's continuously checked.

Why is Maven dependency resolution a security problem?

Maven resolves version conflicts by "nearest wins" — the dependency closest to your project in the tree takes precedence, regardless of which version is newer or safer. This means a deeply nested library can silently downgrade a dependency you thought was patched, and nothing in your pom.xml will show it. The build succeeds, the tests pass, and you're running vulnerable code you never chose. The controls below exist to make resolution predictable and auditable rather than incidental.

Pin versions with dependencyManagement

Declare exact versions in a central <dependencyManagement> block (or import a BOM) so the same version is used everywhere, transitively included or not. This removes the ambiguity of nearest-wins for the libraries you care about:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.17.2</version>
    </dependency>
  </dependencies>
</dependencyManagement>

Anything that transitively pulls jackson-databind now gets the version you pinned, closing the downgrade loophole.

Fail the build on version conflicts

The Maven Enforcer plugin turns silent resolution surprises into hard build failures. Require dependency convergence and ban dynamic ranges so a mirror can't feed you an unexpected version:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions>
    <execution>
      <id>enforce</id>
      <goals><goal>enforce</goal></goals>
      <configuration>
        <rules>
          <dependencyConvergence/>
          <banDuplicatePomDependencyVersions/>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

dependencyConvergence fails the build when two paths resolve the same artifact to different versions, forcing you to make the decision explicitly rather than letting nearest-wins make it for you.

Verify what you download

A pinned version still trusts that the bytes coming from the repository are the real, untampered artifact. Maven Central requires published artifacts to be PGP-signed, and you should verify those signatures and checksums rather than accepting whatever your mirror returns. Keep an eye on the .md5/.sha1/.asc files Maven pulls alongside each JAR, and treat a checksum mismatch as a build-stopping event. Prefer a curated internal repository (Nexus or Artifactory) with a strict proxy policy over letting every developer machine reach arbitrary public mirrors — this also defends against dependency-confusion attacks where a malicious public package shadows an internal groupId.

Inspect the real tree, then scan it

Before you can secure the tree, you have to see it. The dependency plugin prints what actually resolves:

mvn dependency:tree -Dverbose

Then run a vulnerability scan against that resolved set. The OWASP Dependency-Check plugin is a common starting point:

<plugin>
  <groupId>org.owasp</groupId>
  <artifactId>dependency-check-maven</artifactId>
  <configuration>
    <failBuildOnCVSS>7</failBuildOnCVSS>   <!-- fail on High+ -->
  </configuration>
</plugin>

The limitation of a raw CVSS gate is that it can't tell whether a flagged dependency is actually reachable from your code — you either fail the build on everything High-and-above (noisy) or set the threshold so high you miss real risk. That's the gap reachability-aware scanning closes.

Don't forget the plugins

Maven's dependency security conversation usually stops at <dependencies>, but the <build><plugins> section is code that executes during your build with full access to the environment. A compromised or outdated plugin — or one pulled from an untrusted mirror — can inject behavior into every build it touches. Pin plugin versions explicitly (never rely on the latest resolved version), source them from your curated repository, and keep the same patch discipline you apply to runtime dependencies. The build lifecycle is part of your attack surface, and plugins are the part most teams never audit. Combined with a locked-down settings.xml that points at a single trusted mirror, this closes the loop between what you compile and what you run.

Maven security checklist

ControlCommand / configPrevents
Central version pinning<dependencyManagement> / BOMSilent transitive downgrades
Convergence enforcementmaven-enforcer-pluginNearest-wins surprises
Ban dynamic rangesEnforcer requireReleaseDepsUnexpected version pulls
Artifact verificationPGP/checksum, curated proxyTampered/confused artifacts
Continuous CVE scanDependency-Check / SCAVulnerable dependencies

How Safeguard helps

Every control above assumes you can see and act on the resolved tree continuously — which is exactly where a pom.xml and a periodic manual scan fall short. Safeguard's software composition analysis resolves and inventories the full transitive graph on every build, so a Log4j-style dependency buried four layers deep can't hide from you. Rather than a blunt CVSS gate, its reachability engine tells you which flagged dependencies are actually invokable from your application code, cutting the false-positive noise that makes teams disable build gates. When a safe upgrade exists, auto-fix pull requests open the version bump — often just editing your dependencyManagement block — directly against the repo, and the Safeguard CLI runs the same analysis locally and in CI so nothing merges without it. For continuous inventory and license tracking across builds, SBOM Studio turns each build's resolved tree into a signed, queryable bill of materials.

Start free at app.safeguard.sh/register, and follow the Maven setup guide at docs.safeguard.sh.

Never miss an update

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