Agent Support
Vulnpatch exposes CVE dossiers designed for agent runtimes. A dossier is a single JSON document that combines canonical CVE fields, source provenance, data-quality gaps, ingest status, and action hints.
CVE Dossier
GET /api/v1/cve/:id/dossierExample:
curl https://api.vulnpatch.dev/api/v1/cve/CVE-2024-3094/dossierBy default the response is the agent document, and nothing else:
canonical: normalized CVE fields for agent reasoning (id, severity, cvssScore, affectedPackages, epss, kev, availability).provenance: source names, source URLs, integrity hashes, and fetch timestamps.quality: missing fields, conflicts, grade (A–F), and completeness score.agentHints: recommended actions, known fixed versions, and booleans such asneedsIngest,needsTriage, andsafeForAutomation.ingest: freshness metadata includinglastIngestedAt,sourcesChecked, and the canonical dossier URL.links: the human CVE page, API lookup, and dossier URL.
For CVE-2021-44228 that is roughly 6 KB.
Requesting raw upstream data
Two heavier blocks are available on request:
sources: raw payloads from MITRE, NVD, OSV, GitHub Advisories, Nixpkgs, EPSS, and KEV.lookup: the full multi-source envelope from/api/v1/cve/:id, for drilling down without a second request.
GET /api/v1/cve/:id/dossier?include=sources
GET /api/v1/cve/:id/dossier?include=sources,lookup
GET /api/v1/cve/:id/dossier?include=allThese were previously returned by default, which made a single dossier ~195 KB for a well-covered CVE — about 50k tokens, of which the agent document was 3%. lookup also largely duplicates sources. Ask for them when you need to audit a specific upstream payload; otherwise the default is what you want.
An unrecognised include value returns 400 rather than being ignored, so a typo cannot silently cost you data you believed you had requested.
Availability
canonical.availability signals the publication state of the CVE using a stable enum. Agents should branch on this rather than parsing free-text.
| Value | Meaning |
|---|---|
published | At least one authority has usable data. |
partial | Some sources have data, but the canonical MITRE record is missing. |
reserved | The CVE id is reserved with MITRE but details have not been published. |
rejected | The CVE has been rejected by the numbering authority — do not use for remediation. |
withdrawn | The CVE record was withdrawn. |
awaiting_ingest | The id appears in the MITRE delta feed but enrichment has not landed yet. Treat like reserved; request ?fresh=true. |
A response header X-Dossier-Availability carries the same value for quick inspection.
Not found
If no authority has allocated the id, the dossier endpoint returns 404 with a structured body:
{
"success": false,
"error": "not_found",
"code": "CVE_UNKNOWN",
"hint": "No authority has allocated CVE-9999-99999. Verify the ID follows the CVE-YYYY-NNNNN+ format and check for typos."
}Branch on code — it is stable. The same contract is enforced on the raw lookup endpoint.
Agents should fetch the dossier before making remediation or triage decisions. The human-readable view of the same record is available at:
https://cve.vulnpatch.dev/CVE-2024-3094Requesting a fresh dossier
If agentHints.needsIngest is true, or the fetched data looks stale, append ?fresh=true to bypass cache and have the server reconcile the dossier from upstream sources before responding:
GET /api/v1/cve/:id/dossier?fresh=truecurl "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-3094/dossier?fresh=true"?fresh=true is rate-limited and may be disabled for unauthenticated callers under load; treat the cached dossier as the normal path and ?fresh=true as the refresh hint.
Runtime Guidance
Recommended agent flow:
- Fetch
GET /api/v1/cve/:id/dossier. - Read
agentHints. IfneedsIngestis true, retry with?fresh=true. - Use
agentHints.knownFixedVersions,quality.conflicts, andprovenancebefore recommending remediation. - Link users back to
links.htmlso humans can inspect the same evidence.
Do not mutate canonical CVE data directly from model output. The agent requests a refresh; deterministic server code performs the source fetches, normalization, hashing, and storage.
Agent Tools
Agent runtimes that integrate with Vulnpatch expose two dossier-aware tools backed by the public dossier endpoint:
lookup_cve_dossier({ cve_id, fresh? })— returns the dossier JSON. Usefresh: trueto request a refresh.queue_cve_ingest({ cve_id })— signals that the dossier is stale or incomplete and requests reconciliation. Rate-limited per CVE.
Both tools validate cve_id against CVE-YYYY-NNNNN+ and return JSON identical in shape to the dossier endpoint.
Nixpkgs coverage
Every dossier carries a nixpkgs block. It is the one ecosystem no other CVE database reports on, and previously its absence was indistinguishable from irrelevance — a CVE already fixed in nixpkgs, one never tracked, and one with no nixpkgs relevance all rendered as silence.
"nixpkgs": {
"tracked": false,
"coverage": "no_tracker_issue",
"trackerIssues": [],
"searchUrl": "https://github.com/NixOS/nixpkgs/issues?q=CVE-2021-44228",
"note": "Checked: no open nixpkgs security tracker issue references this CVE. ..."
}coverage is one of:
| value | meaning |
|---|---|
tracked | the nixpkgs security tracker references this CVE; see trackerIssues |
no_tracker_issue | sources were consulted and found no open tracker issue |
not_evaluated | nixpkgs sources have not been consulted for this CVE yet |
no_tracker_issue is not a claim that nixpkgs is unaffected. A package may already be patched, or may never have been tracked. Agents should treat it as "no signal", follow searchUrl, and prefer coverage over inferring anything from an empty trackerIssues array.
No nixpkgs attribute is ever guessed from a package name. Every entry comes from a source, because a wrong attribute is worse than no attribute.
Why a fixed version is missing
fixedVersion: null was ambiguous, so each affected package now carries the reason alongside it:
{
"name": "org.xbib.elasticsearch:log4j",
"ecosystem": "maven",
"fixedVersion": null,
"fixedVersionStatus": "unspecified",
"fixedVersionNote": "The GitHub advisory did not provide a first patched version for this package.",
"source": "github"
}fixedVersionStatus is known or unspecified. There is deliberately no "no fix exists" value: no upstream source tells us that, so asserting it would be a guess. unspecified means the source named the package without naming a patched release — look at another source, not that the package is unfixable.
quality.missingFields gains nixpkgs_mapping when no nixpkgs data is present, and fix_versions when packages are known but not one carries a patched release, so both gaps are measurable rather than merely visible.
Source provenance history
Every raw upstream payload is retained, addressed by the SHA-256 of its canonical form. Storage is written only when content actually changes, so a quiet upstream on the thirty-minute refresh costs nothing.
GET /api/v1/cve/:id/provenance{
"cveId": "CVE-2021-44228",
"changes": [
{
"source": "github",
"from": "aaa…", "to": "bbb…",
"at": "2026-08-05T00:00:00Z",
"added": [], "removed": ["vulnerabilities"], "changed": ["severity"],
"regressed": true,
"regressionReason": "payload lost 1 field(s) present in the previous version: vulnerabilities"
}
],
"summary": { "totalChanges": 2, "regressions": 1, "sources": ["github"] }
}regressed is the entry worth noticing: a source that dropped fields it previously reported is usually an upstream failure rather than a correction. That is the signal behind the dossier refusing to replace a complete cached document with a thinner rebuild.
Fetch any retained payload by content hash to see exactly what a source said:
GET /api/v1/cve/:id/provenance?source=github&hash=<hash>Payloads are retained for 90 days; change-log entries outlive them, so an entry may reference a pruned snapshot and return 404 SNAPSHOT_UNKNOWN. The comparison is field-level, not character-level, and ignores key and array ordering — upstreams reorder freely and that is not a change.