@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,82 @@
1
+ """Express.js framework adapter for endpoint discovery."""
2
+
3
+ import re
4
+ from typing import List
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 ExpressFrameworkAdapter(FrameworkAdapter):
11
+ """Detects and extracts Express.js routes and middleware."""
12
+
13
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
14
+ if parsed_file.language not in ("javascript", "typescript"):
15
+ return False
16
+ return any("express" in imp.module.lower() for imp in parsed_file.imports)
17
+
18
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
19
+ endpoints: List[Endpoint] = []
20
+
21
+ # Multi-line regex supporting app, router, server, api
22
+ pattern = re.compile(
23
+ r"""(?:app|router|server|api)\.(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']\s*(?:,\s*([^)]+))?""",
24
+ re.IGNORECASE | re.DOTALL,
25
+ )
26
+
27
+ content_lower = content.lower()
28
+ has_db_sink = any(
29
+ term in content_lower
30
+ for term in ("mongoose", "prisma", "sequelize", "knex", "pool.query", "db.query", "db.collection")
31
+ )
32
+ has_ext_sink = any(
33
+ term in content_lower
34
+ for term in ("axios", "node-fetch", "fetch(", "needle", "got(", "http.request", "https.request")
35
+ )
36
+
37
+ for match in pattern.finditer(content):
38
+ method = match.group(1).upper()
39
+ raw_path = match.group(2)
40
+ handler_and_middleware = match.group(3) or ""
41
+
42
+ # Calculate accurate line number
43
+ line_no = content[:match.start()].count("\n") + 1
44
+
45
+ # Convert Express :id into {id}
46
+ norm_path = re.sub(r":([a-zA-Z0-9_]+)", r"{\1}", raw_path)
47
+ path_params = re.findall(r":([a-zA-Z0-9_]+)", raw_path)
48
+
49
+ auth_required = any(
50
+ term in handler_and_middleware.lower()
51
+ for term in ("auth", "jwt", "passport", "verifytoken", "isauthenticated", "requireauth")
52
+ )
53
+ roles = ["admin"] if "admin" in handler_and_middleware.lower() or "admin" in norm_path.lower() else []
54
+
55
+ ep_id = f"ep_express_{parsed_file.file_path}_{line_no}_{method}".replace("/", "_").replace(".", "_")
56
+
57
+ endpoints.append(
58
+ Endpoint(
59
+ id=ep_id,
60
+ method=method,
61
+ path=norm_path,
62
+ handler_name=f"route_handler_L{line_no}",
63
+ auth_required=auth_required,
64
+ roles=roles,
65
+ parameters=[
66
+ EndpointParameter(name=p, location="path", required=True)
67
+ for p in path_params
68
+ ],
69
+ database_access=has_db_sink,
70
+ object_identifier=bool(path_params),
71
+ state_changing=method in ("POST", "PUT", "DELETE", "PATCH"),
72
+ external_network=has_ext_sink,
73
+ sensitive_data="admin" in norm_path.lower() or "user" in norm_path.lower() or "order" in norm_path.lower(),
74
+ source=SourceLocation(
75
+ file=parsed_file.file_path,
76
+ line_start=line_no,
77
+ line_end=line_no,
78
+ ),
79
+ )
80
+ )
81
+
82
+ return endpoints
@@ -0,0 +1,135 @@
1
+ """FastAPI framework adapter for endpoint and attack-surface discovery."""
2
+
3
+ import re
4
+ from typing import List, 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
+ HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head"}
10
+
11
+
12
+ class FastAPIFrameworkAdapter(FrameworkAdapter):
13
+ """Detects and extracts FastAPI/Starlette routes and auth gates."""
14
+
15
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
16
+ if parsed_file.language != "python":
17
+ return False
18
+ for imp in parsed_file.imports:
19
+ if "fastapi" in imp.module.lower() or "starlette" in imp.module.lower():
20
+ return True
21
+ return False
22
+
23
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
24
+ endpoints: List[Endpoint] = []
25
+ ep_count = 0
26
+
27
+ # Also search decorators in parsed functions
28
+ for fn in parsed_file.functions:
29
+ for dec in fn.decorators:
30
+ dec_text = dec.raw_text.strip()
31
+ # Pattern: @app.get('/path' ...) or @router.post('/path' ...)
32
+ match = re.search(
33
+ r"""@?(?:[a-zA-Z0-9_]+)\.(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']""",
34
+ dec_text,
35
+ re.IGNORECASE,
36
+ )
37
+ if match:
38
+ ep_count += 1
39
+ method = match.group(1).upper()
40
+ path = match.group(2)
41
+ ep_id = f"ep_{parsed_file.file_path.replace('/', '_').replace('.', '_')}_{fn.name}_{method}"
42
+
43
+ # Detect path parameters: {param_name}
44
+ path_params = re.findall(r"\{([a-zA-Z0-9_]+)\}", path)
45
+ params: List[EndpointParameter] = [
46
+ EndpointParameter(name=p, location="path", required=True)
47
+ for p in path_params
48
+ ]
49
+
50
+ # Detect function params from AST
51
+ for p in fn.parameters:
52
+ if p.name not in ("self", "cls") and p.name not in path_params:
53
+ params.append(
54
+ EndpointParameter(
55
+ name=p.name,
56
+ location="query" if method == "GET" else "body",
57
+ param_type=p.type_annotation or "string",
58
+ )
59
+ )
60
+
61
+ # Analyze auth dependencies in decorator or args
62
+ auth_required = False
63
+ roles = []
64
+ fn_body = fn.body_text or ""
65
+
66
+ auth_indicators = [
67
+ "current_user",
68
+ "auth",
69
+ "token",
70
+ "security",
71
+ "jwt",
72
+ "oauth",
73
+ "credentials",
74
+ "verify_token",
75
+ "api_key",
76
+ ]
77
+ for p in fn.parameters:
78
+ p_ann = (p.type_annotation or "").lower()
79
+ p_def = (p.default_value or "").lower()
80
+ if any(ind in p.name.lower() or ind in p_ann or ind in p_def for ind in auth_indicators):
81
+ auth_required = True
82
+ if "admin" in p.name.lower() or "admin" in p_ann or "admin" in p_def:
83
+ roles.append("admin")
84
+
85
+ if "require_admin" in dec_text.lower() or "admin" in path.lower():
86
+ roles.append("admin")
87
+ if "current_user" in dec_text.lower() or "depends(get_current_user" in dec_text.lower():
88
+ auth_required = True
89
+
90
+ # Object identifier flag (e.g. /items/{id})
91
+ has_obj_id = any(
92
+ p in ("id", "user_id", "order_id", "product_id", "item_id")
93
+ for p in path_params
94
+ )
95
+
96
+ # State changing
97
+ state_changing = method in ("POST", "PUT", "DELETE", "PATCH")
98
+
99
+ # Database access check
100
+ db_access = any(
101
+ term in fn_body.lower()
102
+ for term in ("db.", "session.", "cursor.", "repository", "query(", ".filter(", "orders", "database", "find")
103
+ )
104
+
105
+ # Outbound network (SSRF indicator)
106
+ ext_network = any(
107
+ term in fn_body.lower()
108
+ for term in ("requests.", "httpx.", "urllib.", "aiohttp.", "fetch(")
109
+ )
110
+
111
+ # Sensitive data
112
+ sensitive = any(
113
+ term in path.lower() or term in fn_body.lower()
114
+ for term in ("password", "secret", "token", "credit", "card", "user", "order", "refund", "admin")
115
+ )
116
+
117
+ endpoints.append(
118
+ Endpoint(
119
+ id=ep_id,
120
+ method=method,
121
+ path=path,
122
+ handler_name=fn.qualified_name,
123
+ auth_required=auth_required,
124
+ roles=list(set(roles)),
125
+ parameters=params,
126
+ database_access=db_access,
127
+ object_identifier=has_obj_id,
128
+ state_changing=state_changing,
129
+ external_network=ext_network,
130
+ sensitive_data=sensitive,
131
+ source=fn.location,
132
+ )
133
+ )
134
+
135
+ return endpoints
@@ -0,0 +1,108 @@
1
+ """Flask framework adapter for endpoint, Blueprint, and parameter extraction."""
2
+
3
+ import re
4
+ from typing import List, Optional
5
+ from trace_engine.parsing.parser import ParsedFile
6
+ from trace_engine.parsing.locations import SourceLocation
7
+ from trace_engine.framework.base import FrameworkAdapter, Endpoint, EndpointParameter
8
+
9
+
10
+ class FlaskFrameworkAdapter(FrameworkAdapter):
11
+ """Detects and extracts Flask routes, Blueprints, parameters, and database/network sinks."""
12
+
13
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
14
+ if parsed_file.language != "python":
15
+ return False
16
+
17
+ for imp in parsed_file.imports:
18
+ mod = imp.module.lower()
19
+ if "flask" in mod:
20
+ return True
21
+
22
+ # Check for typical Flask route decorators in content
23
+ return False
24
+
25
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
26
+ endpoints: List[Endpoint] = []
27
+ content_lower = content.lower()
28
+
29
+ # Database and sink detection
30
+ has_db_sink = any(
31
+ term in content_lower
32
+ for term in (
33
+ "db.session", ".query.", "session.execute", "cursor.execute",
34
+ "sqlite3.connect", "psycopg2.connect", "find_one", "find(", "collection."
35
+ )
36
+ )
37
+ has_ext_sink = any(
38
+ term in content_lower
39
+ for term in ("requests.get", "requests.post", "httpx.get", "httpx.post", "urllib.request")
40
+ )
41
+
42
+ for fn in parsed_file.functions:
43
+ fn_body = fn.body_text or ""
44
+ fn_body_lower = fn_body.lower()
45
+
46
+ for dec in fn.decorators:
47
+ dec_text = dec.raw_text.strip()
48
+ # Matches @app.route('/path', methods=['GET', 'POST']) or @bp.route('/path') or @blueprint.route
49
+ route_match = re.search(
50
+ r"""@?(?:[a-zA-Z0-9_]+)\.route\s*\(\s*["']([^"']+)["'](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?""",
51
+ dec_text,
52
+ re.IGNORECASE,
53
+ )
54
+ if not route_match:
55
+ continue
56
+
57
+ raw_path = route_match.group(1).strip()
58
+ methods_raw = route_match.group(2)
59
+
60
+ if methods_raw:
61
+ methods = [
62
+ m.strip().strip("'\"").upper()
63
+ for m in methods_raw.split(",")
64
+ if m.strip()
65
+ ]
66
+ else:
67
+ methods = ["GET"]
68
+
69
+ # Flask path parameters: <int:id>, <string:slug>, <path:filename>, <uuid:pk> -> {id}, {slug}, {filename}, {pk}
70
+ clean_path = re.sub(r"<(?:[a-zA-Z0-9_]+:)?([a-zA-Z0-9_]+)>", r"{\1}", raw_path)
71
+ norm_path = clean_path if clean_path.startswith("/") else f"/{clean_path}"
72
+ path_params = re.findall(r"\{([a-zA-Z0-9_]+)\}", norm_path)
73
+
74
+ # Extract request.args or request.form parameters from function body
75
+ query_params = re.findall(r"""request\.args\.get\s*\(\s*["']([a-zA-Z0-9_]+)["']""", fn_body)
76
+ body_params = re.findall(r"""request\.(?:form|json)\.get\s*\(\s*["']([a-zA-Z0-9_]+)["']""", fn_body)
77
+
78
+ params = [EndpointParameter(name=p, location="path", required=True) for p in path_params]
79
+ params.extend([EndpointParameter(name=p, location="query", required=False) for p in query_params])
80
+ params.extend([EndpointParameter(name=p, location="body", required=False) for p in body_params])
81
+
82
+ auth_required = any(
83
+ sig in fn_body_lower or sig in dec_text.lower()
84
+ for sig in ("@login_required", "@jwt_required", "current_user", "get_jwt_identity", "auth")
85
+ )
86
+ roles = ["admin"] if "admin" in fn.name.lower() or "admin" in norm_path.lower() else []
87
+
88
+ for m in methods:
89
+ ep_id = f"ep_flask_{parsed_file.file_path}_{fn.name}_{m}".replace("/", "_").replace(".", "_")
90
+ endpoints.append(
91
+ Endpoint(
92
+ id=ep_id,
93
+ method=m,
94
+ path=norm_path,
95
+ handler_name=fn.qualified_name,
96
+ auth_required=auth_required,
97
+ roles=roles,
98
+ parameters=params,
99
+ database_access=has_db_sink or "execute(" in fn_body_lower or "query" in fn_body_lower,
100
+ object_identifier=bool(path_params),
101
+ state_changing=m in ("POST", "PUT", "DELETE", "PATCH"),
102
+ external_network=has_ext_sink or "requests." in fn_body_lower or "urllib" in fn_body_lower,
103
+ sensitive_data="admin" in norm_path.lower() or "user" in norm_path.lower() or "secret" in norm_path.lower(),
104
+ source=fn.location,
105
+ )
106
+ )
107
+
108
+ return endpoints
@@ -0,0 +1,94 @@
1
+ """Go framework adapter for Gin, Fiber, Chi, Echo, and standard net/http routes."""
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 GoFrameworkAdapter(FrameworkAdapter):
11
+ """Detects and extracts Go web routes, closures, parameters, and middleware across Gin, Echo, Fiber, Chi, and net/http."""
12
+
13
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
14
+ if parsed_file.language != "go":
15
+ norm = parsed_file.file_path.replace("\\", "/").lower()
16
+ return norm.endswith(".go")
17
+ return True
18
+
19
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
20
+ endpoints: List[Endpoint] = []
21
+ norm_file_path = parsed_file.file_path.replace("\\", "/")
22
+ content_lower = content.lower()
23
+
24
+ has_db_sink = any(
25
+ term in content_lower
26
+ for term in ("db.query", "db.exec", "db.raw", "gorm", "sqlx", "mongo.", "redis.", "findone", "sql.open")
27
+ )
28
+ has_ext_sink = any(
29
+ term in content_lower
30
+ for term in ("http.get", "http.post", "http.client", "fasthttp", "resty", "http.newrequest", "client.do")
31
+ )
32
+
33
+ auth_signals = ("auth", "jwt", "bearer", "token", "requireauth", "admin", "middleware.auth", "authrequired")
34
+ auth_required = any(sig in content_lower for sig in auth_signals) or "admin" in norm_file_path.lower()
35
+ roles = ["admin"] if "admin" in content_lower or "admin" in norm_file_path.lower() else []
36
+
37
+ # Matches:
38
+ # 1. Gin/Echo: r.GET("/path", handler) or r.GET("/path", func(c *gin.Context) {
39
+ # 2. Chi/Fiber: r.Get("/path", handler) or app.Post("/path", func(...) {
40
+ # 3. net/http: http.HandleFunc("/path", handler) or mux.Handle("/path", ...)
41
+ pattern = re.compile(
42
+ r"""(?:[a-zA-Z0-9_]+)\.(GET|POST|PUT|DELETE|PATCH|Get|Post|Put|Delete|Patch|Handle|HandleFunc)\s*\(\s*["']([^"']+)["']\s*,\s*([a-zA-Z0-9_\.]+|func\s*\([^)]*\))""",
43
+ re.MULTILINE
44
+ )
45
+
46
+ for match in pattern.finditer(content):
47
+ raw_method = match.group(1).upper()
48
+ method = "GET" if raw_method in ("HANDLE", "HANDLEFUNC") else raw_method
49
+ raw_path = match.group(2)
50
+ raw_handler = match.group(3).strip()
51
+ handler_name = "closure_handler" if raw_handler.startswith("func") else raw_handler
52
+ line_no = content[:match.start()].count("\n") + 1
53
+
54
+ # Convert Go :id or {id} or *wildcard to standard {id}
55
+ norm_path = re.sub(r':([a-zA-Z0-9_]+)', r'{\1}', raw_path)
56
+ norm_path = re.sub(r'\*([a-zA-Z0-9_]+)', r'{\1}', norm_path)
57
+ norm_path = "/" + norm_path.lstrip("/")
58
+
59
+ path_params = re.findall(r'\{([a-zA-Z0-9_]+)\}', norm_path)
60
+
61
+ # Query and body parameter extraction from Go conventions
62
+ query_params = re.findall(r"""(?:c\.Query|r\.URL\.Query\(\)\.Get)\s*\(\s*["']([a-zA-Z0-9_]+)["']""", content)
63
+ body_params = re.findall(r"""(?:c\.PostForm|c\.BindJSON|c\.BodyParser)\s*\(\s*&?([a-zA-Z0-9_]+)""", content)
64
+
65
+ params = [EndpointParameter(name=p, location="path", required=True) for p in path_params]
66
+ params.extend([EndpointParameter(name=p, location="query", required=False) for p in query_params])
67
+ params.extend([EndpointParameter(name=p, location="body", required=False) for p in body_params])
68
+
69
+ ep_id = f"ep_go_{norm_file_path}_{line_no}_{method}".replace("/", "_").replace(".", "_")
70
+
71
+ sensitive = any(
72
+ term in norm_path.lower() or term in content_lower
73
+ for term in ("admin", "user", "auth", "billing", "payment", "token", "secret", "order")
74
+ )
75
+
76
+ endpoints.append(
77
+ Endpoint(
78
+ id=ep_id,
79
+ method=method,
80
+ path=norm_path,
81
+ handler_name=handler_name,
82
+ auth_required=auth_required or ("admin" in norm_path.lower()),
83
+ roles=roles if "admin" not in norm_path.lower() else ["admin"],
84
+ parameters=params,
85
+ database_access=has_db_sink,
86
+ object_identifier=bool(path_params),
87
+ state_changing=method in ("POST", "PUT", "DELETE", "PATCH"),
88
+ external_network=has_ext_sink,
89
+ sensitive_data=sensitive,
90
+ source=SourceLocation(file=parsed_file.file_path, line_start=line_no, line_end=line_no),
91
+ )
92
+ )
93
+
94
+ return endpoints
@@ -0,0 +1,200 @@
1
+ """Next.js framework adapter for App Router and Pages Router endpoint discovery."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import List, Dict, Optional
6
+ from trace_engine.parsing.parser import ParsedFile
7
+ from trace_engine.framework.base import FrameworkAdapter, Endpoint, EndpointParameter
8
+ from trace_engine.parsing.locations import SourceLocation
9
+
10
+
11
+ class NextJSFrameworkAdapter(FrameworkAdapter):
12
+ """Detects and extracts Next.js API routes, route handlers, and page endpoints."""
13
+
14
+ def _infer_app_router_path(self, norm_path: str) -> str:
15
+ # Strip leading app/ or src/app/
16
+ clean = norm_path
17
+ if clean.startswith("src/app/"):
18
+ clean = clean[8:]
19
+ elif clean.startswith("app/"):
20
+ clean = clean[4:]
21
+
22
+ # Strip trailing /route.ts, /route.js, /page.tsx, etc.
23
+ clean = re.sub(r'/(?:route|page)\.[a-zA-Z0-9]+$', '', clean)
24
+
25
+ # Remove route groups like (auth), (dashboard)
26
+ parts = [p for p in clean.split("/") if not (p.startswith("(") and p.endswith(")"))]
27
+ clean = "/".join(parts)
28
+
29
+ # Normalize Next.js params: [[...slug]] -> {slug}, [...slug] -> {slug}, [id] -> {id}
30
+ clean = re.sub(r'\[\[?\.\.\.([a-zA-Z0-9_]+)\]?\]', r'{\1}', clean)
31
+ clean = re.sub(r'\[([a-zA-Z0-9_]+)\]', r'{\1}', clean)
32
+
33
+ norm_url = "/" + clean.lstrip("/")
34
+ return norm_url or "/"
35
+
36
+ def _infer_pages_router_path(self, norm_path: str) -> str:
37
+ # Strip leading pages/ or src/pages/
38
+ clean = norm_path
39
+ if clean.startswith("src/pages/"):
40
+ clean = clean[10:]
41
+ elif clean.startswith("pages/"):
42
+ clean = clean[6:]
43
+
44
+ # Strip file extension
45
+ clean = re.sub(r'\.[a-zA-Z0-9]+$', '', clean)
46
+
47
+ # Normalize params: [id] -> {id}
48
+ clean = re.sub(r'\[\[?\.\.\.([a-zA-Z0-9_]+)\]?\]', r'{\1}', clean)
49
+ clean = re.sub(r'\[([a-zA-Z0-9_]+)\]', r'{\1}', clean)
50
+
51
+ # Strip index
52
+ clean = re.sub(r'(?:^|/)index$', '', clean)
53
+
54
+ norm_url = "/" + clean.lstrip("/")
55
+ return norm_url or "/"
56
+
57
+ def can_handle(self, parsed_file: ParsedFile) -> bool:
58
+ if parsed_file.language not in ("javascript", "typescript"):
59
+ return False
60
+
61
+ norm_path = parsed_file.file_path.replace("\\", "/")
62
+
63
+ # App Router route/page
64
+ if re.search(r'(?:^|/)(?:src/)?app/.+/(?:route|page)\.[a-zA-Z0-9]+$', norm_path):
65
+ return True
66
+
67
+ # Pages Router api or page
68
+ if re.search(r'(?:^|/)(?:src/)?pages/(?:api/)?.+\.[a-zA-Z0-9]+$', norm_path):
69
+ return True
70
+
71
+ # Imports from next
72
+ for imp in parsed_file.imports:
73
+ mod_low = imp.module.lower()
74
+ if mod_low == "next" or mod_low.startswith("next/"):
75
+ return True
76
+
77
+ return False
78
+
79
+ def extract_endpoints(self, parsed_file: ParsedFile, content: str) -> List[Endpoint]:
80
+ endpoints: List[Endpoint] = []
81
+ norm_file_path = parsed_file.file_path.replace("\\", "/")
82
+
83
+ is_app_router = bool(re.search(r'(?:^|/)(?:src/)?app/', norm_file_path))
84
+ is_pages_router = bool(re.search(r'(?:^|/)(?:src/)?pages/', norm_file_path))
85
+
86
+ if is_app_router:
87
+ url_path = self._infer_app_router_path(norm_file_path)
88
+ elif is_pages_router:
89
+ url_path = self._infer_pages_router_path(norm_file_path)
90
+ else:
91
+ url_path = "/" + norm_file_path.split("/")[-1].split(".")[0]
92
+
93
+ path_params = re.findall(r'\{([a-zA-Z0-9_]+)\}', url_path)
94
+
95
+ content_lower = content.lower()
96
+ has_db_sink = any(
97
+ term in content_lower
98
+ for term in (
99
+ "prisma", "mongoose", "sequelize", "supabase", "knex",
100
+ "pool.query", "client.query", "db.query", "db.select", ".from("
101
+ )
102
+ )
103
+ has_ext_sink = any(
104
+ term in content_lower
105
+ for term in (
106
+ "axios", "fetch(", "nodemailer", "got(", "needle",
107
+ "http.request", "https.request", "transporter.sendmail", "octokit", "stripe"
108
+ )
109
+ )
110
+
111
+ auth_signals = (
112
+ "getserversession", "getauth", "auth()", "clerk", "next-auth",
113
+ "verifytoken", "requireauth", "requireadmin", "jwt.verify", "supabase.auth"
114
+ )
115
+ auth_required = any(sig in content_lower for sig in auth_signals) or "admin" in url_path.lower()
116
+ roles = ["admin"] if "admin" in content_lower or "admin" in url_path.lower() else []
117
+
118
+ sensitive_data = any(
119
+ term in url_path.lower() or term in content_lower
120
+ for term in (
121
+ "admin", "user", "auth", "login", "register", "profile",
122
+ "password", "token", "secret", "webhook", "session", "payment"
123
+ )
124
+ )
125
+
126
+ parameters: List[EndpointParameter] = [
127
+ EndpointParameter(name=p, location="path", required=True, user_controlled=True)
128
+ for p in path_params
129
+ ]
130
+
131
+ # Extract searchParams
132
+ for m in re.finditer(r'''(?:searchParams|nextUrl\.searchParams)\.get\s*\(\s*["']([^"']+)["']''', content):
133
+ p_name = m.group(1)
134
+ if not any(p.name == p_name for p in parameters):
135
+ parameters.append(
136
+ EndpointParameter(name=p_name, location="query", required=False, user_controlled=True)
137
+ )
138
+
139
+ # App Router named exports: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
140
+ app_verbs = list(re.finditer(
141
+ r'''export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b''',
142
+ content
143
+ ))
144
+
145
+ for v_match in app_verbs:
146
+ verb = v_match.group(1).upper()
147
+ line_no = content[:v_match.start()].count("\n") + 1
148
+ ep_id = f"ep_next_{verb.lower()}_{norm_file_path}_{line_no}".replace("/", "_").replace(".", "_")
149
+ endpoints.append(
150
+ Endpoint(
151
+ id=ep_id,
152
+ method=verb,
153
+ path=url_path,
154
+ handler_name=verb,
155
+ auth_required=auth_required,
156
+ roles=roles,
157
+ parameters=parameters,
158
+ database_access=has_db_sink,
159
+ object_identifier=bool(path_params),
160
+ state_changing=verb in ("POST", "PUT", "DELETE", "PATCH"),
161
+ external_network=has_ext_sink,
162
+ sensitive_data=sensitive_data,
163
+ source=SourceLocation(file=parsed_file.file_path, line_start=line_no, line_end=line_no),
164
+ )
165
+ )
166
+
167
+ # Pages Router or App Router default export
168
+ if not endpoints:
169
+ # Check for checked HTTP methods in handler body
170
+ checked_methods = []
171
+ for m in re.finditer(r'''req\.method\s*===?\s*["'](GET|POST|PUT|DELETE|PATCH)["']''', content):
172
+ checked_methods.append(m.group(1).upper())
173
+
174
+ methods_to_add = set(checked_methods) if checked_methods else (["GET"] if "page." in norm_file_path else ["GET", "POST"])
175
+
176
+ default_match = re.search(r'''export\s+default\s+(?:async\s+)?(?:function|const|class)?\s*([a-zA-Z0-9_$]+)?''', content)
177
+ handler_name = default_match.group(1) if (default_match and default_match.group(1)) else "handler"
178
+ line_no = content[:default_match.start()].count("\n") + 1 if default_match else 1
179
+
180
+ for meth in sorted(methods_to_add):
181
+ ep_id = f"ep_next_{meth.lower()}_{norm_file_path}_{line_no}".replace("/", "_").replace(".", "_")
182
+ endpoints.append(
183
+ Endpoint(
184
+ id=ep_id,
185
+ method=meth,
186
+ path=url_path,
187
+ handler_name=handler_name,
188
+ auth_required=auth_required,
189
+ roles=roles,
190
+ parameters=parameters,
191
+ database_access=has_db_sink,
192
+ object_identifier=bool(path_params),
193
+ state_changing=meth in ("POST", "PUT", "DELETE", "PATCH"),
194
+ external_network=has_ext_sink,
195
+ sensitive_data=sensitive_data,
196
+ source=SourceLocation(file=parsed_file.file_path, line_start=line_no, line_end=line_no),
197
+ )
198
+ )
199
+
200
+ return endpoints