Skip to content

Repository files navigation

monthlyCVEStats

Monthly CVE statistics from the National Vulnerability Database, published on the first of every month.

An unattended pipeline that pulls the NVD feed, works out what actually changed last month, and commits a report, a set of social-sized charts, and post copy ready to paste. It shares its chart styling with CVEGraphs, so output from either repo sits together in a feed.

Where it stands

Metric Value
CVEs published, September 2026 14,943
2026 year to date 72,812
All time, since 1988 381,691
Average per day, September 498.1
Mean CVSS, September 6.98
Median CVSS, September 7.3

Data through September 30, 2026, excluding rejected CVEs.

This block is regenerated by the monthly run. Full reports live in outputs/, one directory per month.

Quick start

pip install -r requirements.txt

python -m src.cli.main download-data          # ~1.3 GB, resumes if interrupted
python -m src.cli.main run-monthly            # report on the completed month
python -m src.cli.main generate-ytd-report    # YTD charts + post copy

download-data writes data/nvd.jsonl, which is gitignored; every other command reads from it. run-monthly and generate-ytd-report report on the previous month when run on the 1st, and on the current month otherwise.

Other commands: generate-reports --year 2025 --month 1 for a specific month, update-readme-stats to refresh the block above, validate to check paths and data, check-timezone to confirm the scheduled run's timezone.

How it fits together

File Role
src/config.py Paths, NVD source URL, which month to report on, CI detection
src/data/downloader.py Downloads the NVD feed, resumable, with retries
src/data/processor.py Flattens nested CVE JSON into a DataFrame, filters by date
src/analysis/statistics.py CVSS distribution, CNA rankings, CWE counts, daily spread
src/analysis/trends.py Month-over-month and year-over-year movement
src/analysis/ytd_growth.py Year-to-date totals and the social post copy
src/reports/generator.py Writes the Markdown, JSON, and CSV reports
src/reports/style.py Chart house style: palette, fonts, header, date stamp
src/reports/ytd_visualizer.py YTD growth and YoY charts, three ratios, two themes
src/utils/readme_updater.py Regenerates the stats block above
src/cli/main.py Typer CLI: every command listed here
fonts/ Bundled typefaces, committed so CI renders in the right face
tasks/ One-off maintenance scripts, not part of the monthly run
docs/archive/ Superseded planning and migration notes
STYLE.md The house style these reports and charts are written to

Workflow

monthly-update.yml runs on the 1st at 10:00 UTC, which is 5:00 AM Central in summer and 4:00 AM in winter, and does the following:

  1. download-data refreshes data/nvd.jsonl from the NVD mirror.
  2. run-monthly writes outputs/YYYY/Month/Month.{md,json} for the completed month, with the trend sections computed across the full year to date.
  3. generate-ytd-report renders the charts and writes post.txt, enriched_post.txt, and ytd_summary.json.
  4. update-readme-stats regenerates the stats block in this README.
  5. The run commits outputs/ and README.md, then cuts a GitHub release using post.txt as the body.

If any step fails the workflow opens an issue instead of failing quietly.

Output

outputs/2026/
  May/
    May.md                              # the month's report
    May.json                            # same data, machine readable
  CVE_Growth_2026_{dark,light}_landscape.png
  CVE_Growth_2026_{dark,light}_square.png
  CVE_Growth_2026_{dark,light}_portrait.png
  YOY_CVE_Comparison_2026_vs_2025.png
  post.txt                              # the social post, also the release body
  enriched_post.txt                     # longer version with CVSS and CWE detail
  ytd_summary.json                      # YTD + all-time figures for the README block
  alt_text.md                           # alt text for every chart, ready to paste

Each report covers CVSS scores and severity bands, the most active CNAs, the most common weakness types, the daily publication spread, and the year-to-date trend.

The year-to-date analysis also compares against prior complete years, not just the same point last year, so the post copy can call the moment the current year overtakes a previous year's full total. Through July 27, 2026 sits 4,295 CVEs short of all of 2025 and is on track to pass it around August 17.

Charts

Every chart renders in three aspect ratios, each in dark and light: wide (1600×900, X landscape, filed under the historical landscape name), square (1080×1080, LinkedIn/X/Mastodon/Bluesky/IG), and portrait (1080×1350, IG and the LinkedIn feed).

Styling comes from src/reports/style.py, a port of CVEGraphs' style_social.py: Host Grotesk headlines, Roboto body, Roboto Mono for the eyebrow and date stamp, a deep-navy accent with red reserved for the highlighted series, and a hairline grid. The typefaces are committed under fonts/ because these charts render on a bare CI runner; without them matplotlib silently substitutes a fallback face.

Testing

pytest
black --check src/ tests/
flake8 src/ tests/ --max-complexity=10 --max-line-length=100
mypy src/ --ignore-missing-imports

tests.yml runs on push and pull request to main and develop, across Python 3.11, 3.12, and 3.13. Only pytest and the error-level flake8 pass are blocking; black, mypy, and isort report without failing the build.

Status

The monthly run is fully automated end to end, from download through release. Reports cover CVSS, CNA, CWE, and daily distribution for the month, plus month-over-month and year-over-year movement across the year to date.

Known rough edges, in rough order of how much they matter:

  • The CNA table is keyed on NVD's sourceIdentifier, which is an assigning source (often an email address) rather than a normalized CNA name. Useful for ranking, ugly to read.
  • Test coverage is thin and concentrated on configuration and the README updater. The analysis and chart paths are exercised only by the monthly run.
  • processor.py loads the entire feed into memory before filtering, which is fine on a GitHub runner but wasteful for a single month.
  • CVSS figures cover v3.0 and v3.1 only. There is no v4 handling, and CVEs without a v3 score are counted but unscored.

Built on free CVE tooling from RogoLabs.

About

Monthly CVE Stats

Resources

Stars

49 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages