Skip to content

Commit 7ced65f

Browse files
chg: Move HuggingFace card templates to vulntrain/cards/
Move dataset and model card templates from datasets/ and trainers/ into a dedicated cards/ directory. Add dynamic dataset card generation for multi-source datasets with per-source breakdown table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 85dab5c commit 7ced65f

5 files changed

Lines changed: 152 additions & 17 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
task_categories:
3+
- text-classification
4+
license: cc-by-4.0
5+
library_name: datasets
6+
tags:
7+
- vulnerability
8+
- cybersecurity
9+
- security
10+
- cve
11+
- cvss
12+
---
13+
14+
# vulnerability-scores
15+
16+
This dataset comprises **{total_entries:,}** real-world vulnerabilities used to train and evaluate VLAI, a transformer-based model designed to predict software vulnerability severity levels directly from text descriptions, enabling faster and more consistent triage.
17+
18+
The dataset is presented in the paper [VLAI: A RoBERTa-Based Model for Automated Vulnerability Severity Classification](https://huggingface.co/papers/2507.03607).
19+
20+
Project page: [https://vulnerability.circl.lu](https://vulnerability.circl.lu)
21+
Associated code: [https://github.com/vulnerability-lookup/ML-Gateway](https://github.com/vulnerability-lookup/ML-Gateway)
22+
23+
## Sources
24+
25+
{source_table}
26+
27+
Extracted from the database of [Vulnerability-Lookup](https://vulnerability.circl.lu).
28+
Dumps of the data are available [here](https://vulnerability.circl.lu/dumps/).
29+
30+
## Splits
31+
32+
| Split | Examples |
33+
|-------|----------|
34+
| train | {train_examples:,} |
35+
| test | {test_examples:,} |
36+
37+
## Fields
38+
39+
| Field | Type | Description |
40+
|-------|------|-------------|
41+
| `id` | string | Vulnerability identifier (e.g., CVE-2024-1234, GHSA-xxxx, PYSEC-2024-xxx) |
42+
| `title` | string | Vulnerability title |
43+
| `description` | string | Vulnerability description in English |
44+
| `cpes` | list[string] | Common Platform Enumeration identifiers |
45+
| `cvss_v4_0` | float | CVSS v4.0 score |
46+
| `cvss_v3_1` | float | CVSS v3.1 score |
47+
| `cvss_v3_0` | float | CVSS v3.0 score |
48+
| `cvss_v2_0` | float | CVSS v2.0 score |
49+
| `patch_commit_url` | string | URL to the patch commit on GitHub, if available |
50+
| `source` | string | Data source identifier |
51+
52+
## Usage
53+
54+
```python
55+
import json
56+
from datasets import load_dataset
57+
58+
dataset = load_dataset("{repo_id}")
59+
60+
vulnerabilities = ["CVE-2012-2339", "RHSA-2023:5964", "GHSA-7chm-34j8-4f22", "PYSEC-2024-225"]
61+
62+
filtered_entries = dataset.filter(lambda elem: elem["id"] in vulnerabilities)
63+
64+
for entry in filtered_entries["train"]:
65+
print(json.dumps(entry, indent=4))
66+
```
67+
68+
## Related models
69+
70+
- [CIRCL/vulnerability-severity-classification-roberta-base](https://huggingface.co/CIRCL/vulnerability-severity-classification-roberta-base) — RoBERTa severity classifier
71+
- [CIRCL/vulnerability-severity-classification-distilbert-base-uncased](https://huggingface.co/CIRCL/vulnerability-severity-classification-distilbert-base-uncased) — DistilBERT severity classifier
72+
73+
## References
74+
75+
- [Vulnerability-Lookup](https://vulnerability.circl.lu) — the vulnerability data source
76+
- [VulnTrain](https://github.com/vulnerability-lookup/VulnTrain) — training pipeline
77+
- [ML-Gateway](https://github.com/vulnerability-lookup/ML-Gateway) — inference API
78+
- [VLAI paper](https://arxiv.org/abs/2507.03607) — Bonhomme, C., Dulaunoy, A. (2025)
File renamed without changes.

vulntrain/datasets/create_dataset.py

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
import json
3+
from collections import Counter
34
from pathlib import Path
45
from typing import Any, Generator
56

@@ -227,6 +228,58 @@ def extract_patch_commit_url(self, vuln: dict[str, Any]) -> str | None:
227228
return None
228229

229230

231+
SOURCE_LABELS = {
232+
"cvelistv5": "CVE Program (enriched with vulnrichment and Fraunhofer FKIE)",
233+
"github": "GitHub Security Advisories",
234+
"pysec": "PySec advisories",
235+
"csaf_redhat": "CSAF Red Hat",
236+
"csaf_cisco": "CSAF Cisco",
237+
"csaf_cisa": "CSAF CISA",
238+
"cnvd": "China National Vulnerability Database (CNVD)",
239+
}
240+
241+
242+
def _generate_dataset_card(
243+
sources: list[str],
244+
vulns: list[dict[str, Any]],
245+
dataset_dict: DatasetDict,
246+
repo_id: str,
247+
) -> str | None:
248+
"""Generate a dataset card from a template, populated with actual stats."""
249+
# Single-source: use static template if available
250+
if len(sources) == 1:
251+
static_card = Path(__file__).parent.parent / "cards" / f"dataset_card_{sources[0]}.md"
252+
if static_card.exists():
253+
return static_card.read_text()
254+
return None
255+
256+
# Multi-source: use the vulnerability-scores template
257+
template_path = (
258+
Path(__file__).parent.parent / "cards" / "dataset_card_vulnerability_scores.md"
259+
)
260+
if not template_path.exists():
261+
return None
262+
263+
total = len(vulns)
264+
source_counts = Counter(v.get("source", "unknown") for v in vulns)
265+
266+
# Build source table sorted by count descending
267+
rows = ["| Source | Label | Entries | Share |", "|--------|-------|---------|-------|"]
268+
for src, count in source_counts.most_common():
269+
label = SOURCE_LABELS.get(src, src)
270+
pct = 100 * count / total if total else 0
271+
rows.append(f"| `{src}` | {label} | {count:,} | {pct:.1f}% |")
272+
273+
template = template_path.read_text()
274+
return template.format(
275+
repo_id=repo_id,
276+
total_entries=total,
277+
source_table="\n".join(rows),
278+
train_examples=len(dataset_dict["train"]),
279+
test_examples=len(dataset_dict["test"]),
280+
)
281+
282+
230283
def main():
231284
parser = argparse.ArgumentParser(description="Dataset generation.")
232285
parser.add_argument(
@@ -270,22 +323,26 @@ def main():
270323
else:
271324
dataset_dict.push_to_hub(args.repo_id)
272325

273-
# Push dataset card if available for this source
274-
source_key = sources[0] if len(sources) == 1 else None
275-
if source_key:
276-
card_path = Path(__file__).parent / f"dataset_card_{source_key}.md"
277-
if card_path.exists():
278-
from huggingface_hub import HfApi
279-
280-
api = HfApi()
281-
api.upload_file(
282-
path_or_fileobj=str(card_path),
283-
path_in_repo="README.md",
284-
repo_id=args.repo_id,
285-
repo_type="dataset",
286-
commit_message="Update dataset card",
287-
)
288-
print(f"Dataset card pushed to {args.repo_id}")
326+
# Push dataset card
327+
card_content = _generate_dataset_card(
328+
sources, vulns, dataset_dict, args.repo_id
329+
)
330+
if card_content:
331+
from huggingface_hub import HfApi
332+
333+
card_path = Path("dataset_card_generated.md")
334+
card_path.write_text(card_content)
335+
336+
api = HfApi()
337+
api.upload_file(
338+
path_or_fileobj=str(card_path),
339+
path_in_repo="README.md",
340+
repo_id=args.repo_id,
341+
repo_type="dataset",
342+
commit_message="Update dataset card",
343+
)
344+
card_path.unlink()
345+
print(f"Dataset card pushed to {args.repo_id}")
289346

290347

291348
if __name__ == "__main__":

vulntrain/trainers/classify_severity_cnvd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def tokenize_function(examples):
358358
template_vars[f"{label_name}_support"] = f"{count:,}"
359359
template_vars[f"{label_name}_pct"] = 100 * count / test_samples if test_samples else 0
360360

361-
model_card_template = Path(__file__).parent / "model_card_cnvd_severity.md"
361+
model_card_template = Path(__file__).parent.parent / "cards" / "model_card_cnvd_severity.md"
362362
if model_card_template.exists():
363363
from huggingface_hub import HfApi
364364

0 commit comments

Comments
 (0)