Skip to content

Remediation Plan ​

Compute the smallest set of package upgrades that resolves the most CVEs, with the breaking-change risk of each.

Endpoint ​

POST /api/v1/remediation/plan

This is POST only. A GET returns 405 naming the method, because the endpoint exists and saying "not found" about it would send you looking for a typo.

Request ​

FieldTypeRequiredDescription
cvesstring[]YesCVE identifiers to plan around.
packagesobjectNoMap of package name to the version you currently run. Supplies currentVersion and lets the effort and breaking-change analysis work.
bash
curl -X POST https://api.vulnpatch.dev/api/v1/remediation/plan \
  -H 'Content-Type: application/json' \
  -d '{
    "cves": ["CVE-2021-44228", "CVE-2023-4863"],
    "packages": { "org.apache.logging.log4j:log4j-core": "2.14.1" }
  }'

Package names must match the advisory's own naming. Log4Shell is recorded against org.apache.logging.log4j:log4j-core, not log4j. A name that matches nothing is not an error and does not reduce the plan: the upgrade is still returned, with currentVersion null.

Response ​

json
{
  "upgrades": [
    {
      "packageName": "org.apache.logging.log4j:log4j-core",
      "currentVersion": "2.14.1",
      "targetVersion": "2.15.0",
      "fixesCves": ["CVE-2021-44228"],
      "cvssImpact": 10,
      "effort": "moderate",
      "breakingChange": { "isBreaking": false, "reason": null }
    }
  ],
  "stats": {
    "totalCves": 2, "fixableCves": 1, "unfixableCves": 1,
    "upgradeCount": 5, "totalCvssImpact": 10
  },
  "unfixable": [{ "cveId": "CVE-2023-4863", "reason": "No known fix version" }],
  "packagesWithoutFix": [
    { "cveId": "CVE-2021-44228", "packageName": "Apache Log4j2" }
  ]
}

Telling the three outcomes apart ​

The distinction that matters most here is between a CVE nobody can fix and a package we happen to hold no fixed version for.

  • upgrades: one per package, ranked by the CVSS it resolves. A CVE affecting five packages produces five upgrades.
  • unfixable: a CVE where no affected package has a recorded fixed version. One entry per CVE. This is the only place that means "there is nothing to upgrade to".
  • packagesWithoutFix: a package with no recorded fixed version, for a CVE that is fixable through some other package. It does not mean no fix exists. It means we hold no fixed version for that particular package.

stats.totalCvssImpact counts each CVE once at its highest reported score, however many packages it affects. Summing the per-upgrade figure instead would report a plan for Log4Shell alone as 50, for a CVE scored 10 out of 10.

unresolved ​

Present when a CVE could not be looked up at all. It is kept separate from unfixable deliberately: one says we looked and there is no fix, the other says we could not look. Treat unresolved as a reason to retry, not as a finding about the CVE.

What a target version does and does not promise ​

targetVersion is the fixed version the advisory names for the CVEs you asked about. It is not a statement that the version is free of every other vulnerability.

Log4Shell demonstrates this. Ask for CVE-2021-44228 alone and the plan returns 2.15.0, which is what that advisory names. 2.15.0 was later found vulnerable to CVE-2021-45046, and the version you actually want is 2.17.1. The plan is faithful to the advisory and still not the whole answer.

So pass every CVE you know about for a package rather than one at a time, and re-run the plan against the version it gives you. A single-CVE plan answers a single-CVE question.

Errors ​

StatusCause
400Missing or empty cves array. The response carries an example body.
405A method other than POST. The Allow header names POST.
429Rate limited.