Skip to content
corifeoPublic

About

Model every CVE as a disclosure state machine: zero-day, coordination and fix-vs-exploit analytics over NVD, EPSS and CISA KEV data, vectorized with NumPy.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

vulnstate

Model every CVE as a disclosure state machine. Ask "was it a zero-day?", "did the fix beat the exploit?", "what do I patch first?" across your whole dataset in one line of NumPy.

Python 3.9+ License: MIT Typed CI Data: NVD · EPSS · KEV

Install · Quick start · Real-data triage · The model · Analytics · Data import

Three disclosure stories and the analytics vulnstate derives from their event order

A vulnerability's risk isn't just its CVSS score; it's the order things happened in. Did the vendor know before the exploit dropped? Was a patch out before attacks started? vulnstate tracks six lifecycle events per CVE and turns their ordering into zero-day flags, coordination quality, fix effectiveness and the 12 CVD desiderata. It's based on the SEI/CMU model by Householder & Spring (CMU/SEI-2021-SR-021).

  • One CVE or a million. CVDVulnerability for a single timeline, CVDArray for vectorized NumPy analytics. Boolean filters over 1M records run in ~15 ms.
  • Real feeds built in. NVD JSON (1.1 and 2.0), FIRST EPSS, CISA KEV, plus any CSV/JSON with dates, all merged by CVE ID.
  • No manual labelling. Zero-day, premature disclosure, "fix before exploit" and the rest are all derived from timestamps.
  • Plays well with the stack. pandas export, Jupyter HTML repr, Rich console output, type hints (py.typed).

Installation

vulnstate isn't on PyPI yet. Install it from GitHub:

uv add git+https://github.com/corifeo/vulnstate
pip install git+https://github.com/corifeo/vulnstate

From source (includes the sample data, notebooks and tests):

git clone https://github.com/corifeo/vulnstate && cd vulnstate
uv sync --all-extras

Requires Python 3.9+. Extras: viz (matplotlib, seaborn, networkx), notebook, dev.

Quick start

from datetime import datetime
from vulnstate import CVDEvent, CVDVulnerability

vuln = CVDVulnerability("CVE-2024-0001", cvss_score=9.1)
vuln.apply_event(CVDEvent.V, timestamp=datetime(2024, 1, 1))   # vendor aware
vuln.apply_event(CVDEvent.F, timestamp=datetime(2024, 1, 8))   # fix ready
vuln.apply_event(CVDEvent.P, timestamp=datetime(2024, 2, 1))   # public

vuln.state            # 'VFdPxa'
vuln.fix_path          # FixPath.FIX_READY
vuln.threat_state      # ThreatState.DISCLOSED
vuln.is_zero_day       # False
vuln.is_coordinated    # True: vendor knew before the public did

print(vuln.format_timeline())

The V→F→D order is enforced: applying F before V returns False and leaves the state unchanged. P, X and A can arrive in any order, just like in real life.

Triage real data in 10 lines

The repo ships a real sample: 770 CVEs from NVD 2023, joined with EPSS scores and the CISA KEV catalog.

from vulnstate import CVDArray, CVDEvent

arr = CVDArray()
arr.import_nvd("examples/sample_nvdcve_2023.json")   # V, F, P, X from NVD reference tags
arr.import_epss("examples/sample_epss.csv")          # exploitation probability
arr.import_kev("examples/sample_kev.csv")            # known exploited -> event A
arr.transform()                                      # compute analytics (run after imports)

immediate = arr.kev | arr.is_under_attack
urgent = ~immediate & ((arr.epss_percentile > 0.95) |
                       (arr.is_weaponized & (arr.cvss_scores >= 9.0)))

arr.cve_ids[immediate & arr.is_zero_day]   # exploited before the vendor knew

Patch-priority tiers computed from real NVD, EPSS and KEV data

Every number above comes from the sample data; scripts/make_readme_assets.py regenerates it. Note Public → KEV: several 2023 CVEs were in KEV within days of disclosure, and some were listed before NVD published them.

Each CVE sits on two axes, remediation progress (V→F→D) and threat (P→X→A). In this sample, more than 70% have a known vendor and no fix:

Heatmap of 770 real 2023 CVEs by fix path and threat state

The model

Each vulnerability tracks six events. A state like VFdPxa means vendor aware, fix ready, not deployed, public, no exploit, no attacks (uppercase = happened).

Event Meaning Rule
V Vendor aware before F
F Fix ready before D
D Fix deployed needs F
P Public aware any order
X Exploit public any order
A Attacks observed any order

That gives 32 valid states out of 64 bit combinations, stored as a uint8 bitmask. Analytics come from the timestamps: the 12 desiderata are the "good" orderings (V before P, F before X, D before A, …). Each one is satisfied (both events, right order), violated (the later event came first, or arrived without the earlier one; an exploit with no vendor awareness is a zero-day) or pending. desiderata_score is the share not violated.

Analytics

On CVDArray every property is a NumPy array (after arr.transform()), so masks combine with &, | and ~. The zero-day, coordination and fix-effectiveness flags are also available as scalars on CVDVulnerability.

Group Properties
Zero-day is_zero_day, is_zero_day_exploit (X before V), is_zero_day_attack (A before V)
Coordination is_coordinated (V before P), is_responsible_disclosure (V→F→P), is_premature_disclosure (P before F)
Fix effectiveness has_fix_before_exploit, has_fix_before_attack, has_deployment_before_exploit, has_deployment_before_attack
Threat is_weaponized, is_under_attack, is_private_attack (A without X), is_mass_exploitation
Timing (days) disclosure_window_days (V→P), fix_lag_days (V→F), deployment_lag_days (F→D)
Scores desiderata_score, desiderata_mask, anti_desiderata_mask, fix_path, threat_state
Enrichment cvss_scores, attack_vector, epss, epss_percentile, kev, kev_dates
from vulnstate.constants import AntiDesiderataBit, DesiderataBit

arr[arr.is_zero_day_exploit | (arr.kev & ~arr.has_fix_before_exploit)]
arr[arr.where_desiderata_satisfied(DesiderataBit.D1_V_P)]      # vendor knew first
arr[arr.where_desiderata_violated(AntiDesiderataBit.U2_X_V)]   # exploit before vendor
arr[arr.event_age_days(CVDEvent.P) > 90]                       # public for 90+ days

arr.count_by_state()          # {'VfdPxa': 280, 'VfdPXa': 277, ...}
arr.count_by_fix_path()
df = arr.to_dataframe()       # one row per CVE, analytics included

Data import

Sources are merged by CVE ID; each one applies its events with real timestamps. Paths can be local files or URLs.

arr.import_nvd("nvdcve-2.0-2024.json")      # NVD JSON 1.1 or 2.0
arr.import_nvd_glob("nvdcve-*.json")
arr.import_epss("epss_scores.csv")
arr.import_kev("known_exploited_vulnerabilities.csv")

# Any CSV/JSON with a CVE column and a date becomes a lifecycle event
arr.import_csv("patch_rollout.csv", cve_column="cve_id",
               event=CVDEvent.D, timestamp_column="deployed_date")
arr.import_json("intel.json", cve_field="vuln.cve_id",   # dot notation for nesting
                event=CVDEvent.A, timestamp_field="intel.first_seen")

arr.transform()
arr.to_json_batch("snapshot.json", include_computed=True)

Synthetic data for experiments: CVDArray.generate(10_000, seed=42) (realistic timelines and CVSS) or CVDArray.random(n).

Examples & notebooks

06_data_science_workflow.ipynb pandas/matplotlib analysis of the NVD sample
08_security_analysis.ipynb zero-days, coordination quality, KEV vs non-KEV
09_risk_posture.ipynb patch prioritization and risk posture
examples/01–05_*.py terminal tutorials: basics, batch ops, risk, inference, data import
scripts/make_readme_assets.py builds every image in this README
uv run python examples/01_getting_started.py

Development

uv sync --all-extras
uv run pytest                                                   # test suite
uv run mypy src/ --strict
uv run ruff check src/ tests/
uv run --extra viz python scripts/make_readme_assets.py         # regenerate README images

Issues and PRs are welcome, especially new data importers (OSV, GitHub Security Advisories, vendor advisories) and analytics from the CVD literature.

Citation

vulnstate implements the model below. To cite vulnstate itself, use Cite this repository on GitHub (CITATION.cff).

Householder, A. D., & Spring, J. (2021). A State-Based Model for Multi-Party Coordinated Vulnerability Disclosure (MPCVD). CMU/SEI-2021-SR-021. Software Engineering Institute, Carnegie Mellon University.

License

MIT

About

Model every CVE as a disclosure state machine: zero-day, coordination and fix-vs-exploit analytics over NVD, EPSS and CISA KEV data, vectorized with NumPy.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages