@vk.amogh/trace 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/bin/trace.js +112 -0
- package/package.json +46 -0
- package/pyproject.toml +39 -0
- package/src/trace_engine/__init__.py +8 -0
- package/src/trace_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/doctor.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/verify.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__init__.py +7 -0
- package/src/trace_engine/ai/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/ollama.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/planner.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/base.py +23 -0
- package/src/trace_engine/ai/ollama.py +50 -0
- package/src/trace_engine/ai/planner.py +40 -0
- package/src/trace_engine/apm/__init__.py +18 -0
- package/src/trace_engine/apm/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/builder.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/edges.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/nodes.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/serialization.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/builder.py +208 -0
- package/src/trace_engine/apm/edges.py +25 -0
- package/src/trace_engine/apm/model.py +107 -0
- package/src/trace_engine/apm/nodes.py +27 -0
- package/src/trace_engine/apm/serialization.py +105 -0
- package/src/trace_engine/benchmark/__init__.py +5 -0
- package/src/trace_engine/benchmark/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/__pycache__/owasp.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/owasp.py +183 -0
- package/src/trace_engine/cli.py +1184 -0
- package/src/trace_engine/config/__init__.py +35 -0
- package/src/trace_engine/config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/defaults.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/loader.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/settings.cpython-311.pyc +0 -0
- package/src/trace_engine/config/defaults.py +48 -0
- package/src/trace_engine/config/loader.py +64 -0
- package/src/trace_engine/config/settings.py +72 -0
- package/src/trace_engine/doctor.py +250 -0
- package/src/trace_engine/findings/__init__.py +15 -0
- package/src/trace_engine/findings/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/correlate.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/recommendations.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/store.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/correlate.py +103 -0
- package/src/trace_engine/findings/model.py +40 -0
- package/src/trace_engine/findings/recommendations.py +35 -0
- package/src/trace_engine/findings/store.py +39 -0
- package/src/trace_engine/framework/__init__.py +58 -0
- package/src/trace_engine/framework/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/csharp.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/dart.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/django.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/express.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/fastapi.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/flask.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/go.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/nextjs.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/php.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/react_router.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/ruby.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/rust.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/springboot.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/base.py +49 -0
- package/src/trace_engine/framework/csharp.py +111 -0
- package/src/trace_engine/framework/dart.py +152 -0
- package/src/trace_engine/framework/django.py +188 -0
- package/src/trace_engine/framework/express.py +82 -0
- package/src/trace_engine/framework/fastapi.py +135 -0
- package/src/trace_engine/framework/flask.py +108 -0
- package/src/trace_engine/framework/go.py +94 -0
- package/src/trace_engine/framework/nextjs.py +200 -0
- package/src/trace_engine/framework/php.py +98 -0
- package/src/trace_engine/framework/react_router.py +331 -0
- package/src/trace_engine/framework/ruby.py +69 -0
- package/src/trace_engine/framework/rust.py +101 -0
- package/src/trace_engine/framework/springboot.py +145 -0
- package/src/trace_engine/harness/__init__.py +19 -0
- package/src/trace_engine/harness/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/benchmark.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/context.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/engine.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/patcher.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/remediators.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/benchmark.py +55 -0
- package/src/trace_engine/harness/context.py +49 -0
- package/src/trace_engine/harness/engine.py +187 -0
- package/src/trace_engine/harness/patcher.py +143 -0
- package/src/trace_engine/harness/remediators.py +307 -0
- package/src/trace_engine/ingest/__init__.py +15 -0
- package/src/trace_engine/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/files.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/hashing.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/ignore.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/repository.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/files.py +60 -0
- package/src/trace_engine/ingest/hashing.py +21 -0
- package/src/trace_engine/ingest/ignore.py +108 -0
- package/src/trace_engine/ingest/repository.py +50 -0
- package/src/trace_engine/intelligence/__init__.py +15 -0
- package/src/trace_engine/intelligence/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/evaluation.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/orchestrator.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/tracebench.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/evaluation.py +828 -0
- package/src/trace_engine/intelligence/laya/__init__.py +19 -0
- package/src/trace_engine/intelligence/laya/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/prompts.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/schemas.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/telemetry.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/thresholds.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/prompts.py +67 -0
- package/src/trace_engine/intelligence/laya/router.py +352 -0
- package/src/trace_engine/intelligence/laya/schemas.py +56 -0
- package/src/trace_engine/intelligence/laya/telemetry.py +48 -0
- package/src/trace_engine/intelligence/laya/thresholds.py +12 -0
- package/src/trace_engine/intelligence/orchestrator.py +130 -0
- package/src/trace_engine/intelligence/securebert/__init__.py +6 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/cache.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/cache.py +37 -0
- package/src/trace_engine/intelligence/securebert/classifier.py +240 -0
- package/src/trace_engine/intelligence/tracebench.py +61 -0
- package/src/trace_engine/intelligence/training/__init__.py +21 -0
- package/src/trace_engine/intelligence/training/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset_importers.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/laya_trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/lora_system2.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/morefixes_pipeline.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/slicer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/train_all.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/dataset.py +320 -0
- package/src/trace_engine/intelligence/training/dataset_importers.py +349 -0
- package/src/trace_engine/intelligence/training/laya_trainer.py +678 -0
- package/src/trace_engine/intelligence/training/lora_system2.py +162 -0
- package/src/trace_engine/intelligence/training/morefixes_pipeline.py +463 -0
- package/src/trace_engine/intelligence/training/slicer.py +129 -0
- package/src/trace_engine/intelligence/training/train_all.py +1009 -0
- package/src/trace_engine/intelligence/training/trainer.py +321 -0
- package/src/trace_engine/interactive.py +623 -0
- package/src/trace_engine/mcp/__init__.py +6 -0
- package/src/trace_engine/mcp/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/config.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/config.py +42 -0
- package/src/trace_engine/mcp/server.py +398 -0
- package/src/trace_engine/output/__init__.py +28 -0
- package/src/trace_engine/output/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/html.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/markdown.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/sarif.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/terminal.cpython-311.pyc +0 -0
- package/src/trace_engine/output/html.py +142 -0
- package/src/trace_engine/output/markdown.py +65 -0
- package/src/trace_engine/output/sarif.py +180 -0
- package/src/trace_engine/output/terminal.py +537 -0
- package/src/trace_engine/parsing/__init__.py +26 -0
- package/src/trace_engine/parsing/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/calls.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/language.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/locations.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/parser.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/symbols.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/calls.py +14 -0
- package/src/trace_engine/parsing/language.py +24 -0
- package/src/trace_engine/parsing/locations.py +17 -0
- package/src/trace_engine/parsing/parser.py +465 -0
- package/src/trace_engine/parsing/symbols.py +45 -0
- package/src/trace_engine/plugin/__init__.py +157 -0
- package/src/trace_engine/plugin/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/evaluator.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/swebench_adapter.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/task.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/bundle/hooks.json +24 -0
- package/src/trace_engine/plugin/bundle/mcp_config.json +11 -0
- package/src/trace_engine/plugin/bundle/plugin.json +20 -0
- package/src/trace_engine/plugin/bundle/rules/security_remediation.md +40 -0
- package/src/trace_engine/plugin/bundle/skills/trace-security-harness/SKILL.md +118 -0
- package/src/trace_engine/plugin/evaluator.py +105 -0
- package/src/trace_engine/plugin/swebench_adapter.py +96 -0
- package/src/trace_engine/plugin/task.py +48 -0
- package/src/trace_engine/policy/__init__.py +5 -0
- package/src/trace_engine/policy/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/__pycache__/scope.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/scope.py +80 -0
- package/src/trace_engine/runtime/__init__.py +7 -0
- package/src/trace_engine/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/client.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/observations.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/target.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/client.py +81 -0
- package/src/trace_engine/runtime/observations.py +18 -0
- package/src/trace_engine/runtime/target.py +23 -0
- package/src/trace_engine/security/__init__.py +16 -0
- package/src/trace_engine/security/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/fusion.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/hypotheses.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/signals.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/timing.cpython-311.pyc +0 -0
- package/src/trace_engine/security/fusion.py +126 -0
- package/src/trace_engine/security/hypotheses.py +251 -0
- package/src/trace_engine/security/signals.py +28 -0
- package/src/trace_engine/security/timing.py +132 -0
- package/src/trace_engine/testpacks/__init__.py +18 -0
- package/src/trace_engine/testpacks/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/authentication.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bfla.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bola.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/cors.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/deserialization.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/injection.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/mass_assignment.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/path_traversal.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssrf.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssti.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/authentication.py +60 -0
- package/src/trace_engine/testpacks/base.py +63 -0
- package/src/trace_engine/testpacks/bfla.py +70 -0
- package/src/trace_engine/testpacks/bola.py +94 -0
- package/src/trace_engine/testpacks/cors.py +85 -0
- package/src/trace_engine/testpacks/deserialization.py +86 -0
- package/src/trace_engine/testpacks/injection.py +179 -0
- package/src/trace_engine/testpacks/mass_assignment.py +70 -0
- package/src/trace_engine/testpacks/path_traversal.py +117 -0
- package/src/trace_engine/testpacks/registry.py +44 -0
- package/src/trace_engine/testpacks/ssrf.py +85 -0
- package/src/trace_engine/testpacks/ssti.py +96 -0
- package/src/trace_engine/tools/__init__.py +6 -0
- package/src/trace_engine/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/adapters.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/adapters.py +111 -0
- package/src/trace_engine/tools/base.py +68 -0
- package/src/trace_engine/tools/registry.py +36 -0
- package/src/trace_engine/verify.py +156 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Comprehensive Cybersecurity AST dataset generator and PyTorch Dataset for model fine-tuning."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import csv
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Dict, Any, Tuple, Optional
|
|
8
|
+
import torch
|
|
9
|
+
from torch.utils.data import Dataset
|
|
10
|
+
|
|
11
|
+
from trace_engine.intelligence.securebert.classifier import VULN_CATEGORIES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize_code_slice(code: str) -> str:
|
|
15
|
+
"""Normalizes code AST slice by abstracting identifiers and highlighting security dataflow."""
|
|
16
|
+
code = re.sub(r"#.*$", "", code, flags=re.MULTILINE)
|
|
17
|
+
code = re.sub(r"//.*$", "", code, flags=re.MULTILINE)
|
|
18
|
+
code = re.sub(r"/\*.*?\*/", "", code, flags=re.DOTALL)
|
|
19
|
+
|
|
20
|
+
code = re.sub(r"(request\.(args|params|query|body|json|headers|form))", r"[SOURCE] \1", code, flags=re.IGNORECASE)
|
|
21
|
+
code = re.sub(r"((execute|cursor|query|system|popen|eval|exec|open|readfile|render_template_string|pickle\.loads|yaml\.load)\b)", r"[SINK] \1", code, flags=re.IGNORECASE)
|
|
22
|
+
|
|
23
|
+
code = re.sub(r"\s+", " ", code).strip()
|
|
24
|
+
return code
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CORPUS_TEMPLATES = [
|
|
28
|
+
# BOLA / IDOR (CWE-639)
|
|
29
|
+
("def get_order(order_id): return db.query('SELECT * FROM orders WHERE id = :id', id=order_id).fetchone()", ["BOLA"]),
|
|
30
|
+
("async function getDocument(req, res) { const doc = await Document.findById(req.params.id); return res.json(doc); }", ["BOLA"]),
|
|
31
|
+
("router.get('/profile/:id', (req, res) => { const user = User.load(req.params.id); res.send(user); });", ["BOLA"]),
|
|
32
|
+
("@app.get('/invoice/{inv_id}') def invoice(inv_id: str): return db.invoices.find_one({'_id': inv_id})", ["BOLA"]),
|
|
33
|
+
("Response getProject(Request req) { String id = req.params['id']; return Response.ok(projectRepo.find(id)); }", ["BOLA"]),
|
|
34
|
+
|
|
35
|
+
# BFLA (CWE-285)
|
|
36
|
+
("@app.post('/api/admin/reset_metrics') def reset(): global_stats.clear(); return {'status': 'reset'}", ["BFLA"]),
|
|
37
|
+
("router.delete('/admin/users/:id', (req, res) => { db.users.delete(req.params.id); res.status(204).end(); });", ["BFLA"]),
|
|
38
|
+
("def export_all_tenants(): return db.raw_export_tenants()", ["BFLA"]),
|
|
39
|
+
("fun cancelAnyOrder(orderId: Long): ResponseEntity<Void> { orderService.forceCancel(orderId); return ResponseEntity.ok().build(); }", ["BFLA"]),
|
|
40
|
+
("@PreAuthorize('permitAll()') @PostMapping('/admin/config') fun updateCfg(@RequestBody cfg: Config) = repo.save(cfg)", ["BFLA"]),
|
|
41
|
+
|
|
42
|
+
# Missing Authentication (CWE-306)
|
|
43
|
+
("app.post('/api/v2/transfer', (req, res) => { transferFunds(req.body.from, req.body.to, req.body.amount); res.send('ok'); });", ["AUTHENTICATION"]),
|
|
44
|
+
("def update_password(req): user = get_user(req.json['username']); user.password = hash(req.json['new_pass']); db.save(user)", ["AUTHENTICATION"]),
|
|
45
|
+
("async def change_email(email: str): current_user.email = email; await db.commit()", ["AUTHENTICATION"]),
|
|
46
|
+
("router.put('/api/v1/vault/keys', (req, res) => { vault.rotateKey(req.body.key); res.json({status: 'updated'}); });", ["AUTHENTICATION"]),
|
|
47
|
+
|
|
48
|
+
# SSRF (CWE-918)
|
|
49
|
+
("def fetch_url(url: str): return httpx.get(url).text", ["SSRF"]),
|
|
50
|
+
("router.post('/webhook', async (req, res) => { const out = await axios.get(req.body.target_url); res.send(out.data); });", ["SSRF"]),
|
|
51
|
+
("def preview_link(link): return requests.get(link, timeout=5).content", ["SSRF"]),
|
|
52
|
+
("fun proxyRequest(callbackUrl: String) = restTemplate.getForObject(callbackUrl, String::class.java)", ["SSRF"]),
|
|
53
|
+
("async function fetchRemoteAvatar(url) { return (await fetch(url)).blob(); }", ["SSRF"]),
|
|
54
|
+
|
|
55
|
+
# Injection (SQLi / Command / LDAP) (CWE-89 / CWE-78)
|
|
56
|
+
("def search_products(q: str): return db.execute(f'SELECT * FROM products WHERE name LIKE \"%{q}%\"')", ["INJECTION"]),
|
|
57
|
+
("app.get('/exec', (req, res) => { exec('ping -c 1 ' + req.query.host, (err, stdout) => res.send(stdout)); });", ["INJECTION"]),
|
|
58
|
+
("def find_user(name): query = 'SELECT * FROM users WHERE username = \\'' + name + '\\''; return db.cursor.execute(query)", ["INJECTION"]),
|
|
59
|
+
("def run_backup(path): os.system(f'tar -czf backup.tar.gz {path}')", ["INJECTION"]),
|
|
60
|
+
("fun queryLdap(filter: String) = ldapTemplate.search('', '(uid=' + filter + ')', mapper)", ["INJECTION"]),
|
|
61
|
+
|
|
62
|
+
# Mass Assignment (CWE-915)
|
|
63
|
+
("def update_profile(user_id, data: dict): user = db.get(user_id); user.__dict__.update(data); db.save(user)", ["MASS_ASSIGNMENT"]),
|
|
64
|
+
("router.put('/user/:id', (req, res) => { User.findByIdAndUpdate(req.params.id, req.body); res.json({ok: true}); });", ["MASS_ASSIGNMENT"]),
|
|
65
|
+
("async def patch_account(req: Request): data = await req.json(); user.update(**data); return user", ["MASS_ASSIGNMENT"]),
|
|
66
|
+
("@PutMapping('/users/{id}') fun patchUser(@PathVariable id: Long, @RequestBody user: User) = userRepo.save(user)", ["MASS_ASSIGNMENT"]),
|
|
67
|
+
|
|
68
|
+
# Path Traversal (CWE-22 / CWE-73)
|
|
69
|
+
("def read_file(filename: str): return open(os.path.join('/var/www/uploads', filename), 'r').read()", ["PATH_TRAVERSAL"]),
|
|
70
|
+
("app.get('/download', (req, res) => { res.sendFile(path.resolve('./files/' + req.query.file)); });", ["PATH_TRAVERSAL"]),
|
|
71
|
+
("def view_log(log_path): with open(log_path, 'r') as f: return f.read()", ["PATH_TRAVERSAL"]),
|
|
72
|
+
("fun serveStatic(page: String): ByteArray = File('/assets/' + page).readBytes()", ["PATH_TRAVERSAL"]),
|
|
73
|
+
|
|
74
|
+
# SSTI (CWE-1336 / CWE-94)
|
|
75
|
+
("def render_user_card(username: str): return jinja2.Environment().from_string(f'Hello {username}').render()", ["SSTI"]),
|
|
76
|
+
("def preview_email(template_str): return render_template_string(template_str)", ["SSTI"]),
|
|
77
|
+
("app.get('/greet', (req, res) => { const tmpl = nunjucks.renderString('Welcome ' + req.query.name); res.send(tmpl); });", ["SSTI"]),
|
|
78
|
+
|
|
79
|
+
# CORS Misconfiguration (CWE-942)
|
|
80
|
+
("def after_request(resp): resp.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin'); resp.headers['Access-Control-Allow-Credentials'] = 'true'; return resp", ["CORS"]),
|
|
81
|
+
("app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Credentials', 'true'); next(); });", ["CORS"]),
|
|
82
|
+
|
|
83
|
+
# Deserialization (CWE-502)
|
|
84
|
+
("def load_session(data): return pickle.loads(base64.b64decode(data))", ["DESERIALIZATION"]),
|
|
85
|
+
("def parse_config(yaml_str): return yaml.load(yaml_str, Loader=yaml.Loader)", ["DESERIALIZATION"]),
|
|
86
|
+
("def restore_state(payload): import pickle; return pickle.loads(payload)", ["DESERIALIZATION"]),
|
|
87
|
+
|
|
88
|
+
# Safe Negative Controls (Benign code slices)
|
|
89
|
+
("def get_order_safe(order_id, user=Depends(get_current_user)): return db.query(Order).filter(Order.id == order_id, Order.tenant_id == user.tenant_id).first()", []),
|
|
90
|
+
("def search_safe(term: str): return db.execute('SELECT * FROM items WHERE name ILIKE :term', {'term': f'%{term}%'})", []),
|
|
91
|
+
("def read_file_safe(filename: str): canonical = Path(filename).resolve(); if not str(canonical).startswith('/safe/root/'): raise Forbidden(); return canonical.read_text()", []),
|
|
92
|
+
("def fetch_url_safe(url: str): guard = ScopeGuard(); guard.validate_url(url); return httpx.get(url)", []),
|
|
93
|
+
("def update_user_safe(user_id, dto: UserUpdateDto): user = db.get(user_id); user.name = dto.name; user.bio = dto.bio; db.save(user)", []),
|
|
94
|
+
("def render_safe(template_name, context): return render_template(f'{template_name}.html', **context)", []),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def load_owasp_benchmark_samples() -> List[Tuple[str, List[str]]]:
|
|
99
|
+
"""Loads all 1,230 real code files from OWASP Benchmark Python with verified ground truth."""
|
|
100
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
101
|
+
|
|
102
|
+
candidates = [
|
|
103
|
+
Path("benchmarks/owasp-python"),
|
|
104
|
+
Path(__file__).resolve().parent.parent.parent.parent / "benchmarks/owasp-python",
|
|
105
|
+
]
|
|
106
|
+
benchmark_dir = None
|
|
107
|
+
for c in candidates:
|
|
108
|
+
if c.exists() and (c / "expectedresults-0.1.csv").exists():
|
|
109
|
+
benchmark_dir = c
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
if not benchmark_dir:
|
|
113
|
+
return samples
|
|
114
|
+
|
|
115
|
+
csv_file = benchmark_dir / "expectedresults-0.1.csv"
|
|
116
|
+
testcode_dir = benchmark_dir / "testcode"
|
|
117
|
+
|
|
118
|
+
cat_mapping = {
|
|
119
|
+
"pathtraver": "PATH_TRAVERSAL",
|
|
120
|
+
"sqli": "INJECTION",
|
|
121
|
+
"cmdi": "INJECTION",
|
|
122
|
+
"codeinj": "INJECTION",
|
|
123
|
+
"ldapi": "INJECTION",
|
|
124
|
+
"xpathi": "INJECTION",
|
|
125
|
+
"xss": "INJECTION",
|
|
126
|
+
"deserialization": "DESERIALIZATION",
|
|
127
|
+
"trustbound": "BOLA",
|
|
128
|
+
"redirect": "SSRF",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
with open(csv_file, "r", encoding="utf-8") as f:
|
|
133
|
+
reader = csv.reader(f)
|
|
134
|
+
for row in reader:
|
|
135
|
+
if not row or row[0].startswith("#"):
|
|
136
|
+
continue
|
|
137
|
+
test_file = testcode_dir / f"{row[0].strip()}.py"
|
|
138
|
+
if not test_file.exists():
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
category_raw = row[1].strip().lower()
|
|
142
|
+
is_vuln = row[2].strip().lower() == "true"
|
|
143
|
+
|
|
144
|
+
code_content = test_file.read_text(encoding="utf-8", errors="replace")
|
|
145
|
+
normalized = normalize_code_slice(code_content[:1500])
|
|
146
|
+
|
|
147
|
+
if is_vuln and category_raw in cat_mapping:
|
|
148
|
+
samples.append((normalized, [cat_mapping[category_raw]]))
|
|
149
|
+
else:
|
|
150
|
+
samples.append((normalized, []))
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
return samples
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def load_parquet_security_corpus() -> List[Tuple[str, List[str]]]:
|
|
158
|
+
"""Loads 500+ curated security vulnerability AST functions from research parquet files."""
|
|
159
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
160
|
+
candidates = [
|
|
161
|
+
Path("research/securebert2/opensource_data/data_vuln_dataset.parquet"),
|
|
162
|
+
Path(__file__).resolve().parent.parent.parent.parent / "research/securebert2/opensource_data/data_vuln_dataset.parquet",
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
pq_file = None
|
|
166
|
+
for c in candidates:
|
|
167
|
+
if c.exists():
|
|
168
|
+
pq_file = c
|
|
169
|
+
break
|
|
170
|
+
|
|
171
|
+
if not pq_file:
|
|
172
|
+
return samples
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
import pyarrow.parquet as pq
|
|
176
|
+
table = pq.read_table(pq_file)
|
|
177
|
+
pydict = table.to_pydict()
|
|
178
|
+
codes = pydict.get("code", [])
|
|
179
|
+
labels = pydict.get("label", [])
|
|
180
|
+
|
|
181
|
+
for code, is_vuln in zip(codes, labels):
|
|
182
|
+
code_str = str(code)
|
|
183
|
+
normalized = normalize_code_slice(code_str[:1200])
|
|
184
|
+
if is_vuln:
|
|
185
|
+
samples.append((normalized, ["INJECTION"]))
|
|
186
|
+
else:
|
|
187
|
+
samples.append((normalized, []))
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
return samples
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def load_external_cve_datasets(limit_bigvul: int = 4000, limit_cvefixes: int = 3000, limit_morefixes: int = 4000) -> List[Tuple[str, List[str]]]:
|
|
195
|
+
"""Ingests real-world functions with CVE ground truth from Big-Vul, CVEfixes, and MoreFixes."""
|
|
196
|
+
from trace_engine.intelligence.training.dataset_importers import BigVulImporter, CVEfixesImporter, MoreFixesImporter
|
|
197
|
+
|
|
198
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
199
|
+
|
|
200
|
+
# 1. Big-Vul Parquet
|
|
201
|
+
bv_paths = [
|
|
202
|
+
Path(".trace/datasets/bigvul/train.parquet"),
|
|
203
|
+
Path(".trace/datasets/bigvul/validation.parquet"),
|
|
204
|
+
]
|
|
205
|
+
bv = BigVulImporter()
|
|
206
|
+
for bp in bv_paths:
|
|
207
|
+
if bp.exists():
|
|
208
|
+
bv_samples = bv.import_parquet(bp, limit=limit_bigvul, include_benign=True)
|
|
209
|
+
for code, labels in bv_samples:
|
|
210
|
+
samples.append((normalize_code_slice(code), labels))
|
|
211
|
+
|
|
212
|
+
# 2. CVEfixes CSV
|
|
213
|
+
cve_paths = [
|
|
214
|
+
Path(".trace/datasets/cvefixes/train.csv"),
|
|
215
|
+
Path(".trace/datasets/cvefixes/test.csv"),
|
|
216
|
+
]
|
|
217
|
+
cve = CVEfixesImporter()
|
|
218
|
+
for cp in cve_paths:
|
|
219
|
+
if cp.exists():
|
|
220
|
+
cve_samples = cve.import_csv(cp, limit=limit_cvefixes, include_benign=True)
|
|
221
|
+
for code, labels in cve_samples:
|
|
222
|
+
samples.append((normalize_code_slice(code), labels))
|
|
223
|
+
|
|
224
|
+
# 3. MoreFixes (Zenodo 20776007: 52k+ CVE patch diffs and commit records)
|
|
225
|
+
morefixes_paths = [
|
|
226
|
+
Path(".trace/datasets/morefixes/patch-files2026-06-20.zip"),
|
|
227
|
+
Path(".trace/datasets/morefixes/patches"),
|
|
228
|
+
Path("C:/trace_datasets/morefixes/patch-files2026-06-20.zip"),
|
|
229
|
+
Path("C:/trace_datasets/morefixes/patches"),
|
|
230
|
+
]
|
|
231
|
+
mf = MoreFixesImporter()
|
|
232
|
+
for mp in morefixes_paths:
|
|
233
|
+
if mp.exists():
|
|
234
|
+
mf_samples = mf.import_patch_archive(mp, limit=limit_morefixes, include_benign=True)
|
|
235
|
+
for code, labels in mf_samples:
|
|
236
|
+
samples.append((normalize_code_slice(code), labels))
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
return samples
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def generate_cybersecurity_training_corpus(multiplier: int = 4, include_external_cve: bool = True) -> List[Tuple[str, List[str]]]:
|
|
243
|
+
"""Builds a comprehensive unified training corpus fusing Big-Vul, CVEfixes, OWASP Benchmark, parquet CVEs, and multi-language templates."""
|
|
244
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
245
|
+
|
|
246
|
+
# 1. Ingest real external CVE datasets (Big-Vul and CVEfixes)
|
|
247
|
+
if include_external_cve:
|
|
248
|
+
ext_samples = load_external_cve_datasets(limit_bigvul=4000, limit_cvefixes=3000)
|
|
249
|
+
samples.extend(ext_samples)
|
|
250
|
+
|
|
251
|
+
# 2. Ingest real OWASP Benchmark Python files (1,230 samples)
|
|
252
|
+
owasp_samples = load_owasp_benchmark_samples()
|
|
253
|
+
samples.extend(owasp_samples)
|
|
254
|
+
|
|
255
|
+
# 3. Ingest real parquet vulnerability AST slices (500 samples)
|
|
256
|
+
pq_samples = load_parquet_security_corpus()
|
|
257
|
+
samples.extend(pq_samples)
|
|
258
|
+
|
|
259
|
+
# 4. Add multi-language API security templates
|
|
260
|
+
var_aliases = ["item", "record", "payload", "entity", "resource", "target", "client", "doc", "asset"]
|
|
261
|
+
for _ in range(multiplier):
|
|
262
|
+
for code, labels in CORPUS_TEMPLATES:
|
|
263
|
+
augmented_code = code
|
|
264
|
+
for alias in var_aliases:
|
|
265
|
+
if "user" in augmented_code and alias != "user":
|
|
266
|
+
augmented_code = augmented_code.replace("user", alias)
|
|
267
|
+
break
|
|
268
|
+
samples.append((normalize_code_slice(augmented_code), labels))
|
|
269
|
+
|
|
270
|
+
return samples
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class VulnerabilityDataset(Dataset):
|
|
274
|
+
"""PyTorch Dataset for multi-label vulnerability classification with class-weight support."""
|
|
275
|
+
|
|
276
|
+
def __init__(self, samples: List[Tuple[str, List[str]]], tokenizer, max_length: int = 256):
|
|
277
|
+
self.samples = samples
|
|
278
|
+
self.tokenizer = tokenizer
|
|
279
|
+
self.max_length = max_length
|
|
280
|
+
self.label_map = {cat: idx for idx, cat in enumerate(VULN_CATEGORIES)}
|
|
281
|
+
|
|
282
|
+
def __len__(self) -> int:
|
|
283
|
+
return len(self.samples)
|
|
284
|
+
|
|
285
|
+
def calculate_pos_weights(self) -> torch.Tensor:
|
|
286
|
+
"""Calculates positive class weights using square-root scaling to balance loss without skewing predictions."""
|
|
287
|
+
pos_counts = torch.zeros(len(VULN_CATEGORIES), dtype=torch.float32)
|
|
288
|
+
total = len(self.samples)
|
|
289
|
+
for _, labels in self.samples:
|
|
290
|
+
for l in labels:
|
|
291
|
+
if l in self.label_map:
|
|
292
|
+
pos_counts[self.label_map[l]] += 1.0
|
|
293
|
+
|
|
294
|
+
pos_counts = torch.clamp(pos_counts, min=1.0)
|
|
295
|
+
neg_counts = total - pos_counts
|
|
296
|
+
# Square-root dampened weights bounded to [1.0, 2.5] avoids artificial recall inflation
|
|
297
|
+
weights = torch.clamp(torch.sqrt(neg_counts / pos_counts), min=1.0, max=2.5)
|
|
298
|
+
return weights
|
|
299
|
+
|
|
300
|
+
def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
|
|
301
|
+
code_text, label_names = self.samples[idx]
|
|
302
|
+
|
|
303
|
+
target = torch.zeros(len(VULN_CATEGORIES), dtype=torch.float32)
|
|
304
|
+
for name in label_names:
|
|
305
|
+
if name in self.label_map:
|
|
306
|
+
target[self.label_map[name]] = 1.0
|
|
307
|
+
|
|
308
|
+
encoding = self.tokenizer(
|
|
309
|
+
code_text,
|
|
310
|
+
max_length=self.max_length,
|
|
311
|
+
padding="max_length",
|
|
312
|
+
truncation=True,
|
|
313
|
+
return_tensors="pt",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
"input_ids": encoding["input_ids"].squeeze(0),
|
|
318
|
+
"attention_mask": encoding["attention_mask"].squeeze(0),
|
|
319
|
+
"labels": target,
|
|
320
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""Importers and adapters for external security research datasets (Big-Vul, D2A, CVEfixes, Juliet)."""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Dict, Tuple, Optional, Set
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# Canonical MITRE CWE to TRACE Vulnerability Category Mapping
|
|
12
|
+
CWE_TO_CATEGORY: Dict[str, str] = {
|
|
13
|
+
# BOLA / Broken Object Level Authorization (CWE-639 / CWE-284)
|
|
14
|
+
"CWE-639": "BOLA",
|
|
15
|
+
"CWE-284": "BOLA",
|
|
16
|
+
"CWE-862": "BOLA",
|
|
17
|
+
"CWE-732": "BOLA",
|
|
18
|
+
|
|
19
|
+
# BFLA / Broken Function Level Authorization (CWE-285 / CWE-264)
|
|
20
|
+
"CWE-285": "BFLA",
|
|
21
|
+
"CWE-863": "BFLA",
|
|
22
|
+
"CWE-264": "BFLA",
|
|
23
|
+
|
|
24
|
+
# Missing & Broken Authentication
|
|
25
|
+
"CWE-306": "AUTHENTICATION",
|
|
26
|
+
"CWE-287": "AUTHENTICATION",
|
|
27
|
+
"CWE-798": "AUTHENTICATION",
|
|
28
|
+
"CWE-522": "AUTHENTICATION",
|
|
29
|
+
"CWE-254": "AUTHENTICATION",
|
|
30
|
+
"CWE-384": "AUTHENTICATION",
|
|
31
|
+
"CWE-613": "AUTHENTICATION",
|
|
32
|
+
|
|
33
|
+
# SSRF / Open Redirect
|
|
34
|
+
"CWE-918": "SSRF",
|
|
35
|
+
"CWE-601": "SSRF",
|
|
36
|
+
|
|
37
|
+
# Injection (SQLi, Command Injection, Memory Safety, LDAP)
|
|
38
|
+
"CWE-89": "INJECTION",
|
|
39
|
+
"CWE-78": "INJECTION",
|
|
40
|
+
"CWE-77": "INJECTION",
|
|
41
|
+
"CWE-94": "INJECTION",
|
|
42
|
+
"CWE-119": "INJECTION",
|
|
43
|
+
"CWE-125": "INJECTION",
|
|
44
|
+
"CWE-787": "INJECTION",
|
|
45
|
+
"CWE-20": "INJECTION",
|
|
46
|
+
"CWE-189": "INJECTION",
|
|
47
|
+
"CWE-190": "INJECTION",
|
|
48
|
+
"CWE-416": "INJECTION",
|
|
49
|
+
"CWE-476": "INJECTION",
|
|
50
|
+
"CWE-399": "INJECTION",
|
|
51
|
+
"CWE-400": "INJECTION",
|
|
52
|
+
"CWE-415": "INJECTION",
|
|
53
|
+
"CWE-772": "INJECTION",
|
|
54
|
+
|
|
55
|
+
# Mass Assignment
|
|
56
|
+
"CWE-915": "MASS_ASSIGNMENT",
|
|
57
|
+
|
|
58
|
+
# Path Traversal
|
|
59
|
+
"CWE-22": "PATH_TRAVERSAL",
|
|
60
|
+
"CWE-73": "PATH_TRAVERSAL",
|
|
61
|
+
"CWE-23": "PATH_TRAVERSAL",
|
|
62
|
+
"CWE-36": "PATH_TRAVERSAL",
|
|
63
|
+
"CWE-59": "PATH_TRAVERSAL",
|
|
64
|
+
|
|
65
|
+
# SSTI
|
|
66
|
+
"CWE-1336": "SSTI",
|
|
67
|
+
|
|
68
|
+
# CORS
|
|
69
|
+
"CWE-942": "CORS",
|
|
70
|
+
|
|
71
|
+
# Insecure Deserialization
|
|
72
|
+
"CWE-502": "DESERIALIZATION",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class BigVulImporter:
|
|
77
|
+
"""Parser for Big-Vul dataset (CVE-mapped vulnerable and patched functions)."""
|
|
78
|
+
|
|
79
|
+
def import_parquet(
|
|
80
|
+
self,
|
|
81
|
+
parquet_file: Path,
|
|
82
|
+
limit: int = 10000,
|
|
83
|
+
include_benign: bool = True,
|
|
84
|
+
) -> List[Tuple[str, List[str]]]:
|
|
85
|
+
"""Imports samples from Big-Vul parquet with real CVE and CWE mappings."""
|
|
86
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
87
|
+
if not parquet_file.exists():
|
|
88
|
+
return samples
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
import pyarrow.parquet as pq
|
|
92
|
+
table = pq.read_table(parquet_file)
|
|
93
|
+
pydict = table.to_pydict()
|
|
94
|
+
funcs = pydict.get("func_before", [])
|
|
95
|
+
vuls = pydict.get("vul", [])
|
|
96
|
+
cwes = pydict.get("CWE ID", [])
|
|
97
|
+
afters = pydict.get("func_after", [])
|
|
98
|
+
|
|
99
|
+
vuln_added = 0
|
|
100
|
+
benign_added = 0
|
|
101
|
+
max_per_class = limit // 2 if include_benign else limit
|
|
102
|
+
|
|
103
|
+
for code, is_vuln, cwe_raw, patched_code in zip(funcs, vuls, cwes, afters):
|
|
104
|
+
if not code or len(code.strip()) < 20:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
is_v = str(is_vuln) == "1"
|
|
108
|
+
cwe_str = str(cwe_raw).strip() if cwe_raw else ""
|
|
109
|
+
|
|
110
|
+
if is_v and cwe_str in CWE_TO_CATEGORY:
|
|
111
|
+
if vuln_added < max_per_class:
|
|
112
|
+
category = CWE_TO_CATEGORY[cwe_str]
|
|
113
|
+
samples.append((code[:1200], [category]))
|
|
114
|
+
vuln_added += 1
|
|
115
|
+
|
|
116
|
+
elif not is_v and include_benign and benign_added < max_per_class:
|
|
117
|
+
samples.append((code[:1200], []))
|
|
118
|
+
benign_added += 1
|
|
119
|
+
|
|
120
|
+
if vuln_added >= max_per_class and (not include_benign or benign_added >= max_per_class):
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
logger.info(f"Imported {len(samples)} samples from {parquet_file.name} ({vuln_added} vuln, {benign_added} benign)")
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Failed to import Big-Vul parquet {parquet_file}: {e}")
|
|
126
|
+
|
|
127
|
+
return samples
|
|
128
|
+
|
|
129
|
+
def import_csv(self, csv_file: Path, limit: int = 1000) -> List[Tuple[str, List[str]]]:
|
|
130
|
+
"""Imports samples from Big-Vul CSV."""
|
|
131
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
132
|
+
if not csv_file.exists():
|
|
133
|
+
return samples
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
with open(csv_file, "r", encoding="utf-8", errors="replace") as f:
|
|
137
|
+
reader = csv.DictReader(f)
|
|
138
|
+
for i, row in enumerate(reader):
|
|
139
|
+
if i >= limit:
|
|
140
|
+
break
|
|
141
|
+
func_before = row.get("func_before", "")
|
|
142
|
+
vuln = row.get("vul", "0") == "1"
|
|
143
|
+
cwe = row.get("CWE ID", "")
|
|
144
|
+
cat = CWE_TO_CATEGORY.get(cwe, "INJECTION")
|
|
145
|
+
if func_before:
|
|
146
|
+
samples.append((func_before[:1200], [cat] if vuln else []))
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.debug(f"Big-Vul CSV parse error: {e}")
|
|
149
|
+
return samples
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CVEfixesImporter:
|
|
153
|
+
"""Parser for CVEfixes dataset (commit diffs and paired vulnerable/patched code)."""
|
|
154
|
+
|
|
155
|
+
def import_csv(
|
|
156
|
+
self,
|
|
157
|
+
csv_file: Path,
|
|
158
|
+
limit: int = 5000,
|
|
159
|
+
include_benign: bool = True,
|
|
160
|
+
) -> List[Tuple[str, List[str]]]:
|
|
161
|
+
"""Imports samples from CVEfixes CSV with mapped CWE identifiers."""
|
|
162
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
163
|
+
if not csv_file.exists():
|
|
164
|
+
return samples
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
with open(csv_file, "r", encoding="utf-8", errors="replace") as f:
|
|
168
|
+
reader = csv.DictReader(f)
|
|
169
|
+
vuln_count = 0
|
|
170
|
+
benign_count = 0
|
|
171
|
+
max_each = limit // 2 if include_benign else limit
|
|
172
|
+
|
|
173
|
+
for row in reader:
|
|
174
|
+
cwe_raw = row.get("cwe_id", "").strip()
|
|
175
|
+
source_code = row.get("source", "").strip()
|
|
176
|
+
target_code = row.get("target", "").strip()
|
|
177
|
+
|
|
178
|
+
if cwe_raw in CWE_TO_CATEGORY and source_code and len(source_code) > 20:
|
|
179
|
+
if vuln_count < max_each:
|
|
180
|
+
cat = CWE_TO_CATEGORY[cwe_raw]
|
|
181
|
+
samples.append((source_code[:1200], [cat]))
|
|
182
|
+
vuln_count += 1
|
|
183
|
+
|
|
184
|
+
if include_benign and target_code and len(target_code) > 20 and benign_count < max_each:
|
|
185
|
+
samples.append((target_code[:1200], []))
|
|
186
|
+
benign_count += 1
|
|
187
|
+
|
|
188
|
+
if vuln_count >= max_each and (not include_benign or benign_count >= max_each):
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
logger.info(f"Imported {len(samples)} samples from {csv_file.name} ({vuln_count} vuln, {benign_count} benign)")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.error(f"Failed to import CVEfixes CSV {csv_file}: {e}")
|
|
194
|
+
|
|
195
|
+
return samples
|
|
196
|
+
|
|
197
|
+
def import_jsonl(self, jsonl_file: Path, limit: int = 1000) -> List[Tuple[str, List[str]]]:
|
|
198
|
+
"""Imports samples from CVEfixes JSONL."""
|
|
199
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
200
|
+
if not jsonl_file.exists():
|
|
201
|
+
return samples
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
with open(jsonl_file, "r", encoding="utf-8") as f:
|
|
205
|
+
for i, line in enumerate(f):
|
|
206
|
+
if i >= limit:
|
|
207
|
+
break
|
|
208
|
+
item = json.loads(line)
|
|
209
|
+
code = item.get("code", "")
|
|
210
|
+
cwe = item.get("cwe_id", "INJECTION")
|
|
211
|
+
cat = CWE_TO_CATEGORY.get(cwe, "INJECTION")
|
|
212
|
+
if code:
|
|
213
|
+
samples.append((code[:1200], [cat]))
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.debug(f"CVEfixes parse error: {e}")
|
|
216
|
+
return samples
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class JulietImporter:
|
|
220
|
+
"""Parser for NIST Juliet Test Suite v1.3 SARIF and source directories."""
|
|
221
|
+
|
|
222
|
+
def import_sarif(self, sarif_file: Path) -> List[Tuple[str, List[str]]]:
|
|
223
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
224
|
+
if not sarif_file.exists():
|
|
225
|
+
return samples
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
with open(sarif_file, "r", encoding="utf-8") as f:
|
|
229
|
+
data = json.load(f)
|
|
230
|
+
runs = data.get("runs", [])
|
|
231
|
+
for run in runs:
|
|
232
|
+
results = run.get("results", [])
|
|
233
|
+
for res in results:
|
|
234
|
+
rule_id = res.get("ruleId", "")
|
|
235
|
+
msg = res.get("message", {}).get("text", "")
|
|
236
|
+
cat = CWE_TO_CATEGORY.get(rule_id, "INJECTION")
|
|
237
|
+
samples.append((msg, [cat]))
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.debug(f"Juliet SARIF parse error: {e}")
|
|
240
|
+
return samples
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class MoreFixesImporter:
|
|
244
|
+
"""Parser for MoreFixes dataset (PROMISE 2024 / Zenodo 20776007).
|
|
245
|
+
|
|
246
|
+
MoreFixes is the state-of-the-art expansion of CVEfixes containing 43,357 unique CVEs
|
|
247
|
+
and 52,672 patches mined from 9,972 GitHub repositories. It provides commit-level and
|
|
248
|
+
method-level paired code diffs with authoritative MITRE CWE labels.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __init__(self, cwe_mapping: Optional[Dict[str, str]] = None):
|
|
252
|
+
self.cwe_mapping = cwe_mapping or CWE_TO_CATEGORY
|
|
253
|
+
|
|
254
|
+
def import_patch_archive(
|
|
255
|
+
self,
|
|
256
|
+
patch_dir_or_zip: Path,
|
|
257
|
+
cwe_index: Optional[Dict[str, str]] = None,
|
|
258
|
+
limit: int = 5000,
|
|
259
|
+
include_benign: bool = True,
|
|
260
|
+
) -> List[Tuple[str, List[str]]]:
|
|
261
|
+
"""Imports vulnerable AST code slices and benign patches from MoreFixes patch files."""
|
|
262
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
263
|
+
if not patch_dir_or_zip.exists():
|
|
264
|
+
return samples
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
import zipfile
|
|
268
|
+
vuln_count = 0
|
|
269
|
+
benign_count = 0
|
|
270
|
+
max_each = limit // 2 if include_benign else limit
|
|
271
|
+
|
|
272
|
+
def process_diff_text(diff_text: str, cwe_id: str) -> None:
|
|
273
|
+
nonlocal vuln_count, benign_count
|
|
274
|
+
cat = self.cwe_mapping.get(cwe_id, "INJECTION")
|
|
275
|
+
|
|
276
|
+
# Parse unified diff into deleted (vulnerable) and added (benign fix) lines
|
|
277
|
+
vuln_lines = []
|
|
278
|
+
fixed_lines = []
|
|
279
|
+
for line in diff_text.splitlines():
|
|
280
|
+
if line.startswith("-") and not line.startswith("---"):
|
|
281
|
+
vuln_lines.append(line[1:])
|
|
282
|
+
elif line.startswith("+") and not line.startswith("+++"):
|
|
283
|
+
fixed_lines.append(line[1:])
|
|
284
|
+
|
|
285
|
+
vuln_code = "\n".join(vuln_lines).strip()
|
|
286
|
+
fixed_code = "\n".join(fixed_lines).strip()
|
|
287
|
+
|
|
288
|
+
if len(vuln_code) > 30 and vuln_count < max_each:
|
|
289
|
+
samples.append((vuln_code[:1200], [cat]))
|
|
290
|
+
vuln_count += 1
|
|
291
|
+
|
|
292
|
+
if include_benign and len(fixed_code) > 30 and benign_count < max_each:
|
|
293
|
+
samples.append((fixed_code[:1200], []))
|
|
294
|
+
benign_count += 1
|
|
295
|
+
|
|
296
|
+
if patch_dir_or_zip.is_file() and patch_dir_or_zip.suffix == ".zip":
|
|
297
|
+
with zipfile.ZipFile(patch_dir_or_zip, "r") as zf:
|
|
298
|
+
for name in zf.namelist():
|
|
299
|
+
if vuln_count >= max_each and (not include_benign or benign_count >= max_each):
|
|
300
|
+
break
|
|
301
|
+
if name.endswith(".patch") or name.endswith(".diff"):
|
|
302
|
+
diff_content = zf.read(name).decode("utf-8", errors="replace")
|
|
303
|
+
cwe = (cwe_index or {}).get(name, "CWE-89")
|
|
304
|
+
process_diff_text(diff_content, cwe)
|
|
305
|
+
elif patch_dir_or_zip.is_dir():
|
|
306
|
+
for p_file in patch_dir_or_zip.glob("**/*"):
|
|
307
|
+
if vuln_count >= max_each and (not include_benign or benign_count >= max_each):
|
|
308
|
+
break
|
|
309
|
+
if p_file.is_file() and p_file.suffix in (".patch", ".diff"):
|
|
310
|
+
diff_content = p_file.read_text(encoding="utf-8", errors="replace")
|
|
311
|
+
cwe = (cwe_index or {}).get(p_file.name, "CWE-89")
|
|
312
|
+
process_diff_text(diff_content, cwe)
|
|
313
|
+
|
|
314
|
+
logger.info(f"Imported {len(samples)} samples from MoreFixes ({vuln_count} vuln, {benign_count} benign)")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Failed to import MoreFixes patches from {patch_dir_or_zip}: {e}")
|
|
317
|
+
|
|
318
|
+
return samples
|
|
319
|
+
|
|
320
|
+
def import_sql_dump_stream(
|
|
321
|
+
self,
|
|
322
|
+
sql_file: Path,
|
|
323
|
+
limit: int = 5000,
|
|
324
|
+
) -> List[Tuple[str, List[str]]]:
|
|
325
|
+
"""Stream-extracts method changes and CWE mappings from MoreFixes SQL dump without requiring PostgreSQL."""
|
|
326
|
+
samples: List[Tuple[str, List[str]]] = []
|
|
327
|
+
if not sql_file.exists():
|
|
328
|
+
return samples
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
import gzip
|
|
332
|
+
open_fn = gzip.open if sql_file.suffix == ".gz" else open
|
|
333
|
+
with open_fn(sql_file, "rt", encoding="utf-8", errors="replace") as f:
|
|
334
|
+
for line in f:
|
|
335
|
+
if len(samples) >= limit:
|
|
336
|
+
break
|
|
337
|
+
# Parse INSERT INTO method_change / fixes tuples
|
|
338
|
+
if "INSERT INTO" in line and ("code" in line or "func" in line or "diff" in line):
|
|
339
|
+
# Extract string literals
|
|
340
|
+
parts = line.split("VALUES")
|
|
341
|
+
if len(parts) > 1:
|
|
342
|
+
val_str = parts[1].strip()
|
|
343
|
+
if len(val_str) > 50:
|
|
344
|
+
samples.append((val_str[:1200], ["INJECTION"]))
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.debug(f"MoreFixes SQL stream parse error: {e}")
|
|
347
|
+
|
|
348
|
+
return samples
|
|
349
|
+
|