@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,24 @@
1
+ {
2
+ "trace-security-guard": {
3
+ "enabled": true,
4
+ "PostToolUse": [
5
+ {
6
+ "matcher": "replace_file_content",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "trace status",
11
+ "timeout": 15
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "Stop": [
17
+ {
18
+ "type": "command",
19
+ "command": "trace findings",
20
+ "timeout": 20
21
+ }
22
+ ]
23
+ }
24
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "trace-security": {
4
+ "command": "python",
5
+ "args": ["-m", "trace_engine.mcp.server"],
6
+ "env": {
7
+ "PYTHONUNBUFFERED": "1"
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "trace-security",
3
+ "version": "2.0.0",
4
+ "description": "TRACE Agent Harness Plugin - Equips autonomous coding agents with APM attack graphs, live exploit verification, and benchmark self-healing oracles.",
5
+ "author": {
6
+ "name": "TRACE Security Engineering Team",
7
+ "email": "security@trace.dev"
8
+ },
9
+ "license": "Apache-2.0",
10
+ "keywords": [
11
+ "security",
12
+ "agent-harness",
13
+ "swe-bench",
14
+ "owasp",
15
+ "mcp",
16
+ "verification",
17
+ "apm",
18
+ "self-healing"
19
+ ]
20
+ }
@@ -0,0 +1,40 @@
1
+ ---
2
+ trigger: always_on
3
+ description: Mandatory security remediation rules for autonomous coding agents.
4
+ ---
5
+
6
+ # TRACE Autonomous Security Remediation Guidelines
7
+
8
+ When an AI coding agent is tasked with fixing vulnerabilities reported by TRACE or running a TRACE-Bench task, the following standards are strictly mandatory:
9
+
10
+ ## 1. Zero-Dummy-Bypass Rule
11
+ - **FORBIDDEN**: Never resolve a vulnerability by hardcoding boolean flags (`if (true) return 200`), returning mock empty responses, commenting out security filters, or bypassing authentication handlers.
12
+ - **MANDATORY**: Fixes must genuinely enforce the intended business and security invariants in code (e.g. comparing session tenant identity against record tenant identity).
13
+
14
+ ## 2. Broken Object-Level Authorization (BOLA / IDOR)
15
+ - When fetching, updating, or deleting any domain entity (`Record`, `User`, `Order`, `Document`) by identifier:
16
+ 1. Extract the authenticated user's ID and tenant/organization identifier from the verified security context (`Authentication`, `SecurityContextHolder`, or JWT claims).
17
+ 2. Scope the database query or in-memory retrieval to the authenticated tenant (e.g. `findByRecordIdAndTenantId(...)`).
18
+ 3. If the resource does not belong to the caller, return HTTP `403 Forbidden` or `404 Not Found`.
19
+
20
+ ## 3. Broken Function-Level Authorization (BFLA)
21
+ - Privileged endpoints (e.g. administrative exports, role elevation, tenant configuration, metric resets) must explicitly verify user roles:
22
+ 1. Enforce RBAC checks (e.g. `@PreAuthorize("hasRole('ADMIN')")`, `authContext.requireRole("admin")`).
23
+ 2. Deny requests with HTTP `403 Forbidden` for standard or unprivileged users.
24
+
25
+ ## 4. Server-Side Request Forgery (SSRF)
26
+ - When accepting external URLs or callbacks (`callbackUrl`, `webhookUrl`, `targetUri`):
27
+ 1. Validate the URL scheme is strictly `https` (or `http` for allowed domains).
28
+ 2. Reject all loopback, link-local, and private RFC-1918 addresses (`127.0.0.1`, `localhost`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.169.254`).
29
+ 3. Resolve DNS names and check resolved IP addresses against private subnet blocklists before dispatching outbound HTTP calls.
30
+
31
+ ## 5. Broken Authentication & Session Management
32
+ - Never expose unauthenticated endpoints that execute state-changing actions.
33
+ - Ensure all API endpoints under `/api/**` or `/admin/**` participate in the authentication barrier unless explicitly marked public.
34
+
35
+ ## 6. Mass Assignment & Parameter Injection
36
+ - Do not bind untrusted HTTP request bodies directly into persistent domain entities or ORM models.
37
+ - Use explicit DTOs (Data Transfer Objects) with allowlisted fields.
38
+
39
+ ## 7. Verification Requirement
40
+ - After applying any code change, execute the TRACE verification oracle (`trace_verify` or `trace verify <finding_id>`) to prove that the exploit vector is blocked and no regressions were introduced.
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: trace-security-harness
3
+ description: >-
4
+ Standardized runbook and tool guide for autonomous coding agents operating with the TRACE
5
+ Agent Harness Plugin. Guides agents through finding discovery, Attack-Path Model inspection,
6
+ safe surgical remediation, and live verification.
7
+ ---
8
+
9
+ # TRACE Security Agent Harness Runbook
10
+
11
+ The TRACE Agent Harness Plugin equips you with tools to autonomously audit codebases, investigate correlated vulnerability findings, inspect Attack-Path Models (APM), apply surgical security fixes, and verify solutions against live exploit tests.
12
+
13
+ ---
14
+
15
+ ## Autonomous Workflow for Security Remediation
16
+
17
+ Follow these 5 distinct phases when resolving security vulnerabilities:
18
+
19
+ ```
20
+ [Phase 1: Ingest] ➔ [Phase 2: Diagnose] ➔ [Phase 3: Plan Fix] ➔ [Phase 4: Patch] ➔ [Phase 5: Verify]
21
+ ```
22
+
23
+ ### Phase 1: Finding & Task Ingestion
24
+ Query the TRACE findings store or benchmark task queue to identify active vulnerabilities:
25
+
26
+ 1. **List all findings:**
27
+ - MCP Tool: `trace_findings`
28
+ - CLI: `trace findings`
29
+ 2. **Inspect a specific benchmark task:**
30
+ - MCP Tool: `trace_harness_task` with `task_id` or `finding_id`
31
+ - CLI: `trace plugin task <task_id>`
32
+
33
+ Each finding has:
34
+ - `id`: Unique identifier (e.g. `TR-BOLA-001`, `TR-SSRF-003`)
35
+ - `category`: OWASP API Security category (e.g. BOLA, BFLA, SSRF)
36
+ - `severity`: CRITICAL, HIGH, MEDIUM, LOW
37
+ - `endpoint`: The affected HTTP endpoint
38
+ - `source_location`: File path and line numbers
39
+
40
+ ---
41
+
42
+ ### Phase 2: Root-Cause & Attack-Path Diagnosis
43
+ Never guess the fix. Examine the complete correlation evidence and Attack-Path Model (APM) hops:
44
+
45
+ 1. **Retrieve explanation and reproduction steps:**
46
+ - MCP Tool: `trace_explain` with `finding_id: "TR-BOLA-001"`
47
+ - CLI: `trace explain TR-BOLA-001`
48
+ 2. **Inspect the APM graph hops:**
49
+ - MCP Tool: `trace_attack_path` with `endpoint: "/api/records/{id}"`
50
+
51
+ Key items to inspect in the explanation:
52
+ - `attack_path`: Sequence of hops from public HTTP entrypoint to vulnerable sink.
53
+ - `static_evidence`: Code AST patterns that triggered the hypothesis.
54
+ - `runtime_evidence`: Concrete HTTP request/response proofs demonstrating unauthorized access.
55
+ - `remediation`: Architectural guidance on how the endpoint should be secured.
56
+
57
+ ---
58
+
59
+ ### Phase 3: Surgical Patch Planning
60
+ Formulate a patch that enforces the invariant without modifying unrelated behavior:
61
+
62
+ #### BOLA (Broken Object-Level Authorization)
63
+ - **Problem**: Entity retrieved by ID without checking if caller owns the entity or belongs to the same tenant.
64
+ - **Fix**:
65
+ ```kotlin
66
+ // BEFORE (Vulnerable)
67
+ val record = recordRepository.findById(id)
68
+ return ResponseEntity.ok(record)
69
+
70
+ // AFTER (Secure)
71
+ val currentUser = SecurityContext.getCurrentUser()
72
+ val record = recordRepository.findByIdAndTenantId(id, currentUser.tenantId)
73
+ ?: return ResponseEntity.status(403).body("Access denied")
74
+ return ResponseEntity.ok(record)
75
+ ```
76
+
77
+ #### BFLA (Broken Function-Level Authorization)
78
+ - **Problem**: Privileged/administrative endpoint accessible by regular authenticated users.
79
+ - **Fix**:
80
+ ```kotlin
81
+ // Ensure role enforcement is present
82
+ val user = SecurityContext.getCurrentUser()
83
+ if (!user.roles.contains("ADMIN")) {
84
+ return ResponseEntity.status(403).body("Administrator role required")
85
+ }
86
+ ```
87
+
88
+ #### SSRF (Server-Side Request Forgery)
89
+ - **Problem**: Unvalidated user-supplied URL dispatched to internal network or cloud metadata.
90
+ - **Fix**:
91
+ ```kotlin
92
+ // Validate destination before dispatch
93
+ val uri = URI(callbackUrl)
94
+ val host = uri.host.lowercase()
95
+ if (host == "localhost" || host == "127.0.0.1" || host.startsWith("169.254.") || host.startsWith("10.") || host.startsWith("192.168.")) {
96
+ return ResponseEntity.status(403).body("Outbound request to internal IP blocked")
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ### Phase 4: Patch Application
103
+ Apply the modification using standard file editing tools (`replace_file_content` or `write_to_file`).
104
+ - Ensure no syntax or compilation errors.
105
+ - Keep modifications minimal and idiomatic.
106
+
107
+ ---
108
+
109
+ ### Phase 5: Deterministic Verification
110
+ Verify that the exploit vector is blocked and the endpoint remains healthy:
111
+
112
+ 1. **Verify with TRACE Verification Oracle:**
113
+ - MCP Tool: `trace_verify` with `finding_id`
114
+ - CLI: `trace verify <finding_id>`
115
+ 2. **Evaluate Patch Quality:**
116
+ - MCP Tool: `trace_eval_patch`
117
+
118
+ The verification oracle will execute the active exploit test pack against the running target. A status of `RESOLVED` with `runtime_check_passed: true` confirms the fix is sound.
@@ -0,0 +1,105 @@
1
+ """Evaluation oracle for the TRACE Agent Harness Plugin."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional, List
6
+ from pydantic import BaseModel, Field
7
+
8
+ from trace_engine.verify import VerificationEngine, VerificationResult
9
+ from trace_engine.harness.patcher import TransactionalPatcher, PatchOperationResult
10
+
11
+ logger = logging.getLogger("trace_plugin_evaluator")
12
+
13
+
14
+ class EvaluationResult(BaseModel):
15
+ """Evaluation result for an agent-generated patch."""
16
+ task_id: str
17
+ finding_id: str
18
+ passed: bool
19
+ verified_resolved: bool
20
+ reproduced_exploit_blocked: bool
21
+ regressions_detected: bool
22
+ confidence_score: float
23
+ output_message: str
24
+ verification_details: Dict[str, Any] = Field(default_factory=dict)
25
+ patch_applied: bool = False
26
+
27
+
28
+ class HarnessEvaluator:
29
+ """Evaluates patches produced by external AI agent harnesses against TRACE ground-truth test packs."""
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.patcher = TransactionalPatcher(self.repo_path)
35
+ self.verifier = VerificationEngine(repo_path=self.repo_path, target_url=self.target_url)
36
+
37
+ def evaluate_patch(
38
+ self,
39
+ finding_id: str,
40
+ patch_content: Optional[str] = None,
41
+ target_file: Optional[str] = None,
42
+ rollback_after: bool = False,
43
+ ) -> EvaluationResult:
44
+ """Applies an agent patch, runs the TRACE verification oracle, and returns grading results."""
45
+ patch_res: Optional[PatchOperationResult] = None
46
+
47
+ if patch_content and target_file:
48
+ # External patch provided as raw code or diff
49
+ patch_res = self.patcher.apply_full_content(
50
+ finding_id=finding_id,
51
+ rel_path=target_file,
52
+ new_content=patch_content,
53
+ )
54
+ if not patch_res.success:
55
+ return EvaluationResult(
56
+ task_id=f"task_{finding_id}",
57
+ finding_id=finding_id,
58
+ passed=False,
59
+ verified_resolved=False,
60
+ reproduced_exploit_blocked=False,
61
+ regressions_detected=False,
62
+ confidence_score=0.0,
63
+ output_message=f"Failed to apply patch: {patch_res.error}",
64
+ patch_applied=False,
65
+ )
66
+
67
+ # Run verification engine against live runtime / APM
68
+ try:
69
+ ver_res: VerificationResult = self.verifier.verify(finding_id)
70
+ is_resolved = (ver_res.status.value == "FIXED") or ver_res.remediation_verified
71
+ exploit_blocked = is_resolved
72
+
73
+ passed = is_resolved
74
+ msg = f"Verification {ver_res.status.value}: {ver_res.summary}"
75
+
76
+ result = EvaluationResult(
77
+ task_id=f"task_{finding_id}",
78
+ finding_id=finding_id,
79
+ passed=passed,
80
+ verified_resolved=is_resolved,
81
+ reproduced_exploit_blocked=exploit_blocked,
82
+ regressions_detected=False,
83
+ confidence_score=1.0 if passed else 0.0,
84
+ output_message=msg,
85
+ verification_details=ver_res.model_dump(),
86
+ patch_applied=patch_res.success if patch_res else True,
87
+ )
88
+ except Exception as e:
89
+ logger.exception("Error executing verification oracle")
90
+ result = EvaluationResult(
91
+ task_id=f"task_{finding_id}",
92
+ finding_id=finding_id,
93
+ passed=False,
94
+ verified_resolved=False,
95
+ reproduced_exploit_blocked=False,
96
+ regressions_detected=False,
97
+ confidence_score=0.0,
98
+ output_message=f"Verification exception: {e}",
99
+ patch_applied=patch_res.success if patch_res else False,
100
+ )
101
+
102
+ if rollback_after and patch_res and patch_res.backup_file and target_file:
103
+ self.patcher.rollback(target_file, Path(patch_res.backup_file))
104
+
105
+ return result
@@ -0,0 +1,96 @@
1
+ """SWE-bench dataset adapter for the TRACE Agent Harness Plugin."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import List, Dict, Any
6
+
7
+ from trace_engine.findings.model import Finding
8
+ from trace_engine.plugin.task import HarnessTaskSpec
9
+
10
+
11
+ def build_harness_task(finding: Finding, repo_path: Path) -> HarnessTaskSpec:
12
+ """Transform a CorrelatedFinding into a standardized agent harness task specification."""
13
+ rel_path = str(finding.source_location.file) if finding.source_location else "unknown"
14
+ start_line = finding.source_location.line_start if finding.source_location else 1
15
+ end_line = finding.source_location.line_end if finding.source_location else 1
16
+
17
+ code_snippet = ""
18
+ try:
19
+ full_file = repo_path / rel_path
20
+ if not full_file.is_file():
21
+ for sub in repo_path.iterdir():
22
+ if sub.is_dir() and (sub / rel_path).is_file():
23
+ full_file = sub / rel_path
24
+ break
25
+
26
+ if full_file.is_file():
27
+ lines = full_file.read_text(encoding="utf-8", errors="replace").splitlines()
28
+ sl = max(0, start_line - 1)
29
+ el = min(len(lines), end_line + 5)
30
+ code_snippet = "\n".join(lines[sl:el])
31
+ except Exception:
32
+ code_snippet = ""
33
+
34
+ static_ev = "\n".join(f"- `{ev}`" for ev in (finding.static_evidence or []))
35
+
36
+ problem_stmt = (
37
+ f"# Security Vulnerability Remediation: {finding.title}\n\n"
38
+ f"**Category:** {finding.category}\n"
39
+ f"**Severity:** {finding.severity.value}\n"
40
+ f"**Target Endpoint:** `{finding.endpoint}`\n"
41
+ f"**Vulnerable Source:** `{rel_path}:{start_line}`\n\n"
42
+ f"## Vulnerability Description\n"
43
+ f"{finding.correlation_notes or finding.title}\n\n"
44
+ f"## Static Analysis Evidence\n"
45
+ f"{static_ev or 'None'}\n\n"
46
+ f"## Attack Path\n"
47
+ + "\n".join(f"- {hop}" for hop in (finding.attack_path or [])) + "\n\n"
48
+ f"## Live Exploit Proof\n"
49
+ + "\n".join(f"- `{proof}`" for proof in (finding.runtime_evidence or [])) + "\n\n"
50
+ f"## Objective\n"
51
+ f"Modify `{rel_path}` to fix the vulnerability without breaking the endpoint contract or returning static dummy responses."
52
+ )
53
+
54
+ hints = (
55
+ f"Recommended Remediation Architecture:\n"
56
+ f"{finding.remediation}\n\n"
57
+ f"Reproduction steps:\n"
58
+ + "\n".join(f"1. {step}" for step in (finding.reproduction_steps or []))
59
+ )
60
+
61
+ task_id = f"trace__{repo_path.name.lower()}-{finding.id}"
62
+
63
+ return HarnessTaskSpec(
64
+ instance_id=task_id,
65
+ finding_id=finding.id,
66
+ title=finding.title,
67
+ category=finding.category,
68
+ severity=finding.severity.value,
69
+ repo_name=repo_path.name,
70
+ endpoint=finding.endpoint,
71
+ source_file=rel_path,
72
+ line_start=start_line,
73
+ line_end=end_line,
74
+ code_snippet=code_snippet,
75
+ problem_statement=problem_stmt,
76
+ hints_text=hints,
77
+ attack_path_hops=finding.attack_path or [],
78
+ runtime_exploit_proof=finding.runtime_evidence or [],
79
+ reproduction_steps=finding.reproduction_steps or [],
80
+ fail_to_pass=[f"test_exploit_{finding.id}"],
81
+ pass_to_pass=["test_endpoint_functional_health"],
82
+ metadata={
83
+ "finding_id": finding.id,
84
+ "category": finding.category,
85
+ "severity": finding.severity.value,
86
+ },
87
+ )
88
+
89
+
90
+ def export_swebench_jsonl(tasks: List[HarnessTaskSpec], output_file: Path) -> int:
91
+ """Export tasks to a SWE-bench compatible JSONL benchmark dataset file."""
92
+ output_file.parent.mkdir(parents=True, exist_ok=True)
93
+ with output_file.open("w", encoding="utf-8") as f:
94
+ for t in tasks:
95
+ f.write(json.dumps(t.to_swebench_dict()) + "\n")
96
+ return len(tasks)
@@ -0,0 +1,48 @@
1
+ """Task and benchmark specifications for the TRACE Agent Harness Plugin."""
2
+
3
+ from typing import List, Dict, Any, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class HarnessTaskSpec(BaseModel):
8
+ """Standardized task instance specification for AI coding agent harnesses.
9
+
10
+ Compatible with SWE-bench, OpenHands, and Antigravity task schemas.
11
+ """
12
+ instance_id: str
13
+ finding_id: str
14
+ title: str
15
+ category: str
16
+ severity: str
17
+ repo_name: str
18
+ endpoint: str
19
+ source_file: str
20
+ line_start: int
21
+ line_end: int
22
+ code_snippet: str
23
+ problem_statement: str
24
+ hints_text: str
25
+ attack_path_hops: List[str] = Field(default_factory=list)
26
+ runtime_exploit_proof: List[str] = Field(default_factory=list)
27
+ reproduction_steps: List[str] = Field(default_factory=list)
28
+ fail_to_pass: List[str] = Field(default_factory=list)
29
+ pass_to_pass: List[str] = Field(default_factory=list)
30
+ metadata: Dict[str, Any] = Field(default_factory=dict)
31
+
32
+ def to_swebench_dict(self) -> Dict[str, Any]:
33
+ """Convert this task instance into a standard SWE-bench JSON object."""
34
+ return {
35
+ "instance_id": self.instance_id,
36
+ "repo": self.repo_name,
37
+ "base_commit": self.metadata.get("base_commit", "HEAD"),
38
+ "problem_statement": self.problem_statement,
39
+ "hints_text": self.hints_text,
40
+ "created_at": self.metadata.get("created_at", "2026-10-03T00:00:00Z"),
41
+ "version": "1.0",
42
+ "FAIL_TO_PASS": self.fail_to_pass,
43
+ "PASS_TO_PASS": self.pass_to_pass,
44
+ "environment_setup_commit": self.metadata.get("environment_setup_commit", "HEAD"),
45
+ "target_endpoint": self.endpoint,
46
+ "source_location": f"{self.source_file}:{self.line_start}-{self.line_end}",
47
+ "vulnerability_category": self.category,
48
+ }
@@ -0,0 +1,5 @@
1
+ """Target policy and scope enforcement."""
2
+
3
+ from trace_engine.policy.scope import ScopeGuard, ScopeViolationError
4
+
5
+ __all__ = ["ScopeGuard", "ScopeViolationError"]
@@ -0,0 +1,80 @@
1
+ """Scope Guard for enforcing local-only and lab-authorized boundaries."""
2
+
3
+ from urllib.parse import urlparse
4
+ from typing import List, Set
5
+ import ipaddress
6
+
7
+
8
+ class ScopeViolationError(Exception):
9
+ """Raised when an action violates the defined target scope."""
10
+ pass
11
+
12
+
13
+ class ScopeGuard:
14
+ """Enforces strict boundaries to ensure tests only execute against approved local/lab targets."""
15
+
16
+ def __init__(
17
+ self,
18
+ allowed_hosts: List[str] = None,
19
+ allowed_ports: List[int] = None,
20
+ mode: str = "local",
21
+ ):
22
+ self.mode = mode
23
+ self.allowed_hosts: Set[str] = set(
24
+ allowed_hosts or ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
25
+ )
26
+ self.allowed_ports: Set[int] = set(
27
+ allowed_ports or [80, 443, 3000, 5000, 8000, 8080, 18080]
28
+ )
29
+
30
+ def validate_url(self, url: str) -> bool:
31
+ """Validates that a URL is strictly within the allowed target scope."""
32
+ parsed = urlparse(url)
33
+ if parsed.scheme not in ("http", "https"):
34
+ raise ScopeViolationError(f"Unsupported URL scheme: {parsed.scheme}")
35
+
36
+ hostname = parsed.hostname or ""
37
+ if not hostname:
38
+ raise ScopeViolationError(f"Invalid URL host: {url}")
39
+
40
+ # Check hostname directly
41
+ if hostname.lower() in self.allowed_hosts:
42
+ pass
43
+ else:
44
+ # Check if it is a private IP in private-lab mode
45
+ try:
46
+ ip = ipaddress.ip_address(hostname)
47
+ if ip.is_loopback or (self.mode == "private-lab" and ip.is_private):
48
+ pass
49
+ else:
50
+ raise ScopeViolationError(
51
+ f"Target host '{hostname}' is not within local/private scope. External network requests are blocked by TRACE ScopeGuard."
52
+ )
53
+ except ValueError:
54
+ # Hostname is not an IP address and not in allowed hosts
55
+ raise ScopeViolationError(
56
+ f"Target host '{hostname}' is blocked by TRACE ScopeGuard. Only approved local targets are permitted."
57
+ )
58
+
59
+ try:
60
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
61
+ except ValueError as e:
62
+ raise ScopeViolationError(f"Invalid URL port: {e}")
63
+
64
+ is_loopback_host = hostname.lower() in ("localhost", "127.0.0.1", "0.0.0.0", "::1")
65
+ if not is_loopback_host and port not in self.allowed_ports:
66
+ raise ScopeViolationError(
67
+ f"Target port {port} is not in allowed ports list: {sorted(list(self.allowed_ports))}"
68
+ )
69
+ if not (1 <= port <= 65535):
70
+ raise ScopeViolationError(f"Invalid TCP port: {port}")
71
+
72
+ return True
73
+
74
+ def allow_target(self, url: str) -> None:
75
+ """Explicitly add a target's host and port to allowed scope if it is local/private."""
76
+ parsed = urlparse(url)
77
+ if parsed.hostname:
78
+ self.allowed_hosts.add(parsed.hostname.lower())
79
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
80
+ self.allowed_ports.add(port)
@@ -0,0 +1,7 @@
1
+ """Runtime test execution module."""
2
+
3
+ from trace_engine.runtime.target import Target
4
+ from trace_engine.runtime.observations import RuntimeObservation
5
+ from trace_engine.runtime.client import ScopedHttpClient
6
+
7
+ __all__ = ["Target", "RuntimeObservation", "ScopedHttpClient"]
@@ -0,0 +1,81 @@
1
+ """Scoped runtime HTTP client for executing controlled security tests."""
2
+
3
+ import time
4
+ import httpx
5
+ from typing import Optional, Dict, Any
6
+ from trace_engine.policy.scope import ScopeGuard, ScopeViolationError
7
+ from trace_engine.runtime.observations import RuntimeObservation
8
+
9
+
10
+ class ScopedHttpClient:
11
+ """HTTP client bound to ScopeGuard that captures comprehensive runtime observations."""
12
+
13
+ def __init__(self, scope_guard: ScopeGuard, timeout_seconds: float = 8.0):
14
+ self.scope_guard = scope_guard
15
+ self.timeout = timeout_seconds
16
+
17
+ def execute(
18
+ self,
19
+ method: str,
20
+ url: str,
21
+ headers: Optional[Dict[str, str]] = None,
22
+ json_body: Optional[Any] = None,
23
+ data: Optional[Dict[str, Any]] = None,
24
+ ) -> RuntimeObservation:
25
+ """Executes request after scope validation and captures runtime observation."""
26
+ self.scope_guard.validate_url(url)
27
+
28
+ headers = headers or {}
29
+ req_body_str = None
30
+ if json_body is not None:
31
+ import json
32
+ req_body_str = json.dumps(json_body)
33
+ headers["Content-Type"] = "application/json"
34
+
35
+ start_time = time.perf_counter()
36
+ try:
37
+ with httpx.Client(
38
+ timeout=self.timeout,
39
+ follow_redirects=False, # Intercept redirects for scope check
40
+ ) as client:
41
+ resp = client.request(
42
+ method=method.upper(),
43
+ url=url,
44
+ headers=headers,
45
+ content=req_body_str,
46
+ data=data,
47
+ )
48
+ latency_ms = (time.perf_counter() - start_time) * 1000
49
+
50
+ # Check if redirect is safe
51
+ if resp.is_redirect and "location" in resp.headers:
52
+ redirect_target = resp.headers["location"]
53
+ import urllib.parse
54
+ resolved_redirect = urllib.parse.urljoin(url, redirect_target)
55
+ self.scope_guard.validate_url(resolved_redirect)
56
+
57
+ return RuntimeObservation(
58
+ request_method=method.upper(),
59
+ request_url=url,
60
+ request_headers=dict(headers),
61
+ request_body=req_body_str,
62
+ response_status=resp.status_code,
63
+ response_headers=dict(resp.headers),
64
+ response_body=resp.text[:2000], # preview up to 2KB
65
+ latency_ms=round(latency_ms, 2),
66
+ )
67
+ except ScopeViolationError:
68
+ raise
69
+ except Exception as e:
70
+ latency_ms = (time.perf_counter() - start_time) * 1000
71
+ return RuntimeObservation(
72
+ request_method=method.upper(),
73
+ request_url=url,
74
+ request_headers=dict(headers),
75
+ request_body=req_body_str,
76
+ response_status=0,
77
+ response_headers={},
78
+ response_body=f"Connection Error: {str(e)}",
79
+ latency_ms=round(latency_ms, 2),
80
+ observation_notes=[f"Execution exception: {e}"],
81
+ )