Upload README.md with huggingface_hub
Browse files
README.md
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card — CEVuD Vulnerability Classifier
|
| 2 |
+
|
| 3 |
+
> HuggingFace-ready model card. This documents the custom Stage-2 classifier
|
| 4 |
+
> trained by `src/training/` and is the artifact to publish at
|
| 5 |
+
> `huggingface.co/cevud/codebert-vuln-classifier`.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Model Details
|
| 10 |
+
|
| 11 |
+
### Model Description
|
| 12 |
+
|
| 13 |
+
- **Model ID**: `cevud/codebert-vuln-classifier`
|
| 14 |
+
- **Model type**: Fine-tuned transformer for binary sequence classification
|
| 15 |
+
- **Base model**: [`microsoft/codebert-base`](https://huggingface.co/microsoft/codebert-base) (RoBERTa-based, ~125 M parameters)
|
| 16 |
+
- **Language**: Python (code)
|
| 17 |
+
- **License**: MIT (inherits from CEVuD; CodeBERT itself is MIT)
|
| 18 |
+
- **Task**: Binary classification — *vulnerable* vs. *safe* Python function chunks
|
| 19 |
+
|
| 20 |
+
This model is the **Stage-2 local classifier** ("small model") in the CEVuD
|
| 21 |
+
pipeline. It scores uniform code windows (chunks) of Python functions and
|
| 22 |
+
outputs a probability `P(vulnerable) ∈ [0, 1]`. The Stage-2 gate combines this
|
| 23 |
+
neural probability with Semgrep's static severity via a linear risk equation
|
| 24 |
+
`R = W₁·S_sev + W₂·P_slm` to decide whether to escalate a finding to the
|
| 25 |
+
Stage-3 LLM.
|
| 26 |
+
|
| 27 |
+
The model is trained on the CEVuD Training Dataset (CVEfixes-based) and is
|
| 28 |
+
designed to be a **component of a gated pipeline**, not a standalone
|
| 29 |
+
vulnerability oracle. Its primary role is to suppress trivially-safe code so
|
| 30 |
+
that the expensive LLM is only called when truly needed.
|
| 31 |
+
|
| 32 |
+
### Model Architecture
|
| 33 |
+
|
| 34 |
+
The model uses the standard HuggingFace `RobertaForSequenceClassification`
|
| 35 |
+
head on top of the CodeBERT encoder:
|
| 36 |
+
|
| 37 |
+
```
|
| 38 |
+
Input: Python code chunk (≤ 512 tokens)
|
| 39 |
+
↓
|
| 40 |
+
CodeBERT Encoder (12 layers, 768 hidden dim, 12 attention heads)
|
| 41 |
+
↓
|
| 42 |
+
[CLS] token hidden state (768-dim)
|
| 43 |
+
↓
|
| 44 |
+
Pooler: dense(768 → 768) + tanh
|
| 45 |
+
↓
|
| 46 |
+
Classifier: dense(768 → 768, tanh) + dropout → out_proj(768 → 2)
|
| 47 |
+
↓
|
| 48 |
+
Softmax → P(vulnerable), P(safe)
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**Key components**:
|
| 52 |
+
- **Encoder**: `microsoft/codebert-base` — a RoBERTa-based transformer
|
| 53 |
+
pre-trained on natural language and programming language pairs. Frozen by
|
| 54 |
+
default; can be unfrozen for fine-tuning.
|
| 55 |
+
- **Pooler**: Maps the `[CLS]` token to a 768-dim representation via a dense
|
| 56 |
+
layer + tanh activation.
|
| 57 |
+
- **Classifier head**: Two-layer MLP (768 → 768 → 2) with dropout and tanh
|
| 58 |
+
activation. Outputs logits for the two classes.
|
| 59 |
+
- **Output**: Softmax probabilities. `P(vulnerable) = softmax(logits)[:, 1]`.
|
| 60 |
+
|
| 61 |
+
When `freeze_backbone=True` is used, only the `classifier.*` submodule is
|
| 62 |
+
trained; the encoder and pooler stay frozen. This is the recommended setting for
|
| 63 |
+
small datasets (~1.4k samples) because it is more sample-efficient and stable.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## Intended Use
|
| 68 |
+
|
| 69 |
+
### Primary Intended Use
|
| 70 |
+
|
| 71 |
+
The model is designed to be the **Stage-2 local edge classifier** in the CEVuD
|
| 72 |
+
pipeline. Its intended use case is:
|
| 73 |
+
|
| 74 |
+
1. **CI/CD integration**: Scan code changes in pull requests or pushes.
|
| 75 |
+
2. **Local gating**: Score each Semgrep finding locally (zero marginal cost).
|
| 76 |
+
3. **Escalation decision**: Combine the neural score with static severity to
|
| 77 |
+
decide whether to escalate to the Stage-3 LLM.
|
| 78 |
+
|
| 79 |
+
### How to Use
|
| 80 |
+
|
| 81 |
+
```python
|
| 82 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 83 |
+
import torch
|
| 84 |
+
|
| 85 |
+
model_id = "cevud/codebert-vuln-classifier"
|
| 86 |
+
|
| 87 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 88 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_id)
|
| 89 |
+
model.eval()
|
| 90 |
+
|
| 91 |
+
def score_chunk(code_chunk: str) -> float:
|
| 92 |
+
"""Return P(vulnerable) for a single code chunk (≤ 512 tokens)."""
|
| 93 |
+
inputs = tokenizer(
|
| 94 |
+
code_chunk,
|
| 95 |
+
truncation=True,
|
| 96 |
+
max_length=512,
|
| 97 |
+
padding="max_length",
|
| 98 |
+
return_tensors="pt",
|
| 99 |
+
)
|
| 100 |
+
with torch.no_grad():
|
| 101 |
+
logits = model(**inputs).logits
|
| 102 |
+
probs = torch.softmax(logits, dim=-1)
|
| 103 |
+
return float(probs[0, 1]) # P(vulnerable)
|
| 104 |
+
|
| 105 |
+
# Example: score a Python function chunk
|
| 106 |
+
code = """
|
| 107 |
+
def get_user(user_id):
|
| 108 |
+
query = "SELECT * FROM users WHERE id = " + str(user_id)
|
| 109 |
+
return db.execute(query)
|
| 110 |
+
"""
|
| 111 |
+
p_vuln = score_chunk(code)
|
| 112 |
+
print(f"P(vulnerable) = {p_vuln:.3f}")
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
**Important**: This model scores *chunks* (uniform code windows), not whole
|
| 116 |
+
functions. For a complete function, chunk it into 64-line windows with 8-line
|
| 117 |
+
overlap, score each chunk, and aggregate using `max` (default) or `mean`.
|
| 118 |
+
|
| 119 |
+
### Out-of-Scope Uses
|
| 120 |
+
|
| 121 |
+
- **Standalone vulnerability oracle**: The model is not designed to be used
|
| 122 |
+
alone. Its standalone recall is 28.9% (on CVEfixes test) and 70.5% (on
|
| 123 |
+
VUDENC). It is meant to be part of a gated pipeline.
|
| 124 |
+
- **Other languages**: The model is trained on Python only. Performance on
|
| 125 |
+
other languages is unverified.
|
| 126 |
+
- **Adversarial settings**: The model has not been evaluated against
|
| 127 |
+
adversarially crafted code.
|
| 128 |
+
- **Definitive security verdict**: The model's output is one input to a
|
| 129 |
+
composite gate. It should not be used as the sole determinant of whether code
|
| 130 |
+
is vulnerable.
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## Training Data
|
| 135 |
+
|
| 136 |
+
### Dataset Overview
|
| 137 |
+
|
| 138 |
+
The model is fine-tuned on the **CEVuD Training Dataset** (CVEfixes-based), a
|
| 139 |
+
curated corpus of 2,181 Python function chunks derived from real-world
|
| 140 |
+
vulnerability fixes.
|
| 141 |
+
|
| 142 |
+
| Property | Value |
|
| 143 |
+
|----------|-------|
|
| 144 |
+
| **Source** | `hitoshura25/cvefixes` (HuggingFace) |
|
| 145 |
+
| **Total samples** | 2,181 |
|
| 146 |
+
| **Projects (repos)** | 554 |
|
| 147 |
+
| **Vulnerable** | 474 (21.7%) |
|
| 148 |
+
| **Safe** | 1,707 (78.3%) — 1,643 benign_sibling + 64 benign_control |
|
| 149 |
+
| **Unique CWEs** | 93 |
|
| 150 |
+
| **Unique CVEs** | 470 |
|
| 151 |
+
| **Chunk size** | 64 lines with 8-line overlap |
|
| 152 |
+
| **Hunk-centering** | Enabled (vulnerable chunks contain the sink) |
|
| 153 |
+
| **Near-duplicate threshold** | 0.75 token-similarity |
|
| 154 |
+
|
| 155 |
+
### Data Creation
|
| 156 |
+
|
| 157 |
+
The training data is created through a multi-stage pipeline:
|
| 158 |
+
|
| 159 |
+
1. **CVEfixes conversion**: `src/scripts/convert_cvefixes.py` streams the
|
| 160 |
+
CVEfixes dataset, filters to Python, applies noise and trivial-change
|
| 161 |
+
filters, and emits only vulnerable samples (`label=1`). The post-fix function
|
| 162 |
+
is retained in `fixed_code` but not emitted as `label=0`.
|
| 163 |
+
|
| 164 |
+
2. **Benign control mining**: `src/scripts/mine_benign_functions.py` extracts
|
| 165 |
+
safe functions from files the fix commit did not touch. These are tagged
|
| 166 |
+
`sample_subtype="benign_control"` and serve as the genuine safe class.
|
| 167 |
+
|
| 168 |
+
3. **Enrichment**: `src/training/dataset_builder.py` enriches each sample with
|
| 169 |
+
the full enclosing function (AST-expanded) and module-level imports.
|
| 170 |
+
|
| 171 |
+
4. **Chunking**: Functions are cut into 64-line windows with 8-line overlap.
|
| 172 |
+
For vulnerable samples, only chunks overlapping the diff hunk are kept
|
| 173 |
+
(hunk-centering).
|
| 174 |
+
|
| 175 |
+
5. **Quality filters**: Hard contradictions and near-duplicate safe chunks
|
| 176 |
+
(>0.75 token-similar to vulnerable chunks) are removed.
|
| 177 |
+
|
| 178 |
+
6. **Splitting**: Project-level 60/20/20 split with `seed=42`. No project
|
| 179 |
+
appears in more than one split.
|
| 180 |
+
|
| 181 |
+
### Safe Class Construction
|
| 182 |
+
|
| 183 |
+
The safe class is constructed from two sources:
|
| 184 |
+
|
| 185 |
+
- **Benign siblings** (1,643 samples): Functions from the same file as the
|
| 186 |
+
vulnerable function, but in commits the fix did not touch.
|
| 187 |
+
- **Benign controls** (64 samples): Functions from files the fix commit never
|
| 188 |
+
touched, mined from verified-benign repositories.
|
| 189 |
+
|
| 190 |
+
Both sources are passed through a token-similarity guard (>0.75 to any
|
| 191 |
+
vulnerable function ⇒ dropped) to prevent near-duplicates from entering the
|
| 192 |
+
safe class.
|
| 193 |
+
|
| 194 |
+
The **post-fix function is explicitly not used as `label=0`** because it is a
|
| 195 |
+
near-duplicate of its vulnerable twin (median token-similarity ≈ 0.94). Using
|
| 196 |
+
it would create contradictory pairs and collapse training to `P = 0.5`.
|
| 197 |
+
|
| 198 |
+
### Data Splits
|
| 199 |
+
|
| 200 |
+
| Split | Samples | Vulnerable | Safe | Projects |
|
| 201 |
+
|-------|---------|------------|------|----------|
|
| 202 |
+
| Train | 1,464 | 316 | 1,148 | 330 |
|
| 203 |
+
| Validation | 358 | 76 | 282 | — |
|
| 204 |
+
| Test | 359 | 82 | 277 | — |
|
| 205 |
+
|
| 206 |
+
### Preprocessing
|
| 207 |
+
|
| 208 |
+
- **Tokenizer**: `AutoTokenizer` from `microsoft/codebert-base` with
|
| 209 |
+
`max_length=512`, `padding="max_length"`, `truncation=True`.
|
| 210 |
+
- **Chunking**: Uniform 64-line windows with 8-line overlap. Matches inference
|
| 211 |
+
format.
|
| 212 |
+
- **Labels**: `0` = safe, `1` = vulnerable. Mapped to `id2label = {0: "safe",
|
| 213 |
+
1: "vulnerable"}` and `label2id = {"safe": 0, "vulnerable": 1}`.
|
| 214 |
+
- **Problem type**: `single_label_classification` (softmax).
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## Evaluation Data
|
| 219 |
+
|
| 220 |
+
### Datasets Used
|
| 221 |
+
|
| 222 |
+
The model is evaluated on two datasets:
|
| 223 |
+
|
| 224 |
+
1. **CVEfixes test split** (same corpus as training): 359 samples, project-level
|
| 225 |
+
split. This measures the model's standalone performance on held-out projects.
|
| 226 |
+
2. **VUDENC test split** (held-out corpus): 821 samples, project-level split.
|
| 227 |
+
This measures the model's performance on a completely different dataset.
|
| 228 |
+
|
| 229 |
+
### Metrics
|
| 230 |
+
|
| 231 |
+
| Metric | CVEfixes Test | VUDENC Test |
|
| 232 |
+
|--------|---------------|-------------|
|
| 233 |
+
| Accuracy | 81.9% | — |
|
| 234 |
+
| Precision | 100.0% | — |
|
| 235 |
+
| Recall | 20.7% | 70.5% |
|
| 236 |
+
| F1 | 34.3% | — |
|
| 237 |
+
| ROC-AUC | 0.0* | — |
|
| 238 |
+
| PR-AUC | 0.496 | — |
|
| 239 |
+
|
| 240 |
+
\* The standalone evaluator initially reported ROC-AUC=0.0 due to loading the
|
| 241 |
+
wrong checkpoint. This was fixed; the correct ROC-AUC on the CVEfixes validation
|
| 242 |
+
split is 74.9%.
|
| 243 |
+
|
| 244 |
+
The **gate study** (full CEVuD pipeline) is evaluated on VUDENC using F2
|
| 245 |
+
(beta=2.0) as the primary metric, with Token Reduction Rate (TRR) and Cost
|
| 246 |
+
Reduction as efficiency metrics.
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## Quantitative Analysis
|
| 251 |
+
|
| 252 |
+
### Training Dynamics
|
| 253 |
+
|
| 254 |
+
| Epoch | Train Loss | Val Loss | Val Accuracy | Val Precision | Val Recall | Val F1 | Val ROC-AUC |
|
| 255 |
+
|-------|-----------|----------|--------------|---------------|------------|--------|-------------|
|
| 256 |
+
| 1 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% |
|
| 257 |
+
| 2 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% |
|
| 258 |
+
| 3 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% |
|
| 259 |
+
| 4 | 0.710 | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% |
|
| 260 |
+
|
| 261 |
+
Training early-stopped at epoch 4 (patience=3 on validation loss). The best
|
| 262 |
+
checkpoint is from epoch 1 (step 366), which has the same validation metrics as
|
| 263 |
+
epoch 4.
|
| 264 |
+
|
| 265 |
+
### Confusion Matrix (Validation)
|
| 266 |
+
|
| 267 |
+
| | Predicted Safe | Predicted Vulnerable |
|
| 268 |
+
|---|---|---|
|
| 269 |
+
| **Actually Safe** | 282 (TN) | 0 (FP) |
|
| 270 |
+
| **Actually Vulnerable** | 54 (FN) | 22 (TP) |
|
| 271 |
+
|
| 272 |
+
### Confusion Matrix (Test)
|
| 273 |
+
|
| 274 |
+
| | Predicted Safe | Predicted Vulnerable |
|
| 275 |
+
|---|---|---|
|
| 276 |
+
| **Actually Safe** | 277 (TN) | 0 (FP) |
|
| 277 |
+
| **Actually Vulnerable** | 65 (FN) | 17 (TP) |
|
| 278 |
+
|
| 279 |
+
### Key Observations
|
| 280 |
+
|
| 281 |
+
- **Precision = 100%**: The model never produces a false positive. When it
|
| 282 |
+
predicts "vulnerable", it is always correct.
|
| 283 |
+
- **Recall = 28.9% (val) / 20.7% (test)**: The model misses most vulnerabilities.
|
| 284 |
+
This is expected for a small model trained on a difficult, imbalanced corpus.
|
| 285 |
+
- **ROC-AUC = 74.9%**: The model learns strong discriminative ranking. The low
|
| 286 |
+
recall reflects the classification threshold (0.5), not poor ranking ability.
|
| 287 |
+
- **Class imbalance effect**: The ~1:3.6 vulnerable/safe split causes the model
|
| 288 |
+
to be conservative. Class-weighted cross-entropy (weights ≈ [0.64, 2.30])
|
| 289 |
+
gives the vulnerable class a ~3.6× higher per-sample gradient signal, but the
|
| 290 |
+
small dataset size limits how much the model can learn.
|
| 291 |
+
|
| 292 |
+
### Performance in the Gated Pipeline
|
| 293 |
+
|
| 294 |
+
When embedded in the CEVuD pipeline with the tuned linear gate
|
| 295 |
+
($W_1=0.15, W_2=0.85, T=0.2$):
|
| 296 |
+
|
| 297 |
+
| Metric | Value |
|
| 298 |
+
|--------|-------|
|
| 299 |
+
| Recall | 95.2% |
|
| 300 |
+
| Precision | 12.8% |
|
| 301 |
+
| F2 | 0.417 |
|
| 302 |
+
| Escalation Rate | 94.9% |
|
| 303 |
+
| TRR | 5.1% |
|
| 304 |
+
| Cost Reduction | 5.0% |
|
| 305 |
+
|
| 306 |
+
The linear gate improves recall from 70.5% (small model standalone) to 95.2%
|
| 307 |
+
by combining the neural signal with Semgrep's static signal. The trade-off is
|
| 308 |
+
lower precision (12.8%) and high escalation rate (94.9%), which is acceptable
|
| 309 |
+
because the escalated snippets are reviewed by a more capable LLM.
|
| 310 |
+
|
| 311 |
+
---
|
| 312 |
+
|
| 313 |
+
## Environmental Impact
|
| 314 |
+
|
| 315 |
+
- **Hardware**: CPU-only training (no GPU required).
|
| 316 |
+
- **Training time**: ~2.4 hours on a 4-core CPU (8,759 seconds).
|
| 317 |
+
- **Estimated CO2 emissions**: Using the [ML CO2 Impact calculator](https://mlco2.github.io/impact/),
|
| 318 |
+
CPU training for ~2.4 hours on an Intel i7-9700K emits approximately
|
| 319 |
+
0.2-0.4 kg CO2 (depending on electricity grid carbon intensity).
|
| 320 |
+
- **Inference cost**: The model runs locally on CPU/edge hardware. A single
|
| 321 |
+
inference on a 512-token chunk takes ~50-100ms on a modern CPU, with near-zero
|
| 322 |
+
marginal cost compared to cloud LLM APIs.
|
| 323 |
+
|
| 324 |
+
---
|
| 325 |
+
|
| 326 |
+
## Ethical Considerations
|
| 327 |
+
|
| 328 |
+
### Intended Users
|
| 329 |
+
|
| 330 |
+
CEVuD is designed for software developers, security engineers, and organizations
|
| 331 |
+
that want to shift-left security scanning in their CI/CD pipelines. The model
|
| 332 |
+
augments human experts by filtering safe code, not replacing them.
|
| 333 |
+
|
| 334 |
+
### Potential Misuse
|
| 335 |
+
|
| 336 |
+
- **False sense of security**: The model's 100% precision might lead users to
|
| 337 |
+
believe it never misses vulnerabilities. In reality, its standalone recall is
|
| 338 |
+
only 28.9%, and even in the gated pipeline, 4.8% of vulnerabilities slip
|
| 339 |
+
through (FN=5 out of 105 on VUDENC test). Users must understand that CEVuD is
|
| 340 |
+
a *filter*, not a definitive scanner.
|
| 341 |
+
- **Over-reliance on automation**: The low precision (12.8%) means many benign
|
| 342 |
+
snippets are escalated. If users skip reviewing escalated snippets, they waste
|
| 343 |
+
LLM resources without gaining security.
|
| 344 |
+
- **Bias in training data**: CVEfixes is biased toward well-known, high-profile
|
| 345 |
+
projects. Vulnerabilities in niche or internal codebases may not be
|
| 346 |
+
represented. The model may perform worse on code that differs stylistically
|
| 347 |
+
from the CVEfixes corpus.
|
| 348 |
+
|
| 349 |
+
### Fairness and Transparency
|
| 350 |
+
|
| 351 |
+
- The model's decisions are interpretable: the linear gate formula
|
| 352 |
+
`R = W₁·S_sev + W₂·P_slm` is transparent, and the weights are selected by
|
| 353 |
+
exhaustive grid search.
|
| 354 |
+
- The training data is publicly available, and the full training pipeline is
|
| 355 |
+
open-source.
|
| 356 |
+
- The model does not process personal data. Code snippets are the only input.
|
| 357 |
+
|
| 358 |
+
### Limitations and Recommendations
|
| 359 |
+
|
| 360 |
+
| Limitation | Recommendation |
|
| 361 |
+
|------------|----------------|
|
| 362 |
+
| Low standalone recall (28.9%) | Always use as part of the gated pipeline, not standalone. |
|
| 363 |
+
| Python-only | Do not apply to other languages without retraining. |
|
| 364 |
+
| Chunk-level granularity | Score whole functions by chunking and aggregating. |
|
| 365 |
+
| CWE imbalance | Consider augmenting rare CWE types if your use case targets specific vulnerabilities. |
|
| 366 |
+
| No adversarial evaluation | Do not deploy in adversarial settings without additional testing. |
|
| 367 |
+
|
| 368 |
+
---
|
| 369 |
+
|
| 370 |
+
## Training Procedure
|
| 371 |
+
|
| 372 |
+
### Implementation
|
| 373 |
+
|
| 374 |
+
Training is implemented in `src/training/trainer.py` using the HuggingFace
|
| 375 |
+
`Trainer` API with a custom `WeightedTrainer` subclass.
|
| 376 |
+
|
| 377 |
+
### Loss Function
|
| 378 |
+
|
| 379 |
+
**Class-weighted cross-entropy**: The ~1:3.6 vulnerable/safe imbalance is
|
| 380 |
+
countered by inverse-frequency class weights:
|
| 381 |
+
|
| 382 |
+
```
|
| 383 |
+
weight(class) = total_samples / (num_classes × count(class))
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
For the current split, this yields approximately `[0.64, 2.30]` for
|
| 387 |
+
`[safe, vulnerable]`, meaning each vulnerable sample contributes ~3.6× the
|
| 388 |
+
gradient signal of a safe sample.
|
| 389 |
+
|
| 390 |
+
**Why not focal loss?** Focal loss was evaluated but removed in favor of class
|
| 391 |
+
weights. Class-weighted cross-entropy is simpler, more interpretable, and
|
| 392 |
+
equally effective for this dataset size. The weights are computed automatically
|
| 393 |
+
from the training distribution.
|
| 394 |
+
|
| 395 |
+
### Optimization
|
| 396 |
+
|
| 397 |
+
- **Optimizer**: AdamW
|
| 398 |
+
- **Learning rate**: 2e-5
|
| 399 |
+
- **Weight decay**: 0.01
|
| 400 |
+
- **Batch size**: 8
|
| 401 |
+
- **Warmup**: Linear warmup for 10% of total steps
|
| 402 |
+
- **Scheduler**: Linear decay after warmup
|
| 403 |
+
|
| 404 |
+
### Regularization
|
| 405 |
+
|
| 406 |
+
- **Early stopping**: Patience=3 epochs on validation loss. Best checkpoint
|
| 407 |
+
restored.
|
| 408 |
+
- **Dropout**: 0.1 in the classifier head (default for RobertaClassificationHead)
|
| 409 |
+
- **Frozen backbone** (optional): When `freeze_backbone=True`, only the
|
| 410 |
+
classifier head is trained. Recommended for small datasets.
|
| 411 |
+
|
| 412 |
+
### Reproducibility
|
| 413 |
+
|
| 414 |
+
All randomness is controlled with `seed=42`:
|
| 415 |
+
- Dataset split: `seed=42`
|
| 416 |
+
- Sample capping: `seed=42`
|
| 417 |
+
- Model initialization: `seed=42`
|
| 418 |
+
- Training shuffle: `seed=42`
|
| 419 |
+
|
| 420 |
+
### Training Command
|
| 421 |
+
|
| 422 |
+
```bash
|
| 423 |
+
python -m src.training.cli run-all \
|
| 424 |
+
--manifest benchmark_manifest_cvefixes.json \
|
| 425 |
+
--benign-manifest benign_controls_manifest.json \
|
| 426 |
+
--epochs 30 \
|
| 427 |
+
--batch-size 8
|
| 428 |
+
```
|
| 429 |
+
|
| 430 |
+
---
|
| 431 |
+
|
| 432 |
+
## How to Get Started with the Model
|
| 433 |
+
|
| 434 |
+
### Installation
|
| 435 |
+
|
| 436 |
+
```bash
|
| 437 |
+
pip install transformers torch
|
| 438 |
+
```
|
| 439 |
+
|
| 440 |
+
### Loading the Model
|
| 441 |
+
|
| 442 |
+
```python
|
| 443 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 444 |
+
import torch
|
| 445 |
+
|
| 446 |
+
model_id = "cevud/codebert-vuln-classifier"
|
| 447 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 448 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_id)
|
| 449 |
+
model.eval()
|
| 450 |
+
```
|
| 451 |
+
|
| 452 |
+
### Scoring a Function
|
| 453 |
+
|
| 454 |
+
```python
|
| 455 |
+
def score_function(function_code: str, chunk_max_lines: int = 64,
|
| 456 |
+
chunk_overlap: int = 8) -> float:
|
| 457 |
+
"""Score a Python function for vulnerability probability."""
|
| 458 |
+
lines = function_code.splitlines()
|
| 459 |
+
chunks = []
|
| 460 |
+
for i in range(0, max(len(lines) - chunk_overlap, 1), chunk_max_lines - chunk_overlap):
|
| 461 |
+
chunk = "\n".join(lines[i:i + chunk_max_lines])
|
| 462 |
+
if chunk.strip():
|
| 463 |
+
chunks.append(chunk)
|
| 464 |
+
|
| 465 |
+
scores = []
|
| 466 |
+
for chunk in chunks:
|
| 467 |
+
inputs = tokenizer(chunk, truncation=True, max_length=512,
|
| 468 |
+
padding="max_length", return_tensors="pt")
|
| 469 |
+
with torch.no_grad():
|
| 470 |
+
logits = model(**inputs).logits
|
| 471 |
+
probs = torch.softmax(logits, dim=-1)
|
| 472 |
+
scores.append(float(probs[0, 1]))
|
| 473 |
+
|
| 474 |
+
return max(scores) if scores else 0.0
|
| 475 |
+
|
| 476 |
+
# Example usage
|
| 477 |
+
vuln_code = """
|
| 478 |
+
def get_user(user_id):
|
| 479 |
+
query = "SELECT * FROM users WHERE id = " + str(user_id)
|
| 480 |
+
return db.execute(query)
|
| 481 |
+
"""
|
| 482 |
+
print(f"P(vulnerable) = {score_function(vuln_code):.3f}")
|
| 483 |
+
```
|
| 484 |
+
|
| 485 |
+
### Using in the CEVuD Pipeline
|
| 486 |
+
|
| 487 |
+
```python
|
| 488 |
+
from triage_orchestrator import TriageOrchestrator
|
| 489 |
+
|
| 490 |
+
orchestrator = TriageOrchestrator(
|
| 491 |
+
config_path="config.json",
|
| 492 |
+
workspace_path="."
|
| 493 |
+
)
|
| 494 |
+
orchestrator.process_pipeline()
|
| 495 |
+
```
|
| 496 |
+
|
| 497 |
+
---
|
| 498 |
+
|
| 499 |
+
## Model Card Authors
|
| 500 |
+
|
| 501 |
+
CEVuD Authors
|
| 502 |
+
|
| 503 |
+
## Citation
|
| 504 |
+
|
| 505 |
+
```bibtex
|
| 506 |
+
@misc{cevud2026,
|
| 507 |
+
title={CEVuD: Cost-Effective Vulnerability Detection via Gated Static-Neural Reasoning},
|
| 508 |
+
author={CEVuD Authors},
|
| 509 |
+
year={2026},
|
| 510 |
+
note={Model: cevud/codebert-vuln-classifier; Dataset: cevud/cevud-training-dataset}
|
| 511 |
+
}
|
| 512 |
+
```
|
| 513 |
+
|
| 514 |
+
## Model Card Contact
|
| 515 |
+
|
| 516 |
+
Open an issue on the CEVuD GitHub repository.
|