Safeguard
Supply Chain Security•

Fix Java Maven and Gradle vulnerabilities with the Safeguard CLI

Find vulnerable Java libraries, pin safe versions with Maven dependencyManagement or Gradle constraints, approve each step, test and rescan with the CLI.

Safeguard Security Team
18 min read

To fix a vulnerable Java dependency with the Safeguard CLI, scan the project, find which library pulls in the flagged artifact, then ask the assistant to pin a safe version in your pom.xml or Gradle build. You approve each edit and command, run your tests, and rescan. Scans run on your machine and only results go to the platform.

You can keep the AI plan you already use while you do it. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. If you would rather work from a chat app, connect Claude or ChatGPT to the Safeguard MCP server and ask about your Java findings there.

What you will have at the end of this guide:

  • The Safeguard CLI installed and signed in.
  • A list of vulnerable Java artifacts in your build, with the fixed version for each.
  • A clear picture of which direct dependency drags each one in.
  • A minimal, tested change in pom.xml, build.gradle.kts or your lockfile that removes the finding.
  • A rescan of the source tree and of the container image you ship.
  • A routine you can run from the terminal, or hand to your computer from the desktop app.

Coordinates in the examples, such as org.example:xml-parser, are placeholders. Use the ones from your own findings.

How Maven and Gradle pick a version

Most Java findings sit in transitive dependencies. You add one framework and it brings dozens of artifacts with it. So the first job is not editing a version; it is understanding why the build resolved the version it did.

Maven uses what its docs call dependency mediation. Maven "picks the 'nearest definition'", meaning the version closest to your project in the dependency tree wins, and "if two dependency versions are at the same depth in the dependency tree, the first declaration wins." That rule surprises people: a newer, fixed version deep in the tree can lose to an older one nearer the top.

Maven gives you a way to take control. The docs describe dependencyManagement as a way "to control the versions of artifacts used in transitive dependencies", and state that "dependency management takes precedence over dependency mediation for transitive dependencies." Declare the safe version there and every path that pulls the artifact in gets it.

Two more Maven facts matter in larger builds. You can import a BOM, a POM full of managed versions, with type set to pom and scope set to import inside dependencyManagement. And in multi-module builds, "the current POM's declaration takes precedence over its parent's", so a child module can quietly override the version you set in the parent.

Gradle uses dependency constraints for the same job. Its docs say you can "use constraints to select transitive modules without introducing them as direct dependencies", and that if the module is not pulled in at all, the constraint has no effect and does not add it. Gradle can also import a Maven BOM with platform(...). Plain platform versions are recommendations other dependencies can upgrade, and the docs warn that enforcedPlatform "should generally only be used in applications, not in libraries or other components consumed by others."

Gradle dependency locking adds a lockfile. The lock state lives in gradle.lockfile, and the docs say "lockfiles should be checked in to source control." If your build uses locking, a fix is not done until the lockfile changes.

Here are the commands side by side, all from the Maven and Gradle docs:

TaskMavenGradle
Show the whole treemvn dependency:tree./gradlew -q dependencies --configuration runtimeClasspath
Focus on one artifactmvn dependency:tree -Dincludes=org.example:xml-parser./gradlew -q dependencyInsight --dependency xml-parser --configuration runtimeClasspath
Show omitted or conflicting versionsadd -Dverboseread the insight report
Force a transitive versiondependencyManagement entryconstraints block
Import a BOMscope import, type pomplatform(...)
List newer versionsmvn versions:display-dependency-updatesread the insight report or your scan results
Update a locked versionnot applicable--update-locks org.example:xml-parser

In Gradle's tree output, (c) marks a dependency constraint rather than a dependency, and (*) marks a subtree already shown elsewhere.

Do the work properly with the Safeguard CLI

The Safeguard CLI scans code, container images and SBOMs on your own machine, sends only the results to the platform, and includes an assistant that can fix what it finds. It can write code, not only scan. For a Java project, that means it can read the finding, run mvn dependency:tree with your approval, edit pom.xml or build.gradle.kts, run your tests and rescan, all in one session.

Install

On macOS and Linux:

curl -fsSL https://cli.safeguard.sh/install | bash
safeguard --version

On Windows, in PowerShell:

irm https://cli.safeguard.sh/install.ps1 | iex
safeguard --version

It is a single static binary. It needs no Node, Python or JVM of its own, so it will not care which JDK your build uses. It runs on macOS, Linux and Windows, on x86_64 or arm64 (Windows ships x86_64 only).

First session

  1. Open a terminal at the root of your build, where pom.xml or settings.gradle.kts lives.
  2. Run safeguard. The interactive terminal opens.
  3. Type /login and sign in through the browser.
  4. Type /scan . to scan the folder.
  5. Ask: "which Java artifacts have high or critical findings, which direct dependency brings each one in, and what is the fixed version?"

Use @ to attach a file from the folder you started in, such as @pom.xml or @build.gradle.kts. /resume picks up your last session in that folder, which helps when a fix spans more than one sitting.

The recorded session below shows the full loop on a Mac. Its sample project, payments-api, is a Node app, but the approval flow is identical for Maven and Gradle. You see the install with the curl command (checksum verified), safeguard --version, and safeguard opening the interactive terminal. Then the prompt "fix the lodash CVE in this repo": the assistant reads the finding, files and advisory, asks before each command (press y or n), edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and prints a summary with a suggested commit message. It pushes nothing. The session ends with /permissions, /model and /scan ..

Click through it at your own pace, or open it full screen.

Writing and fixing code

For Java, a prompt that states the method and the limits works well:

Fix the high severity findings in @pom.xml. Pin safe versions in
dependencyManagement in the parent POM, do not add new direct dependencies,
then run mvn -q verify and show me the dependency tree for each artifact you changed.

There is also a direct command, safeguard fix, that updates vulnerable packages to safe versions and writes a report. Its help lists pom.xml as a manifest it accepts. Preview first:

safeguard fix --manifest pom.xml --severity high --dry-run

Useful flags include --package to limit the fix to named packages, --patch-only for patch upgrades only, and --major-upgrades to allow major jumps, which are off unless you ask. It backs up original files by default and writes fix-report.json. It can also open a pull request with --create-pr, but for a first run, review the change locally.

Using your Claude, ChatGPT or Codex plan

Run the assistant on the plan you already have. See the CLI docs or type /model to see and switch the model. To use Safeguard from the chat app instead, follow our guides for Claude and ChatGPT.

Approvals

Tools that change things ask before they act, according to /permissions:

  • Ask: reads run; edits and commands ask first.
  • Accept edits: edits in this folder and read-only commands run; other commands ask.
  • Plan: read and plan only.
  • Auto: read-only commands and what the policy allows run; anything else asks. The default.
  • Strict: pre-approved actions only, for CI.

A --yolo flag approves everything; keep it for throwaway sandboxes. Plan mode is handy for Java: ask for a remediation plan across a multi-module build, read it, then switch to Auto and let it apply the plan one module at a time.

Undo and CI

Files the assistant writes or edits are snapshotted before each change, and /rewind or safeguard checkpoints restore puts them back. Anything a shell command writes is not snapshotted, which includes a gradle.lockfile rewritten by --update-locks. Commit before you start.

For pipelines, safeguard exec runs one non-interactive turn with strict permissions by default and refuses to run outside a git repository:

safeguard exec "summarise the critical findings in this repository"

Our safeguard exec guide covers the pipeline side. Policies and gates are evaluated in the platform, not by the CLI.

Step by step: fix a Java finding end to end

The walkthrough has a shared start and finish, with separate Maven and Gradle steps in the middle. Each command is one you can run yourself or approve when the assistant proposes it.

1. Start clean and list findings

git status
git switch -c fix/java-deps
safeguard findings sca --dir .

safeguard findings runs the scanner on your machine and prints one normalized list with severity, package@version and the fixed version. Nothing is uploaded and no project is registered. For results in the platform too, run safeguard scan --dir ., which writes JSON under ./output, including vuln.json and license.json, and sends only results. Reachability analysis in the platform helps you decide which findings to fix first.

2. Maven: find the path

mvn dependency:tree -Dincludes=org.example:xml-parser
mvn dependency:tree -Dincludes=org.example:xml-parser -Dverbose

The first command shows every path to the artifact. The second adds the omitted nodes, so you can see when Maven chose a different version from the one a library asked for. If your project has the Maven wrapper, use ./mvnw instead of mvn.

3. Maven: pin the safe version

If the artifact is a direct dependency, raise its version where it is declared. If its version comes from a property, change the property:

<properties>
  <xml-parser.version>2.9.4</xml-parser.version>
</properties>

If it is transitive, add it to dependencyManagement, in the parent POM for multi-module builds:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.example</groupId>
      <artifactId>xml-parser</artifactId>
      <version>2.9.4</version>
    </dependency>
  </dependencies>
</dependencyManagement>

The Versions Maven Plugin can make the edit for you. versions:display-dependency-updates lists dependencies with newer versions, and versions:use-dep-version sets an exact one:

mvn versions:display-dependency-updates
mvn versions:use-dep-version -Dincludes=org.example:xml-parser -DdepVersion=2.9.4

Its docs list processDependencyManagement as true by default and processProperties as false. So if your version lives in a property, edit the property or use versions:update-properties.

If you manage versions through an imported BOM, check whether a newer BOM release already includes the fix. Upgrading the BOM is often a smaller change than overriding one artifact.

4. Gradle: find the path

./gradlew -q dependencies --configuration runtimeClasspath
./gradlew -q dependencyInsight --dependency xml-parser --configuration runtimeClasspath

dependencyInsight needs --dependency, which accepts a full group:name or part of one. In Java plugin projects --configuration defaults to compileClasspath, but the runtime classpath is usually what ships, so name it.

5. Gradle: add a constraint

In build.gradle.kts, add a constraint with a reason your teammates will read later:

dependencies {
    implementation("org.example:web-framework:5.2.0")

    constraints {
        implementation("org.example:xml-parser:2.9.4") {
            because("raise transitive xml-parser to the fixed release")
        }
    }
}

If the build uses dependency locking, update just that module and commit the lockfile:

./gradlew dependencies --update-locks org.example:xml-parser
git diff gradle.lockfile

6. Or hand steps 2 to 5 to the assistant

Inside safeguard:

For each high finding, show the dependency path, then pin the fixed version
using dependencyManagement (Maven) or a constraints block (Gradle).
Update gradle.lockfile if locking is on. Run the tests after each change.

With Auto mode, reads run straight away; edits and the build commands wait for you. Read each diff before you press y. If it proposes an exclusion or a major upgrade, press n and ask for the smaller option first.

7. Build and test

mvn -q verify
# or
./gradlew test

A Java version bump can compile cleanly and still fail at runtime, so run the tests that exercise the library you changed, not just the compile step.

8. Rescan the source and the image

safeguard findings sca --dir .
safeguard review --base main

safeguard review reviews the changes for correctness and security. If you ship a container, scan the image too, since that is what runs in production:

docker build -t payments-api:fix .
safeguard scan --image payments-api:fix

safeguard container-scan runs the same pipeline as scan --image. Our post on reviewing AI generated code before merge covers the review step in more depth.

9. Commit

git add pom.xml
git commit -m "Pin org.example:xml-parser to 2.9.4 via dependencyManagement"

The assistant suggests a commit message when it finishes and pushes nothing. Edit the message so it names the artifact and the method.

Send the work from the desktop app

A CLI session is live: it shows in the web app, the desktop app and the phone app, and other devices can send work to it. From the desktop or phone app you pick a computer running the CLI and a folder, then approve each command and file edit there. That suits Java work, where a full build can take a while and you may want to start it from wherever you are.

In the recorded desktop session below, the CLI session appears in the desktop app's conversation list, and the connection indicator shows the computer, the browser and Safeguard cloud. You ask for work on your computer, the app asks which computer and folder, you pick the folder, then Approve or Deny each command and file edit. It ends with the upgrade done and the tests passing.

Click through it at your own pace, or open it full screen.

The desktop app is available for Windows 10 and 11, macOS 12 and later, and Linux as an AppImage. Our desktop app guide covers install and first run.

Troubleshooting

The scan finds no Java artifacts. Start the CLI at the build root, or pass --dir to point at the module.

The tree still shows the old version. For Maven, check child modules: a child's own dependencyManagement entry takes precedence over the parent's. Run mvn dependency:tree -Dverbose inside that module. For Gradle, check whether another constraint, a platform or a lockfile holds the old version.

The Gradle constraint does nothing. Constraints only apply when something actually pulls the module in. Run dependencyInsight on the configuration that ships, not one where the module is absent.

The lockfile check fails in CI. You changed the build but not gradle.lockfile. Run the --update-locks command for that module and commit the file.

Tests fail after the bump. Paste the failure to the assistant, or attach the report with @. Often the fix is upgrading the direct dependency that wraps the artifact so the two versions match again.

safeguard fix does not change anything. Read the dry run and fix-report.json. The fix may need a minor or major upgrade beyond what the flags allow, or your build may set the version in a way the command does not edit. Fall back to the interactive assistant.

Anything else. safeguard doctor checks the environment, sign in, policy and tools without changing anything. safeguard self-update updates the binary, and safeguard update refreshes vulnerability databases.

Common mistakes

Fixing the artifact in one module only. In multi-module builds, put the pin in the parent dependencyManagement and check that no child overrides it.

Adding the artifact as a direct dependency. It works, but you now own a dependency you do not use directly. dependencyManagement and Gradle constraints set the version without that cost.

Reaching for exclusions. Excluding a transitive artifact removes it entirely. Unless something else provides it, you get a runtime failure. Pin a version instead.

Using enforcedPlatform in a library. Gradle's docs reserve it for applications. In a published library it forces versions on everyone who consumes you.

Scanning source and forgetting the image. The image can contain artifacts your build file does not show, such as a base image's own packages. Scan both.

Trusting checkpoints for lockfiles. A lockfile rewritten by a Gradle command is written by a shell command, so checkpoints do not cover it. Commit first, then let git undo it.

Approving builds without reading. The prompt shows the exact command. Check the module and the goal before you press y.

Committing scan output. safeguard scan writes JSON under ./output. Add it to .gitignore unless you mean to keep it.

FAQ

Can the Safeguard CLI fix Java vulnerabilities or only report them?

Both. It scans on your machine, and its assistant can edit pom.xml or Gradle files, run builds and tests, and rescan, each with your approval. The safeguard fix command also updates vulnerable packages and accepts pom.xml as a manifest.

Can I keep using my Claude, ChatGPT or Codex subscription?

Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. See the CLI docs or /model for connecting a model. You can also connect Claude or ChatGPT to the Safeguard MCP server.

Does my Java source code leave my machine?

The scan runs locally and the CLI sends only results to the platform. When the local runner runs platform scan jobs, only SBOMs and findings are uploaded. safeguard findings uploads nothing.

Should I use dependencyManagement or a direct dependency to fix a transitive artifact?

Use dependencyManagement. Maven's docs say it takes precedence over dependency mediation for transitive dependencies, so it sets the version without making the artifact a direct dependency.

How do I fix a transitive dependency in Gradle?

Add a constraint in the constraints block with a because reason. It raises the version wherever the module is pulled in, and has no effect where it is not.

Does it work with multi-module builds?

Yes. Start the CLI at the build root. Ask for a plan in Plan mode first, then apply it module by module. Put shared pins in the parent POM and check that no child overrides them.

Can I run it in CI?

Yes. safeguard exec runs one non-interactive turn with strict permissions by default. Gates and policies are evaluated in the platform.

Can I approve the build from somewhere else?

Yes. A CLI session shows in the desktop and phone apps, and you can send work to the computer running the CLI and approve each command and edit from there.

Try it yourself

This demo shows the local runner: set it up on your own machine, pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.

Click through it at your own pace, or open it full screen.

Ready to fix your own build? Download the Safeguard CLI and keep the CLI docs open for your first session.

Quick check

In Maven, what beats dependency mediation for a transitive artifact?

A dependencyManagement entry. Maven's docs say dependency management takes precedence over dependency mediation for transitive dependencies.

Why might a Gradle constraint have no effect?

Constraints only apply when the module is actually pulled in. If nothing in that configuration depends on it, the constraint does nothing and does not add the module.

Do Safeguard CLI checkpoints cover a rewritten gradle.lockfile?

No. Checkpoints cover files the assistant writes or edits directly, not files written by shell commands. Commit before you start and use git to undo.

Sources

Last reviewed: 21 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.