@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.
Files changed (249) hide show
  1. package/README.md +208 -0
  2. package/bin/trace.js +112 -0
  3. package/package.json +46 -0
  4. package/pyproject.toml +39 -0
  5. package/src/trace_engine/__init__.py +8 -0
  6. package/src/trace_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  7. package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
  8. package/src/trace_engine/__pycache__/doctor.cpython-311.pyc +0 -0
  9. package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
  10. package/src/trace_engine/__pycache__/verify.cpython-311.pyc +0 -0
  11. package/src/trace_engine/ai/__init__.py +7 -0
  12. package/src/trace_engine/ai/__pycache__/__init__.cpython-311.pyc +0 -0
  13. package/src/trace_engine/ai/__pycache__/base.cpython-311.pyc +0 -0
  14. package/src/trace_engine/ai/__pycache__/ollama.cpython-311.pyc +0 -0
  15. package/src/trace_engine/ai/__pycache__/planner.cpython-311.pyc +0 -0
  16. package/src/trace_engine/ai/base.py +23 -0
  17. package/src/trace_engine/ai/ollama.py +50 -0
  18. package/src/trace_engine/ai/planner.py +40 -0
  19. package/src/trace_engine/apm/__init__.py +18 -0
  20. package/src/trace_engine/apm/__pycache__/__init__.cpython-311.pyc +0 -0
  21. package/src/trace_engine/apm/__pycache__/builder.cpython-311.pyc +0 -0
  22. package/src/trace_engine/apm/__pycache__/edges.cpython-311.pyc +0 -0
  23. package/src/trace_engine/apm/__pycache__/model.cpython-311.pyc +0 -0
  24. package/src/trace_engine/apm/__pycache__/nodes.cpython-311.pyc +0 -0
  25. package/src/trace_engine/apm/__pycache__/serialization.cpython-311.pyc +0 -0
  26. package/src/trace_engine/apm/builder.py +208 -0
  27. package/src/trace_engine/apm/edges.py +25 -0
  28. package/src/trace_engine/apm/model.py +107 -0
  29. package/src/trace_engine/apm/nodes.py +27 -0
  30. package/src/trace_engine/apm/serialization.py +105 -0
  31. package/src/trace_engine/benchmark/__init__.py +5 -0
  32. package/src/trace_engine/benchmark/__pycache__/__init__.cpython-311.pyc +0 -0
  33. package/src/trace_engine/benchmark/__pycache__/owasp.cpython-311.pyc +0 -0
  34. package/src/trace_engine/benchmark/owasp.py +183 -0
  35. package/src/trace_engine/cli.py +1184 -0
  36. package/src/trace_engine/config/__init__.py +35 -0
  37. package/src/trace_engine/config/__pycache__/__init__.cpython-311.pyc +0 -0
  38. package/src/trace_engine/config/__pycache__/defaults.cpython-311.pyc +0 -0
  39. package/src/trace_engine/config/__pycache__/loader.cpython-311.pyc +0 -0
  40. package/src/trace_engine/config/__pycache__/settings.cpython-311.pyc +0 -0
  41. package/src/trace_engine/config/defaults.py +48 -0
  42. package/src/trace_engine/config/loader.py +64 -0
  43. package/src/trace_engine/config/settings.py +72 -0
  44. package/src/trace_engine/doctor.py +250 -0
  45. package/src/trace_engine/findings/__init__.py +15 -0
  46. package/src/trace_engine/findings/__pycache__/__init__.cpython-311.pyc +0 -0
  47. package/src/trace_engine/findings/__pycache__/correlate.cpython-311.pyc +0 -0
  48. package/src/trace_engine/findings/__pycache__/model.cpython-311.pyc +0 -0
  49. package/src/trace_engine/findings/__pycache__/recommendations.cpython-311.pyc +0 -0
  50. package/src/trace_engine/findings/__pycache__/store.cpython-311.pyc +0 -0
  51. package/src/trace_engine/findings/correlate.py +103 -0
  52. package/src/trace_engine/findings/model.py +40 -0
  53. package/src/trace_engine/findings/recommendations.py +35 -0
  54. package/src/trace_engine/findings/store.py +39 -0
  55. package/src/trace_engine/framework/__init__.py +58 -0
  56. package/src/trace_engine/framework/__pycache__/__init__.cpython-311.pyc +0 -0
  57. package/src/trace_engine/framework/__pycache__/base.cpython-311.pyc +0 -0
  58. package/src/trace_engine/framework/__pycache__/csharp.cpython-311.pyc +0 -0
  59. package/src/trace_engine/framework/__pycache__/dart.cpython-311.pyc +0 -0
  60. package/src/trace_engine/framework/__pycache__/django.cpython-311.pyc +0 -0
  61. package/src/trace_engine/framework/__pycache__/express.cpython-311.pyc +0 -0
  62. package/src/trace_engine/framework/__pycache__/fastapi.cpython-311.pyc +0 -0
  63. package/src/trace_engine/framework/__pycache__/flask.cpython-311.pyc +0 -0
  64. package/src/trace_engine/framework/__pycache__/go.cpython-311.pyc +0 -0
  65. package/src/trace_engine/framework/__pycache__/nextjs.cpython-311.pyc +0 -0
  66. package/src/trace_engine/framework/__pycache__/php.cpython-311.pyc +0 -0
  67. package/src/trace_engine/framework/__pycache__/react_router.cpython-311.pyc +0 -0
  68. package/src/trace_engine/framework/__pycache__/ruby.cpython-311.pyc +0 -0
  69. package/src/trace_engine/framework/__pycache__/rust.cpython-311.pyc +0 -0
  70. package/src/trace_engine/framework/__pycache__/springboot.cpython-311.pyc +0 -0
  71. package/src/trace_engine/framework/base.py +49 -0
  72. package/src/trace_engine/framework/csharp.py +111 -0
  73. package/src/trace_engine/framework/dart.py +152 -0
  74. package/src/trace_engine/framework/django.py +188 -0
  75. package/src/trace_engine/framework/express.py +82 -0
  76. package/src/trace_engine/framework/fastapi.py +135 -0
  77. package/src/trace_engine/framework/flask.py +108 -0
  78. package/src/trace_engine/framework/go.py +94 -0
  79. package/src/trace_engine/framework/nextjs.py +200 -0
  80. package/src/trace_engine/framework/php.py +98 -0
  81. package/src/trace_engine/framework/react_router.py +331 -0
  82. package/src/trace_engine/framework/ruby.py +69 -0
  83. package/src/trace_engine/framework/rust.py +101 -0
  84. package/src/trace_engine/framework/springboot.py +145 -0
  85. package/src/trace_engine/harness/__init__.py +19 -0
  86. package/src/trace_engine/harness/__pycache__/__init__.cpython-311.pyc +0 -0
  87. package/src/trace_engine/harness/__pycache__/benchmark.cpython-311.pyc +0 -0
  88. package/src/trace_engine/harness/__pycache__/context.cpython-311.pyc +0 -0
  89. package/src/trace_engine/harness/__pycache__/engine.cpython-311.pyc +0 -0
  90. package/src/trace_engine/harness/__pycache__/patcher.cpython-311.pyc +0 -0
  91. package/src/trace_engine/harness/__pycache__/remediators.cpython-311.pyc +0 -0
  92. package/src/trace_engine/harness/benchmark.py +55 -0
  93. package/src/trace_engine/harness/context.py +49 -0
  94. package/src/trace_engine/harness/engine.py +187 -0
  95. package/src/trace_engine/harness/patcher.py +143 -0
  96. package/src/trace_engine/harness/remediators.py +307 -0
  97. package/src/trace_engine/ingest/__init__.py +15 -0
  98. package/src/trace_engine/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
  99. package/src/trace_engine/ingest/__pycache__/files.cpython-311.pyc +0 -0
  100. package/src/trace_engine/ingest/__pycache__/hashing.cpython-311.pyc +0 -0
  101. package/src/trace_engine/ingest/__pycache__/ignore.cpython-311.pyc +0 -0
  102. package/src/trace_engine/ingest/__pycache__/repository.cpython-311.pyc +0 -0
  103. package/src/trace_engine/ingest/files.py +60 -0
  104. package/src/trace_engine/ingest/hashing.py +21 -0
  105. package/src/trace_engine/ingest/ignore.py +108 -0
  106. package/src/trace_engine/ingest/repository.py +50 -0
  107. package/src/trace_engine/intelligence/__init__.py +15 -0
  108. package/src/trace_engine/intelligence/__pycache__/__init__.cpython-311.pyc +0 -0
  109. package/src/trace_engine/intelligence/__pycache__/evaluation.cpython-311.pyc +0 -0
  110. package/src/trace_engine/intelligence/__pycache__/orchestrator.cpython-311.pyc +0 -0
  111. package/src/trace_engine/intelligence/__pycache__/tracebench.cpython-311.pyc +0 -0
  112. package/src/trace_engine/intelligence/evaluation.py +828 -0
  113. package/src/trace_engine/intelligence/laya/__init__.py +19 -0
  114. package/src/trace_engine/intelligence/laya/__pycache__/__init__.cpython-311.pyc +0 -0
  115. package/src/trace_engine/intelligence/laya/__pycache__/prompts.cpython-311.pyc +0 -0
  116. package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
  117. package/src/trace_engine/intelligence/laya/__pycache__/schemas.cpython-311.pyc +0 -0
  118. package/src/trace_engine/intelligence/laya/__pycache__/telemetry.cpython-311.pyc +0 -0
  119. package/src/trace_engine/intelligence/laya/__pycache__/thresholds.cpython-311.pyc +0 -0
  120. package/src/trace_engine/intelligence/laya/prompts.py +67 -0
  121. package/src/trace_engine/intelligence/laya/router.py +352 -0
  122. package/src/trace_engine/intelligence/laya/schemas.py +56 -0
  123. package/src/trace_engine/intelligence/laya/telemetry.py +48 -0
  124. package/src/trace_engine/intelligence/laya/thresholds.py +12 -0
  125. package/src/trace_engine/intelligence/orchestrator.py +130 -0
  126. package/src/trace_engine/intelligence/securebert/__init__.py +6 -0
  127. package/src/trace_engine/intelligence/securebert/__pycache__/__init__.cpython-311.pyc +0 -0
  128. package/src/trace_engine/intelligence/securebert/__pycache__/cache.cpython-311.pyc +0 -0
  129. package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
  130. package/src/trace_engine/intelligence/securebert/cache.py +37 -0
  131. package/src/trace_engine/intelligence/securebert/classifier.py +240 -0
  132. package/src/trace_engine/intelligence/tracebench.py +61 -0
  133. package/src/trace_engine/intelligence/training/__init__.py +21 -0
  134. package/src/trace_engine/intelligence/training/__pycache__/__init__.cpython-311.pyc +0 -0
  135. package/src/trace_engine/intelligence/training/__pycache__/dataset.cpython-311.pyc +0 -0
  136. package/src/trace_engine/intelligence/training/__pycache__/dataset_importers.cpython-311.pyc +0 -0
  137. package/src/trace_engine/intelligence/training/__pycache__/laya_trainer.cpython-311.pyc +0 -0
  138. package/src/trace_engine/intelligence/training/__pycache__/lora_system2.cpython-311.pyc +0 -0
  139. package/src/trace_engine/intelligence/training/__pycache__/morefixes_pipeline.cpython-311.pyc +0 -0
  140. package/src/trace_engine/intelligence/training/__pycache__/slicer.cpython-311.pyc +0 -0
  141. package/src/trace_engine/intelligence/training/__pycache__/train_all.cpython-311.pyc +0 -0
  142. package/src/trace_engine/intelligence/training/__pycache__/trainer.cpython-311.pyc +0 -0
  143. package/src/trace_engine/intelligence/training/dataset.py +320 -0
  144. package/src/trace_engine/intelligence/training/dataset_importers.py +349 -0
  145. package/src/trace_engine/intelligence/training/laya_trainer.py +678 -0
  146. package/src/trace_engine/intelligence/training/lora_system2.py +162 -0
  147. package/src/trace_engine/intelligence/training/morefixes_pipeline.py +463 -0
  148. package/src/trace_engine/intelligence/training/slicer.py +129 -0
  149. package/src/trace_engine/intelligence/training/train_all.py +1009 -0
  150. package/src/trace_engine/intelligence/training/trainer.py +321 -0
  151. package/src/trace_engine/interactive.py +623 -0
  152. package/src/trace_engine/mcp/__init__.py +6 -0
  153. package/src/trace_engine/mcp/__pycache__/__init__.cpython-311.pyc +0 -0
  154. package/src/trace_engine/mcp/__pycache__/config.cpython-311.pyc +0 -0
  155. package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
  156. package/src/trace_engine/mcp/config.py +42 -0
  157. package/src/trace_engine/mcp/server.py +398 -0
  158. package/src/trace_engine/output/__init__.py +28 -0
  159. package/src/trace_engine/output/__pycache__/__init__.cpython-311.pyc +0 -0
  160. package/src/trace_engine/output/__pycache__/html.cpython-311.pyc +0 -0
  161. package/src/trace_engine/output/__pycache__/markdown.cpython-311.pyc +0 -0
  162. package/src/trace_engine/output/__pycache__/sarif.cpython-311.pyc +0 -0
  163. package/src/trace_engine/output/__pycache__/terminal.cpython-311.pyc +0 -0
  164. package/src/trace_engine/output/html.py +142 -0
  165. package/src/trace_engine/output/markdown.py +65 -0
  166. package/src/trace_engine/output/sarif.py +180 -0
  167. package/src/trace_engine/output/terminal.py +537 -0
  168. package/src/trace_engine/parsing/__init__.py +26 -0
  169. package/src/trace_engine/parsing/__pycache__/__init__.cpython-311.pyc +0 -0
  170. package/src/trace_engine/parsing/__pycache__/calls.cpython-311.pyc +0 -0
  171. package/src/trace_engine/parsing/__pycache__/language.cpython-311.pyc +0 -0
  172. package/src/trace_engine/parsing/__pycache__/locations.cpython-311.pyc +0 -0
  173. package/src/trace_engine/parsing/__pycache__/parser.cpython-311.pyc +0 -0
  174. package/src/trace_engine/parsing/__pycache__/symbols.cpython-311.pyc +0 -0
  175. package/src/trace_engine/parsing/calls.py +14 -0
  176. package/src/trace_engine/parsing/language.py +24 -0
  177. package/src/trace_engine/parsing/locations.py +17 -0
  178. package/src/trace_engine/parsing/parser.py +465 -0
  179. package/src/trace_engine/parsing/symbols.py +45 -0
  180. package/src/trace_engine/plugin/__init__.py +157 -0
  181. package/src/trace_engine/plugin/__pycache__/__init__.cpython-311.pyc +0 -0
  182. package/src/trace_engine/plugin/__pycache__/evaluator.cpython-311.pyc +0 -0
  183. package/src/trace_engine/plugin/__pycache__/swebench_adapter.cpython-311.pyc +0 -0
  184. package/src/trace_engine/plugin/__pycache__/task.cpython-311.pyc +0 -0
  185. package/src/trace_engine/plugin/bundle/hooks.json +24 -0
  186. package/src/trace_engine/plugin/bundle/mcp_config.json +11 -0
  187. package/src/trace_engine/plugin/bundle/plugin.json +20 -0
  188. package/src/trace_engine/plugin/bundle/rules/security_remediation.md +40 -0
  189. package/src/trace_engine/plugin/bundle/skills/trace-security-harness/SKILL.md +118 -0
  190. package/src/trace_engine/plugin/evaluator.py +105 -0
  191. package/src/trace_engine/plugin/swebench_adapter.py +96 -0
  192. package/src/trace_engine/plugin/task.py +48 -0
  193. package/src/trace_engine/policy/__init__.py +5 -0
  194. package/src/trace_engine/policy/__pycache__/__init__.cpython-311.pyc +0 -0
  195. package/src/trace_engine/policy/__pycache__/scope.cpython-311.pyc +0 -0
  196. package/src/trace_engine/policy/scope.py +80 -0
  197. package/src/trace_engine/runtime/__init__.py +7 -0
  198. package/src/trace_engine/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
  199. package/src/trace_engine/runtime/__pycache__/client.cpython-311.pyc +0 -0
  200. package/src/trace_engine/runtime/__pycache__/observations.cpython-311.pyc +0 -0
  201. package/src/trace_engine/runtime/__pycache__/target.cpython-311.pyc +0 -0
  202. package/src/trace_engine/runtime/client.py +81 -0
  203. package/src/trace_engine/runtime/observations.py +18 -0
  204. package/src/trace_engine/runtime/target.py +23 -0
  205. package/src/trace_engine/security/__init__.py +16 -0
  206. package/src/trace_engine/security/__pycache__/__init__.cpython-311.pyc +0 -0
  207. package/src/trace_engine/security/__pycache__/fusion.cpython-311.pyc +0 -0
  208. package/src/trace_engine/security/__pycache__/hypotheses.cpython-311.pyc +0 -0
  209. package/src/trace_engine/security/__pycache__/signals.cpython-311.pyc +0 -0
  210. package/src/trace_engine/security/__pycache__/timing.cpython-311.pyc +0 -0
  211. package/src/trace_engine/security/fusion.py +126 -0
  212. package/src/trace_engine/security/hypotheses.py +251 -0
  213. package/src/trace_engine/security/signals.py +28 -0
  214. package/src/trace_engine/security/timing.py +132 -0
  215. package/src/trace_engine/testpacks/__init__.py +18 -0
  216. package/src/trace_engine/testpacks/__pycache__/__init__.cpython-311.pyc +0 -0
  217. package/src/trace_engine/testpacks/__pycache__/authentication.cpython-311.pyc +0 -0
  218. package/src/trace_engine/testpacks/__pycache__/base.cpython-311.pyc +0 -0
  219. package/src/trace_engine/testpacks/__pycache__/bfla.cpython-311.pyc +0 -0
  220. package/src/trace_engine/testpacks/__pycache__/bola.cpython-311.pyc +0 -0
  221. package/src/trace_engine/testpacks/__pycache__/cors.cpython-311.pyc +0 -0
  222. package/src/trace_engine/testpacks/__pycache__/deserialization.cpython-311.pyc +0 -0
  223. package/src/trace_engine/testpacks/__pycache__/injection.cpython-311.pyc +0 -0
  224. package/src/trace_engine/testpacks/__pycache__/mass_assignment.cpython-311.pyc +0 -0
  225. package/src/trace_engine/testpacks/__pycache__/path_traversal.cpython-311.pyc +0 -0
  226. package/src/trace_engine/testpacks/__pycache__/registry.cpython-311.pyc +0 -0
  227. package/src/trace_engine/testpacks/__pycache__/ssrf.cpython-311.pyc +0 -0
  228. package/src/trace_engine/testpacks/__pycache__/ssti.cpython-311.pyc +0 -0
  229. package/src/trace_engine/testpacks/authentication.py +60 -0
  230. package/src/trace_engine/testpacks/base.py +63 -0
  231. package/src/trace_engine/testpacks/bfla.py +70 -0
  232. package/src/trace_engine/testpacks/bola.py +94 -0
  233. package/src/trace_engine/testpacks/cors.py +85 -0
  234. package/src/trace_engine/testpacks/deserialization.py +86 -0
  235. package/src/trace_engine/testpacks/injection.py +179 -0
  236. package/src/trace_engine/testpacks/mass_assignment.py +70 -0
  237. package/src/trace_engine/testpacks/path_traversal.py +117 -0
  238. package/src/trace_engine/testpacks/registry.py +44 -0
  239. package/src/trace_engine/testpacks/ssrf.py +85 -0
  240. package/src/trace_engine/testpacks/ssti.py +96 -0
  241. package/src/trace_engine/tools/__init__.py +6 -0
  242. package/src/trace_engine/tools/__pycache__/__init__.cpython-311.pyc +0 -0
  243. package/src/trace_engine/tools/__pycache__/adapters.cpython-311.pyc +0 -0
  244. package/src/trace_engine/tools/__pycache__/base.cpython-311.pyc +0 -0
  245. package/src/trace_engine/tools/__pycache__/registry.cpython-311.pyc +0 -0
  246. package/src/trace_engine/tools/adapters.py +111 -0
  247. package/src/trace_engine/tools/base.py +68 -0
  248. package/src/trace_engine/tools/registry.py +36 -0
  249. 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
+