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,60 @@
1
+ """Authentication bypass and missing authentication test pack."""
2
+
3
+ import re
4
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
5
+ from trace_engine.security.hypotheses import SecurityHypothesis
6
+ from trace_engine.runtime.client import ScopedHttpClient
7
+
8
+
9
+ class AuthenticationTestPack(TestPack):
10
+ @property
11
+ def name(self) -> str:
12
+ return "authentication"
13
+
14
+ @property
15
+ def description(self) -> str:
16
+ return "Tests whether sensitive endpoints enforce authentication headers."
17
+
18
+ def execute(
19
+ self,
20
+ hypothesis: SecurityHypothesis,
21
+ client: ScopedHttpClient,
22
+ context: TestContext,
23
+ ) -> TestExecutionResult:
24
+ base_url = context.target_base_url.rstrip("/")
25
+ parts = hypothesis.endpoint_display.split(" ")
26
+ method = parts[0] if len(parts) > 1 else "GET"
27
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
28
+
29
+ # Replace any path param with dummy id
30
+ probe_path = re.sub(r"\{[a-zA-Z0-9_]+\}", "1", raw_path)
31
+ target_url = f"{base_url}{probe_path}"
32
+
33
+ # Send completely unauthenticated request
34
+ obs = client.execute(method=method, url=target_url, headers={})
35
+ observations = [obs]
36
+
37
+ confirmed = False
38
+ summary = "Authentication properly enforced (received 401/403)."
39
+ steps = []
40
+
41
+ if obs.response_status in (200, 201, 204):
42
+ confirmed = True
43
+ summary = (
44
+ f"Missing Authentication confirmed: Sensitive endpoint {raw_path} responded "
45
+ f"with {obs.response_status} to an unauthenticated request."
46
+ )
47
+ steps = [
48
+ f"Send unauthenticated {method} request to {target_url}",
49
+ f"Received HTTP {obs.response_status} rather than expected 401/403",
50
+ ]
51
+
52
+ return TestExecutionResult(
53
+ testpack_name=self.name,
54
+ hypothesis_id=hypothesis.id,
55
+ confirmed=confirmed,
56
+ confidence=0.90 if confirmed else 0.15,
57
+ summary=summary,
58
+ observations=observations,
59
+ reproduction_steps=steps,
60
+ )
@@ -0,0 +1,63 @@
1
+ """Base interfaces and context for TRACE test packs."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Dict, Any, Optional, List
5
+ from pydantic import BaseModel, Field
6
+ from trace_engine.runtime.client import ScopedHttpClient
7
+ from trace_engine.runtime.observations import RuntimeObservation
8
+ from trace_engine.security.hypotheses import SecurityHypothesis
9
+
10
+
11
+ class UserIdentity(BaseModel):
12
+ """Synthetic test identity credentials."""
13
+ username: str
14
+ password: str
15
+ token: Optional[str] = None
16
+ role: str = "user"
17
+
18
+
19
+ class TestContext(BaseModel):
20
+ """Context provided to a test pack execution."""
21
+ __test__ = False
22
+ target_base_url: str
23
+ identities: Dict[str, UserIdentity] = Field(default_factory=dict)
24
+ active_tokens: Dict[str, str] = Field(default_factory=dict)
25
+
26
+
27
+ class TestExecutionResult(BaseModel):
28
+ """Outcome of running a test pack against a hypothesis."""
29
+ __test__ = False
30
+ testpack_name: str
31
+ hypothesis_id: str
32
+ confirmed: bool
33
+ confidence: float
34
+ summary: str
35
+ observations: List[RuntimeObservation] = Field(default_factory=list)
36
+ reproduction_steps: List[str] = Field(default_factory=list)
37
+
38
+
39
+ class TestPack(ABC):
40
+ """Abstract base class for all security test packs."""
41
+ __test__ = False
42
+
43
+ @property
44
+ @abstractmethod
45
+ def name(self) -> str:
46
+ """Name of the test pack (e.g., 'bola', 'ssrf')."""
47
+ pass
48
+
49
+ @property
50
+ @abstractmethod
51
+ def description(self) -> str:
52
+ """Brief description of what this pack validates."""
53
+ pass
54
+
55
+ @abstractmethod
56
+ def execute(
57
+ self,
58
+ hypothesis: SecurityHypothesis,
59
+ client: ScopedHttpClient,
60
+ context: TestContext,
61
+ ) -> TestExecutionResult:
62
+ """Run the test pack against the given hypothesis using the scoped client."""
63
+ pass
@@ -0,0 +1,70 @@
1
+ """BFLA (Broken Function Level Authorization) test pack."""
2
+
3
+ import re
4
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
5
+ from trace_engine.security.hypotheses import SecurityHypothesis
6
+ from trace_engine.runtime.client import ScopedHttpClient
7
+
8
+
9
+ class BflaTestPack(TestPack):
10
+ @property
11
+ def name(self) -> str:
12
+ return "bfla"
13
+
14
+ @property
15
+ def description(self) -> str:
16
+ return "Validates whether administrative and privileged endpoints enforce proper RBAC."
17
+
18
+ def execute(
19
+ self,
20
+ hypothesis: SecurityHypothesis,
21
+ client: ScopedHttpClient,
22
+ context: TestContext,
23
+ ) -> TestExecutionResult:
24
+ base_url = context.target_base_url.rstrip("/")
25
+ parts = hypothesis.endpoint_display.split(" ")
26
+ method = parts[0] if len(parts) > 1 else "POST"
27
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
28
+
29
+ clean_path = re.sub(r"\{[a-zA-Z0-9_]+\}", "1", raw_path)
30
+ target_url = f"{base_url}{clean_path}"
31
+ token_user = context.active_tokens.get("user-a") or context.active_tokens.get("user-b", "")
32
+
33
+ observations = []
34
+ confirmed = False
35
+ summary = "No BFLA behavior observed."
36
+ steps = []
37
+
38
+ headers = {}
39
+ if token_user:
40
+ headers["Authorization"] = f"Bearer {token_user}"
41
+
42
+ # Probe with low-privileged token
43
+ test_payload = {"order_id": "order-101", "amount": 50, "reason": "test"}
44
+ obs = client.execute(
45
+ method=method, url=target_url, headers=headers, json_body=test_payload if method != "GET" else None
46
+ )
47
+ observations.append(obs)
48
+
49
+ # If HTTP 200 or 201 is returned rather than 403 Forbidden, privilege check is flawed
50
+ if obs.response_status in (200, 201):
51
+ confirmed = True
52
+ summary = (
53
+ f"BFLA confirmed: Privileged endpoint {raw_path} executed successfully "
54
+ f"under standard user identity without 403 Forbidden check."
55
+ )
56
+ steps = [
57
+ f"Authenticate as standard user 'user-a'",
58
+ f"Invoke privileged endpoint {method} {target_url}",
59
+ f"Received HTTP {obs.response_status} indicating unauthorized function execution",
60
+ ]
61
+
62
+ return TestExecutionResult(
63
+ testpack_name=self.name,
64
+ hypothesis_id=hypothesis.id,
65
+ confirmed=confirmed,
66
+ confidence=0.92 if confirmed else 0.20,
67
+ summary=summary,
68
+ observations=observations,
69
+ reproduction_steps=steps,
70
+ )
@@ -0,0 +1,94 @@
1
+ """BOLA (Broken Object Level Authorization) dual-tenant differential test pack."""
2
+
3
+ import re
4
+ from typing import List
5
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
6
+ from trace_engine.security.hypotheses import SecurityHypothesis
7
+ from trace_engine.runtime.client import ScopedHttpClient
8
+ from trace_engine.runtime.observations import RuntimeObservation
9
+
10
+
11
+ class BolaTestPack(TestPack):
12
+ @property
13
+ def name(self) -> str:
14
+ return "bola"
15
+
16
+ @property
17
+ def description(self) -> str:
18
+ return "Validates whether object lookups enforce tenant and ownership boundaries using dual-identity differential testing."
19
+
20
+ def execute(
21
+ self,
22
+ hypothesis: SecurityHypothesis,
23
+ client: ScopedHttpClient,
24
+ context: TestContext,
25
+ ) -> TestExecutionResult:
26
+ base_url = context.target_base_url.rstrip("/")
27
+ parts = hypothesis.endpoint_display.split(" ")
28
+ method = parts[0] if len(parts) > 1 else "GET"
29
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
30
+
31
+ test_ids = ["order-101", "101", "1", "order-102", "order-user-a", "2", "3", "user-1", "doc-1"]
32
+ observations: List[RuntimeObservation] = []
33
+ confirmed = False
34
+ summary = "No BOLA behavior observed."
35
+ steps: List[str] = []
36
+
37
+ token_a = context.active_tokens.get("user-a", "")
38
+ token_b = context.active_tokens.get("user-b", "")
39
+
40
+ for test_id in test_ids:
41
+ probe_path = re.sub(r"\{[a-zA-Z0-9_]+\}", test_id, raw_path)
42
+ target_url = f"{base_url}{probe_path}"
43
+
44
+ # Differential Phase 1: Request with User A context
45
+ headers_a = {"Authorization": f"Bearer {token_a}"} if token_a else {}
46
+ obs_a = client.execute(method=method, url=target_url, headers=headers_a)
47
+ observations.append(obs_a)
48
+
49
+ # Differential Phase 2: Request with User B context (or unauthenticated)
50
+ headers_b = {"Authorization": f"Bearer {token_b}"} if token_b else {}
51
+ obs_b = client.execute(method=method, url=target_url, headers=headers_b)
52
+ observations.append(obs_b)
53
+
54
+ # Case A: True dual-tenant differential match
55
+ if obs_a.response_status == 200 and obs_b.response_status == 200:
56
+ body_b_lower = obs_b.response_body.lower()
57
+ # If User B receives legitimate record data without authorization restriction
58
+ if any(k in body_b_lower for k in ("id", "user", "order", "item", "total", "data", "status")):
59
+ confirmed = True
60
+ summary = (
61
+ f"BOLA / IDOR (CWE-639) CONFIRMED via Dual-Tenant Differential Test: Object '{test_id}' on {raw_path} "
62
+ f"is accessible by both Identity A and Identity B without tenant isolation."
63
+ )
64
+ steps = [
65
+ f"Query {method} {target_url} using Tenant A credentials (HTTP {obs_a.response_status})",
66
+ f"Query identical resource {method} {target_url} using Tenant B credentials (HTTP {obs_b.response_status})",
67
+ "Observe cross-tenant data leak without HTTP 403 Forbidden or 404 Not Found scoping",
68
+ ]
69
+ break
70
+
71
+ # Case B: Single token / unauthenticated BOLA check
72
+ elif obs_b.response_status == 200:
73
+ body_b_lower = obs_b.response_body.lower()
74
+ if "user-a" in body_b_lower or "total" in body_b_lower or "items" in body_b_lower or "product" in body_b_lower:
75
+ confirmed = True
76
+ summary = (
77
+ f"BOLA confirmed: User 'user-b' accessed object '{test_id}' "
78
+ f"belonging to another identity on endpoint {raw_path} with status 200 OK."
79
+ )
80
+ steps = [
81
+ f"Send {method} {target_url} with user-b credentials",
82
+ "Observe HTTP status 200 OK returning private data of user-a",
83
+ ]
84
+ break
85
+
86
+ return TestExecutionResult(
87
+ testpack_name=self.name,
88
+ hypothesis_id=hypothesis.id,
89
+ confirmed=confirmed,
90
+ confidence=0.98 if confirmed else 0.20,
91
+ summary=summary,
92
+ observations=observations,
93
+ reproduction_steps=steps,
94
+ )
@@ -0,0 +1,85 @@
1
+ """CORS (Cross-Origin Resource Sharing) Misconfiguration test pack (CWE-942)."""
2
+
3
+ from typing import List, Dict, Any
4
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
5
+ from trace_engine.security.hypotheses import SecurityHypothesis
6
+ from trace_engine.runtime.client import ScopedHttpClient
7
+ from trace_engine.runtime.observations import RuntimeObservation
8
+
9
+ PROBE_ORIGINS = [
10
+ "https://attacker.evil.com",
11
+ "https://untrusted-cross-origin.net",
12
+ "null",
13
+ ]
14
+
15
+
16
+ class CorsTestPack(TestPack):
17
+ @property
18
+ def name(self) -> str:
19
+ return "cors"
20
+
21
+ @property
22
+ def description(self) -> str:
23
+ return "Validates whether arbitrary untrusted origins are reflected with credentials enabled."
24
+
25
+ def execute(
26
+ self,
27
+ hypothesis: SecurityHypothesis,
28
+ client: ScopedHttpClient,
29
+ context: TestContext,
30
+ ) -> TestExecutionResult:
31
+ base_url = context.target_base_url.rstrip("/")
32
+ parts = hypothesis.endpoint_display.split(" ")
33
+ method = parts[0] if len(parts) > 1 else "GET"
34
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
35
+
36
+ target_url = f"{base_url}{raw_path}"
37
+ observations: List[RuntimeObservation] = []
38
+ confirmed = False
39
+ summary = "No exploitable CORS misconfiguration detected."
40
+ steps: List[str] = []
41
+
42
+ for origin in PROBE_ORIGINS:
43
+ headers = {"Origin": origin}
44
+ obs = client.execute(method=method, url=target_url, headers=headers)
45
+ observations.append(obs)
46
+
47
+ resp_hdrs = {k.lower(): v for k, v in obs.response_headers.items()}
48
+ allow_origin = resp_hdrs.get("access-control-allow-origin", "")
49
+ allow_creds = resp_hdrs.get("access-control-allow-credentials", "").lower() == "true"
50
+
51
+ # 1. Arbitrary origin reflection with credentials
52
+ if allow_origin in (origin, "*") and allow_creds:
53
+ confirmed = True
54
+ summary = (
55
+ f"CORS Misconfiguration (CWE-942) CONFIRMED: Endpoint {raw_path} reflected untrusted origin "
56
+ f"'{origin}' with 'Access-Control-Allow-Credentials: true', enabling cross-origin authenticated data theft."
57
+ )
58
+ steps = [
59
+ f"Send {method} request to {target_url} with header Origin: {origin}",
60
+ f"Observe response headers containing Access-Control-Allow-Origin: {allow_origin} and Access-Control-Allow-Credentials: true",
61
+ ]
62
+ break
63
+
64
+ # 2. 'null' origin reflection with credentials
65
+ if origin == "null" and allow_origin == "null" and allow_creds:
66
+ confirmed = True
67
+ summary = (
68
+ f"CORS Misconfiguration (CWE-942) CONFIRMED: Endpoint {raw_path} trusts origin 'null' "
69
+ f"with credentials, exposing authenticated session to sandboxed iframe attacks."
70
+ )
71
+ steps = [
72
+ f"Send {method} request to {target_url} with header Origin: null",
73
+ "Observe response header Access-Control-Allow-Origin: null with credentials allowed",
74
+ ]
75
+ break
76
+
77
+ return TestExecutionResult(
78
+ testpack_name=self.name,
79
+ hypothesis_id=hypothesis.id,
80
+ confirmed=confirmed,
81
+ confidence=0.90 if confirmed else 0.1,
82
+ summary=summary,
83
+ observations=observations,
84
+ reproduction_steps=steps,
85
+ )
@@ -0,0 +1,86 @@
1
+ """Insecure Deserialization test pack (CWE-502)."""
2
+
3
+ from typing import List, Dict, Any
4
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
5
+ from trace_engine.security.hypotheses import SecurityHypothesis
6
+ from trace_engine.runtime.client import ScopedHttpClient
7
+ from trace_engine.runtime.observations import RuntimeObservation
8
+
9
+ DESER_PROBES = [
10
+ ("pickle_base64", "gASVBAAAAAAAAACMCXRyYWNlX2JveJSULg=="),
11
+ ("yaml_object", "!!python/object/apply:builtins.str [\"trace_probe\"]"),
12
+ ("java_serial", "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sBFlmeFAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAAAeA=="),
13
+ ]
14
+
15
+ DESER_ERROR_SIGNATURES = [
16
+ "unpicklingerror",
17
+ "pickle.unpicklingerror",
18
+ "cannot deserialize",
19
+ "invalidstreamheader",
20
+ "java.io.invalidclassexception",
21
+ "constructorerror",
22
+ "yaml.constructor",
23
+ "unsafe deserialization",
24
+ ]
25
+
26
+
27
+ class DeserializationTestPack(TestPack):
28
+ @property
29
+ def name(self) -> str:
30
+ return "deserialization"
31
+
32
+ @property
33
+ def description(self) -> str:
34
+ return "Validates whether endpoints accept untrusted serialized byte-streams or object definitions without schema validation."
35
+
36
+ def execute(
37
+ self,
38
+ hypothesis: SecurityHypothesis,
39
+ client: ScopedHttpClient,
40
+ context: TestContext,
41
+ ) -> TestExecutionResult:
42
+ base_url = context.target_base_url.rstrip("/")
43
+ parts = hypothesis.endpoint_display.split(" ")
44
+ method = parts[0] if len(parts) > 1 else "POST"
45
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
46
+
47
+ target_url = f"{base_url}{raw_path}"
48
+ token = context.active_tokens.get("user-a", "")
49
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
50
+
51
+ observations: List[RuntimeObservation] = []
52
+ confirmed = False
53
+ summary = "No Insecure Deserialization vulnerabilities detected."
54
+ steps: List[str] = []
55
+
56
+ for probe_type, payload in DESER_PROBES:
57
+ json_payload = {"data": payload, "object": payload, "state": payload}
58
+ obs = client.execute(method=method, url=target_url, headers=headers, json_body=json_payload)
59
+ observations.append(obs)
60
+
61
+ body_lower = obs.response_body.lower()
62
+ for sig in DESER_ERROR_SIGNATURES:
63
+ if sig in body_lower:
64
+ confirmed = True
65
+ summary = (
66
+ f"Insecure Deserialization (CWE-502) CONFIRMED: Endpoint {raw_path} attempted to deserialize "
67
+ f"untrusted object probe ({probe_type}) resulting in deserialization parser reaction: '{sig}'."
68
+ )
69
+ steps = [
70
+ f"Send {method} request to {target_url} containing serialized {probe_type} payload",
71
+ f"Observe deserialization runtime exception trace '{sig}' in server response",
72
+ ]
73
+ break
74
+
75
+ if confirmed:
76
+ break
77
+
78
+ return TestExecutionResult(
79
+ testpack_name=self.name,
80
+ hypothesis_id=hypothesis.id,
81
+ confirmed=confirmed,
82
+ confidence=0.90 if confirmed else 0.1,
83
+ summary=summary,
84
+ observations=observations,
85
+ reproduction_steps=steps,
86
+ )
@@ -0,0 +1,179 @@
1
+ """Injection indicator test pack with polymorphic SQLi and Command Injection mutations."""
2
+
3
+ import re
4
+ import urllib.parse
5
+ from typing import List
6
+ from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
7
+ from trace_engine.security.hypotheses import SecurityHypothesis
8
+ from trace_engine.runtime.client import ScopedHttpClient
9
+ from trace_engine.runtime.observations import RuntimeObservation
10
+
11
+ INJECTION_PROBES = [
12
+ # Classic SQLi Boolean
13
+ "' OR '1'='1",
14
+ "' OR 1=1--",
15
+ "admin'--",
16
+ # SQL Syntax breakers
17
+ "'",
18
+ "''",
19
+ "test'--",
20
+ "';--",
21
+ # Stacked queries / Command delimiters
22
+ "; cat /etc/passwd",
23
+ "| whoami",
24
+ "$(id)",
25
+ ]
26
+
27
+ SQL_ERROR_PATTERNS = [
28
+ "syntax error",
29
+ "sqlite3.operationalerror",
30
+ "unrecognized token",
31
+ "sql syntax",
32
+ "database error",
33
+ "pg_query",
34
+ "ora-01756",
35
+ "mysql_fetch",
36
+ "sqlstate",
37
+ "odbc sql server driver",
38
+ "pdoexception",
39
+ "unterminated quotation mark",
40
+ "unclosed quotation mark",
41
+ ]
42
+
43
+
44
+ class InjectionTestPack(TestPack):
45
+ @property
46
+ def name(self) -> str:
47
+ return "injection"
48
+
49
+ @property
50
+ def description(self) -> str:
51
+ return "Tests query, path, and body parameters for dynamic SQL/command injection vulnerabilities across mutated probe varieties."
52
+
53
+ def execute(
54
+ self,
55
+ hypothesis: SecurityHypothesis,
56
+ client: ScopedHttpClient,
57
+ context: TestContext,
58
+ ) -> TestExecutionResult:
59
+ base_url = context.target_base_url.rstrip("/")
60
+ parts = hypothesis.endpoint_display.split(" ")
61
+ method = parts[0] if len(parts) > 1 else "GET"
62
+ raw_path = parts[1] if len(parts) > 1 else parts[0]
63
+
64
+ clean_path = re.sub(r"\{[a-zA-Z0-9_]+\}", "1", raw_path)
65
+ token = context.active_tokens.get("user-a", "")
66
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
67
+
68
+ observations: List[RuntimeObservation] = []
69
+ confirmed = False
70
+ summary = "No dynamic injection signatures observed."
71
+ steps: List[str] = []
72
+
73
+ for probe in INJECTION_PROBES:
74
+ encoded_probe = urllib.parse.quote(probe)
75
+ sep = "&" if "?" in clean_path else "?"
76
+ probe_url = f"{base_url}{clean_path}{sep}q={encoded_probe}&query={encoded_probe}&search={encoded_probe}&id={encoded_probe}"
77
+
78
+ # 1. Query probe
79
+ obs = client.execute(method=method, url=probe_url, headers=headers)
80
+ observations.append(obs)
81
+
82
+ body_lower = obs.response_body.lower()
83
+ matched_error = next((pat for pat in SQL_ERROR_PATTERNS if pat in body_lower), None)
84
+
85
+ if matched_error or (obs.response_status == 500 and "error" in body_lower):
86
+ confirmed = True
87
+ detail = f"database error signature '{matched_error}'" if matched_error else "unhandled 500 server crash"
88
+ summary = (
89
+ f"Injection vulnerability confirmed: Probe '{probe}' on {raw_path} triggered "
90
+ f"{detail} indicating unparameterized query execution."
91
+ )
92
+ steps = [
93
+ f"Send request with probe: {probe_url}",
94
+ f"Observe server reaction: {detail}",
95
+ ]
96
+ break
97
+
98
+ # 2. Body probe for mutating requests
99
+ if method in ("POST", "PUT", "PATCH"):
100
+ body_probe = {"q": probe, "query": probe, "search": probe, "name": probe}
101
+ body_obs = client.execute(
102
+ method=method,
103
+ url=f"{base_url}{clean_path}",
104
+ headers=headers,
105
+ json_body=body_probe,
106
+ )
107
+ observations.append(body_obs)
108
+ body_res_lower = body_obs.response_body.lower()
109
+ matched_body_err = next((pat for pat in SQL_ERROR_PATTERNS if pat in body_res_lower), None)
110
+ if matched_body_err or (body_obs.response_status == 500 and "error" in body_res_lower):
111
+ confirmed = True
112
+ detail = f"database error signature '{matched_body_err}'" if matched_body_err else "unhandled 500 server crash"
113
+ summary = (
114
+ f"Injection vulnerability confirmed in request body: Probe '{probe}' on {raw_path} triggered "
115
+ f"{detail}."
116
+ )
117
+ steps = [
118
+ f"Send {method} to {base_url}{clean_path} with JSON body payload containing '{probe}'",
119
+ f"Observe database reaction: {detail}",
120
+ ]
121
+ break
122
+
123
+ # 3. Statistical Blind Timing Oracle (Welch's t-test) if error-based probes were inconclusive
124
+ if not confirmed:
125
+ from trace_engine.security.timing import StatisticalTimingOracle
126
+
127
+ # Measure baseline latencies (N=3 baseline samples)
128
+ base_latencies: List[float] = []
129
+ for _ in range(3):
130
+ sep = "&" if "?" in clean_path else "?"
131
+ b_url = f"{base_url}{clean_path}{sep}q=trace_baseline_test"
132
+ b_obs = client.execute(method=method, url=b_url, headers=headers)
133
+ observations.append(b_obs)
134
+ base_latencies.append(b_obs.latency_ms / 1000.0)
135
+
136
+ # Test blind sleep payloads with Welch's t-test (N=3 delay samples)
137
+ timing_probes = [
138
+ "'; SELECT pg_sleep(1.5)--",
139
+ "' OR SLEEP(1.5)--",
140
+ "1; sleep 1.5",
141
+ ]
142
+ expected_sec = 1.5
143
+
144
+ for t_probe in timing_probes:
145
+ encoded_t = urllib.parse.quote(t_probe)
146
+ sep = "&" if "?" in clean_path else "?"
147
+ t_url = f"{base_url}{clean_path}{sep}q={encoded_t}"
148
+
149
+ delay_latencies: List[float] = []
150
+ for _ in range(3):
151
+ t_obs = client.execute(method=method, url=t_url, headers=headers)
152
+ observations.append(t_obs)
153
+ delay_latencies.append(t_obs.latency_ms / 1000.0)
154
+
155
+ t_res = StatisticalTimingOracle.evaluate_timing_vulnerability(
156
+ baseline_latencies=base_latencies,
157
+ delay_latencies=delay_latencies,
158
+ expected_delay_sec=expected_sec,
159
+ )
160
+
161
+ if t_res.is_confirmed:
162
+ confirmed = True
163
+ summary = t_res.summary
164
+ steps = [
165
+ f"Measure baseline latency across non-delayed requests ({t_res.mean_baseline_sec:.2f}s mean)",
166
+ f"Send time-delay injection probe: {t_url}",
167
+ f"Observe statistically significant delay shift: +{t_res.observed_shift_sec:.2f}s (Welch's t={t_res.t_statistic}, p={t_res.p_value:.2e})",
168
+ ]
169
+ break
170
+
171
+ return TestExecutionResult(
172
+ testpack_name=self.name,
173
+ hypothesis_id=hypothesis.id,
174
+ confirmed=confirmed,
175
+ confidence=0.98 if confirmed else 0.15,
176
+ summary=summary,
177
+ observations=observations,
178
+ reproduction_steps=steps,
179
+ )