VulnSea

Depth score methodology

Every CVE on VulnSea carries a depth score (0–100) and a depth zone (Sunlit → Hadal). Both are deterministic functions of public signals; nothing is hand-tuned per CVE. This page is the model, verbatim from the code that ranks the feed, plus worked examples computed live so they cannot drift.

Inputs

  • CVSS base score (NVD analysis; the assigning CNA's score from CVE.org until NVD publishes one). Falls back to the severity word when no score exists.
  • EPSS — FIRST's daily exploit-prediction probability (0–1), refreshed every day.
  • CISA KEV membership, and the KEV "known ransomware campaign use" flag.
  • Exploited in the wild — KEV, inthewild.io, CISA SSVC "active", or explicit evidence in the advisory text.
  • Public exploit code — Exploit-DB, GitHub PoC repositories, Metasploit modules, Nuclei templates, or SSVC "poc".
  • 0-day — exploited at or before disclosure.

The score

Four additive terms, clamped to 0–100 and rounded:

impact       = (cvss / 10  or  severity fraction) × 55      // 0–55
likelihood   = epss × 20                                    // 0–20
exploitation = KEV or 0-day → 25 · in-the-wild → 18 · public PoC → 12 · else 0
ransomware   = KEV ransomware flag → 5 · else 0

score = round(clamp(impact + likelihood + exploitation + ransomware, 0, 100))

severity fraction (no CVSS): critical 0.95 · high 0.75 · medium 0.50 · low 0.25 · none 0.05

Why these weights: impact is the largest term because a 9.8 with no exploitation still deserves attention; exploitation is a step function, not a multiplier, so a confirmed in-the-wild medium can outrank an untouched critical (a 6.1 exploited medium scores about the same as a 9.8 with only a PoC); EPSS is capped at 20 points because it is a probability estimate, not an observation. The exploitation tiers are ordered by evidence quality: a catalogue entry (KEV) beats a report, which beats code that merely exists.

Bands

ScoreBand
019minimal
2039low
4059moderate
6079elevated
80100severe

Zones

The zone is a coarser, rule-based label from severity and the strongest exploitation signal. It does not use EPSS, so it is stable day to day; the score is what moves.

ZoneMeaning
SunlitLow / medium · no exploitation signal
TwilightHigh severity, or a signal on a lesser flaw
MidnightCritical, or high with PoC / in-the-wild
AbyssalCritical with a public exploit or in-the-wild use
HadalCritical and actively exploited (CISA KEV / 0day)
critical: KEV/0-day → Hadal · any signal → Abyssal · else Midnight
high:     KEV/0-day → Abyssal · any signal → Midnight · else Twilight
medium:   KEV/0-day → Midnight · any signal → Twilight · else Sunlit
low/none: any signal → Twilight · else Sunlit

Worked examples

Computed by the production code at render time.

CaseImpactLikelihoodExploitationRansomwareScoreZone
Log4Shell-class: critical, exploited, in KEV, ransomware use5519.4255100 SevereHadal
Critical with a public PoC, no exploitation seen53.92.412068 ElevatedAbyssal
High severity, nothing but the score41.30.10041 ModerateTwilight
Medium severity but exploited in the wild (non-KEV report)33.6618058 ModerateTwilight
Unscored stub (NVD still analysing), no signals2.80003 MinimalSunlit

Where it shows up

  • sort=risk on the feed and /api/cve orders by score (materialised in the index, so it is an index scan, not a sort).
  • depthScore and depthScoreParts on /api/cve/<id> return the number and its four terms, so a client can explain a ranking.
  • depth=hadal,abyssal filters by zone anywhere the feed accepts filters.

Limits

  • It scores the vulnerability, not your exposure. Whether the product is in your estate, reachable, or patched is your side of the triage (the affected checker and /api/sbom help with that).
  • Signals lag reality. EPSS is daily; KEV additions and exploit repositories are picked up on the next enrichment pass (see status for freshness).
  • A record with no CVSS yet is scored on its severity word or, failing that, at the floor; it will jump when analysis lands.

Model version: the constants above are read from lib/brand.ts in the running build. Changes are recorded in the project changelog.