|
Download README.md from Denash/codebert-vuln-classifier: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/Denash/codebert-vuln-classifier/resolve/main/README.md
- Command line
-
hf download hf://Denash/codebert-vuln-classifier/README.md
-
curl -L -o README.md https://huggingface.co/Denash/codebert-vuln-classifier/resolve/main/README.md
18.8 kB
| license: mit | |
| language: | |
| - en | |
| tags: | |
| - code | |
| - vulnerability | |
| - security | |
| - python | |
| pipeline_tag: text-classification | |
| # Model Card — CEVuD Vulnerability Classifier | |
| > HuggingFace-ready model card. This documents the custom Stage-2 classifier | |
| > trained by `src/training/` and is the artifact to publish at | |
| > `huggingface.co/Denash/codebert-vuln-classifier`. | |
| --- | |
| ## Model Details | |
| ### Model Description | |
| - **Model ID**: `Denash/codebert-vuln-classifier` | |
| - **Model type**: Fine-tuned transformer for binary sequence classification | |
| - **Base model**: [`microsoft/codebert-base`](https://huggingface.co/microsoft/codebert-base) (RoBERTa-based, ~125 M parameters) | |
| - **Language**: Python (code) | |
| - **License**: MIT (inherits from CEVuD; CodeBERT itself is MIT) | |
| - **Task**: Binary classification — *vulnerable* vs. *safe* Python function chunks | |
| This model is the **Stage-2 local classifier** ("small model") in the CEVuD | |
| pipeline. It scores uniform code windows (chunks) of Python functions and | |
| outputs a probability `P(vulnerable) ∈ [0, 1]`. The Stage-2 gate combines this | |
| neural probability with Semgrep's static severity via a linear risk equation | |
| `R = W₁·S_sev + W₂·P_slm` to decide whether to escalate a finding to the | |
| Stage-3 LLM. | |
| The model is trained on the CEVuD Training Dataset (CVEfixes-based) and is | |
| designed to be a **component of a gated pipeline**, not a standalone | |
| vulnerability oracle. Its primary role is to suppress trivially-safe code so | |
| that the expensive LLM is only called when truly needed. | |
| ### Model Architecture | |
| The model uses the standard HuggingFace `RobertaForSequenceClassification` | |
| head on top of the CodeBERT encoder: | |
| ``` | |
| Input: Python code chunk (≤ 512 tokens) | |
| ↓ | |
| CodeBERT Encoder (12 layers, 768 hidden dim, 12 attention heads) | |
| ↓ | |
| [CLS] token hidden state (768-dim) | |
| ↓ | |
| Pooler: dense(768 → 768) + tanh | |
| ↓ | |
| Classifier: dense(768 → 768, tanh) + dropout → out_proj(768 → 2) | |
| ↓ | |
| Softmax → P(vulnerable), P(safe) | |
| ``` | |
| **Key components**: | |
| - **Encoder**: `microsoft/codebert-base` — a RoBERTa-based transformer | |
| pre-trained on natural language and programming language pairs. Frozen by | |
| default; can be unfrozen for fine-tuning. | |
| - **Pooler**: Maps the `[CLS]` token to a 768-dim representation via a dense | |
| layer + tanh activation. | |
| - **Classifier head**: Two-layer MLP (768 → 768 → 2) with dropout and tanh | |
| activation. Outputs logits for the two classes. | |
| - **Output**: Softmax probabilities. `P(vulnerable) = softmax(logits)[:, 1]`. | |
| When `freeze_backbone=True` is used, only the `classifier.*` submodule is | |
| trained; the encoder and pooler stay frozen. This is the recommended setting for | |
| small datasets (~1.4k samples) because it is more sample-efficient and stable. | |
| --- | |
| ## Intended Use | |
| ### Primary Intended Use | |
| The model is designed to be the **Stage-2 local edge classifier** in the CEVuD | |
| pipeline. Its intended use case is: | |
| 1. **CI/CD integration**: Scan code changes in pull requests or pushes. | |
| 2. **Local gating**: Score each Semgrep finding locally (zero marginal cost). | |
| 3. **Escalation decision**: Combine the neural score with static severity to | |
| decide whether to escalate to the Stage-3 LLM. | |
| ### How to Use | |
| ```python | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| import torch | |
| model_id = "Denash/codebert-vuln-classifier" | |
| tokenizer = AutoTokenizer.from_pretrained(model_id) | |
| model = AutoModelForSequenceClassification.from_pretrained(model_id) | |
| model.eval() | |
| def score_chunk(code_chunk: str) -> float: | |
| """Return P(vulnerable) for a single code chunk (≤ 512 tokens).""" | |
| inputs = tokenizer( | |
| code_chunk, | |
| truncation=True, | |
| max_length=512, | |
| padding="max_length", | |
| return_tensors="pt", | |
| ) | |
| with torch.no_grad(): | |
| logits = model(**inputs).logits | |
| probs = torch.softmax(logits, dim=-1) | |
| return float(probs[0, 1]) # P(vulnerable) | |
| # Example: score a Python function chunk | |
| code = """ | |
| def get_user(user_id): | |
| query = "SELECT * FROM users WHERE id = " + str(user_id) | |
| return db.execute(query) | |
| """ | |
| p_vuln = score_chunk(code) | |
| print(f"P(vulnerable) = {p_vuln:.3f}") | |
| ``` | |
| **Important**: This model scores *chunks* (uniform code windows), not whole | |
| functions. For a complete function, chunk it into 64-line windows with 8-line | |
| overlap, score each chunk, and aggregate using `max` (default) or `mean`. | |
| ### Out-of-Scope Uses | |
| - **Standalone vulnerability oracle**: The model is not designed to be used | |
| alone. Its standalone recall is 28.9% (on CVEfixes test) and 70.5% (on | |
| VUDENC). It is meant to be part of a gated pipeline. | |
| - **Other languages**: The model is trained on Python only. Performance on | |
| other languages is unverified. | |
| - **Adversarial settings**: The model has not been evaluated against | |
| adversarially crafted code. | |
| - **Definitive security verdict**: The model's output is one input to a | |
| composite gate. It should not be used as the sole determinant of whether code | |
| is vulnerable. | |
| --- | |
| ## Training Data | |
| ### Dataset Overview | |
| The model is fine-tuned on the **CEVuD Training Dataset** (CVEfixes-based), a | |
| curated corpus of 2,181 Python function chunks derived from real-world | |
| vulnerability fixes. | |
| | Property | Value | | |
| |----------|-------| | |
| | **Source** | `hitoshura25/cvefixes` (HuggingFace) | | |
| | **Total samples** | 2,181 | | |
| | **Projects (repos)** | 554 | | |
| | **Vulnerable** | 474 (21.7%) | | |
| | **Safe** | 1,707 (78.3%) — 1,643 benign_sibling + 64 benign_control | | |
| | **Unique CWEs** | 93 | | |
| | **Unique CVEs** | 470 | | |
| | **Chunk size** | 64 lines with 8-line overlap | | |
| | **Hunk-centering** | Enabled (vulnerable chunks contain the sink) | | |
| | **Near-duplicate threshold** | 0.75 token-similarity | | |
| ### Data Creation | |
| The training data is created through a multi-stage pipeline: | |
| 1. **CVEfixes conversion**: `src/scripts/convert_cvefixes.py` streams the | |
| CVEfixes dataset, filters to Python, applies noise and trivial-change | |
| filters, and emits only vulnerable samples (`label=1`). The post-fix function | |
| is retained in `fixed_code` but not emitted as `label=0`. | |
| 2. **Benign control mining**: `src/scripts/mine_benign_functions.py` extracts | |
| safe functions from files the fix commit did not touch. These are tagged | |
| `sample_subtype="benign_control"` and serve as the genuine safe class. | |
| 3. **Enrichment**: `src/training/dataset_builder.py` enriches each sample with | |
| the full enclosing function (AST-expanded) and module-level imports. | |
| 4. **Chunking**: Functions are cut into 64-line windows with 8-line overlap. | |
| For vulnerable samples, only chunks overlapping the diff hunk are kept | |
| (hunk-centering). | |
| 5. **Quality filters**: Hard contradictions and near-duplicate safe chunks | |
| (>0.75 token-similar to vulnerable chunks) are removed. | |
| 6. **Splitting**: Project-level 60/20/20 split with `seed=42`. No project | |
| appears in more than one split. | |
| ### Safe Class Construction | |
| The safe class is constructed from two sources: | |
| - **Benign siblings** (1,643 samples): Functions from the same file as the | |
| vulnerable function, but in commits the fix did not touch. | |
| - **Benign controls** (64 samples): Functions from files the fix commit never | |
| touched, mined from verified-benign repositories. | |
| Both sources are passed through a token-similarity guard (>0.75 to any | |
| vulnerable function ⇒ dropped) to prevent near-duplicates from entering the | |
| safe class. | |
| The **post-fix function is explicitly not used as `label=0`** because it is a | |
| near-duplicate of its vulnerable twin (median token-similarity ≈ 0.94). Using | |
| it would create contradictory pairs and collapse training to `P = 0.5`. | |
| ### Data Splits | |
| | Split | Samples | Vulnerable | Safe | Projects | | |
| |-------|---------|------------|------|----------| | |
| | Train | 1,464 | 316 | 1,148 | 330 | | |
| | Validation | 358 | 76 | 282 | — | | |
| | Test | 359 | 82 | 277 | — | | |
| ### Preprocessing | |
| - **Tokenizer**: `AutoTokenizer` from `microsoft/codebert-base` with | |
| `max_length=512`, `padding="max_length"`, `truncation=True`. | |
| - **Chunking**: Uniform 64-line windows with 8-line overlap. Matches inference | |
| format. | |
| - **Labels**: `0` = safe, `1` = vulnerable. Mapped to `id2label = {0: "safe", | |
| 1: "vulnerable"}` and `label2id = {"safe": 0, "vulnerable": 1}`. | |
| - **Problem type**: `single_label_classification` (softmax). | |
| --- | |
| ## Evaluation Data | |
| ### Datasets Used | |
| The model is evaluated on two datasets: | |
| 1. **CVEfixes test split** (same corpus as training): 359 samples, project-level | |
| split. This measures the model's standalone performance on held-out projects. | |
| 2. **VUDENC test split** (held-out corpus): 821 samples, project-level split. | |
| This measures the model's performance on a completely different dataset. | |
| ### Metrics | |
| | Metric | CVEfixes Test | VUDENC Test | | |
| |--------|---------------|-------------| | |
| | Accuracy | 81.9% | — | | |
| | Precision | 100.0% | — | | |
| | Recall | 20.7% | 70.5% | | |
| | F1 | 34.3% | — | | |
| | ROC-AUC | 0.0* | — | | |
| | PR-AUC | 0.496 | — | | |
| \* The standalone evaluator initially reported ROC-AUC=0.0 due to loading the | |
| wrong checkpoint. This was fixed; the correct ROC-AUC on the CVEfixes validation | |
| split is 74.9%. | |
| The **gate study** (full CEVuD pipeline) is evaluated on VUDENC using F2 | |
| (beta=2.0) as the primary metric, with Token Reduction Rate (TRR) and Cost | |
| Reduction as efficiency metrics. | |
| --- | |
| ## Quantitative Analysis | |
| ### Training Dynamics | |
| | Epoch | Train Loss | Val Loss | Val Accuracy | Val Precision | Val Recall | Val F1 | Val ROC-AUC | | |
| |-------|-----------|----------|--------------|---------------|------------|--------|-------------| | |
| | 1 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% | | |
| | 2 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% | | |
| | 3 | — | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% | | |
| | 4 | 0.710 | 0.419 | 84.9% | 100.0% | 28.9% | 44.9% | 74.9% | | |
| Training early-stopped at epoch 4 (patience=3 on validation loss). The best | |
| checkpoint is from epoch 1 (step 366), which has the same validation metrics as | |
| epoch 4. | |
| ### Confusion Matrix (Validation) | |
| | | Predicted Safe | Predicted Vulnerable | | |
| |---|---|---| | |
| | **Actually Safe** | 282 (TN) | 0 (FP) | | |
| | **Actually Vulnerable** | 54 (FN) | 22 (TP) | | |
| ### Confusion Matrix (Test) | |
| | | Predicted Safe | Predicted Vulnerable | | |
| |---|---|---| | |
| | **Actually Safe** | 277 (TN) | 0 (FP) | | |
| | **Actually Vulnerable** | 65 (FN) | 17 (TP) | | |
| ### Key Observations | |
| - **Precision = 100%**: The model never produces a false positive. When it | |
| predicts "vulnerable", it is always correct. | |
| - **Recall = 28.9% (val) / 20.7% (test)**: The model misses most vulnerabilities. | |
| This is expected for a small model trained on a difficult, imbalanced corpus. | |
| - **ROC-AUC = 74.9%**: The model learns strong discriminative ranking. The low | |
| recall reflects the classification threshold (0.5), not poor ranking ability. | |
| - **Class imbalance effect**: The ~1:3.6 vulnerable/safe split causes the model | |
| to be conservative. Class-weighted cross-entropy (weights ≈ [0.64, 2.30]) | |
| gives the vulnerable class a ~3.6× higher per-sample gradient signal, but the | |
| small dataset size limits how much the model can learn. | |
| ### Performance in the Gated Pipeline | |
| When embedded in the CEVuD pipeline with the tuned linear gate | |
| ($W_1=0.15, W_2=0.85, T=0.2$): | |
| | Metric | Value | | |
| |--------|-------| | |
| | Recall | 95.2% | | |
| | Precision | 12.8% | | |
| | F2 | 0.417 | | |
| | Escalation Rate | 94.9% | | |
| | TRR | 5.1% | | |
| | Cost Reduction | 5.0% | | |
| The linear gate improves recall from 70.5% (small model standalone) to 95.2% | |
| by combining the neural signal with Semgrep's static signal. The trade-off is | |
| lower precision (12.8%) and high escalation rate (94.9%), which is acceptable | |
| because the escalated snippets are reviewed by a more capable LLM. | |
| --- | |
| ## Environmental Impact | |
| - **Hardware**: CPU-only training (no GPU required). | |
| - **Training time**: ~2.4 hours on a 4-core CPU (8,759 seconds). | |
| - **Estimated CO2 emissions**: Using the [ML CO2 Impact calculator](https://mlco2.github.io/impact/), | |
| CPU training for ~2.4 hours on an Intel i7-9700K emits approximately | |
| 0.2-0.4 kg CO2 (depending on electricity grid carbon intensity). | |
| - **Inference cost**: The model runs locally on CPU/edge hardware. A single | |
| inference on a 512-token chunk takes ~50-100ms on a modern CPU, with near-zero | |
| marginal cost compared to cloud LLM APIs. | |
| --- | |
| ## Ethical Considerations | |
| ### Intended Users | |
| CEVuD is designed for software developers, security engineers, and organizations | |
| that want to shift-left security scanning in their CI/CD pipelines. The model | |
| augments human experts by filtering safe code, not replacing them. | |
| ### Potential Misuse | |
| - **False sense of security**: The model's 100% precision might lead users to | |
| believe it never misses vulnerabilities. In reality, its standalone recall is | |
| only 28.9%, and even in the gated pipeline, 4.8% of vulnerabilities slip | |
| through (FN=5 out of 105 on VUDENC test). Users must understand that CEVuD is | |
| a *filter*, not a definitive scanner. | |
| - **Over-reliance on automation**: The low precision (12.8%) means many benign | |
| snippets are escalated. If users skip reviewing escalated snippets, they waste | |
| LLM resources without gaining security. | |
| - **Bias in training data**: CVEfixes is biased toward well-known, high-profile | |
| projects. Vulnerabilities in niche or internal codebases may not be | |
| represented. The model may perform worse on code that differs stylistically | |
| from the CVEfixes corpus. | |
| ### Fairness and Transparency | |
| - The model's decisions are interpretable: the linear gate formula | |
| `R = W₁·S_sev + W₂·P_slm` is transparent, and the weights are selected by | |
| exhaustive grid search. | |
| - The training data is publicly available, and the full training pipeline is | |
| open-source. | |
| - The model does not process personal data. Code snippets are the only input. | |
| ### Limitations and Recommendations | |
| | Limitation | Recommendation | | |
| |------------|----------------| | |
| | Low standalone recall (28.9%) | Always use as part of the gated pipeline, not standalone. | | |
| | Python-only | Do not apply to other languages without retraining. | | |
| | Chunk-level granularity | Score whole functions by chunking and aggregating. | | |
| | CWE imbalance | Consider augmenting rare CWE types if your use case targets specific vulnerabilities. | | |
| | No adversarial evaluation | Do not deploy in adversarial settings without additional testing. | | |
| --- | |
| ## Training Procedure | |
| ### Implementation | |
| Training is implemented in `src/training/trainer.py` using the HuggingFace | |
| `Trainer` API with a custom `WeightedTrainer` subclass. | |
| ### Loss Function | |
| **Class-weighted cross-entropy**: The ~1:3.6 vulnerable/safe imbalance is | |
| countered by inverse-frequency class weights: | |
| ``` | |
| weight(class) = total_samples / (num_classes × count(class)) | |
| ``` | |
| For the current split, this yields approximately `[0.64, 2.30]` for | |
| `[safe, vulnerable]`, meaning each vulnerable sample contributes ~3.6× the | |
| gradient signal of a safe sample. | |
| **Why not focal loss?** Focal loss was evaluated but removed in favor of class | |
| weights. Class-weighted cross-entropy is simpler, more interpretable, and | |
| equally effective for this dataset size. The weights are computed automatically | |
| from the training distribution. | |
| ### Optimization | |
| - **Optimizer**: AdamW | |
| - **Learning rate**: 2e-5 | |
| - **Weight decay**: 0.01 | |
| - **Batch size**: 8 | |
| - **Warmup**: Linear warmup for 10% of total steps | |
| - **Scheduler**: Linear decay after warmup | |
| ### Regularization | |
| - **Early stopping**: Patience=3 epochs on validation loss. Best checkpoint | |
| restored. | |
| - **Dropout**: 0.1 in the classifier head (default for RobertaClassificationHead) | |
| - **Frozen backbone** (optional): When `freeze_backbone=True`, only the | |
| classifier head is trained. Recommended for small datasets. | |
| ### Reproducibility | |
| All randomness is controlled with `seed=42`: | |
| - Dataset split: `seed=42` | |
| - Sample capping: `seed=42` | |
| - Model initialization: `seed=42` | |
| - Training shuffle: `seed=42` | |
| ### Training Command | |
| ```bash | |
| python -m src.training.cli run-all \ | |
| --manifest benchmark_manifest_cvefixes.json \ | |
| --benign-manifest benign_controls_manifest.json \ | |
| --epochs 30 \ | |
| --batch-size 8 | |
| ``` | |
| --- | |
| ## How to Get Started with the Model | |
| ### Installation | |
| ```bash | |
| pip install transformers torch | |
| ``` | |
| ### Loading the Model | |
| ```python | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| import torch | |
| model_id = "Denash/codebert-vuln-classifier" | |
| tokenizer = AutoTokenizer.from_pretrained(model_id) | |
| model = AutoModelForSequenceClassification.from_pretrained(model_id) | |
| model.eval() | |
| ``` | |
| ### Scoring a Function | |
| ```python | |
| def score_function(function_code: str, chunk_max_lines: int = 64, | |
| chunk_overlap: int = 8) -> float: | |
| """Score a Python function for vulnerability probability.""" | |
| lines = function_code.splitlines() | |
| chunks = [] | |
| for i in range(0, max(len(lines) - chunk_overlap, 1), chunk_max_lines - chunk_overlap): | |
| chunk = "\n".join(lines[i:i + chunk_max_lines]) | |
| if chunk.strip(): | |
| chunks.append(chunk) | |
| scores = [] | |
| for chunk in chunks: | |
| inputs = tokenizer(chunk, truncation=True, max_length=512, | |
| padding="max_length", return_tensors="pt") | |
| with torch.no_grad(): | |
| logits = model(**inputs).logits | |
| probs = torch.softmax(logits, dim=-1) | |
| scores.append(float(probs[0, 1])) | |
| return max(scores) if scores else 0.0 | |
| # Example usage | |
| vuln_code = """ | |
| def get_user(user_id): | |
| query = "SELECT * FROM users WHERE id = " + str(user_id) | |
| return db.execute(query) | |
| """ | |
| print(f"P(vulnerable) = {score_function(vuln_code):.3f}") | |
| ``` | |
| ### Using in the CEVuD Pipeline | |
| ```python | |
| from triage_orchestrator import TriageOrchestrator | |
| orchestrator = TriageOrchestrator( | |
| config_path="config.json", | |
| workspace_path="." | |
| ) | |
| orchestrator.process_pipeline() | |
| ``` | |
| --- | |
| ## Model Card Authors | |
| CEVuD Authors | |
| ## Citation | |
| ```bibtex | |
| @misc{cevud2026, | |
| title={CEVuD: Cost-Effective Vulnerability Detection via Gated Static-Neural Reasoning}, | |
| author={CEVuD Authors}, | |
| year={2026}, | |
| note={Model: Denash/codebert-vuln-classifier; Training Dataset: Denash/cevud-training-dataset; Pipeline Dataset: Denash/cevud-pipeline-dataset} | |
| } | |
| ``` | |
| ## Model Card Contact | |
| Open an issue on the CEVuD GitHub repository. | |
| ## Related Resources | |
| | Resource | Link | | |
| |----------|------| | |
| | **Training Dataset (CVEfixes)** | [`Denash/cevud-training-dataset`](https://huggingface.co/datasets/Denash/cevud-training-dataset) | | |
| | **Pipeline Dataset (VUDENC)** | [`Denash/cevud-pipeline-dataset`](https://huggingface.co/datasets/Denash/cevud-pipeline-dataset) | | |
| | **Source Dataset (CVEfixes)** | [`hitoshura25/cvefixes`](https://huggingface.co/datasets/hitoshura25/cvefixes) | | |
| | **Source Dataset (VUDENC)** | [`DetectVul/Vudenc`](https://huggingface.co/datasets/DetectVul/Vudenc) | | |
| | **CEVuD GitHub** | https://github.com/Denash/CEVuD | | |