Skip to content

CVE Matching ​

List the OSV advisories filed under a package name, alongside the versions nixpkgs carries for it, with a confidence score per advisory.

GET /api/v1/vulns/:package

Path Parameters ​

ParameterTypeRequiredDescription
packagestringYesnixpkgs attribute or package name. Letters, digits, ., _, / and - only; anything else answers 400. Package-set prefixes such as python3Packages. are stripped before lookup

There are no query parameters. A query string is not read: ?version=8.4.0 returns the same body as no query at all. Version comparison uses the nixpkgs-unstable version this endpoint looks up itself, never a version supplied by the caller.

Response ​

A live response for python3Packages.requests:

json
{
  "success": true,
  "data": {
    "package": "python3Packages.requests",
    "normalizedName": "requests",
    "nixpkgsVersions": {
      "nixos-25.11": { "version": "2.31.0", "status": "outdated", "vulnerable": null },
      "nixos-26.05": { "version": "2.32.3", "status": "newest", "vulnerable": null },
      "nixpkgs-unstable": { "version": "2.32.3", "status": "newest", "vulnerable": null }
    },
    "newestVersion": "2.32.3",
    "versionSource": "nixos-search",
    "truncated": false,
    "vulnerabilities": [
      {
        "matches": [{ "product": "requests", "ecosystem": "PyPI", "ranges": [{ "introduced": "0", "fixed": "2.32.0" }], "cveId": "CVE-2024-35195" }],
        "confidence": { "score": 80, "factors": ["exact_name_match", "version_in_vulnerable_range"], "level": "high" },
        "cveId": "CVE-2024-35195",
        "source": "osv",
        "ecosystem": "PyPI",
        "osvId": "GHSA-9wx4-h78v-vm56",
        "summary": "Requests Session object leaks proxy credentials",
        "severity": [{ "type": "CVSS_V3", "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N" }],
        "references": [{ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-9wx4-h78v-vm56" }]
      }
    ],
    "totalVulnerabilities": 1,
    "highConfidenceCount": 1,
    "ecosystemsQueried": ["PyPI"]
  },
  "timestamp": "2026-10-21T19:51:47.988Z"
}

requests matches the python3Packages. naming segment, so this request queried PyPI and the advisory above is a real match for the Python package. A C library like curl or openssl matches no naming segment: OSV is never queried by ecosystem for it ("ecosystemsQueried": []), but the endpoint does not stop there -- it additionally checks the same advisory index the weekly digest uses (see What is queried), so vulnerabilities/totalVulnerabilities can still be non-empty for such a package, carrying "ecosystemFallback": true and lower-confidence entries (see Vulnerability). Only when that index itself could not be checked does the response fall back further, to "advisoryCoverageUnavailable": true with totalVulnerabilities: null -- distinct from a real 0, which means the check ran and found nothing.

Response Fields ​

Top level (data) ​

FieldTypeDescription
packagestringThe name as requested
normalizedNamestringThe name used for the version lookup and the OSV query, with any package-set prefix removed (python3Packages.requests becomes requests)
nixpkgsVersionsobjectVersions keyed by channel: nixpkgs-unstable and the current and previous NixOS releases. Empty when no version source answered
newestVersionstring|nullNewest version the version source reports. From Repology this spans every repository it tracks, nixpkgs or not, so it can be ahead of every nixpkgs channel. Absent when no version source answered
versionSourcestring|nullrepology or nixos-search, whichever supplied nixpkgsVersions. null when neither did
degradedbooleanPresent, and true, only when versionSource is null. See Degraded responses
vulnerabilitiesarrayOne entry per OSV advisory for the per-ecosystem path; for the ecosystemFallback path, one entry per deduplicated CVE ID found (an advisory with more than one CVE alias yields more than one entry; the same CVE found on more than one advisory yields one), plus one entry for a matched advisory with no CVE alias at all, keyed by its own id. Sorted by confidence.score descending
totalVulnerabilitiesnumber|nullLength of vulnerabilities. null, never a fabricated 0, when advisoryCoverageUnavailable is true
highConfidenceCountnumber|nullEntries whose confidence.level is high. null under the same condition as totalVulnerabilities
ecosystemsQueriedstring[]The OSV ecosystems this request actually asked, from What is queried. Empty for a name matching no naming segment (most C/C++ packages) -- see ecosystemFallback for what covers that case instead
ecosystemFallbackbooleanPresent, and true, only when ecosystemsQueried is empty and the broader advisory-index lookup described in What is queried ran and answered. Absent for the ordinary per-ecosystem path, and absent (in favor of advisoryCoverageUnavailable) when that lookup itself could not be checked
advisoryCoverageUnavailablebooleanPresent, and true, only when ecosystemsQueried is empty and the advisory-index fallback could not be read either (unbound, a failed query, or a failed upstream-CVE join). vulnerabilities is [], and totalVulnerabilities/highConfidenceCount are null -- this is "could not check," never cached, and never the same as a real zero
truncatedbooleantrue when OSV held more pages for an ecosystem than this endpoint's bound reads (4 pages, or a byte budget of 12 MB divided evenly across every ecosystem queried), or the advisory-index fallback had more rows than its own one-page read
truncatedEcosystemsstring[]Always present; empty unless truncated is true. When non-empty: which ecosystems hit the bound, or ["search-index"] for the fallback path

Channel version (nixpkgsVersions.<channel>) ​

FieldTypeDescription
versionstring|nullVersion in that channel; null when identityUnresolved is true
statusstring|nullRepology's status for it: newest, outdated, devel and so on; null when identityUnresolved is true
vulnerableboolean|nullRepology's own vulnerable flag for that exact version in that exact repository, when Repology sent one. null whenever Repology sent no flag for that row (most rows) or identityUnresolved is true -- never a fabricated false, which is not an assertion that the version is safe
identityUnresolvedbooleanPresent only when true: Repology named more than one candidate package for this name in this channel with no way to tell them apart, so version/status/vulnerable are withheld rather than guessed
ambiguousPackagesnumberPresent whenever Repology named more than one candidate, whether or not identity resolved -- a resolved channel can still carry this if the match it picked had siblings

Vulnerability ​

FieldTypeDescription
osvIdstringOSV identifier (GHSA-..., PYSEC-..., GO-...), or the advisory index's own row id for an ecosystemFallback entry (which may not itself be a CVE or an OSV id, e.g. a distro advisory id)
cveIdstringThe CVE alias when the record carries one, otherwise the identifier above
sourcestringAlways osv
lookupSourcestringPresent, and 'search-index', only on an ecosystemFallback entry: which retrieval path found it, distinct from source (which records where the data itself came from)
ecosystemstring|undefinedOSV ecosystem the advisory was found under (npm, PyPI, Go, ...). Absent on an ecosystemFallback entry, which has no single OSV ecosystem
confidenceobject{ score, factors, level }; see Confidence. An ecosystemFallback entry is always { score: 0, factors: [], level: 'low' } -- a name match only, never a fabricated version-range verdict
matchBasisstringPresent, and 'name-match-only', only on an ecosystemFallback entry
limitationsstring[]|undefinedPresent only on an ecosystemFallback entry: why its confidence is capped (no OSV affected-range data available from that lookup path)
matchesarray|undefinedOne { product, ecosystem, ranges, cveId } entry per affected package in the record whose name matches the query. Absent on an ecosystemFallback entry
summarystring|undefinedAdvisory summary, when the record carries one
severityarray|undefinedOSV severity entries, when present. Absent on an ecosystemFallback entry
referencesarray|undefinedOSV reference objects, when present. Absent on an ecosystemFallback entry

What is queried ​

The endpoint makes two lookups and joins them.

  1. Versions. Repology (or the service's copy of it) is asked for the project; if it has no nixpkgs entry, the NixOS package search is tried, using the last segment of a dotted name. The result fills nixpkgsVersions.
  2. Advisories. OSV is queried for normalizedName in a set of ecosystems chosen from the requested name, by a nixpkgs naming segment, not a bare substring: PyPI for a python segment or a py prefix, npm for a node or npm segment, RubyGems for ruby or gem, Go for go, crates.io for rust or cargo -- so python3Packages.django is queried in PyPI, but django alone matches no segment and queries nothing. A name matching no segment is queried in no ecosystem at all -- see ecosystemsQueried below for which ecosystems a given response actually asked.

No distribution ecosystem (Debian, Ubuntu, Alpine and so on) is queried by this per-ecosystem path, and the nixpkgs security issues are not consulted. An empty vulnerabilities array with a non-empty ecosystemsQueried means OSV held nothing under that name in the ecosystems actually asked; it is not evidence that the nixpkgs package is free of vulnerabilities.

  1. Advisory-index fallback, for a name matching no naming segment. A name matching no segment above additionally queries the D1 advisory index the weekly digest also relies on -- built from OSV's advisory dump across every package, independent of the naming-segment heuristic. A match there sets ecosystemFallback: true and produces a Vulnerability entry with confidence.level: 'low' and no version-range verdict: the index stores a flattened summary, not OSV's full affected structure, so this path cannot compare a version the way the per-ecosystem path can. When that index itself cannot be read, the response instead reports advisoryCoverageUnavailable: true -- "could not check," never cached, and never the same as a real zero. Still no distribution ecosystem is queried directly by either path: their OSV records carry non-standard ids and distro-specific version schemes this endpoint does not attempt to compare against nixpkgs.

For CVE coverage of a C library beyond what the above finds, use CVE Lookup, Nix CVEs or the nixpkgs security issues.

Confidence ​

The scoring rules are:

FactorPointsCondition
exact_name_match40An affected package in the record has exactly normalizedName
partial_name_match20One name contains the other
version_in_vulnerable_range30The nixpkgs-unstable version falls before a range's fixed event
version_fixed10The nixpkgs-unstable version is at or past a range's fixed event
critical_severity10CVSS 9.0 or above, or OSV severity CRITICAL
high_severity5CVSS 7.0 or above, or OSV severity HIGH

Each factor counts once at its strongest, not once per matching entry in the record, so the maximum score is 80 (exact_name_match + one of the version/severity factors) in practice; 100 would need every factor to fire at once. level is high at 70 or above, medium at 40 or above, and low below that.

Severity is read from the record's own CVSS v3 vector when one parses; when none does, it falls back to a GitHub-style severity word (database_specific.severity) if the record carries one. Version ranges are read as ordered introduced/fixed intervals per matching affected entry; last_affected is treated as inclusive, and an interval with no fixed event counts as still vulnerable. The full record for any entry is available from OSV Details by its osvId.

Degraded responses ​

When neither Repology nor the NixOS package search returns versions for the name, the response carries versionSource: null and degraded: true, nixpkgsVersions is empty and newestVersion is absent. OSV is still queried, so vulnerabilities can be populated. A live response for golang.org/x/crypto, with vulnerabilities truncated to its first entry:

json
{
  "success": true,
  "data": {
    "package": "golang.org/x/crypto",
    "normalizedName": "golang.org/x/crypto",
    "nixpkgsVersions": {},
    "versionSource": null,
    "degraded": true,
    "truncated": false,
    "vulnerabilities": [
      {
        "matches": [{ "product": "golang.org/x/crypto", "ecosystem": "Go", "ranges": [{ "introduced": "0", "fixed": "0.17.0" }], "cveId": "CVE-2023-48795" }],
        "confidence": { "score": 70, "factors": ["exact_name_match", "version_in_vulnerable_range"], "level": "high" },
        "cveId": "CVE-2023-48795",
        "source": "osv",
        "ecosystem": "Go",
        "osvId": "GHSA-3vm4-22fp-5rfm",
        "summary": "golang.org/x/crypto vulnerable to Terrapin Attack",
        "severity": [{ "type": "CVSS_V3", "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N" }]
      }
    ],
    "totalVulnerabilities": 58,
    "highConfidenceCount": 9
  },
  "timestamp": "2026-10-15T19:55:05.238Z"
}

The same shape is returned for a name nixpkgs does not carry at all and for one whose version lookup failed; the response does not distinguish the two. Degraded responses are served with Cache-Control: no-store and are never cached.

Errors ​

StatusBodyCause
400{"success": false, "error": "Invalid package name"}The name contains a character outside the allowed set
404{"success": false, "error": "Not found. ..."}No package segment in the path (/api/v1/vulns/)
500{"success": false, "error": "Failed to find vulnerabilities"}OSV did not answer. The endpoint fails rather than return an empty list for a package it could not check
429See Rate LimitsRate limit exceeded

Examples ​

bash
curl "https://api.vulnpatch.dev/api/v1/vulns/openssl"
curl "https://api.vulnpatch.dev/api/v1/vulns/python3Packages.requests"
javascript
async function listAdvisories(packageName) {
  const response = await fetch(
    `https://api.vulnpatch.dev/api/v1/vulns/${packageName}`
  );
  const body = await response.json();
  if (!body.success) throw new Error(body.error);

  const { data } = body;
  if (data.degraded) {
    console.warn(`No nixpkgs versions found for ${data.package}`);
  } else {
    console.log(`nixpkgs-unstable: ${data.nixpkgsVersions['nixpkgs-unstable']?.version}`);
  }

  for (const v of data.vulnerabilities) {
    console.log(`${v.osvId} (${v.ecosystem})`);
  }
}
python
import requests

def list_advisories(package_name):
    body = requests.get(
        f'https://api.vulnpatch.dev/api/v1/vulns/{package_name}'
    ).json()
    if not body['success']:
        raise RuntimeError(body['error'])

    data = body['data']
    if data.get('degraded'):
        print(f"No nixpkgs versions found for {data['package']}")
    else:
        unstable = data['nixpkgsVersions'].get('nixpkgs-unstable', {})
        print(f"nixpkgs-unstable: {unstable.get('version')}")

    for v in data['vulnerabilities']:
        print(f"{v['osvId']} ({v['ecosystem']})")

Differences from /osv ​

Aspect/osv/:package/vulns/:package
Ecosystems queriedAll OSV ecosystems, or one via ?ecosystem=Chosen from the name; the search-index fallback covers a name matching none of them
nixpkgs versionsNoYes, per channel
Per-advisory confidenceNoYes, scored from the matched affected entry's name, version range and severity
Name normalizationNoPackage-set prefixes stripped

Caching ​

Successful, non-degraded responses are cached at the edge for two hours, keyed on the full URL plus an internal cache-format version. X-Cache reads HIT or MISS; a hit also carries X-Cache-Time, and its timestamp is the time the cached response was built, not the time of the request. A response with advisoryCoverageUnavailable: true is sent with Cache-Control: no-store and is never cached.