Safeguard's software composition analysis (SCA, generally available) reads Maven pom.xml files and Gradle build.gradle, build.gradle.kts and gradle.lockfile files, resolves the full transitive tree up to 100 levels deep, and matches every resolved artifact against vulnerability advisories enriched with EPSS and CISA KEV. For Java, Kotlin and Scala on the JVM, Safeguard's docs list both static and dynamic reachability as generally available, and Griffin fix pull requests support Maven and Gradle. Autonomous remediation ships switched off, so every fix waits for your approval until you change that.
This post walks through what Safeguard reads in a JVM repository, how it handles the parts of Maven and Gradle resolution that make Java dependency risk hard to see, how reachability and exploit signals rank the results, and how a fix becomes a pull request. Every step is taken from Safeguard's public docs. Where the docs are silent, the post says so instead of guessing.
Key takeaways
- SCA (GA) covers Maven and Gradle. The CLI support matrix lists
pom.xmlfor Maven,build.gradleandbuild.gradle.ktswithgradle.lockfilefor Gradle,build.gradle.ktsfor Kotlin andbuild.sbtfor Scala, all marked full support. - Deep dependency scanning (GA) walks the resolved graph to depth 100 and records the full path from your project root to each vulnerable artifact.
- Reachability analysis (GA): Safeguard's docs list Java, Kotlin and Scala (JVM) as GA for both static and dynamic reachability, with analysis of compiled code. Reflection and other dynamic dispatch come back as Unknown and are treated as potentially reachable.
- Vulnerability management (GA) enriches findings with EPSS and CISA KEV, and the Vulnerabilities tab filters on both.
- Autonomous remediation and Griffin fix pull requests (GA, ships off) list Maven and Gradle with full AI Remediate support. Every category starts at Manual.
- License compliance (GA) reads the
<licenses>block in Maven POMs and understands SPDX expressions such as the Classpath exception. - CLI (GA) and CI templates run the same scan locally or in a pipeline. The default CI mode also runs SAST, which is GA.
Why Java dependency risk hides in the tree
A JVM service rarely declares most of the code it ships. A Spring or Ktor application might list twenty direct dependencies and pull in several hundred transitive ones. The question an SCA tool has to answer is not "what did we declare" but "what version of each artifact did the build actually resolve, and how did it get there". Maven and Gradle answer that question differently, and both answers surprise people.
Maven picks the nearest definition, not the newest
Maven's own documentation describes dependency mediation as picking the "nearest definition": when the same artifact appears at several places in the tree, Maven uses the version closest to your project. If two versions sit at the same depth, the first one declared wins. The docs also point out that you can guarantee a version by declaring it explicitly in your own POM or controlling it through dependencyManagement.
The practical effect is that a vulnerable version three levels down can win over a fixed version five levels down, purely because of tree shape. Adding an unrelated library can shift which version wins. And Maven has no built-in lockfile, so the resolved tree is a function of the POMs, the parent POMs, any imported BOMs and the repositories at build time.
Gradle picks the highest requested version
Gradle's documentation on conflict resolution says that Gradle considers every requested version wherever it appears in the graph, and by default selects the highest. That sounds safer, and often is, but it means a transitive upgrade you never asked for can arrive with a minor release of something else. Gradle offers dependency locking, which writes a gradle.lockfile after you run a build with --write-locks, and Gradle's docs say the lockfile is expected to be committed to source control. Locking is opt in, configured per configuration or with lockAllConfigurations(), and the Gradle docs note it does not cover buildscript classpaths unless you activate it there too.
So for Gradle projects the resolved state may be pinned in a lockfile, or it may not exist anywhere on disk until the build runs.
Shaded and fat JARs hide artifacts entirely
Many JVM teams ship an uber JAR, a WAR or an EAR. Shading copies classes from dependencies into your artifact, sometimes with renamed packages. A manifest scan of the source repository sees the declared dependency; a scan of the built artifact sees what actually shipped. They are not always the same.
What the standards ask for
None of this is optional for teams under a secure development framework.
- NIST SP 800-218 (SSDF) v1.1 task PW.4.4 asks producers to verify that acquired open source and other third-party components comply with the organisation's requirements, and its first example is to check regularly for publicly known vulnerabilities that have not yet been fixed. Task RV.1.1 asks teams to monitor vulnerability databases and other sources on an ongoing basis, and PS.3.2 asks teams to collect and maintain provenance data, including an SBOM, for every component of each release.
- OWASP Top 10:2021, A06 Vulnerable and Outdated Components says you are likely vulnerable if you do not know the versions of all components you use, and it explicitly includes nested dependencies in that list.
- CISA BOD 26-04, issued 10 June 2026, sets risk-based remediation timelines for US federal civilian agencies. It revoked BOD 22-01 and BOD 19-02. The urgency of each fix in its Table 1 depends on four factors: whether the asset is publicly exposed, whether the CVE is in CISA's KEV catalog, whether exploitation can be automated, and whether the attacker gains partial or total control. Even if you are not a federal agency, that four-factor model is a useful way to argue about order.
- FIRST's EPSS gives a daily probability that a published CVE will be exploited in the wild in the next 30 days, which is the other exploit signal most teams use next to KEV.
Log4Shell is the standing example of why the JVM tree matters. Apache's Log4j security page lists CVE-2021-44228 in log4j-core from 2.0-beta9, with 2.15.0 as the first fixed release for Java 8 and later and 2.12.2 for Java 7. NVD records that CISA added it to the KEV catalog on 10 December 2021. Many services carried log4j-core only as a transitive dependency of a logging bridge, an internal library or an application server, which is why "do we use Log4j" took many teams days to answer. For the longer history, see Log4Shell and the supply chain lessons of transitive Java dependencies.
Which Maven and Gradle files Safeguard reads
The table below is built only from Safeguard's docs: the CLI support matrix, the deep dependency scanning page, the IDE support page and the license compliance page. If a file is not listed here, the docs do not claim it.
| Ecosystem | Manifest files listed | Lock files listed | Status in the CLI support matrix |
|---|---|---|---|
| Maven | pom.xml | none | Full support |
| Gradle (Groovy DSL) | build.gradle | gradle.lockfile | Full support |
| Gradle (Kotlin DSL) | build.gradle.kts | gradle.lockfile | Full support |
| Kotlin | build.gradle.kts | none listed separately | Full support |
| Scala SBT | build.sbt | none | Full support |
| Built artifacts | .jar, .war, .ear | not applicable | Full support |
A few details are worth drawing out.
Maven has no lock file column. That matches Maven itself, which has no native lockfile. Safeguard's deep scanning page says it "resolves pom.xml, build.gradle, lockfiles" for Maven and Gradle. Read that as: for Maven the POM is the input, and resolution of the transitive tree is Safeguard's job.
Kotlin is a Gradle story. Safeguard lists Kotlin through build.gradle.kts. Kotlin projects built with Maven use a pom.xml and fall under the Maven row. The docs do not list Kotlin Multiplatform-specific metadata, so do not assume coverage for non-JVM Kotlin targets.
Built JVM archives are scanned too. The support matrix lists Java archives (.jar, .war, .ear) as full support for binary scanning. This is how you see what a shaded or fat JAR actually contains. Android builds appear in the same matrix as .apk and .aab, and the Integrations page has an Upload APK card for scanning an Android package without a repository.
License data comes from the POM. The license compliance docs list the <licenses> element in Maven POMs as one of the sources Safeguard reconciles, alongside SPDX identifiers, full-text matching of LICENSE and NOTICE files, and source file headers.
The IDE extension activates on pom.xml. Safeguard's IDE extensions (GA) list Maven and Gradle with pom.xml, build.gradle and build.gradle.kts as recognised manifests. The docs' list of files that activate the extension automatically in VS Code and Cursor names pom.xml but not the Gradle files, so in a Gradle-only workspace trigger a scan on demand the first time. For setup, see Safeguard in your IDE and in the Claude and ChatGPT connector directories.
How Safeguard resolves transitive depth
Safeguard's deep dependency scanning (GA) walks every edge of the resolved dependency graph down to level 100. At each node, according to the docs, it resolves the package URL and version against its vulnerability knowledge graph, computes license and provenance, feeds the node into reachability analysis, and records the full path from root to leaf.
Three implementation details from the docs matter for large Java builds:
- Shared subgraphs are resolved once. A library used by a hundred modules in a Maven reactor build is not resolved a hundred times.
- Negative results are cached. Paths with no known vulnerabilities are not re-walked on every advisory update.
- Rescoring is feed driven. When a new CVE lands, Safeguard rescores affected nodes instead of re-traversing the whole tree.
The docs give typical scan times of under a second for small projects under 500 nodes, 3 to 5 seconds for around 5,000 nodes, and 15 to 45 seconds for large monorepos above 50,000 nodes. Those are Safeguard's stated figures for the graph walk, not a promise about your build.
Where the path shows up
In the platform, clicking a finding shows the chain of dependencies that pulls in the vulnerable artifact, whether that chain is reachable, whether an alternative path exists, and what the safe version would be along with the other transitive changes that version would introduce. That last part is the one Java teams care about most. Bumping a transitive artifact under Maven mediation can change which version of a third library wins, and the path view is where you see that before you merge.
On the command line
The CLI usage docs list a --resolve-transitive flag on safeguard scan, which resolves transitive dependencies through an external dependency metadata service. The deep scanning page shows the depth set explicitly:
safeguard scan --dir . --resolve-transitive --transitive-depth 100
The CLI docs say that safeguard scan --help is the authority if a page and the help text disagree, and the usage page's flag table does not list --transitive-depth. Check --help on your installed version before you script it.
Getting the tree you actually ship
Safeguard reads what is in the repository. You control how close that is to the build.
- Gradle: turn on dependency locking and commit
gradle.lockfile. Then the file Safeguard reads is the resolved state, not just the requested ranges. - Maven: pin versions that matter in
dependencyManagementor import a BOM, so mediation is predictable. Then scan the built.jar,.waror.earas well, so shading and packaging are covered. - Both: scan the artifact you deploy, not only the source tree. The repository scan tells you what you asked for; the artifact scan tells you what you shipped.
Reachability for JVM code
Reachability analysis (GA) answers whether your code actually calls the vulnerable function. Safeguard's reachability docs describe three kinds:
- Static reachability builds a call graph from your source, lockfile and bytecode and checks whether any path leads to the vulnerable sink.
- Dynamic reachability records which modules and functions are loaded and invoked in running workloads, using a runtime collector, and flags never-executed vulnerable paths as cold.
- Configuration reachability parses framework configuration to decide whether code is wired in at all. The docs name Spring bean definitions as an example, which is directly relevant to Java.
The language table in the docs lists Java / Kotlin / Scala (JVM) as GA for static reachability and GA for dynamic reachability, with the note "High-fidelity analysis of compiled code." Working from bytecode rather than only source helps with Kotlin and Scala, whose compilers generate code that does not map one to one onto the source. Dynamic reachability needs the runtime collector deployed in your environment, so confirm with Safeguard how that fits your deployment before you count on it.
Each finding gets one of four classes:
| Class | Meaning in Safeguard's docs | What to do in a JVM codebase |
|---|---|---|
| Reachable | A real call path exists and is instantiated at runtime | Fix first, in priority order |
| Conditionally reachable | A path exists but needs a feature flag, route or configuration not currently enabled | Check your Spring profiles and feature flags before deferring |
| Unreachable | No call path exists in your compiled code | Schedule the upgrade, but do not ignore it if it is KEV listed |
| Unknown | The analyser could not decide, for example reflection or native code | Treat as potentially reachable, which is what Safeguard does |
The JVM-specific blind spots
The docs are candid that reachability is weakest when code runs through reflection, eval or unsafe dispatch, when the flaw is data driven, and when the vulnerable path depends on configuration the analyser cannot enumerate. On the JVM that covers a lot of ground: dependency injection containers, serialisation frameworks, JNDI lookups, service loaders, annotation-driven routing and scripting engines. Safeguard marks those cases Unknown and does not suppress them. That is the right default, and it means you should expect a noticeable Unknown bucket in a framework-heavy Spring codebase. Our earlier deep dive on reachability analysis for Java covers why reflection and DI are hard for any analyser.
EPSS and KEV prioritisation
Vulnerability management (GA) in Safeguard pulls advisories from NVD, GitHub Security Advisories, OSV, CISA KEV, vendor bulletins and researcher disclosures, according to the Vulnerabilities docs. Each finding carries the CVE ID, CVSS score, EPSS score, an Exploitable flag for known exploitation in the wild, the affected component and version, the fixed version, references and the reachability verdict.
The reachability docs describe a Priority Score from 0 to 100 that combines base severity, EPSS probability, KEV listing, public proof-of-concept availability, reachability class, environment, exposure, data sensitivity and business criticality. Weights are configurable per tenant under Settings › Prioritization. The default bands in the docs are:
- Reachable, KEV listed and in production: priority 90 to 100 (P0).
- Reachable, EPSS above 0.5 and in production: 75 to 89 (P1).
- Reachable, high CVSS and in staging: 50 to 74 (P2).
- Unreachable at any severity: 0 to 30, the backlog.
The Vulnerabilities tab has Priority and Reachability columns, sorted by priority by default. The docs list these filters:
reachable:true
kev:true
epss:>0.3
env:production reachable:true kev:true
The last line is the P0 backlog. For the reasoning behind these weights, and how to defend the order to an auditor, see Prioritise vulnerabilities with reachability, EPSS and KEV in Safeguard.
Mapping the score onto BOD 26-04
BOD 26-04's four factors line up reasonably well with Safeguard's inputs. KEV status is a direct input. Asset exposure maps to Safeguard's exposure label (internet facing, internal or air gapped). Technical impact and exploit automation are not separate Safeguard fields in the docs, but CVSS impact metrics and the public proof-of-concept signal cover part of the same ground. If you report against BOD 26-04, use Safeguard to find and rank the JVM findings, and keep your own record of how each one maps to Table 1. The directive's own timelines come from CISA, not from a vendor's score.
A note on Unknown and KEV together
A KEV-listed CVE in a JVM library that comes back Unknown because of reflection should be treated as reachable. A KEV-listed CVE that comes back Unreachable still deserves a fix on a short clock, because the analyser can be wrong about data-driven flaws, and because KEV means someone is already exploiting it somewhere. Safeguard's default bands put Unreachable findings low; your policy can keep KEV findings visible regardless.
Fix pull requests for Maven and Gradle
Griffin AI Remediate is part of autonomous remediation (GA, ships off). The AI Remediate docs list Java with Maven and Gradle as Full support. The documented flow is analysis, solution generation, impact assessment, pull request creation and validation with your tests. For dependency upgrades, the docs say Griffin upgrades vulnerable packages to fixed versions, handles transitive dependency conflicts, updates lock files and aims to keep the code compatible.
Autonomous remediation ships switched off. Every one of its categories starts at Manual, and nothing is applied until an administrator turns the master toggle on. Until then, every fix is a pull request you ask for and review.
Three ways to start a fix
From Safeguard's AI Remediate docs:
- Single vulnerability. Open the vulnerability, click AI Remediate, review the proposed changes, then click Create Pull Request.
- Bulk. Select several vulnerabilities, click AI Remediate Selected. Griffin batches compatible fixes, you review all of them, and Safeguard creates one pull request per repository.
- Page level. On the Vulnerabilities page, click AI Remediate at the top. Griffin analyses every fixable vulnerability and shows a remediation plan, which you approve before pull requests are created.
Settings that matter for JVM builds
Under Settings › AI Remediate the docs list five settings: Auto-Create PRs for safe fixes, Require Approval before pull requests, Test Integration to run tests before creating a pull request, Breaking Change Policy, and Branch Naming. For Java and Kotlin, turn on Test Integration before anything else. A dependency bump that compiles can still fail at runtime when a transitive artifact changes under Maven mediation or Gradle's highest-version rule, and only your tests will catch that.
Griffin rates each change as Safe, Minor, Major or Critical for breaking risk. The docs recommend starting with safe fixes and never auto-merging breaking changes.
The vulnerability and dependency categories each have four strategies, described in Safeguard's offering material: Safest (backward compatible fixes only), Balanced (minor updates and patches), Aggressive (all fixes including major versions) and Smart (the model picks per item). For a large Maven multi-module build, Safest or Balanced is the sensible place to start. For the full matrix, see Autonomous remediation in Safeguard: eight categories, four strategies, off by default.
What to check in a JVM fix pull request
The docs describe what Griffin does, not exactly which lines it edits in a POM or Gradle script. When you review the pull request, check these Java-specific points yourself:
- Maven: is the fix a direct version bump, a new or changed entry in
dependencyManagement, an exclusion, or a parent or BOM upgrade? Each has different blast radius in a multi-module build. - Gradle: if you use locking, was
gradle.lockfileupdated in the same change? If you use a version catalog or constraints, did the change land in the right place? - Kotlin DSL: did the edit go into
build.gradle.ktswith correct Kotlin syntax? - Tests: did the test run cover the module that uses the upgraded library, not only the module whose build file changed?
- The path view: after the change, does the finding's transitive path show the fixed version winning?
License data from Maven POMs
License compliance (GA) classifies every resolved component as permissive, weak copyleft, strong copyleft, network copyleft, commercial, public domain or unknown. When the POM's <licenses> block disagrees with a LICENSE file in the artifact, Safeguard raises a license discrepancy finding.
Java has one licensing case that trips many tools: the GNU Classpath exception. Safeguard's docs say it understands full SPDX expression syntax and give GPL-2.0-only WITH Classpath-exception-2.0 as an example, along with MIT OR Apache-2.0, where the most permissive option that matches your policy is chosen. License policies are written in YAML with allow, deny and require-review lists, can be scoped by asset label, and plug into gates at CI time, at admission and at pull request time.
There is no CLI command yet for license reports or policy evaluation, according to the docs, but safeguard scan writes a license.json alongside its other results. Attribution files (a NOTICE file, a licenses directory and an SPDX document per release) are generated in the platform. For the obligations side, see Open source license obligations, OpenChain and attribution with Safeguard.
Step by step: scan a Maven or Gradle project
The walkthrough below uses only commands and screens shown in Safeguard's docs. There are three routes: the CLI on your machine, a connected repository in the platform, and CI.
Route A: the CLI on your machine
-
Install the CLI (GA).
curl -fsSL https://cli.safeguard.sh/install | bashThe support matrix says no JVM, Node or Python runtime needs to be installed alongside the CLI.
-
Check the lock state first. For Gradle, if you use locking, regenerate it so the file matches the build:
./gradlew dependencies --write-locksThat is Gradle's own command from the Gradle docs, not a Safeguard one. Commit the result.
-
Open the interactive terminal and sign in. Run
safeguard, then type/login. Sign-in is an OAuth device code flow; on a machine with no browser,safeguard login --no-browserprints a URL and a code to enter elsewhere. -
Scan the project. In the terminal, type
/scan .. While you are signed in, the runner uploads the SBOM and findings to the platform; Safeguard's docs say your source never leaves the machine. -
Or scan from the command line and keep the files.
safeguard scan --dir .Results land under
./outputin a folder per scan, includingvuln.json,license.jsonandcollection.json. This route writes local files only; use/scan .when you want the results in the platform. -
Scan the built artifact as well. Point the same scan at the directory that holds your built
.jar,.waror.ear, or at the container image you deploy withsafeguard scan --image <name>. -
Read the exit code correctly.
safeguard scanexits 0 when the scan ran, whatever it found, 1 when the command failed, and 2 when results were written but the vulnerability scan did not run. There is no severity threshold flag onsafeguard scan; gating is done by policy gates in the platform or by checking the output yourself.
If your team will not give Safeguard repository access at all, the local runner is the route; see Repo connection, CLI or local runner: choosing how Safeguard touches your code.
Route B: a connected repository
- In the app, open Integrations and use the Source Code (SCM) filter. The Generate SBOM docs list GitHub, GitLab, Bitbucket and a generic Git URL.
- Click Connect on your provider, choose public or private, and for private repositories enter and verify credentials.
- On Review & Configure Repositories, pick the repositories, set the branch or tag, the project name and the version.
- Choose generation options. The docs list Include Dev Dependencies and Deep Analysis (Griffin). The docs do not say how the dev dependencies option maps to Maven's
testandprovidedscopes or Gradle's test configurations, so check the resulting SBOM once before you rely on it. - Turn on scanning triggers: On Push, Scheduled or On Release. Safeguard also runs a daily continuous scan of every project against new advisories.
- Open Projects, then the project, and go to the Vulnerabilities tab for findings, priorities and reachability.
Route C: CI with GitHub Actions or GitLab
Safeguard's CI integration runs safeguard ci scan in a plain shell step. There is no plugin to install.
-
Create a secret key under Settings › Developer with the scopes
scans:triggerandscans:read. You can lock it to one project. -
Store it in your CI as a secret named
SAFEGUARD_API_KEY. -
Add the step. This is the GitHub Actions workflow from Safeguard's docs, saved as
.github/workflows/safeguard.yml:name: Safeguard on: push: branches: [main] pull_request: branches: [main] jobs: scan: runs-on: ubuntu-latest steps: - name: Safeguard scan env: SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }} SAFEGUARD_INSTALL_DIR: ${{ runner.temp }}/safeguard-bin run: | set -eu curl -fsSL https://cli.safeguard.sh/install | bash "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on criticalFor GitLab, the docs show this job without the shared template:
safeguard: stage: test image: alpine:3.20 variables: SAFEGUARD_INSTALL_DIR: /usr/local/bin before_script: - apk add --no-cache bash curl ca-certificates - curl -fsSL https://cli.safeguard.sh/install | bash script: - safeguard ci scan --wait --fail-on critical rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "main" -
Pick where the scan runs. By default Safeguard clones the connected repository and scans it, which the docs say covers SAST (GA), SBOM and dependency vulnerabilities. With
--local, the scan runs on the CI runner against a checkout, covers SBOM and dependency vulnerabilities, and only the results leave the runner. The docs listpom.xmlamong the manifests the local mode reads. For GitHub Actions, add a checkout step before the scan when you use--local. -
Choose how the build fails.
--fail-on critical(orhigh,medium,low) fails on a dependency vulnerability at or above that severity.--fail-on-gate allfails when any policy gate is not Passed or Warning. The docs note SAST findings are not part of--fail-onyet. -
Pin the CLI version with
SAFEGUARD_VERSIONso a release does not change your build.
Each push becomes a new version of the Safeguard project, named after the branch and commit. If you prefer to scan on the runner and gate on the output file, the CI docs show this pattern:
safeguard scan --dir . --output out/
jq -e '[.data.matches[].vulnerability.severity | ascii_downcase]
| index("critical") | not' out/*/vuln.json
Gate on reachable, exploited findings
A severity gate alone will fail builds on unreachable criticals and pass reachable, KEV-listed highs. Safeguard's reachability docs show a policy that blocks the second case instead:
- id: no-reachable-kev-in-prod
condition: |
env == "production"
AND any(vulnerabilities,
reachability_class in ["reachable", "conditional"]
AND kev == true
)
effect: BLOCK
Policies, gates and guardrails are GA. Attach a policy like this to a gate and use --fail-on-gate in CI. To write policies in plain English instead of YAML, see Plain English security policies to release gates in Safeguard.
What a JVM finding looks like
The example below is illustrative. It uses the transitive path format from Safeguard's deep scanning docs and the finding fields from the SCA and Vulnerabilities docs, with a made-up project and internal library. The Log4j versions and KEV date come from Apache and NVD.
payments-api (Maven)
└─ com.example:internal-logging:3.2.0
└─ org.apache.logging.log4j:log4j-core:2.14.1 ← vulnerable (CVE-2021-44228)
CVE: CVE-2021-44228
Severity: Critical (CVSS 10.0)
Exploitable: yes (CISA KEV, added 2021-12-10)
EPSS: shown as a percentage in the finding
Reachability: Unknown (JNDI lookup through logging configuration)
Fixed version: 2.15.0 for Java 8 and later (later releases fixed follow-on CVEs)
Path depth: 2
Three things in that record drive the next step. The path tells you the fix is in internal-logging, or in a dependencyManagement pin in payments-api until that library ships. Unknown plus KEV means the finding stays at the top of the queue. And the fixed version shown is the minimum; Apache's page lists further CVEs fixed in 2.16.0 and later, which is why a Safest or Balanced fix strategy may still land on a newer release than the first fixed one.
Decision table: requirement, Safeguard, and what you still own
| Requirement | Source | What Safeguard does | What you still own |
|---|---|---|---|
| Know every component version, including nested ones | OWASP A06:2021 | SCA (GA) and deep dependency scanning (GA) resolve the tree to depth 100 with the path to each artifact | Committing gradle.lockfile, scanning the built artifact as well as the repo |
| Check acquired components for known, unfixed vulnerabilities | SSDF PW.4.4 | Vulnerability matching against NVD, GHSA, OSV and KEV, rescanned daily (GA) | Deciding acceptance criteria and exceptions |
| Monitor vulnerability sources continuously | SSDF RV.1.1 | Continuous scanning and feed-driven rescoring (GA) | Watching for alerts and acting on them |
| Keep an SBOM per release | SSDF PS.3.2 | SBOM generation (GA) in SPDX and CycloneDX from the scan | Tagging releases and distributing SBOMs to customers |
| Prioritise by exposure, KEV, automation and impact | CISA BOD 26-04 | Priority Score from reachability (GA), EPSS, KEV and asset context | Mapping each finding to Table 1 timelines and reporting |
| Plan and carry out a risk response for each vulnerability | SSDF RV.2.2 | Griffin fix pull requests for Maven and Gradle (GA, ships off) | Reviewing, testing and merging; turning categories to Auto if you choose |
| Respect license obligations | Your license policy | License compliance (GA), POM license parsing, SPDX expressions | Legal decisions on exceptions |
What Safeguard does not do
- It does not run your build. Safeguard reads
pom.xml, Gradle build files andgradle.lockfile. It does not execute your Maven or Gradle build, so build logic that changes dependencies at build time, such as custom plugins, profiles activated by environment, or dynamic versions with no lockfile, can resolve differently in your pipeline. Lock Gradle, pin Maven throughdependencyManagement, and scan the built artifact. - It does not list a Maven lock file or every JVM build tool. The docs list Maven, Gradle, Kotlin through
build.gradle.kts, and Scala SBT. Other JVM build tools are not in the support matrix, so do not assume coverage. - Reachability is not proof of safety. Safeguard's own docs say reachability is weakest with reflection, dynamic dispatch, data-driven flaws and configuration it cannot enumerate. A lot of JVM framework code falls into those categories. Unknown is treated as reachable, and you should keep it that way.
- Dynamic reachability needs a collector in your workloads. Static reachability works from source, lockfile and bytecode. The dynamic signal depends on a runtime collector being deployed, which is a separate decision for your platform team.
- Fix pull requests do not merge themselves by default. Autonomous remediation ships off with every category at Manual. Even after you enable it, your tests, code owners and branch protections still decide what merges. Safeguard cannot know that a minor Jackson or Netty upgrade breaks a serialisation format your clients rely on unless a test covers it.
- It does not fix code it does not build. If the vulnerable JAR sits inside an application server, a vendor product or a JDK you run but do not package, the fix still comes from that vendor. Safeguard can show it in an image or SBOM, but cannot change it.
- There is no CLI command yet for priority queries, dependency graph queries or license reports. The docs say so on each page. Use the platform for those.
- The
/v1REST endpoints shown on some docs pages are not available yet. Do not build automation on them; use the CLI, CI or the app.
Common mistakes
Scanning only the repository. A Maven repository scan tells you what the POMs request. A shaded JAR, a WAR with bundled libraries or a container image can carry something different. Scan both.
Leaving Gradle unlocked. Without gradle.lockfile, the resolved versions exist only at build time. Turn on locking with lockAllConfigurations(), run --write-locks, and commit the file, as Gradle's docs recommend.
Fixing the transitive artifact in the wrong module. In a multi-module Maven build, a version pin in one child POM may not affect the module that actually ships the vulnerable artifact. Use the finding's path view to see which module pulls it in.
Treating Unreachable as closed. Unreachable lowers priority. It does not remove the vulnerable code from your artifact, and a refactor next quarter can make it reachable. Keep these findings on a backlog with an upgrade date.
Gating CI on severity alone. --fail-on critical blocks on CVSS. Add a policy gate for reachable, KEV-listed findings so the builds that fail are the ones that should.
Turning on Auto before Test Integration. A fix pull request without a test run is a guess. Turn on Test Integration under Settings › AI Remediate first, then consider Auto for the Safest strategy only.
Expecting SAST findings to fail the CI step. The default CI scan includes SAST (GA), but its findings are not part of --fail-on yet. Review them in the platform.
Forgetting buildscript dependencies. Gradle's docs say lockAllConfigurations() does not lock buildscript classpaths. Plugins pulled into the build are dependencies too; lock them separately.
FAQ
Does Safeguard support Maven and Gradle?
Yes. Safeguard SCA (GA) lists Maven (pom.xml) and Gradle (build.gradle, build.gradle.kts, gradle.lockfile) with full support in the CLI support matrix, and the deep dependency scanning page lists Maven and Gradle among its ecosystems.
Does Safeguard support Kotlin projects?
Kotlin projects built with Gradle are covered through build.gradle.kts, and Kotlin projects built with Maven through pom.xml. The reachability docs list Java, Kotlin and Scala on the JVM as GA for static and dynamic reachability. The docs do not cover Kotlin Multiplatform targets beyond the JVM.
Does Safeguard read gradle.lockfile?
Yes. The CLI support matrix lists gradle.lockfile as the Gradle lock file. Commit it so Safeguard reads the resolved versions instead of requested ranges.
Is there a Maven lockfile Safeguard reads?
No. Maven has no native lockfile and the support matrix lists none. Safeguard resolves the transitive tree from the POM. Pin versions in dependencyManagement and scan the built artifact to close the gap.
How deep does Safeguard scan Java transitive dependencies?
The deep dependency scanning docs say Safeguard walks the resolved graph to depth 100 and records the full path from your project to each vulnerable artifact.
Can Safeguard scan a fat JAR or WAR file?
The CLI support matrix lists .jar, .war and .ear archives as full support. Scanning the built archive is the way to see dependencies that shading or packaging bundled into it.
Does Safeguard reachability work with Spring?
Safeguard's configuration reachability parses framework configuration, and the docs name Spring bean definitions as an example. Code reached only through reflection or other dynamic dispatch is marked Unknown and treated as potentially reachable.
Will Safeguard open pull requests to fix vulnerable Maven dependencies?
Yes, when you ask. Griffin AI Remediate lists Maven and Gradle with full support and opens one pull request per repository for bulk fixes. Autonomous remediation ships off with every category at Manual, so nothing opens on its own until you enable it.
How do I fail a Java build on KEV-listed vulnerabilities with Safeguard?
Create a policy that blocks reachable or conditionally reachable KEV-listed findings, attach it to a gate, and run safeguard ci scan --wait --fail-on-gate all in CI. --fail-on critical alone gates on severity, not exploitation.
Does Safeguard handle the GPL Classpath exception?
Yes. The license compliance docs list GPL-2.0-only WITH Classpath-exception-2.0 as an SPDX expression Safeguard evaluates against your policy.
Try it
Install the CLI and run your first scan from safeguard.sh/download, and keep the full reference open at docs.safeguard.sh/docs. The free Portal entry tier covers one existing repository plus one open source repository from GitHub, GitLab or Bitbucket, one project per month, SCA only, which is enough to try this on a Maven or Gradle service.
Finding the vulnerable artifact deep in a Maven or Gradle tree is half the job; the other half is getting a fixed version into the build. The block below is Safeguard's standard description of how that happens, including what to do when upstream has not shipped a fix.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session: install, run safeguard to open the interactive terminal, and ask it to "fix the lodash CVE in this repo". It reads the finding and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing. It ends with /permissions, /model and /scan .. The demo uses an npm project, but the loop is the same for a Maven or Gradle service: read the finding, change the build file, run your tests, review before anything is pushed.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. This is the route for a JVM monorepo you cannot connect to Safeguard directly: the scan runs where your code lives, and the SBOM and findings go to the platform.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to Safeguard: you install Safeguard from Claude's connector directory, sign in with OAuth, and choose which Safeguard tools Claude can use. Once your Maven and Gradle projects are scanned, this is how an engineer could ask an assistant which services carry a given log4j-core version or list the KEV-listed findings in one product. Safeguard is listed in Claude's directory as a community connector.
Click through it at your own pace, or open it full screen.
Quick check
Your Gradle service has no gradle.lockfile and Safeguard shows a different log4j-core version from the one in production. What is the most likely cause?
Without a lockfile, Gradle resolves versions at build time and picks the highest requested version wherever it appears in the graph, so the repository alone does not record what shipped. Turn on dependency locking, run a build with --write-locks, commit gradle.lockfile, and scan the built JAR or image as well.
A KEV-listed CVE in a JVM library comes back as Unknown reachability because the call goes through reflection. Should you defer it?
No. Safeguard's docs treat Unknown as potentially reachable, and KEV means exploitation has been observed. Fix it in priority order with your other reachable KEV findings, and use a policy gate so builds that carry it fail.
Sources
- Safeguard docs: Software Composition Analysis
- Safeguard docs: Deep Dependency Scanning
- Safeguard docs: Vulnerability Prioritization (Reachability Analysis)
- Safeguard docs: CLI support matrix
- Safeguard docs: CLI usage
- Safeguard docs: CI/CD integration
- Safeguard docs: GitHub Actions
- Safeguard docs: GitLab CI/CD
- Safeguard docs: AI Remediate
- Safeguard docs: Vulnerabilities
- Safeguard docs: Generate SBOM
- Safeguard docs: License Compliance
- Safeguard docs: IDE support
- Apache Maven: Introduction to the Dependency Mechanism
- Gradle: Dependency locking
- Gradle: Dependency constraints and conflict resolution
- Gradle: Dependency resolution
- NIST SP 800-218, Secure Software Development Framework v1.1
- NIST SP 800-218 full text (PDF)
- CISA BOD 26-04: Prioritizing Security Updates Based on Risk
- CISA Known Exploited Vulnerabilities Catalog
- FIRST: Exploit Prediction Scoring System
- OWASP Top 10:2021 A06 Vulnerable and Outdated Components
- Apache Log4j security vulnerabilities
- NVD: CVE-2021-44228
- CycloneDX specification overview
- SPDX specifications
Last reviewed: 24 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.