The problem
Given a CVE's free-text description, predict its CWE vulnerability category — the same triage step a security team does by hand when new vulnerabilities land. The data comes from three years (2023-2025) of the NVD feed, 115,910 CVEs combined — reconstructed from a community GitHub mirror, since NIST deprecated the official bulk JSON feed in December 2023 in favor of a paginated API. Filtered to CVEs with an English description and a real (non-placeholder) CWE category, then restricted to the 15 most frequent categories: 60,858 examples. The categories are far from balanced — the most common, Cross-Site Scripting, outnumbers the rarest kept category almost 14 to 1, which shaped the loss function and the evaluation metric used throughout.
Key results
Finding label noise in the source taxonomy
CWE-74 ("Improper Neutralization of Special Elements") is the generic parent category of CWE-89 (SQL Injection) in the CWE tree. A first baseline model struggled badly on CWE-74 — F1 of 0.49, far below every other class — and the confusion matrix pointed at one specific culprit: CWE-89 examples being misclassified as CWE-74 far more than chance would explain. Rather than tune around it, the actual text was checked.
These 1,127 records were relabeled to CWE-89 before re-training. CWE-89's F1 rose from 0.83 to 0.99 — but interestingly, the F1 macro average barely moved from this step alone. Macro-averaging weights every class equally regardless of size, so a large gain on an 851-example class and almost no change on a 41-example one cancel out in the average; accuracy and weighted-F1, which are size-sensitive, are what actually showed the improvement. What remained of CWE-74 after the fix — 407 examples of CRLF injection, LDAP injection, and other unrelated mechanisms with no shared textual pattern — was excluded rather than kept as an artificially difficult 15th class.
When a confusion turns out not to be a bug
The same hypothesis was tested on the model's next-weakest classes: CWE-77 (generic Command Injection) and CWE-78 (OS Command Injection), another parent/child pair in the CWE taxonomy. This time the check came back negative — only 9.2% of CWE-77 examples explicitly mention "OS command," nowhere near the 73.5% seen with CWE-74. There is no systematic mislabeling to fix here. The confusion instead reflects genuine semantic overlap: most CWE-77 and CWE-78 descriptions read almost identically ("command injection vulnerability... allows arbitrary command execution"), and the choice between the generic and specific label is a judgment call made inconsistently by whoever files the CVE — not a bug in the model or the data.
Results across all three runs
| Run | Change | Classes | Accuracy | F1 macro |
|---|---|---|---|---|
| 1 | Baseline | 15 | 0.90 | 0.85 |
| 2 | Relabeled CWE-74 → CWE-89 (text-verified) | 15 | 0.93 | 0.85 |
| 3 | Excluded remaining CWE-74 (heterogeneous residual) | 14 | 0.94 | 0.89 |