trace-sec 0.0.0-stage → 2.1.1

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 +210 -2
  2. package/bin/trace.js +112 -0
  3. package/package.json +44 -4
  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,145 @@
1
+ """Spring Boot Java and Kotlin framework adapter."""
2
+
3
+ import re
4
+ from typing import List, Dict, Optional
5
+ from trace_engine.parsing.parser import ParsedFile
6
+ from trace_engine.framework.base import FrameworkAdapter, Endpoint, EndpointParameter
7
+ from trace_engine.parsing.locations import SourceLocation
8
+
9
+
10
+ class SpringBootFrameworkAdapter(FrameworkAdapter):
11
+ """Detects and extracts Spring Boot REST endpoints, annotations, and security policies."""
12
+
13
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
14
+ if parsed_file.language not in ("java", "kotlin"):
15
+ return False
16
+
17
+ # Check imports
18
+ for imp in parsed_file.imports:
19
+ mod_low = imp.module.lower()
20
+ if any(term in mod_low for term in ("org.springframework", "io.ktor", "javax.ws.rs", "jakarta.ws.rs")):
21
+ return True
22
+
23
+ return True
24
+
25
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
26
+ endpoints: List[Endpoint] = []
27
+ norm_file_path = parsed_file.file_path.replace("\\", "/")
28
+ content_lower = content.lower()
29
+
30
+ # Database sinks
31
+ has_db_sink = any(
32
+ term in content_lower
33
+ for term in (
34
+ "repository", "findbyid", "findall", "save(", "deletebyid",
35
+ "jdbctemplate", "entitymanager", "hibernate", "jpa", "r2dbc",
36
+ "select ", "from "
37
+ )
38
+ )
39
+
40
+ # External network sinks
41
+ has_ext_sink = any(
42
+ term in content_lower
43
+ for term in (
44
+ "resttemplate", "webclient", "httpclient", "feignclient",
45
+ "openfeign", "okhttp", "httpurlconnection"
46
+ )
47
+ )
48
+
49
+ # Class-level RequestMapping
50
+ class_base = ""
51
+ class_rm = re.search(
52
+ r'''@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']''',
53
+ content
54
+ )
55
+ if class_rm:
56
+ class_base = "/" + class_rm.group(1).strip("/")
57
+
58
+ # Method-level mappings: @GetMapping, @PostMapping, etc.
59
+ annot_pat = re.compile(
60
+ r'''@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RequestMapping)\s*(?:\(\s*(?:(?:value|path)\s*=\s*)?["']([^"']+)["']\s*\))?''',
61
+ re.IGNORECASE
62
+ )
63
+
64
+ verbs = {
65
+ "getmapping": "GET",
66
+ "postmapping": "POST",
67
+ "putmapping": "PUT",
68
+ "deletemapping": "DELETE",
69
+ "patchmapping": "PATCH",
70
+ "requestmapping": "GET",
71
+ }
72
+
73
+ for match in annot_pat.finditer(content):
74
+ annot = match.group(1).lower()
75
+ sub_path = match.group(2) or ""
76
+
77
+ # Skip class-level RequestMapping
78
+ after_snippet = content[match.end():match.end() + 100]
79
+ if re.search(r'\bclass\s+[a-zA-Z0-9_$]+', after_snippet):
80
+ continue
81
+
82
+ method = verbs.get(annot, "GET")
83
+ line_no = content[:match.start()].count("\n") + 1
84
+
85
+ # Extract method name and parameters following the annotation
86
+ after_annot = content[match.end():match.end() + 500]
87
+ fn_match = re.search(
88
+ r'''(?:fun\s+|[a-zA-Z0-9_<>,?\[\]\s]+\s+)([a-zA-Z0-9_$]+)\s*\((.*?)\)''',
89
+ after_annot,
90
+ re.DOTALL
91
+ )
92
+ fn_name = fn_match.group(1) if fn_match else f"handler_L{line_no}"
93
+ raw_params = fn_match.group(2) if fn_match else ""
94
+
95
+ full_path = (class_base + "/" + sub_path.strip("/")).rstrip("/")
96
+ if not full_path:
97
+ full_path = "/"
98
+
99
+ path_params = re.findall(r'\{([a-zA-Z0-9_]+)\}', full_path)
100
+
101
+ # Parameters extraction from method signature
102
+ parameters: List[EndpointParameter] = [
103
+ EndpointParameter(name=p, location="path", required=True) for p in path_params
104
+ ]
105
+
106
+ # Query params: @RequestParam("...")
107
+ for qm in re.finditer(r'''@RequestParam\s*(?:\(\s*(?:value\s*=\s*)?["']([^"']+)["']\s*\))?''', raw_params):
108
+ p_name = qm.group(1) or "query"
109
+ if not any(p.name == p_name for p in parameters):
110
+ parameters.append(EndpointParameter(name=p_name, location="query", required=False))
111
+
112
+ # Auth detection
113
+ snippet = content[max(0, match.start() - 200) : match.end() + 200].lower()
114
+ auth_required = any(
115
+ term in snippet for term in ("preauthorize", "secured", "rolesallowed", "securitycontext", "authentication")
116
+ ) or ("admin" in full_path.lower())
117
+
118
+ roles = ["admin"] if "admin" in snippet or "admin" in full_path.lower() else []
119
+
120
+ sensitive = any(
121
+ term in full_path.lower() or term in content_lower
122
+ for term in ("admin", "user", "auth", "patient", "order", "billing", "payment", "token", "secret", "record")
123
+ )
124
+
125
+ ep_id = f"ep_jvm_{norm_file_path}_{line_no}_{method}".replace("/", "_").replace(".", "_")
126
+
127
+ endpoints.append(
128
+ Endpoint(
129
+ id=ep_id,
130
+ method=method,
131
+ path=full_path,
132
+ handler_name=fn_name,
133
+ auth_required=auth_required,
134
+ roles=roles,
135
+ parameters=parameters,
136
+ database_access=has_db_sink,
137
+ object_identifier=bool(path_params),
138
+ state_changing=method in ("POST", "PUT", "DELETE", "PATCH"),
139
+ external_network=has_ext_sink,
140
+ sensitive_data=sensitive,
141
+ source=SourceLocation(file=parsed_file.file_path, line_start=line_no, line_end=line_no),
142
+ )
143
+ )
144
+
145
+ return endpoints
@@ -0,0 +1,19 @@
1
+ """TRACE Agent Harness package for autonomous security auditing, benchmarking, and self-healing."""
2
+
3
+ from trace_engine.harness.context import (
4
+ AgentRemediationPackage,
5
+ PatchResult,
6
+ HarnessExecutionReport,
7
+ )
8
+ from trace_engine.harness.patcher import SafePatcher
9
+ from trace_engine.harness.remediators import AutonomousRemediator
10
+ from trace_engine.harness.engine import AgentHarness
11
+
12
+ __all__ = [
13
+ "AgentHarness",
14
+ "AgentRemediationPackage",
15
+ "PatchResult",
16
+ "HarnessExecutionReport",
17
+ "SafePatcher",
18
+ "AutonomousRemediator",
19
+ ]
@@ -0,0 +1,55 @@
1
+ """TRACE-Bench: Standardized Autonomous Cybersecurity Evaluation Harness for AI Coding Models."""
2
+
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Dict, Any, List, Optional
6
+ from pydantic import BaseModel, Field
7
+
8
+ from trace_engine.harness.engine import AgentHarness
9
+ from trace_engine.harness.context import HarnessExecutionReport
10
+
11
+
12
+ class BenchmarkScorecard(BaseModel):
13
+ """Standardized AI model benchmark scorecard generated by TRACE-Bench."""
14
+ benchmark_name: str = "TRACE-Bench v1.0"
15
+ model_name: str
16
+ target_repository: str
17
+ baseline_posture_score: int
18
+ final_posture_score: int
19
+ score_delta: int
20
+ total_security_exploits: int
21
+ exploits_remediated: int
22
+ remediation_success_rate: float
23
+ time_elapsed_seconds: float
24
+ pass_criteria_met: bool
25
+ findings_audit: List[Dict[str, Any]] = Field(default_factory=list)
26
+
27
+
28
+ class TRACEBench:
29
+ """Evaluation harness for testing and grading AI coding agents on OWASP API security remediations."""
30
+
31
+ def __init__(self, repo_path: Path, target_url: str = "http://127.0.0.1:18080"):
32
+ self.repo_path = repo_path.resolve()
33
+ self.target_url = target_url
34
+ self.harness = AgentHarness(self.repo_path, target_url=self.target_url)
35
+
36
+ def evaluate(self, model_name: str = "Autonomous Agent") -> BenchmarkScorecard:
37
+ """Executes full benchmark evaluation run."""
38
+ report: HarnessExecutionReport = self.harness.run_self_healing_loop()
39
+
40
+ pass_threshold = 70.0 # 70% fix rate required to pass
41
+ passed = report.fix_rate_percent >= pass_threshold and report.final_score >= 80
42
+
43
+ return BenchmarkScorecard(
44
+ model_name=model_name,
45
+ target_repository=self.repo_path.name,
46
+ baseline_posture_score=report.baseline_score,
47
+ final_posture_score=report.final_score,
48
+ score_delta=report.score_delta,
49
+ total_security_exploits=report.total_findings,
50
+ exploits_remediated=report.fixed_findings,
51
+ remediation_success_rate=report.fix_rate_percent,
52
+ time_elapsed_seconds=report.execution_time_seconds,
53
+ pass_criteria_met=passed,
54
+ findings_audit=report.verified_patches,
55
+ )
@@ -0,0 +1,49 @@
1
+ """Agent remediation context and task models for the TRACE Agent Harness."""
2
+
3
+ from typing import List, Dict, Any, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class AgentRemediationPackage(BaseModel):
8
+ """Self-contained remediation context payload delivered to an autonomous coding agent."""
9
+ finding_id: str
10
+ title: str
11
+ category: str
12
+ severity: str
13
+ priority_rank: int
14
+ endpoint: str
15
+ source_file: str
16
+ line_start: int
17
+ line_end: int
18
+ code_snippet: str
19
+ attack_path_hops: List[str] = Field(default_factory=list)
20
+ runtime_exploit_proof: List[str] = Field(default_factory=list)
21
+ remediation_guidance: str
22
+ reproduction_steps: List[str] = Field(default_factory=list)
23
+ verification_criteria: str
24
+ raw_observation: Optional[Dict[str, Any]] = None
25
+
26
+
27
+ class PatchResult(BaseModel):
28
+ """Result of applying a code modification via the Agent Harness."""
29
+ finding_id: str
30
+ target_file: str
31
+ success: bool
32
+ backup_file: Optional[str] = None
33
+ diff: Optional[str] = None
34
+ error: Optional[str] = None
35
+
36
+
37
+ class HarnessExecutionReport(BaseModel):
38
+ """Outcome of a complete Agent Harness remediation or benchmark run."""
39
+ project_name: str
40
+ baseline_score: int
41
+ final_score: int
42
+ score_delta: int
43
+ total_findings: int
44
+ fixed_findings: int
45
+ remaining_findings: int
46
+ fix_rate_percent: float
47
+ verified_patches: List[Dict[str, Any]] = Field(default_factory=list)
48
+ unresolved_findings: List[str] = Field(default_factory=list)
49
+ execution_time_seconds: float
@@ -0,0 +1,187 @@
1
+ """Core Agent Harness execution engine for autonomous security auditing and self-healing."""
2
+
3
+ import time
4
+ from pathlib import Path
5
+ from typing import List, Dict, Any, Optional, Tuple
6
+
7
+ from trace_engine.findings.model import Finding, Severity
8
+ from trace_engine.findings.store import FindingStore
9
+ from trace_engine.verify import VerificationEngine, VerificationResult, VerificationStatus
10
+ from trace_engine.harness.context import (
11
+ AgentRemediationPackage,
12
+ PatchResult,
13
+ HarnessExecutionReport,
14
+ )
15
+ from trace_engine.harness.patcher import SafePatcher
16
+ from trace_engine.harness.remediators import AutonomousRemediator
17
+
18
+
19
+ class AgentHarness:
20
+ """The TRACE Agent Harness provides an autonomous execution and verification environment
21
+
22
+ for AI coding models to inspect, patch, and mathematically prove security remediations.
23
+ """
24
+
25
+ def __init__(self, repo_path: Path, target_url: str = "http://127.0.0.1:18080"):
26
+ self.repo_path = repo_path.resolve()
27
+ self.target_url = target_url
28
+ self.trace_dir = self.repo_path / ".trace"
29
+ self.store = FindingStore(self.trace_dir)
30
+ self.patcher = SafePatcher(self.repo_path)
31
+ self.verifier = VerificationEngine(self.repo_path, target_url=target_url)
32
+
33
+ def get_findings(self) -> List[Finding]:
34
+ """Load currently stored findings ordered strictly by priority."""
35
+ raw_findings = self.store.load_findings()
36
+ severity_order = {
37
+ Severity.CRITICAL: 0,
38
+ Severity.HIGH: 1,
39
+ Severity.MEDIUM: 2,
40
+ Severity.LOW: 3,
41
+ Severity.INFO: 4,
42
+ }
43
+ return sorted(raw_findings, key=lambda f: severity_order.get(f.severity, 5))
44
+
45
+ def calculate_posture_score(self, findings: List[Finding]) -> int:
46
+ """Calculates 0-100 security health posture score."""
47
+ crit = sum(1 for f in findings if f.severity == Severity.CRITICAL)
48
+ high = sum(1 for f in findings if f.severity == Severity.HIGH)
49
+ med = sum(1 for f in findings if f.severity == Severity.MEDIUM)
50
+ return max(0, 100 - (crit * 20 + high * 10 + med * 3))
51
+
52
+ def build_remediation_package(self, finding: Finding, rank: int = 1) -> AgentRemediationPackage:
53
+ """Packages rich context, surrounding AST lines, and exploit proof for an AI agent."""
54
+ file_path = self.repo_path / finding.source_location.file
55
+ if not file_path.exists():
56
+ for sub in self.repo_path.iterdir():
57
+ if sub.is_dir() and (sub / finding.source_location.file).exists():
58
+ file_path = sub / finding.source_location.file
59
+ break
60
+
61
+ code_snippet = ""
62
+ if file_path.exists():
63
+ lines = file_path.read_text(encoding="utf-8", errors="replace").splitlines()
64
+ start = max(0, finding.source_location.line_start - 5)
65
+ end = min(len(lines), finding.source_location.line_end + 10)
66
+ code_snippet = "\n".join(lines[start:end])
67
+
68
+ return AgentRemediationPackage(
69
+ finding_id=finding.id,
70
+ title=finding.title,
71
+ category=finding.category,
72
+ severity=finding.severity.value,
73
+ priority_rank=rank,
74
+ endpoint=finding.endpoint,
75
+ source_file=finding.source_location.file,
76
+ line_start=finding.source_location.line_start,
77
+ line_end=finding.source_location.line_end,
78
+ code_snippet=code_snippet,
79
+ attack_path_hops=finding.attack_path,
80
+ runtime_exploit_proof=finding.runtime_evidence,
81
+ remediation_guidance=finding.remediation,
82
+ reproduction_steps=finding.reproduction_steps,
83
+ verification_criteria=(
84
+ f"Neutralize exploit so re-executing {finding.endpoint} receives HTTP 401/403 "
85
+ "or validates tenant/object authorization boundaries."
86
+ ),
87
+ )
88
+
89
+ def get_next_target(self) -> Optional[AgentRemediationPackage]:
90
+ """Returns the highest priority unmitigated finding packaged for agent consumption."""
91
+ findings = self.get_findings()
92
+ if not findings:
93
+ return None
94
+ return self.build_remediation_package(findings[0], rank=1)
95
+
96
+ def apply_patch(
97
+ self,
98
+ finding_id: str,
99
+ rel_path: str,
100
+ target_content: str,
101
+ replacement_content: str,
102
+ ) -> PatchResult:
103
+ """Applies patch with transactional backup."""
104
+ return self.patcher.apply_replacement(
105
+ finding_id=finding_id,
106
+ rel_path=rel_path,
107
+ target_content=target_content,
108
+ replacement_content=replacement_content,
109
+ )
110
+
111
+ def verify_patch(self, finding_id: str) -> VerificationResult:
112
+ """Verifies patch via AST re-indexing and live runtime test pack replay."""
113
+ return self.verifier.verify(finding_id)
114
+
115
+ def run_self_healing_loop(self) -> HarnessExecutionReport:
116
+ """Autonomous self-healing execution loop:
117
+
118
+ Iterates through prioritized findings, synthesizes defensive patches,
119
+ applies them transactionally, and verifies runtime neutralization.
120
+ """
121
+ t0 = time.perf_counter()
122
+ initial_findings = self.get_findings()
123
+ baseline_score = self.calculate_posture_score(initial_findings)
124
+
125
+ verified_patches = []
126
+ unresolved = []
127
+
128
+ for idx, finding in enumerate(initial_findings, 1):
129
+ pkg = self.build_remediation_package(finding, rank=idx)
130
+ synth_patch = AutonomousRemediator.generate_patch(pkg)
131
+
132
+ if not synth_patch:
133
+ unresolved.append(finding.id)
134
+ continue
135
+
136
+ target_block, replacement_block = synth_patch
137
+ patch_res = self.apply_patch(
138
+ finding_id=finding.id,
139
+ rel_path=finding.source_location.file,
140
+ target_content=target_block,
141
+ replacement_content=replacement_block,
142
+ )
143
+
144
+ if not patch_res.success:
145
+ unresolved.append(finding.id)
146
+ continue
147
+
148
+ # Verify differential impact with verification oracle
149
+ v_res = self.verify_patch(finding.id)
150
+ if v_res.status == VerificationStatus.FIXED:
151
+ verified_patches.append({
152
+ "finding_id": finding.id,
153
+ "title": finding.title,
154
+ "file": finding.source_location.file,
155
+ "diff": patch_res.diff,
156
+ "verification": v_res.model_dump(),
157
+ })
158
+ else:
159
+ # Verification failed or broke runtime tests: cleanly roll back patch
160
+ if patch_res.backup_file:
161
+ self.patcher.rollback(finding.source_location.file, Path(patch_res.backup_file))
162
+ unresolved.append(finding.id)
163
+
164
+ remaining_count = len(unresolved)
165
+ fixed_count = len(verified_patches)
166
+ total_count = len(initial_findings)
167
+
168
+ # New score calculation based on neutralized findings
169
+ final_score = min(100, baseline_score + (fixed_count * 15))
170
+ if fixed_count == total_count and total_count > 0:
171
+ final_score = 100
172
+
173
+ elapsed = time.perf_counter() - t0
174
+
175
+ return HarnessExecutionReport(
176
+ project_name=self.repo_path.name,
177
+ baseline_score=baseline_score,
178
+ final_score=final_score,
179
+ score_delta=final_score - baseline_score,
180
+ total_findings=total_count,
181
+ fixed_findings=fixed_count,
182
+ remaining_findings=remaining_count,
183
+ fix_rate_percent=round((fixed_count / total_count * 100), 1) if total_count else 100.0,
184
+ verified_patches=verified_patches,
185
+ unresolved_findings=unresolved,
186
+ execution_time_seconds=round(elapsed, 2),
187
+ )
@@ -0,0 +1,143 @@
1
+ """Safe code patching and rollback engine for the TRACE Agent Harness."""
2
+
3
+ import difflib
4
+ import shutil
5
+ import time
6
+ from pathlib import Path
7
+ from typing import Optional, Tuple
8
+ from trace_engine.harness.context import PatchResult
9
+
10
+
11
+ class SafePatcher:
12
+ """Manages transactional file edits, backups, and rollback for AI coding agents."""
13
+
14
+ def __init__(self, workspace_root: Path):
15
+ self.workspace_root = workspace_root.resolve()
16
+ self.backup_dir = self.workspace_root / ".trace" / "backups"
17
+ self.backup_dir.mkdir(parents=True, exist_ok=True)
18
+
19
+ def backup(self, rel_path: str) -> Path:
20
+ """Create timestamped backup of the target file."""
21
+ target = self.workspace_root / rel_path
22
+ if not target.exists():
23
+ raise FileNotFoundError(f"Target file {rel_path} does not exist in workspace.")
24
+
25
+ timestamp = int(time.time() * 1000)
26
+ safe_name = rel_path.replace("/", "_").replace("\\", "_")
27
+ backup_path = self.backup_dir / f"{safe_name}.{timestamp}.bak"
28
+ shutil.copy2(target, backup_path)
29
+ return backup_path
30
+
31
+ def apply_replacement(
32
+ self,
33
+ finding_id: str,
34
+ rel_path: str,
35
+ target_content: str,
36
+ replacement_content: str,
37
+ ) -> PatchResult:
38
+ """Replace target string block with replacement content, creating a rollback backup."""
39
+ file_path = self.workspace_root / rel_path
40
+ if not file_path.exists():
41
+ return PatchResult(
42
+ finding_id=finding_id,
43
+ target_file=rel_path,
44
+ success=False,
45
+ error=f"File not found: {rel_path}",
46
+ )
47
+
48
+ try:
49
+ original = file_path.read_text(encoding="utf-8")
50
+ if target_content not in original:
51
+ return PatchResult(
52
+ finding_id=finding_id,
53
+ target_file=rel_path,
54
+ success=False,
55
+ error="Target content block not found in file.",
56
+ )
57
+
58
+ backup_file = self.backup(rel_path)
59
+ new_content = original.replace(target_content, replacement_content, 1)
60
+ file_path.write_text(new_content, encoding="utf-8")
61
+
62
+ # Generate unified diff
63
+ diff_lines = list(
64
+ difflib.unified_diff(
65
+ original.splitlines(keepends=True),
66
+ new_content.splitlines(keepends=True),
67
+ fromfile=f"a/{rel_path}",
68
+ tofile=f"b/{rel_path}",
69
+ )
70
+ )
71
+ diff_str = "".join(diff_lines)
72
+
73
+ return PatchResult(
74
+ finding_id=finding_id,
75
+ target_file=rel_path,
76
+ success=True,
77
+ backup_file=str(backup_file),
78
+ diff=diff_str,
79
+ )
80
+ except Exception as e:
81
+ return PatchResult(
82
+ finding_id=finding_id,
83
+ target_file=rel_path,
84
+ success=False,
85
+ error=str(e),
86
+ )
87
+
88
+ def apply_full_content(
89
+ self,
90
+ finding_id: str,
91
+ rel_path: str,
92
+ new_content: str,
93
+ ) -> PatchResult:
94
+ """Write entire new file content, creating a rollback backup."""
95
+ file_path = self.workspace_root / rel_path
96
+ if not file_path.exists():
97
+ return PatchResult(
98
+ finding_id=finding_id,
99
+ target_file=rel_path,
100
+ success=False,
101
+ error=f"File not found: {rel_path}",
102
+ )
103
+
104
+ try:
105
+ original = file_path.read_text(encoding="utf-8")
106
+ backup_file = self.backup(rel_path)
107
+ file_path.write_text(new_content, encoding="utf-8")
108
+
109
+ diff_lines = list(
110
+ difflib.unified_diff(
111
+ original.splitlines(keepends=True),
112
+ new_content.splitlines(keepends=True),
113
+ fromfile=f"a/{rel_path}",
114
+ tofile=f"b/{rel_path}",
115
+ )
116
+ )
117
+ return PatchResult(
118
+ finding_id=finding_id,
119
+ target_file=rel_path,
120
+ success=True,
121
+ backup_file=str(backup_file),
122
+ diff="".join(diff_lines),
123
+ )
124
+ except Exception as e:
125
+ return PatchResult(
126
+ finding_id=finding_id,
127
+ target_file=rel_path,
128
+ success=False,
129
+ error=str(e),
130
+ )
131
+
132
+ def rollback(self, rel_path: str, backup_path: Path) -> bool:
133
+ """Restore file from backup."""
134
+ target = self.workspace_root / rel_path
135
+ if backup_path.exists():
136
+ shutil.copy2(backup_path, target)
137
+ return True
138
+ return False
139
+
140
+
141
+ TransactionalPatcher = SafePatcher
142
+ PatchOperationResult = PatchResult
143
+