Inspiration

Your data sits behind thousands of locks, and a few are already being picked. Tens of thousands of software flaws (CVEs) are published every year, but only about 1 in 855 ends up on CISA's Known Exploited Vulnerabilities (KEV) list. Security teams can only fix a few hundred, so the real question is which ones first. Fixing the right few matters more than fixing many: IBM's Cost of a Data Breach Report puts the average breach at $4.99M. The usual rule is "public attack code first, then the highest EPSS score". We wanted to see what that rule misses.

What it does

Fortres ranks all 72,670 CVEs from 2026 and picks the 500 to fix first. Each pick gets a one-sentence, plain-English reason written by IBM Granite. The site at fortres.us tells the story in plain words first, then numbers, then simple visuals: why ranking matters, how we rank, how we compare with the usual rule, and the top picks.

What we found

• Most holes on CISA's list gave no warning. In our 2024 and 2025 test, 44 of the 75 had no public attack code and a low EPSS score. The usual rule catches 0 of them, and Fortres caught 5, three of which come from one Ivanti event. • Severity labels hide danger. 1 in 9 entries added to CISA's list from 2021 to 2025 (162 of 1,484) is rated only Medium, so a team that fixes Critical and High first reaches these last. • Data that arrives after an attack makes results look better than they are. In our 2024 and 2025 test, fields NVD fills in later would raise 27 catches to 33, so we left them out. • Outside data can carry hindsight dates. In Metasploit, for 249 of the 490 pairs of a hole and a module, the link to the hole was added more than 30 days after the module, and some 2025 holes are new numbers for exploits from 2010. • Less work for the same catch. Fortres caught those 27 with a list of only 441 fixes in our 2024 and 2025 test. To reach the same 27, the usual rule would have to go 1,116 deep, sorting by severity 6,858, and EPSS alone 10,026.

How we built it

• Honest practice years: we rebuilt 2024 and 2025 exactly the way 2026 is scored, using only information available at the time. We trained on one year and tested on the other. • IBM Granite text features (open models, run locally): granite-embedding-30m-english reads 178,175 descriptions and scores how much each one reads like past KEV entries. Phrases that only appear after an attack ("exploited in the wild") are removed first. • Model: a 20-seed LightGBM bag on exploit signals, EPSS rank within each month, vendor history and the Granite features. Its settings were picked by a rule we fixed before seeing any results. • Reasons: granite-3.3-2b-instruct turns our facts into one sentence. A local checker blocks invented numbers or products, and IBM Granite Guardian checks that the sentence is supported by the facts. • Site: plain HTML, CSS and JavaScript with a few IBM Carbon touches, at fortres.us (domain from GoDaddy Registry via MLH). • Python, pandas, scikit-learn, LightGBM, DuckDB.

Challenges we ran into

• Leakage: today's NVD records get updated after a CVE becomes famous. Adding those fields lifted us from 27 to 33 catches, which was too good to be true, so we banned them. • Drift: EPSS scores jumped about 6x from July 2026, so we rank EPSS within each month instead of using raw scores. • Very few examples: only 75 practice-year CVEs were added to KEV (27 in 2024, 48 in 2025), so small differences are noise.

Accomplishments that we're proud of

• On the strictest test (train on 2024, test on 2025), we caught 18 vs 13 for the usual rule. Across both years it's 27 vs 19. • Blind spots: 44 of the 75 had no public attack code and a low EPSS score, so the usual rule catches none of them. We caught 5. That's a small number, and we say so. • 318 of 500 Granite reasons passed a groundedness check. The other 182 use a plain template.

What we learned

• The usual rule and the text see different flaws. The rule catches high-EPSS bugs with public code; the text catches enterprise and edge devices whose descriptions read like past KEV entries. • Some kinds of software end up on KEV far more often: enterprise software 25.5 per 1,000 and edge devices 17.3, against 5.6 overall. • Honest testing beats a big headline number. Our first "great" result was leakage.

What's next for Fortres

• More practice years, so a difference of 2 or 3 catches means something. • Run the Granite writing step on watsonx as well; the hosted watsonx.governance check already covers all 500 reasons. • Let a team plug in its own software list and see its own top picks.

Best Use of Entire

We used Entire as an audit trail for the AI-assisted part of the work: Claude Code sessions captured as checkpoints and linked to commits, inside our own repository. The evidence we publish is a counts-and-timestamps file (30 checkpoints from 2 sessions in outputs/entire_summary.json) made by a script that never reads a prompt or transcript, with tests that fail if a path or an address ever reaches it. The checkpoints themselves stay on Jack's laptop with push_sessions off, because the repository is public and transcripts contain personal data (home-folder paths and e-mail-shaped strings); the remote has no Entire refs, and the page docs/entire.md has the read-only commands to check that. Of the snapshot, 27 are imports of earlier history and 3 were captured live, since we enabled Entire part-way through, and it covers only that one laptop.

Screen recording of the audit trail: https://youtu.be/_rM3jJBfXMg

Best Use of Vultr

The public site, https://fortres.us, ran on a Vultr server (Ubuntu 24.04) with Caddy handling HTTPS. The server was set up from the script in deploy/vultr/cloud-init.yaml, and every response carried the header X-Served-By: Vultr, so anyone could check where the page came from. The steps are in deploy/README.md.

Built With

Share this project:

Updates

Submission history