A 1024-dim StaticEmbedding model fine-tuned for retrieving CVE descriptions by vulnerability class (same CWE). 41M parameters, runs ~80× faster on CPU than typical sentence-transformer encoders.
521k pairs from real CVE corpus (same-CWE + CWE-definition pairs)
Trained on
M4 Max MPS, 12 min wall time
Best for
balanced general use — strong on common-CWE CVEs
This is v1 of a multi-phase ablation series to push static-embedding quality on the CVE corpus. The full result trio (v1 / v2-longtail) covers a Pareto frontier of recall vs precision trade-offs; this model is the strongest baseline before synthetic-data augmentation. See oneryalcin/static-retrieval-cve-en-v2-longtail for the rare-class-improved variant.
⚠️ Scope and limitations (read first)
This model is specialized for one narrow task and does NOT generalize to all CVE retrieval use cases. Please read this section before adopting it.
What this IS for
Given an NVD-formatted CVE description (~300-char paragraph, NVD prose style), retrieve other CVEs sharing the same MITRE CWE class from a corpus of CVE descriptions. On that task it dominates general-purpose models — see numbers below.
Real deployment fits include:
First-stage CWE-tagging — untagged CVE in, same-CWE peers out, vote on class
Active learning / borderline-case mining for CWE classifiers
CWE-balanced retrieval when you want diverse vulnerability classes rather than a flood of one type
Cheap full-corpus embedding on CPU (185× faster than mpnet, encodes all of NVD in ~70 sec)
What this is NOT for
We measured these explicitly and our model underperforms off-the-shelf alternatives here:
Cross-format retrieval (GHSA / vendor advisories with short summary → long markdown)
0.87-0.89 NDCG@10
static-retrieval-mrl-en-v1: 0.91
Use the off-the-shelf static model
If your use case looks like the bottom table rows, use sentence-transformers/static-retrieval-mrl-en-v1 or BAAI/bge-large-en-v1.5 instead. Both are competitive or better on out-of-distribution security text.
Why the specialization trade-off exists
We trained this model on contrastive pairs of (CVE_i, CVE_j) where both descriptions share the same MITRE CWE class. The corpus-mined special tokens that boost in-domain performance (e.g. "stack-based buffer overflow" as a single token) help cluster CVEs by vulnerability class but reduce the model's ability to distinguish them by product, vendor, or topic. This is a classic narrow-task fine-tuning trade-off: dramatic gain on the trained metric, modest regression on adjacent tasks.
A v3 with mixed training objectives (same-CWE + product-similarity + cross-format pairs) could likely close the OOD gap, but does not exist yet.
Quick start
python
1from sentence_transformers import SentenceTransformer
23model = SentenceTransformer("oneryalcin/static-retrieval-cve-en-v1")45cves =[6"Buffer overflow in Apache HTTP Server 2.4.49 mod_proxy allows remote attackers to execute arbitrary code via crafted Content-Length headers.",7"Cross-site scripting (XSS) vulnerability in WordPress plugin XYZ 3.1.0 allows attackers to inject arbitrary JavaScript via the search parameter.",8"SQL injection in MyApp 5.0 login form allows authentication bypass via crafted username field.",9]10embeddings = model.encode(cves)11print(embeddings.shape)# (3, 1024)1213# Similarity14sim = model.similarity(embeddings, embeddings)15print(sim)
For lower-dim usage (faster retrieval at minimal quality cost), use Matryoshka truncation:
Domain training beats general-purpose architecture here. Both our 41M-param token-bag models outperform a 335M-param SOTA general-English encoder (bge-large-en-v1.5) by a wide margin on in-domain CVE retrieval — and run ~185× faster on CPU than mpnet-base-v2 (see speed table above). This is not a quality-vs-speed trade-off: for CVE retrieval by CWE, the domain-tuned static model dominates on both axes.
Pick between v1 (best aggregate NDCG, strong on common CWEs) and v2-longtail (best Acc@1, doubles rare-class precision via synthetic-data augmentation) — see comparison details below.
Benchmark reproducer: quality_bench.py in this repo.
Inference speed (CPU)
Benchmarked on Apple M4 Max CPU, encoding 1000 real CVE descriptions (avg 328 chars), batch=128, best of 3 runs:
Same architecture as the reference static model so the speed matches it. The entire current NVD CVE catalog (~300k entries) encodes in under 25 seconds on a single CPU core — no GPU required for deployment. (On Apple Silicon, CPU outperforms MPS for this model since StaticEmbedding is memory-bound rather than compute-bound — the GPU transfer overhead exceeds the parallelism gain.)
Benchmark reproducer: speed_bench.py in this repo.
Out-of-distribution generalization (measured)
To check whether the in-distribution wins above generalize, we ran the model on three out-of-distribution tests. Results posted honestly so you can judge fit for your use case.
Test 1: GHSA cross-corpus, summary → description (asymmetric, short-query)
GitHub Security Advisories (gh api /advisories) have a different style than NVD (Markdown sections, ~10× longer descriptions, ~64-char summaries). We sampled 10k GHSAs, took 300 short summaries as queries against a 5,000-doc corpus of GHSA descriptions, same-CWE retrieval:
Off-the-shelf baseline wins. Our domain-tuned models slot in the middle. The lift we get on in-distribution evals (NVD descriptions) does not transfer to short-query GHSA retrieval.
Reproducer: eval_ghsa.py + fetch_ghsa.py in this repo.
Test 2: Hand-built informal queries against NVD corpus
100 short informal queries representing how a security analyst, SOC operator, or threat-intel researcher would actually search. Categories: famous-vuln names (log4shell), CWE-class descriptions (SQL injection in login form), product mentions (wordpress plugin XSS), question form (how does TOCTOU work), typos. For each, an "expected" target (CVE-ID, CWE-ID, or keyword) was hand-specified. Searched against 30,000 real NVD CVEs.
Famous vulnerability names: every model scores zero. Pop-culture names like "log4shell", "heartbleed", "zerologon" don't appear in CVE description text. This needs a name → CVE-ID lookup table, not embedding retrieval.
CWE-class queries: our v2-longtail wins. Strongest for class-level semantic queries.
Product/keyword queries: our models LOSE by 30+ percentage points. Our embedding geometry clusters by CWE-class, not by product/vendor. Asking "find wordpress vulnerabilities" returns 3-4 of 10 actual WordPress CVEs (the rest are same-vuln-class CVEs in other products). The baseline returns 7-8.
Reproducer: informal_queries.py in this repo.
Takeaway
Our model is overspecialized to NVD-style same-CWE retrieval. The specialization that lets us beat bge-large by 40% on the trained task also makes the model less useful for general security search. For programmatic CVE→CWE retrieval pipelines this is a feature. For human-typed security search this is a bug. Pick accordingly.
Reproducibility
The training script train.py is included in this repo. To reproduce from scratch:
Corpus-mined special tokens dominate the quality gains. Adding 10k frequency-weighted tokens dropped query subword count by 12.8% and contributed most of the lift from 0.355 → 0.581 NDCG@10.
Multi-iteration same-CWE pair construction (5 deduplicated shuffles vs single shuffle) added meaningful contrastive variance — pairs grew from 94k → 469k for the same source data.
CWE-definition pairs (CVE description ↔ MITRE CWE name+description text) provided a "categorical anchor" signal complementing same-CWE peers.
The 4,417 synthetic CVEs used to train v2-longtail
Limitations
Rare-class blind spot. Acc@1 on CWE classes with <200 real pairs is 0.083, essentially random.
No negation / sentiment handling. Static embeddings cannot distinguish "exceeded expectations" from "missed expectations" — for security retrieval where directional words matter, pair with a cross-encoder reranker.
English only. All training data is English-language CVE descriptions.
CVE-ID strings are not whole tokens.CVE-2021-44228 still fragments — special-token mining favored class-vocab over per-CVE identifiers (which would balloon the embedding table).
Some "misses" reflect label ambiguity. CWE-189 ↔ CWE-190 and CWE-264 ↔ CWE-287 are confusion pairs where the ground-truth CWE labels themselves are partially overlapping in the MITRE hierarchy.
License
Apache-2.0 (matching the underlying dataset and CWE catalog).
Citation
bibtex
1@misc{oneryalcin_cve_static_p3,
2 author = {Mehmet Oner Yalcin},
3 title = {static-retrieval-cve-en-v1: a static-embedding CVE retrieval model},
4 year = {2026},
5 howpublished = {Hugging Face model},
6 url = {https://huggingface.co/oneryalcin/static-retrieval-cve-en-v1},
7}