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.05Why 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
| Score | Band |
|---|---|
| 0–19 | minimal |
| 20–39 | low |
| 40–59 | moderate |
| 60–79 | elevated |
| 80–100 | severe |
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.
| Zone | Meaning |
|---|---|
| Sunlit | Low / medium · no exploitation signal |
| Twilight | High severity, or a signal on a lesser flaw |
| Midnight | Critical, or high with PoC / in-the-wild |
| Abyssal | Critical with a public exploit or in-the-wild use |
| Hadal | Critical 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 SunlitWorked examples
Computed by the production code at render time.
| Case | Impact | Likelihood | Exploitation | Ransomware | Score | Zone |
|---|---|---|---|---|---|---|
| Log4Shell-class: critical, exploited, in KEV, ransomware use | 55 | 19.4 | 25 | 5 | 100 Severe | Hadal |
| Critical with a public PoC, no exploitation seen | 53.9 | 2.4 | 12 | 0 | 68 Elevated | Abyssal |
| High severity, nothing but the score | 41.3 | 0.1 | 0 | 0 | 41 Moderate | Twilight |
| Medium severity but exploited in the wild (non-KEV report) | 33.6 | 6 | 18 | 0 | 58 Moderate | Twilight |
| Unscored stub (NVD still analysing), no signals | 2.8 | 0 | 0 | 0 | 3 Minimal | Sunlit |
Where it shows up
sort=riskon the feed and/api/cveorders by score (materialised in the index, so it is an index scan, not a sort).depthScoreanddepthScorePartson/api/cve/<id>return the number and its four terms, so a client can explain a ranking.depth=hadal,abyssalfilters 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/sbomhelp 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.