Scanners are configured with one of the supported feeds.
A backpatch applies an upstream security fix to the release you already run and publishes it under a version
derived from that release, so a fix for 2.14.1 ships as 2.14.1.1-backpatch-00001.
Scanners that don't read an attestation from a feed see a version that starts with the release it patches, so they keep reporting the
CVEs filed against that release after you install the fix.
Each statement in this feed names a backpatched artifact by package URL and says whether a CVE is fixed in it or does not affect the code it ships. A scanner that reads VEX or CycloneDX moves those findings to suppressed and leaves every other finding open.
OpenVEX
Grype and Trivy read this file.
backpatch.moderne.io/feeds/openvex/all.json
CycloneDX
JFrog Xray, Sonatype, and Dependency-Track read this file. Each artifact also carries its own
entry beside its pom in the repository, as <artifact>-<version>-cyclonedx.json, signed and readable without credentials.
backpatch.moderne.io/feeds/cyclonedx/all.cdx.json
Each scanner reads the feed in a format it already supports.
As additional formats are supported, Backpatch Alliance will add more attestation sources. For example, currently the industry is weighing adding per-artifact VEX attestation that would eliminate the need for a separate feed. But this is not consistently supported by artifact repositories yet.
Grype reads OpenVEX.
Grype reads a local OpenVEX file, so download the feed and pass it with --vex.
Anchore Enterprise runs the same engine.
curl -fsSLO https://backpatch.moderne.io/feeds/openvex/all.json &&
grype sbom:my-app.cdx.json --vex all.json --show-suppressed
Trivy reads OpenVEX.
Trivy takes the same file through --vex and suppresses findings whose package URL matches a
statement. Harbor
and GitLab container scanning
run Trivy underneath.
curl -fsSLO https://backpatch.moderne.io/feeds/openvex/all.json &&
trivy sbom my-app.cdx.json --vex ./all.json
JFrog Xray reads CycloneDX.
Xray imports external CycloneDX VEX in JFrog Advanced Security 3.136 and later. After the import, turn on Skip Not Applicable CVEs and add an ignore rule scoped to each component and CVE.
curl -fsSLO https://backpatch.moderne.io/feeds/cyclonedx/all.cdx.json
Sonatype reads CycloneDX
Sonatype identifies components by binary hash, which often clears a rebuilt backpatch without the feed, and every artifact's checksums and CycloneDX document sit beside it in the repository for that ingest. To waive a finding that remains, post the CycloneDX document to the experimental vulnerability analysis API.
curl -fsSLO https://backpatch.moderne.io/feeds/cyclonedx/all.cdx.json &&
curl -u "$IQ_USER:$IQ_TOKEN" -X POST \
"$IQ_URL/api/experimental/vex/application/$APP_ID/report/$SCAN_ID" \
--data-binary @all.cdx.json
OWASP Dependency-Track reads CycloneDX
Dependency-Track applies CycloneDX VEX to one project at a time. Apply it again after every SBOM upload, which is simplest when the same CI step uploads both.
curl -fsSLO https://backpatch.moderne.io/feeds/cyclonedx/all.cdx.json &&
curl -X POST https://dtrack.example.com/api/v1/vex \
-H "X-Api-Key: $DT_API_KEY" \
-F "project=$PROJECT_UUID" -F "vex=@all.cdx.json"
You can check the feed from a terminal before it reaches a pipeline.
The feed is rewritten on every publish and served with Cache-Control: no-cache, so
each fetch revalidates against the latest copy.
# count the statements in the feed
curl -fsS https://backpatch.moderne.io/feeds/openvex/all.json | jq '.statements | length'
# confirm Grype suppresses a backpatched artifact
grype my-app.cdx.json --vex all.json --show-suppressed | grep 'suppressed by VEX'
A VEX statement records whether one CVE affects one artifact.
A scanner reads your SBOM and reports every CVE filed against the versions it finds.
VEX
(Vulnerability Exploitability eXchange) answers each report in a machine-readable form by naming the artifact,
the CVE, and a status. This feed uses two statuses: fixed when the backpatch ships the fix, and
not_affected when the CVE is filed against the baseline release but the vulnerable code is not in
the artifact.
Every statement is published twice, because Grype and Trivy read OpenVEX while Xray, Sonatype, and Dependency-Track expect CycloneDX.
OpenVEX from OpenSSF
OpenVEX is a small standalone format whose documents are JSON lists of statements, which is why command-line scanners can read them directly. The OpenVEX specification defines each field.
CycloneDX from OWASP
CycloneDX is a full SBOM standard that carries vulnerability analysis next to the components and can embed pedigree linking a fix to its upstream commit. Its VEX guide covers how statements attach to components.
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://backpatch.moderne.io/feeds/openvex/gson-2.8.8.1-backpatch-00001.json",
"author": "Moderne Backpatch Alliance <support@moderne.io>",
"timestamp": "2026-07-05T06:53:21Z",
"version": 1,
"statements": [
{
"vulnerability": { "name": "CVE-2022-25647", "aliases": ["GHSA-4jrv-ppp4-jm57"] },
"products": [
{
"@id": "pkg:maven/com.google.code.gson/gson@2.8.8.1-backpatch-00001",
"identifiers": { "purl": "pkg:maven/com.google.code.gson/gson@2.8.8.1-backpatch-00001" },
"hashes": { "sha-256": "2575baa2fd678a378998e9c3f238d68bdffe327303c00192401eff3ee570be17" }
}
],
"status": "fixed",
"action_statement": "CVE-2022-25647 fixed by backporting the upstream fix onto the 2.8.8 baseline."
}
]
}
- vulnerability
- The CVE and its aliases, including the GHSA identifier that Grype uses to key Java findings.
- products
- The patched artifact as a package URL (purl), with the SHA-256 of the jar that was built.
- status
fixedwhen the backpatch ships a fix for the CVE, ornot_affectedwhen the CVE never applied to the artifact.- action_statement
- On a
fixedstatement, a sentence describing what was changed. - justification
- On a
not_affectedstatement, the OpenVEX label for the reason, usuallyvulnerable_code_not_present, with an optionalimpact_statementthat gives the reason in prose.
A Gradle build can take the newest backpatch on its own.
The platform io.moderne.backpatch:backpatch-platform constrains
every artifact of ours to its newest backpatch, so Gradle's conflict resolution takes the backpatch wherever it is
later than the version the build resolves. A new version is published with the feeds whenever a backpatch is
released. npm has no platforms, so the npm index names the newest backpatch for each upstream release.
dependencies {
implementation(platform("io.moderne.backpatch:backpatch-platform:latest.release"))
}