Basilisk
Architecture

Advisory grouping

How Basilisk groups OSV and NVD records that refer to the same CVE.

OSV and NVD often describe the same vulnerability under different IDs. OSV may use a GHSA ID and list the CVE as an alias, while NVD uses the CVE ID directly.

Basilisk groups those records for the API and explorer, but keeps the original source records as they are.

Grouping is a view over source records

OSV and NVD still own their records. Basilisk keeps the original IDs and data, so you can trace a grouped response back to each source.

Choosing the canonical ID

Check the aliases

If an advisory has one or more CVE aliases, Basilisk sorts them and takes the first one.

Fall back to the source ID

If there is no CVE alias, Basilisk uses the source advisory ID. Advisories without a CVE still get a stable ID this way.

Group matching IDs

Advisories with the same canonical ID are shown as one group.

Worked example: GHSA and NVD

Here is a real example from Pillow. OSV uses GHSA-3f63-hfp8-52jq, while NVD uses CVE-2023-50447.

{
  "id": "GHSA-3f63-hfp8-52jq",
  "aliases": ["CVE-2023-50447"],
  "affected": [{
    "package": { "ecosystem": "PyPI", "name": "Pillow" }
  }]
}

The CVE alias makes CVE-2023-50447 the canonical ID.

The canonical ID is only used to group and display the records. Each member keeps its own source ID, timestamps, affected data, and references.

What gets combined

The OSV-compatible response combines a few fields from the group:

  • The canonical ID becomes the document ID.
  • Source IDs and aliases are collected into aliases.
  • Severities are kept and deduplicated.
  • References are deduplicated by URL.
  • The earliest publication time and latest modification time are used.
  • Affected entries from the source records are kept.
  • Matched conda-forge packages and versions are added as affected entries.

Some fields, such as the summary and details, need one source. Basilisk prefers an OSV record when one is available. The REST response still lists the source IDs and source kinds, and each match keeps its own provenance.

Grouping match results

Matching happens against source advisories first. Grouping happens afterwards.

Two records in the same group can still produce different evidence. One may match through a PURL and an OSV range. Another may match through a CPE and an NVD range. Basilisk keeps both match rows and shows them under the same canonical advisory.

Package-specific advisory lists use one row per (package, canonical advisory). If that package/group pair has several applicability values, Basilisk picks them in this order:

  1. evaluated
  2. unevaluated
  3. version_unbounded

The global GET /v1/advisories browse endpoint has a different cardinality: one row per canonical advisory across all matched packages. It groups first, then applies search and source/severity filters, computes saved-view totals, sorts, and selects the requested bounded window. Each row reports the distinct matched-package count and retains source-attributed severity records. Package-specific remediation is intentionally absent because one canonical advisory can have different fixes and conda-forge publication states for different packages.

The grouped detail still includes the individual source matches and their provenance. For example, an evaluated OSV/PURL match wins over a version_unbounded NVD/CPE match in a package-specific list. The NVD match is still there when you open the grouped detail.

Looking up an advisory

GET /v1/vulns/{id} and the advisory detail route accept:

  • the canonical ID,
  • a source advisory ID,
  • a known alias.

All three open the same grouped advisory, so clients can use whichever identifier they already have.

On this page