fingerprint-mcp 0.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 (269) hide show
  1. package/LICENSE +663 -0
  2. package/README.ar.md +767 -0
  3. package/README.bn.md +767 -0
  4. package/README.bs.md +767 -0
  5. package/README.da.md +767 -0
  6. package/README.de.md +770 -0
  7. package/README.el.md +767 -0
  8. package/README.es.md +769 -0
  9. package/README.fr.md +769 -0
  10. package/README.hi.md +767 -0
  11. package/README.it.md +767 -0
  12. package/README.ja.md +767 -0
  13. package/README.ko.md +772 -0
  14. package/README.md +767 -0
  15. package/README.no.md +767 -0
  16. package/README.pl.md +767 -0
  17. package/README.pt-BR.md +767 -0
  18. package/README.ru.md +767 -0
  19. package/README.th.md +767 -0
  20. package/README.tr.md +767 -0
  21. package/README.uk.md +767 -0
  22. package/README.vi.md +767 -0
  23. package/README.zh-TW.md +768 -0
  24. package/README.zh.md +768 -0
  25. package/dist/app/index.d.ts +3 -0
  26. package/dist/app/index.d.ts.map +1 -0
  27. package/dist/app/index.js +614 -0
  28. package/dist/app/index.js.map +1 -0
  29. package/dist/composite/analyze.d.ts +3 -0
  30. package/dist/composite/analyze.d.ts.map +1 -0
  31. package/dist/composite/analyze.js +59 -0
  32. package/dist/composite/analyze.js.map +1 -0
  33. package/dist/composite/correlate.d.ts +3 -0
  34. package/dist/composite/correlate.d.ts.map +1 -0
  35. package/dist/composite/correlate.js +184 -0
  36. package/dist/composite/correlate.js.map +1 -0
  37. package/dist/composite/enumerate.d.ts +3 -0
  38. package/dist/composite/enumerate.d.ts.map +1 -0
  39. package/dist/composite/enumerate.js +94 -0
  40. package/dist/composite/enumerate.js.map +1 -0
  41. package/dist/composite/helpers.d.ts +22 -0
  42. package/dist/composite/helpers.d.ts.map +1 -0
  43. package/dist/composite/helpers.js +111 -0
  44. package/dist/composite/helpers.js.map +1 -0
  45. package/dist/composite/index.d.ts +3 -0
  46. package/dist/composite/index.d.ts.map +1 -0
  47. package/dist/composite/index.js +29 -0
  48. package/dist/composite/index.js.map +1 -0
  49. package/dist/composite/meta.d.ts +3 -0
  50. package/dist/composite/meta.d.ts.map +1 -0
  51. package/dist/composite/meta.js +23 -0
  52. package/dist/composite/meta.js.map +1 -0
  53. package/dist/composite/osint.d.ts +3 -0
  54. package/dist/composite/osint.d.ts.map +1 -0
  55. package/dist/composite/osint.js +40 -0
  56. package/dist/composite/osint.js.map +1 -0
  57. package/dist/composite/recon.d.ts +3 -0
  58. package/dist/composite/recon.d.ts.map +1 -0
  59. package/dist/composite/recon.js +131 -0
  60. package/dist/composite/recon.js.map +1 -0
  61. package/dist/composite/scan-dns.d.ts +3 -0
  62. package/dist/composite/scan-dns.d.ts.map +1 -0
  63. package/dist/composite/scan-dns.js +44 -0
  64. package/dist/composite/scan-dns.js.map +1 -0
  65. package/dist/composite/scan-http.d.ts +3 -0
  66. package/dist/composite/scan-http.d.ts.map +1 -0
  67. package/dist/composite/scan-http.js +68 -0
  68. package/dist/composite/scan-http.js.map +1 -0
  69. package/dist/composite/scan-paths.d.ts +3 -0
  70. package/dist/composite/scan-paths.d.ts.map +1 -0
  71. package/dist/composite/scan-paths.js +32 -0
  72. package/dist/composite/scan-paths.js.map +1 -0
  73. package/dist/composite/scan-ports.d.ts +3 -0
  74. package/dist/composite/scan-ports.d.ts.map +1 -0
  75. package/dist/composite/scan-ports.js +79 -0
  76. package/dist/composite/scan-ports.js.map +1 -0
  77. package/dist/composite/scan-services.d.ts +3 -0
  78. package/dist/composite/scan-services.d.ts.map +1 -0
  79. package/dist/composite/scan-services.js +111 -0
  80. package/dist/composite/scan-services.js.map +1 -0
  81. package/dist/composite/scan-tls.d.ts +3 -0
  82. package/dist/composite/scan-tls.d.ts.map +1 -0
  83. package/dist/composite/scan-tls.js +32 -0
  84. package/dist/composite/scan-tls.js.map +1 -0
  85. package/dist/composite/scan-waf.d.ts +3 -0
  86. package/dist/composite/scan-waf.d.ts.map +1 -0
  87. package/dist/composite/scan-waf.js +35 -0
  88. package/dist/composite/scan-waf.js.map +1 -0
  89. package/dist/correlation/index.d.ts +3 -0
  90. package/dist/correlation/index.d.ts.map +1 -0
  91. package/dist/correlation/index.js +1044 -0
  92. package/dist/correlation/index.js.map +1 -0
  93. package/dist/data/analytics-patterns.d.ts +21 -0
  94. package/dist/data/analytics-patterns.d.ts.map +1 -0
  95. package/dist/data/analytics-patterns.js +449 -0
  96. package/dist/data/analytics-patterns.js.map +1 -0
  97. package/dist/data/app-signatures.d.ts +52 -0
  98. package/dist/data/app-signatures.d.ts.map +1 -0
  99. package/dist/data/app-signatures.js +1143 -0
  100. package/dist/data/app-signatures.js.map +1 -0
  101. package/dist/data/c2-signatures.d.ts +54 -0
  102. package/dist/data/c2-signatures.d.ts.map +1 -0
  103. package/dist/data/c2-signatures.js +256 -0
  104. package/dist/data/c2-signatures.js.map +1 -0
  105. package/dist/data/cloud-ranges.d.ts +34 -0
  106. package/dist/data/cloud-ranges.d.ts.map +1 -0
  107. package/dist/data/cloud-ranges.js +628 -0
  108. package/dist/data/cloud-ranges.js.map +1 -0
  109. package/dist/data/cookie-patterns.d.ts +28 -0
  110. package/dist/data/cookie-patterns.d.ts.map +1 -0
  111. package/dist/data/cookie-patterns.js +225 -0
  112. package/dist/data/cookie-patterns.js.map +1 -0
  113. package/dist/data/error-signatures.d.ts +19 -0
  114. package/dist/data/error-signatures.d.ts.map +1 -0
  115. package/dist/data/error-signatures.js +321 -0
  116. package/dist/data/error-signatures.js.map +1 -0
  117. package/dist/data/favicon-hashes.d.ts +25 -0
  118. package/dist/data/favicon-hashes.d.ts.map +1 -0
  119. package/dist/data/favicon-hashes.js +249 -0
  120. package/dist/data/favicon-hashes.js.map +1 -0
  121. package/dist/data/h2-signatures.d.ts +50 -0
  122. package/dist/data/h2-signatures.d.ts.map +1 -0
  123. package/dist/data/h2-signatures.js +211 -0
  124. package/dist/data/h2-signatures.js.map +1 -0
  125. package/dist/data/header-order.d.ts +38 -0
  126. package/dist/data/header-order.d.ts.map +1 -0
  127. package/dist/data/header-order.js +185 -0
  128. package/dist/data/header-order.js.map +1 -0
  129. package/dist/data/jarm-signatures.d.ts +25 -0
  130. package/dist/data/jarm-signatures.d.ts.map +1 -0
  131. package/dist/data/jarm-signatures.js +213 -0
  132. package/dist/data/jarm-signatures.js.map +1 -0
  133. package/dist/data/saas-patterns.d.ts +23 -0
  134. package/dist/data/saas-patterns.d.ts.map +1 -0
  135. package/dist/data/saas-patterns.js +139 -0
  136. package/dist/data/saas-patterns.js.map +1 -0
  137. package/dist/data/sensitive-paths.d.ts +12 -0
  138. package/dist/data/sensitive-paths.d.ts.map +1 -0
  139. package/dist/data/sensitive-paths.js +1539 -0
  140. package/dist/data/sensitive-paths.js.map +1 -0
  141. package/dist/data/service-banners.d.ts +59 -0
  142. package/dist/data/service-banners.d.ts.map +1 -0
  143. package/dist/data/service-banners.js +323 -0
  144. package/dist/data/service-banners.js.map +1 -0
  145. package/dist/data/smtp-signatures.d.ts +12 -0
  146. package/dist/data/smtp-signatures.d.ts.map +1 -0
  147. package/dist/data/smtp-signatures.js +350 -0
  148. package/dist/data/smtp-signatures.js.map +1 -0
  149. package/dist/data/takeover-patterns.d.ts +37 -0
  150. package/dist/data/takeover-patterns.d.ts.map +1 -0
  151. package/dist/data/takeover-patterns.js +249 -0
  152. package/dist/data/takeover-patterns.js.map +1 -0
  153. package/dist/data/tech-patterns.d.ts +44 -0
  154. package/dist/data/tech-patterns.d.ts.map +1 -0
  155. package/dist/data/tech-patterns.js +690 -0
  156. package/dist/data/tech-patterns.js.map +1 -0
  157. package/dist/data/waf-signatures.d.ts +21 -0
  158. package/dist/data/waf-signatures.d.ts.map +1 -0
  159. package/dist/data/waf-signatures.js +318 -0
  160. package/dist/data/waf-signatures.js.map +1 -0
  161. package/dist/dns/index.d.ts +3 -0
  162. package/dist/dns/index.d.ts.map +1 -0
  163. package/dist/dns/index.js +1067 -0
  164. package/dist/dns/index.js.map +1 -0
  165. package/dist/enum/index.d.ts +3 -0
  166. package/dist/enum/index.d.ts.map +1 -0
  167. package/dist/enum/index.js +818 -0
  168. package/dist/enum/index.js.map +1 -0
  169. package/dist/h2/index.d.ts +3 -0
  170. package/dist/h2/index.d.ts.map +1 -0
  171. package/dist/h2/index.js +414 -0
  172. package/dist/h2/index.js.map +1 -0
  173. package/dist/http/index.d.ts +3 -0
  174. package/dist/http/index.d.ts.map +1 -0
  175. package/dist/http/index.js +2444 -0
  176. package/dist/http/index.js.map +1 -0
  177. package/dist/identify/index.d.ts +3 -0
  178. package/dist/identify/index.d.ts.map +1 -0
  179. package/dist/identify/index.js +447 -0
  180. package/dist/identify/index.js.map +1 -0
  181. package/dist/index.d.ts +3 -0
  182. package/dist/index.d.ts.map +1 -0
  183. package/dist/index.js +165 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/infra/index.d.ts +3 -0
  186. package/dist/infra/index.d.ts.map +1 -0
  187. package/dist/infra/index.js +989 -0
  188. package/dist/infra/index.js.map +1 -0
  189. package/dist/iot/index.d.ts +3 -0
  190. package/dist/iot/index.d.ts.map +1 -0
  191. package/dist/iot/index.js +610 -0
  192. package/dist/iot/index.js.map +1 -0
  193. package/dist/meta/index.d.ts +3 -0
  194. package/dist/meta/index.d.ts.map +1 -0
  195. package/dist/meta/index.js +442 -0
  196. package/dist/meta/index.js.map +1 -0
  197. package/dist/osint/index.d.ts +3 -0
  198. package/dist/osint/index.d.ts.map +1 -0
  199. package/dist/osint/index.js +687 -0
  200. package/dist/osint/index.js.map +1 -0
  201. package/dist/passive/index.d.ts +3 -0
  202. package/dist/passive/index.d.ts.map +1 -0
  203. package/dist/passive/index.js +944 -0
  204. package/dist/passive/index.js.map +1 -0
  205. package/dist/path/index.d.ts +3 -0
  206. package/dist/path/index.d.ts.map +1 -0
  207. package/dist/path/index.js +878 -0
  208. package/dist/path/index.js.map +1 -0
  209. package/dist/protocol/mcp-server.d.ts +4 -0
  210. package/dist/protocol/mcp-server.d.ts.map +1 -0
  211. package/dist/protocol/mcp-server.js +32 -0
  212. package/dist/protocol/mcp-server.js.map +1 -0
  213. package/dist/protocol/tools.d.ts +3 -0
  214. package/dist/protocol/tools.d.ts.map +1 -0
  215. package/dist/protocol/tools.js +5 -0
  216. package/dist/protocol/tools.js.map +1 -0
  217. package/dist/service/index.d.ts +3 -0
  218. package/dist/service/index.d.ts.map +1 -0
  219. package/dist/service/index.js +1053 -0
  220. package/dist/service/index.js.map +1 -0
  221. package/dist/smtp/index.d.ts +3 -0
  222. package/dist/smtp/index.d.ts.map +1 -0
  223. package/dist/smtp/index.js +437 -0
  224. package/dist/smtp/index.js.map +1 -0
  225. package/dist/ssh/index.d.ts +3 -0
  226. package/dist/ssh/index.d.ts.map +1 -0
  227. package/dist/ssh/index.js +676 -0
  228. package/dist/ssh/index.js.map +1 -0
  229. package/dist/tcp/index.d.ts +3 -0
  230. package/dist/tcp/index.d.ts.map +1 -0
  231. package/dist/tcp/index.js +369 -0
  232. package/dist/tcp/index.js.map +1 -0
  233. package/dist/timing/index.d.ts +3 -0
  234. package/dist/timing/index.d.ts.map +1 -0
  235. package/dist/timing/index.js +425 -0
  236. package/dist/timing/index.js.map +1 -0
  237. package/dist/tls/index.d.ts +3 -0
  238. package/dist/tls/index.d.ts.map +1 -0
  239. package/dist/tls/index.js +1332 -0
  240. package/dist/tls/index.js.map +1 -0
  241. package/dist/types/index.d.ts +26 -0
  242. package/dist/types/index.d.ts.map +1 -0
  243. package/dist/types/index.js +8 -0
  244. package/dist/types/index.js.map +1 -0
  245. package/dist/utils/cache.d.ts +11 -0
  246. package/dist/utils/cache.d.ts.map +1 -0
  247. package/dist/utils/cache.js +35 -0
  248. package/dist/utils/cache.js.map +1 -0
  249. package/dist/utils/murmurhash3.d.ts +3 -0
  250. package/dist/utils/murmurhash3.d.ts.map +1 -0
  251. package/dist/utils/murmurhash3.js +57 -0
  252. package/dist/utils/murmurhash3.js.map +1 -0
  253. package/dist/utils/rate-limiter.d.ts +10 -0
  254. package/dist/utils/rate-limiter.d.ts.map +1 -0
  255. package/dist/utils/rate-limiter.js +35 -0
  256. package/dist/utils/rate-limiter.js.map +1 -0
  257. package/dist/utils/require-key.d.ts +2 -0
  258. package/dist/utils/require-key.d.ts.map +1 -0
  259. package/dist/utils/require-key.js +8 -0
  260. package/dist/utils/require-key.js.map +1 -0
  261. package/dist/waf/index.d.ts +3 -0
  262. package/dist/waf/index.d.ts.map +1 -0
  263. package/dist/waf/index.js +767 -0
  264. package/dist/waf/index.js.map +1 -0
  265. package/dist/web/index.d.ts +3 -0
  266. package/dist/web/index.d.ts.map +1 -0
  267. package/dist/web/index.js +1366 -0
  268. package/dist/web/index.js.map +1 -0
  269. package/package.json +66 -0
@@ -0,0 +1,878 @@
1
+ import { z } from "zod";
2
+ import { json } from "../types/index.js";
3
+ import { RateLimiter } from "../utils/rate-limiter.js";
4
+ import { TTLCache } from "../utils/cache.js";
5
+ // ─── Rate Limiter & Cache ───
6
+ const limiter = new RateLimiter(200);
7
+ const cache = new TTLCache(300_000);
8
+ // ─── Constants ───
9
+ const REQUEST_TIMEOUT = 8_000;
10
+ const BATCH_SIZE = 10;
11
+ const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
12
+ // ─── Path Databases ───
13
+ const SENSITIVE_PATHS = {
14
+ admin: [
15
+ "/admin/", "/admin/login", "/wp-admin/", "/wp-login.php", "/administrator/",
16
+ "/manager/html", "/phpmyadmin/", "/adminer.php", "/cockpit/", "/grafana/",
17
+ "/kibana/", "/jenkins/", "/hudson/", "/nagios/", "/cacti/", "/zabbix/",
18
+ "/webmin/", "/cpanel", "/plesk", "/directadmin", "/console", "/dashboard", "/portal",
19
+ ],
20
+ config: [
21
+ "/.env", "/.env.bak", "/.env.local", "/.env.production",
22
+ "/wp-config.php", "/wp-config.php.bak", "/web.config", "/config.php",
23
+ "/configuration.php", "/settings.py", "/application.yml", "/application.properties",
24
+ "/appsettings.json", "/config.json", "/config.yaml", "/database.yml",
25
+ "/secrets.yml", "/credentials.json", "/local.settings.json",
26
+ ],
27
+ debug: [
28
+ "/actuator", "/actuator/env", "/actuator/health", "/actuator/info",
29
+ "/actuator/mappings", "/actuator/configprops", "/actuator/heapdump",
30
+ "/actuator/threaddump", "/debug/vars", "/debug/pprof", "/debug/pprof/goroutine",
31
+ "/server-info", "/server-status", "/nginx_status", "/stub_status",
32
+ "/console", "/elmah.axd", "/_profiler", "/trace", "/metrics",
33
+ "/health", "/healthz", "/ready", "/status",
34
+ ],
35
+ backup: [
36
+ "/backup/", "/backup.zip", "/backup.tar.gz", "/backup.sql", "/dump.sql",
37
+ "/db.sql", "/database.sql", "/site.zip", "/www.zip", "/htdocs.zip",
38
+ "/public.zip", "/wp-content/debug.log", "/error.log", "/access.log", "/debug.log",
39
+ ],
40
+ vcs: [
41
+ "/.git/config", "/.git/HEAD", "/.git/index", "/.git/logs/HEAD",
42
+ "/.gitignore", "/.svn/entries", "/.svn/wc.db", "/.hg/hgrc",
43
+ "/.bzr/README", "/CVS/Root",
44
+ ],
45
+ dependency: [
46
+ "/composer.json", "/composer.lock", "/package.json", "/package-lock.json",
47
+ "/yarn.lock", "/requirements.txt", "/Pipfile", "/Pipfile.lock",
48
+ "/go.mod", "/go.sum", "/Gemfile", "/Gemfile.lock", "/pom.xml",
49
+ "/build.gradle", "/Cargo.toml", "/Cargo.lock", "/mix.exs",
50
+ ],
51
+ "well-known": [
52
+ "/.well-known/security.txt", "/.well-known/openid-configuration",
53
+ "/.well-known/apple-app-site-association", "/.well-known/assetlinks.json",
54
+ "/.well-known/change-password", "/.well-known/acme-challenge/",
55
+ "/.well-known/matrix/server", "/.well-known/nodeinfo", "/.well-known/webfinger",
56
+ ],
57
+ "api-docs": [
58
+ "/swagger.json", "/swagger.yaml", "/openapi.json", "/openapi.yaml",
59
+ "/api-docs", "/api-docs.json", "/swagger-ui.html", "/swagger-ui/",
60
+ "/swagger-resources", "/graphql", "/graphiql", "/playground", "/altair",
61
+ "/voyager", "/_catalog", "/v2/_catalog", "/api/v1", "/api/v2", "/api/v3",
62
+ ],
63
+ cloud: [
64
+ "/latest/meta-data/", "/latest/user-data/",
65
+ "/computeMetadata/v1/", "/metadata/instance", "/metadata/v1/",
66
+ ],
67
+ "ci-cd": [
68
+ "/Jenkinsfile", "/.github/workflows/", "/.gitlab-ci.yml",
69
+ "/Dockerfile", "/docker-compose.yml", "/docker-compose.yaml",
70
+ "/.drone.yml", "/.circleci/config.yml", "/.travis.yml",
71
+ "/Vagrantfile", "/Procfile",
72
+ ],
73
+ secrets: [
74
+ "/.npmrc", "/.pypirc", "/.docker/config.json", "/id_rsa", "/id_rsa.pub",
75
+ "/.ssh/authorized_keys", "/.htpasswd", "/.htaccess", "/.netrc",
76
+ "/.pgpass", "/.my.cnf", "/wp-content/uploads/",
77
+ "/.aws/credentials", "/.azure/accessTokens.json",
78
+ ],
79
+ };
80
+ const CATEGORY_ENUM = [
81
+ "admin", "config", "debug", "backup", "vcs", "dependency",
82
+ "well-known", "api-docs", "cloud", "ci-cd", "secrets",
83
+ ];
84
+ const DEBUG_ENDPOINTS = [
85
+ // Spring Boot Actuator
86
+ { path: "/actuator/env", severity: "CRITICAL", technology: "Spring Boot Actuator", description: "Exposes all environment variables including secrets, API keys, and database passwords" },
87
+ { path: "/actuator/heapdump", severity: "CRITICAL", technology: "Spring Boot Actuator", description: "JVM heap dump — contains in-memory secrets, session tokens, and credentials" },
88
+ { path: "/actuator/mappings", severity: "HIGH", technology: "Spring Boot Actuator", description: "Lists all URL mappings/routes — reveals hidden endpoints and internal APIs" },
89
+ { path: "/actuator/configprops", severity: "HIGH", technology: "Spring Boot Actuator", description: "Exposes all configuration properties including database URLs and credentials" },
90
+ { path: "/actuator/info", severity: "LOW", technology: "Spring Boot Actuator", description: "Application info endpoint — may reveal version, git commit, build info" },
91
+ { path: "/actuator/health", severity: "INFO", technology: "Spring Boot Actuator", description: "Health check — confirms Spring Boot, may reveal component status" },
92
+ { path: "/actuator/beans", severity: "MEDIUM", technology: "Spring Boot Actuator", description: "Lists all Spring beans — reveals application architecture and dependencies" },
93
+ { path: "/actuator/conditions", severity: "LOW", technology: "Spring Boot Actuator", description: "Auto-configuration conditions — reveals why beans were/weren't created" },
94
+ { path: "/actuator/threaddump", severity: "MEDIUM", technology: "Spring Boot Actuator", description: "Thread dump — reveals active operations, database connections, internal URLs" },
95
+ { path: "/actuator/loggers", severity: "MEDIUM", technology: "Spring Boot Actuator", description: "Logger configuration — can be used to enable debug logging and leak data" },
96
+ { path: "/actuator/metrics", severity: "LOW", technology: "Spring Boot Actuator", description: "Application metrics — reveals request counts, error rates, JVM stats" },
97
+ { path: "/actuator/scheduledtasks", severity: "LOW", technology: "Spring Boot Actuator", description: "Scheduled tasks — reveals cron jobs and internal processing logic" },
98
+ { path: "/actuator", severity: "MEDIUM", technology: "Spring Boot Actuator", description: "Actuator index — lists all enabled actuator endpoints" },
99
+ // Go debug
100
+ { path: "/debug/vars", severity: "MEDIUM", technology: "Go expvar", description: "Go runtime variables — memory stats, goroutine count, custom vars" },
101
+ { path: "/debug/pprof", severity: "HIGH", technology: "Go pprof", description: "CPU profiling endpoint — can be used to profile the application remotely" },
102
+ { path: "/debug/pprof/goroutine", severity: "MEDIUM", technology: "Go pprof", description: "Goroutine dump — reveals active operations and stack traces" },
103
+ // Apache
104
+ { path: "/server-info", severity: "HIGH", technology: "Apache", description: "Full Apache module list, configuration, and server info" },
105
+ { path: "/server-status", severity: "HIGH", technology: "Apache", description: "Active connections, request URIs, client IPs, worker status" },
106
+ // Flask/Werkzeug
107
+ { path: "/console", severity: "CRITICAL", technology: "Flask/Werkzeug", description: "Interactive Python shell — full RCE if no PIN or PIN is bypassed" },
108
+ // Tomcat
109
+ { path: "/manager/html", severity: "CRITICAL", technology: "Apache Tomcat", description: "Tomcat Manager — deploy/undeploy WARs, full server control (try admin:admin, tomcat:tomcat)" },
110
+ { path: "/host-manager/html", severity: "HIGH", technology: "Apache Tomcat", description: "Tomcat Host Manager — create/delete virtual hosts" },
111
+ // PHP
112
+ { path: "/phpinfo.php", severity: "HIGH", technology: "PHP", description: "Full PHP configuration — paths, modules, environment variables, server info" },
113
+ { path: "/info.php", severity: "HIGH", technology: "PHP", description: "PHP info page — same as phpinfo.php, alternate filename" },
114
+ // Node.js
115
+ { path: "/debug", severity: "MEDIUM", technology: "Node.js", description: "Node.js debug endpoint — may expose inspector or diagnostic data" },
116
+ // Nginx
117
+ { path: "/nginx_status", severity: "MEDIUM", technology: "Nginx", description: "Nginx stub status — active connections, request counts" },
118
+ { path: "/stub_status", severity: "MEDIUM", technology: "Nginx", description: "Nginx stub status — alternate path for connection stats" },
119
+ // Laravel
120
+ { path: "/telescope", severity: "HIGH", technology: "Laravel Telescope", description: "Debug dashboard — requests, queries, logs, mail, notifications, jobs" },
121
+ { path: "/_debugbar", severity: "HIGH", technology: "Laravel Debugbar", description: "Debug toolbar — queries, routes, views, session data" },
122
+ ];
123
+ // ─── API Version Paths ───
124
+ const API_VERSION_PATHS = [
125
+ "/api/v0", "/api/v1", "/api/v2", "/api/v3", "/api/v4",
126
+ "/api/beta", "/api/internal", "/api/legacy", "/api/dev", "/api/staging",
127
+ "/v0", "/v1", "/v2", "/v3",
128
+ "/api", "/rest", "/graphql", "/grpc",
129
+ ];
130
+ // ─── VCS/Secret Exposure Paths ───
131
+ const VCS_LEAK_PATHS = [
132
+ "/.git/config", "/.git/HEAD", "/.git/logs/HEAD", "/.git/index",
133
+ "/.svn/entries", "/.svn/wc.db",
134
+ "/.env", "/.DS_Store", "/Thumbs.db",
135
+ ];
136
+ // ─── Helpers ───
137
+ function stripTrailingSlash(url) {
138
+ return url.replace(/\/+$/, "");
139
+ }
140
+ async function probePath(baseUrl, path, method = "HEAD", followRedirect = true) {
141
+ try {
142
+ const url = `${stripTrailingSlash(baseUrl)}${path}`;
143
+ const res = await fetch(url, {
144
+ method,
145
+ headers: { "User-Agent": USER_AGENT },
146
+ redirect: followRedirect ? "follow" : "manual",
147
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT),
148
+ });
149
+ const contentType = res.headers.get("content-type") ?? "unknown";
150
+ const redirectUrl = res.headers.get("location") ?? undefined;
151
+ const allHeaders = {};
152
+ res.headers.forEach((v, k) => { allHeaders[k] = v; });
153
+ let body;
154
+ let size = 0;
155
+ if (method === "GET") {
156
+ body = await res.text();
157
+ size = body.length;
158
+ }
159
+ else {
160
+ const cl = res.headers.get("content-length");
161
+ size = cl ? parseInt(cl, 10) : 0;
162
+ }
163
+ return {
164
+ path,
165
+ status: res.status,
166
+ contentType,
167
+ size,
168
+ redirectUrl,
169
+ headers: allHeaders,
170
+ body,
171
+ };
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ }
177
+ async function batchProbe(baseUrl, paths, method = "HEAD", batchSize = BATCH_SIZE) {
178
+ const results = [];
179
+ for (let i = 0; i < paths.length; i += batchSize) {
180
+ await limiter.acquire();
181
+ const batch = paths.slice(i, i + batchSize);
182
+ const settled = await Promise.allSettled(batch.map((p) => probePath(baseUrl, p, method)));
183
+ for (const result of settled) {
184
+ if (result.status === "fulfilled" && result.value !== null) {
185
+ results.push(result.value);
186
+ }
187
+ }
188
+ }
189
+ return results;
190
+ }
191
+ // ─── Tool 1: path_sensitive ───
192
+ const pathSensitive = {
193
+ name: "path_sensitive",
194
+ description: "Sensitive path probing: 200+ paths organized by 11 categories (admin, config, debug, backup, vcs, dependency, " +
195
+ "well-known, api-docs, cloud, ci-cd, secrets). Sends HEAD requests in batches, reports status code, content-type, " +
196
+ "and response size for every non-404 hit. Powerful attack surface discovery tool.",
197
+ schema: {
198
+ url: z.string().url().describe("Base URL to probe sensitive paths"),
199
+ categories: z
200
+ .array(z.enum(CATEGORY_ENUM))
201
+ .optional()
202
+ .describe("Categories to probe (default: all)"),
203
+ },
204
+ async execute(args) {
205
+ const baseUrl = stripTrailingSlash(args.url);
206
+ const selectedCategories = args.categories ?? [...CATEGORY_ENUM];
207
+ const cacheKey = `path_sensitive:${baseUrl}:${selectedCategories.sort().join(",")}`;
208
+ const cached = cache.get(cacheKey);
209
+ if (cached)
210
+ return json(cached);
211
+ // Collect all paths from selected categories
212
+ const pathsByCategory = {};
213
+ const allPaths = [];
214
+ for (const cat of selectedCategories) {
215
+ const paths = SENSITIVE_PATHS[cat];
216
+ if (paths) {
217
+ pathsByCategory[cat] = paths;
218
+ allPaths.push(...paths);
219
+ }
220
+ }
221
+ // Phase 1: HEAD requests for all paths
222
+ const headResults = await batchProbe(baseUrl, allPaths, "HEAD");
223
+ // Filter: only non-404 results
224
+ const interestingResults = headResults.filter((r) => r.status !== 404);
225
+ // Phase 2: GET request for interesting paths to check content
226
+ const interestingPaths = interestingResults
227
+ .filter((r) => r.status === 200 || r.status === 301 || r.status === 302)
228
+ .map((r) => r.path);
229
+ const getResults = await batchProbe(baseUrl, interestingPaths, "GET");
230
+ const getMap = new Map(getResults.map((r) => [r.path, r]));
231
+ // Build categorized results
232
+ const findings = {};
233
+ let totalHits = 0;
234
+ for (const cat of selectedCategories) {
235
+ const catPaths = pathsByCategory[cat] ?? [];
236
+ const catFindings = [];
237
+ for (const path of catPaths) {
238
+ const headResult = interestingResults.find((r) => r.path === path);
239
+ if (!headResult)
240
+ continue;
241
+ const getResult = getMap.get(path);
242
+ const finding = {
243
+ path,
244
+ status: headResult.status,
245
+ contentType: headResult.contentType,
246
+ size: getResult?.size ?? headResult.size,
247
+ };
248
+ if (headResult.redirectUrl) {
249
+ finding.redirectUrl = headResult.redirectUrl;
250
+ }
251
+ catFindings.push(finding);
252
+ totalHits++;
253
+ }
254
+ if (catFindings.length > 0) {
255
+ findings[cat] = catFindings;
256
+ }
257
+ }
258
+ const result = {
259
+ target: baseUrl,
260
+ totalPathsProbed: allPaths.length,
261
+ totalHits,
262
+ categoriesProbed: selectedCategories,
263
+ findings,
264
+ };
265
+ cache.set(cacheKey, result);
266
+ return json(result);
267
+ },
268
+ };
269
+ // ─── Tool 2: path_robots ───
270
+ const pathRobots = {
271
+ name: "path_robots",
272
+ description: "Fetch and parse robots.txt, sitemap.xml, security.txt, and humans.txt from a target URL. " +
273
+ "Disallow entries in robots.txt are an attack surface roadmap. Sitemap reveals page structure. " +
274
+ "Security.txt reveals bug bounty programs. Humans.txt reveals developer names and technologies.",
275
+ schema: {
276
+ url: z.string().url().describe("Base URL to fetch robots/sitemap/security files"),
277
+ },
278
+ async execute(args) {
279
+ const baseUrl = stripTrailingSlash(args.url);
280
+ const cacheKey = `path_robots:${baseUrl}`;
281
+ const cached = cache.get(cacheKey);
282
+ if (cached)
283
+ return json(cached);
284
+ const result = { target: baseUrl };
285
+ // Fetch all four files concurrently
286
+ await limiter.acquire();
287
+ const [robotsRes, sitemapRes, securityRes, humansRes] = await Promise.allSettled([
288
+ probePath(baseUrl, "/robots.txt", "GET"),
289
+ probePath(baseUrl, "/sitemap.xml", "GET"),
290
+ probePath(baseUrl, "/.well-known/security.txt", "GET"),
291
+ probePath(baseUrl, "/humans.txt", "GET"),
292
+ ]);
293
+ // Parse robots.txt
294
+ if (robotsRes.status === "fulfilled" && robotsRes.value?.status === 200 && robotsRes.value.body) {
295
+ const body = robotsRes.value.body;
296
+ const lines = body.split("\n").map((l) => l.trim());
297
+ const disallow = [];
298
+ const allow = [];
299
+ const sitemaps = [];
300
+ let currentAgent = "*";
301
+ const agents = {};
302
+ for (const line of lines) {
303
+ if (line.startsWith("#") || line === "")
304
+ continue;
305
+ const [directive, ...valueParts] = line.split(":");
306
+ const value = valueParts.join(":").trim();
307
+ switch (directive.toLowerCase()) {
308
+ case "user-agent":
309
+ currentAgent = value;
310
+ if (!agents[currentAgent])
311
+ agents[currentAgent] = { disallow: [], allow: [] };
312
+ break;
313
+ case "disallow":
314
+ if (value) {
315
+ disallow.push(value);
316
+ if (!agents[currentAgent])
317
+ agents[currentAgent] = { disallow: [], allow: [] };
318
+ agents[currentAgent].disallow.push(value);
319
+ }
320
+ break;
321
+ case "allow":
322
+ if (value) {
323
+ allow.push(value);
324
+ if (!agents[currentAgent])
325
+ agents[currentAgent] = { disallow: [], allow: [] };
326
+ agents[currentAgent].allow.push(value);
327
+ }
328
+ break;
329
+ case "sitemap":
330
+ if (value)
331
+ sitemaps.push(value);
332
+ break;
333
+ }
334
+ }
335
+ result.robots = {
336
+ found: true,
337
+ disallowedPaths: Array.from(new Set(disallow)),
338
+ allowedPaths: Array.from(new Set(allow)),
339
+ sitemapUrls: sitemaps,
340
+ userAgents: agents,
341
+ totalDisallowed: new Set(disallow).size,
342
+ rawLength: body.length,
343
+ };
344
+ }
345
+ else {
346
+ result.robots = { found: false };
347
+ }
348
+ // Parse sitemap.xml
349
+ if (sitemapRes.status === "fulfilled" && sitemapRes.value?.status === 200 && sitemapRes.value.body) {
350
+ const body = sitemapRes.value.body;
351
+ const urls = [];
352
+ const lastmods = [];
353
+ // Simple XML parsing for <loc> and <lastmod> tags
354
+ const locMatches = Array.from(body.matchAll(/<loc>\s*(.*?)\s*<\/loc>/gi));
355
+ for (const match of locMatches) {
356
+ urls.push(match[1]);
357
+ }
358
+ const lastmodMatches = Array.from(body.matchAll(/<lastmod>\s*(.*?)\s*<\/lastmod>/gi));
359
+ for (const match of lastmodMatches) {
360
+ lastmods.push(match[1]);
361
+ }
362
+ // Check for sitemap index (contains other sitemaps)
363
+ const isSitemapIndex = body.includes("<sitemapindex");
364
+ result.sitemap = {
365
+ found: true,
366
+ isSitemapIndex,
367
+ totalUrls: urls.length,
368
+ urls: urls.slice(0, 100), // Limit to first 100
369
+ lastModified: lastmods.slice(0, 20),
370
+ truncated: urls.length > 100,
371
+ rawLength: body.length,
372
+ };
373
+ }
374
+ else {
375
+ result.sitemap = { found: false };
376
+ }
377
+ // Parse security.txt
378
+ if (securityRes.status === "fulfilled" && securityRes.value?.status === 200 && securityRes.value.body) {
379
+ const body = securityRes.value.body;
380
+ const lines = body.split("\n").map((l) => l.trim());
381
+ const security = { found: true };
382
+ for (const line of lines) {
383
+ if (line.startsWith("#") || line === "")
384
+ continue;
385
+ const colonIdx = line.indexOf(":");
386
+ if (colonIdx === -1)
387
+ continue;
388
+ const key = line.substring(0, colonIdx).trim().toLowerCase();
389
+ const value = line.substring(colonIdx + 1).trim();
390
+ switch (key) {
391
+ case "contact":
392
+ security.contact = security.contact ? `${security.contact}, ${value}` : value;
393
+ break;
394
+ case "encryption":
395
+ security.encryption = value;
396
+ break;
397
+ case "acknowledgments":
398
+ case "acknowledgements":
399
+ security.acknowledgments = value;
400
+ break;
401
+ case "preferred-languages":
402
+ security.preferredLanguages = value;
403
+ break;
404
+ case "canonical":
405
+ security.canonical = value;
406
+ break;
407
+ case "policy":
408
+ security.policy = value;
409
+ break;
410
+ case "hiring":
411
+ security.hiring = value;
412
+ break;
413
+ case "expires":
414
+ security.expires = value;
415
+ break;
416
+ }
417
+ }
418
+ security.hasBugBounty = !!(security.policy || security.acknowledgments);
419
+ security.rawLength = body.length;
420
+ result.security = security;
421
+ }
422
+ else {
423
+ result.security = { found: false };
424
+ }
425
+ // Parse humans.txt
426
+ if (humansRes.status === "fulfilled" && humansRes.value?.status === 200 && humansRes.value.body) {
427
+ const body = humansRes.value.body;
428
+ const lines = body.split("\n").map((l) => l.trim()).filter((l) => l !== "");
429
+ result.humans = {
430
+ found: true,
431
+ content: lines.slice(0, 50),
432
+ rawLength: body.length,
433
+ };
434
+ }
435
+ else {
436
+ result.humans = { found: false };
437
+ }
438
+ cache.set(cacheKey, result);
439
+ return json(result);
440
+ },
441
+ };
442
+ // ─── Tool 3: path_git_leak ───
443
+ const pathGitLeak = {
444
+ name: "path_git_leak",
445
+ description: "Version control and secret exposure detection. Checks for exposed .git/config, .git/HEAD, .git/logs/HEAD, " +
446
+ ".git/index, .svn/entries, .svn/wc.db, .env, .DS_Store, and Thumbs.db. Parses .git/config to extract remote " +
447
+ "URLs and author info. Detects if a full git repo is cloneable via exposed .git/index.",
448
+ schema: {
449
+ url: z.string().url().describe("Base URL to check for VCS/secret exposure"),
450
+ },
451
+ async execute(args) {
452
+ const baseUrl = stripTrailingSlash(args.url);
453
+ const cacheKey = `path_git_leak:${baseUrl}`;
454
+ const cached = cache.get(cacheKey);
455
+ if (cached)
456
+ return json(cached);
457
+ const findings = [];
458
+ // Probe all VCS/secret paths with GET
459
+ const results = await batchProbe(baseUrl, VCS_LEAK_PATHS, "GET");
460
+ for (const r of results) {
461
+ if (r.status !== 200)
462
+ continue;
463
+ const finding = {
464
+ path: r.path,
465
+ status: r.status,
466
+ contentType: r.contentType,
467
+ size: r.size,
468
+ severity: "HIGH",
469
+ };
470
+ // Parse .git/config (INI format)
471
+ if (r.path === "/.git/config" && r.body) {
472
+ finding.severity = "CRITICAL";
473
+ finding.description = "Git configuration file exposed — contains remote repository URL and author info";
474
+ const remotes = [];
475
+ const lines = r.body.split("\n");
476
+ let currentRemote = null;
477
+ for (const line of lines) {
478
+ const remoteMatch = line.match(/^\[remote\s+"(.+)"\]/);
479
+ if (remoteMatch) {
480
+ currentRemote = remoteMatch[1];
481
+ continue;
482
+ }
483
+ if (currentRemote) {
484
+ const urlMatch = line.match(/^\s*url\s*=\s*(.+)/);
485
+ if (urlMatch) {
486
+ remotes.push({ name: currentRemote, url: urlMatch[1].trim() });
487
+ currentRemote = null;
488
+ }
489
+ }
490
+ if (line.startsWith("[") && !line.startsWith("[remote")) {
491
+ currentRemote = null;
492
+ }
493
+ }
494
+ // Extract branch info
495
+ const branches = [];
496
+ let currentBranch = null;
497
+ let branchRemote = "";
498
+ let branchMerge = "";
499
+ for (const line of lines) {
500
+ const branchMatch = line.match(/^\[branch\s+"(.+)"\]/);
501
+ if (branchMatch) {
502
+ if (currentBranch) {
503
+ branches.push({ name: currentBranch, remote: branchRemote, merge: branchMerge });
504
+ }
505
+ currentBranch = branchMatch[1];
506
+ branchRemote = "";
507
+ branchMerge = "";
508
+ continue;
509
+ }
510
+ if (currentBranch) {
511
+ const rm = line.match(/^\s*remote\s*=\s*(.+)/);
512
+ if (rm)
513
+ branchRemote = rm[1].trim();
514
+ const mg = line.match(/^\s*merge\s*=\s*(.+)/);
515
+ if (mg)
516
+ branchMerge = mg[1].trim();
517
+ }
518
+ }
519
+ if (currentBranch) {
520
+ branches.push({ name: currentBranch, remote: branchRemote, merge: branchMerge });
521
+ }
522
+ finding.parsed = { remotes, branches };
523
+ }
524
+ // Parse .git/HEAD
525
+ if (r.path === "/.git/HEAD" && r.body) {
526
+ finding.severity = "HIGH";
527
+ finding.description = "Git HEAD exposed — reveals current branch name";
528
+ const refMatch = r.body.trim().match(/^ref:\s*(.+)/);
529
+ if (refMatch) {
530
+ finding.parsed = { ref: refMatch[1], branch: refMatch[1].replace("refs/heads/", "") };
531
+ }
532
+ else {
533
+ finding.parsed = { detachedHead: r.body.trim() };
534
+ }
535
+ }
536
+ // Parse .git/logs/HEAD
537
+ if (r.path === "/.git/logs/HEAD" && r.body) {
538
+ finding.severity = "CRITICAL";
539
+ finding.description = "Git reflog exposed — reveals commit history with author emails and timestamps";
540
+ const logLines = r.body.trim().split("\n").slice(-20); // Last 20 entries
541
+ const commits = [];
542
+ for (const line of logLines) {
543
+ // Format: old_hash new_hash Author Name <email> timestamp timezone message
544
+ const logMatch = line.match(/^([0-9a-f]+)\s+([0-9a-f]+)\s+(.+?)\s+<(.+?)>\s+(\d+)\s+([+-]\d{4})\t(.+)$/);
545
+ if (logMatch) {
546
+ commits.push({
547
+ from: logMatch[1].substring(0, 8),
548
+ to: logMatch[2].substring(0, 8),
549
+ author: logMatch[3],
550
+ email: logMatch[4],
551
+ timestamp: new Date(parseInt(logMatch[5], 10) * 1000).toISOString(),
552
+ message: logMatch[7],
553
+ });
554
+ }
555
+ }
556
+ finding.parsed = { recentCommits: commits, totalEntries: r.body.trim().split("\n").length };
557
+ }
558
+ // .git/index — binary file, just flag it
559
+ if (r.path === "/.git/index" && r.body) {
560
+ finding.severity = "CRITICAL";
561
+ finding.description = "Git index file exposed — full repository may be cloneable with git-dumper or similar tools";
562
+ finding.parsed = {
563
+ cloneable: true,
564
+ recommendation: "Use git-dumper or GitTools to attempt full repository download",
565
+ };
566
+ }
567
+ // .svn/entries
568
+ if (r.path === "/.svn/entries" && r.body) {
569
+ finding.severity = "HIGH";
570
+ finding.description = "SVN entries file exposed — reveals repository structure and revision info";
571
+ const svnLines = r.body.split("\n").slice(0, 20);
572
+ finding.parsed = { firstLines: svnLines };
573
+ }
574
+ // .svn/wc.db
575
+ if (r.path === "/.svn/wc.db" && r.body) {
576
+ finding.severity = "CRITICAL";
577
+ finding.description = "SVN working copy database exposed — SQLite DB with full repo metadata";
578
+ finding.parsed = { isSQLite: r.body.startsWith("SQLite"), size: r.size };
579
+ }
580
+ // .env
581
+ if (r.path === "/.env" && r.body) {
582
+ finding.severity = "CRITICAL";
583
+ finding.description = "Environment file exposed — likely contains API keys, database credentials, and secrets";
584
+ // Extract variable names only, not values (for safety)
585
+ const varNames = [];
586
+ const sensitiveVars = [];
587
+ const lines = r.body.split("\n");
588
+ for (const line of lines) {
589
+ if (line.startsWith("#") || line.trim() === "")
590
+ continue;
591
+ const eqIdx = line.indexOf("=");
592
+ if (eqIdx > 0) {
593
+ const varName = line.substring(0, eqIdx).trim();
594
+ varNames.push(varName);
595
+ const lower = varName.toLowerCase();
596
+ if (lower.includes("key") || lower.includes("secret") || lower.includes("password") ||
597
+ lower.includes("token") || lower.includes("auth") || lower.includes("credential") ||
598
+ lower.includes("db_") || lower.includes("database") || lower.includes("api")) {
599
+ sensitiveVars.push(varName);
600
+ }
601
+ }
602
+ }
603
+ finding.parsed = { variableNames: varNames, sensitiveVariables: sensitiveVars, totalVars: varNames.length };
604
+ }
605
+ // .DS_Store
606
+ if (r.path === "/.DS_Store" && r.body) {
607
+ finding.severity = "MEDIUM";
608
+ finding.description = "macOS .DS_Store file exposed — reveals directory listing and file names";
609
+ finding.parsed = { size: r.size };
610
+ }
611
+ // Thumbs.db
612
+ if (r.path === "/Thumbs.db" && r.body) {
613
+ finding.severity = "LOW";
614
+ finding.description = "Windows Thumbs.db file exposed — reveals file thumbnails and names";
615
+ finding.parsed = { size: r.size };
616
+ }
617
+ findings.push(finding);
618
+ }
619
+ const result = {
620
+ target: baseUrl,
621
+ totalChecked: VCS_LEAK_PATHS.length,
622
+ totalExposed: findings.length,
623
+ findings,
624
+ overallRisk: findings.some((f) => f.severity === "CRITICAL")
625
+ ? "CRITICAL"
626
+ : findings.some((f) => f.severity === "HIGH")
627
+ ? "HIGH"
628
+ : findings.length > 0
629
+ ? "MEDIUM"
630
+ : "NONE",
631
+ };
632
+ cache.set(cacheKey, result);
633
+ return json(result);
634
+ },
635
+ };
636
+ // ─── Tool 4: path_debug ───
637
+ const pathDebug = {
638
+ name: "path_debug",
639
+ description: "Debug and admin endpoint detection with severity ratings. Checks Spring Boot Actuator, Go pprof, " +
640
+ "Apache server-info/status, Flask/Werkzeug console, Tomcat Manager, PHP info, Nginx status, and Laravel " +
641
+ "Telescope/Debugbar. Each finding includes severity (CRITICAL/HIGH/MEDIUM/LOW/INFO) and attacker impact description.",
642
+ schema: {
643
+ url: z.string().url().describe("Base URL to check for debug/admin endpoints"),
644
+ },
645
+ async execute(args) {
646
+ const baseUrl = stripTrailingSlash(args.url);
647
+ const cacheKey = `path_debug:${baseUrl}`;
648
+ const cached = cache.get(cacheKey);
649
+ if (cached)
650
+ return json(cached);
651
+ const allDebugPaths = DEBUG_ENDPOINTS.map((e) => e.path);
652
+ const endpointMap = new Map(DEBUG_ENDPOINTS.map((e) => [e.path, e]));
653
+ // Probe all debug endpoints
654
+ const results = await batchProbe(baseUrl, allDebugPaths, "GET", 15);
655
+ const findings = [];
656
+ for (const r of results) {
657
+ // Skip 404s and connection errors
658
+ if (r.status === 404)
659
+ continue;
660
+ const endpoint = endpointMap.get(r.path);
661
+ if (!endpoint)
662
+ continue;
663
+ const finding = {
664
+ path: r.path,
665
+ status: r.status,
666
+ contentType: r.contentType,
667
+ size: r.size,
668
+ severity: endpoint.severity,
669
+ technology: endpoint.technology,
670
+ description: endpoint.description,
671
+ accessible: r.status === 200,
672
+ };
673
+ // Special handling for specific endpoints
674
+ if (r.path === "/console" && r.status === 200 && r.body) {
675
+ finding.werkzeugPinRequired = r.body.includes("PIN") || r.body.includes("pin");
676
+ finding.interactiveShell = r.body.includes("interactive") || r.body.includes("console");
677
+ }
678
+ if (r.path === "/manager/html" && r.status === 401) {
679
+ finding.description = "Tomcat Manager exists but requires authentication — try default credentials";
680
+ finding.defaultCredentials = ["admin:admin", "tomcat:tomcat", "admin:tomcat", "tomcat:s3cret", "admin:"];
681
+ }
682
+ if (r.path === "/actuator" && r.status === 200 && r.body) {
683
+ // Parse actuator index to find enabled endpoints
684
+ try {
685
+ const actuatorData = JSON.parse(r.body);
686
+ if (actuatorData._links) {
687
+ finding.enabledEndpoints = Object.keys(actuatorData._links);
688
+ }
689
+ }
690
+ catch {
691
+ // Not JSON, might be behind auth or different format
692
+ }
693
+ }
694
+ if (r.path === "/actuator/env" && r.status === 200) {
695
+ finding.criticalNote = "Environment variables exposed — likely contains secrets, API keys, and database passwords";
696
+ }
697
+ if (r.path === "/actuator/heapdump" && r.status === 200) {
698
+ finding.criticalNote = "JVM heap dump downloadable — use Eclipse MAT or jhat to extract secrets from memory";
699
+ }
700
+ // Auth state detection
701
+ if (r.status === 401) {
702
+ finding.authRequired = true;
703
+ finding.wwwAuthenticate = r.headers?.["www-authenticate"] ?? null;
704
+ }
705
+ else if (r.status === 403) {
706
+ finding.forbidden = true;
707
+ finding.note = "Endpoint exists but access is denied — may be bypassable via header manipulation or IP spoofing";
708
+ }
709
+ findings.push(finding);
710
+ }
711
+ // Sort by severity
712
+ const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, INFO: 4 };
713
+ findings.sort((a, b) => (severityOrder[a.severity] ?? 5) - (severityOrder[b.severity] ?? 5));
714
+ const result = {
715
+ target: baseUrl,
716
+ totalChecked: allDebugPaths.length,
717
+ totalFound: findings.length,
718
+ criticalCount: findings.filter((f) => f.severity === "CRITICAL").length,
719
+ highCount: findings.filter((f) => f.severity === "HIGH").length,
720
+ mediumCount: findings.filter((f) => f.severity === "MEDIUM").length,
721
+ lowCount: findings.filter((f) => f.severity === "LOW").length,
722
+ infoCount: findings.filter((f) => f.severity === "INFO").length,
723
+ findings,
724
+ overallRisk: findings.some((f) => f.severity === "CRITICAL" && f.accessible)
725
+ ? "CRITICAL"
726
+ : findings.some((f) => f.severity === "HIGH" && f.accessible)
727
+ ? "HIGH"
728
+ : findings.some((f) => f.severity === "MEDIUM")
729
+ ? "MEDIUM"
730
+ : findings.length > 0
731
+ ? "LOW"
732
+ : "NONE",
733
+ };
734
+ cache.set(cacheKey, result);
735
+ return json(result);
736
+ },
737
+ };
738
+ // ─── Tool 5: path_api_version ───
739
+ const pathApiVersion = {
740
+ name: "path_api_version",
741
+ description: "API version probing: systematically tests version prefixes (/api/v0-v4, /api/beta, /api/internal, " +
742
+ "/api/legacy, /api/dev, /api/staging, /v0-v3, /api, /rest, /graphql, /grpc) and compares responses. " +
743
+ "Detects auth requirements per version — older versions often have weaker auth. Identifies response format differences.",
744
+ schema: {
745
+ url: z.string().url().describe("Base URL to probe API version endpoints"),
746
+ },
747
+ async execute(args) {
748
+ const baseUrl = stripTrailingSlash(args.url);
749
+ const cacheKey = `path_api_version:${baseUrl}`;
750
+ const cached = cache.get(cacheKey);
751
+ if (cached)
752
+ return json(cached);
753
+ // Probe all API version paths with GET
754
+ const results = await batchProbe(baseUrl, API_VERSION_PATHS, "GET", 15);
755
+ const endpoints = [];
756
+ for (const r of results) {
757
+ if (r.status === 404)
758
+ continue;
759
+ const endpoint = {
760
+ path: r.path,
761
+ status: r.status,
762
+ contentType: r.contentType,
763
+ size: r.size,
764
+ };
765
+ // Auth requirement detection
766
+ if (r.status === 401) {
767
+ endpoint.authRequired = true;
768
+ endpoint.authType = r.headers?.["www-authenticate"] ?? "unknown";
769
+ }
770
+ else if (r.status === 403) {
771
+ endpoint.authRequired = true;
772
+ endpoint.authType = "forbidden";
773
+ }
774
+ else if (r.status === 200) {
775
+ endpoint.authRequired = false;
776
+ }
777
+ // Response format detection
778
+ const ct = (r.contentType ?? "").toLowerCase();
779
+ if (ct.includes("json")) {
780
+ endpoint.responseFormat = "JSON";
781
+ }
782
+ else if (ct.includes("xml")) {
783
+ endpoint.responseFormat = "XML";
784
+ }
785
+ else if (ct.includes("html")) {
786
+ endpoint.responseFormat = "HTML";
787
+ }
788
+ else if (ct.includes("grpc")) {
789
+ endpoint.responseFormat = "gRPC";
790
+ }
791
+ else if (ct.includes("protobuf")) {
792
+ endpoint.responseFormat = "Protobuf";
793
+ }
794
+ else {
795
+ endpoint.responseFormat = ct || "unknown";
796
+ }
797
+ // Extract relevant headers
798
+ const relevantHeaders = {};
799
+ const interestingHeaders = [
800
+ "x-powered-by", "server", "x-api-version", "api-version",
801
+ "x-ratelimit-limit", "x-ratelimit-remaining", "x-request-id",
802
+ "access-control-allow-origin", "x-frame-options", "content-security-policy",
803
+ "x-content-type-options", "strict-transport-security",
804
+ ];
805
+ for (const h of interestingHeaders) {
806
+ if (r.headers?.[h]) {
807
+ relevantHeaders[h] = r.headers[h];
808
+ }
809
+ }
810
+ if (Object.keys(relevantHeaders).length > 0) {
811
+ endpoint.headers = relevantHeaders;
812
+ }
813
+ // Check for common API patterns in body
814
+ if (r.body && r.status === 200) {
815
+ const bodyLower = r.body.toLowerCase();
816
+ if (bodyLower.includes("swagger") || bodyLower.includes("openapi")) {
817
+ endpoint.hasApiDocs = true;
818
+ }
819
+ if (bodyLower.includes("graphql") || bodyLower.includes("__schema")) {
820
+ endpoint.graphqlDetected = true;
821
+ }
822
+ if (bodyLower.includes("deprecated")) {
823
+ endpoint.deprecated = true;
824
+ }
825
+ }
826
+ endpoints.push(endpoint);
827
+ }
828
+ // Auth comparison analysis
829
+ const authAnalysis = {
830
+ unauthenticatedEndpoints: endpoints.filter((e) => e.authRequired === false).map((e) => e.path),
831
+ authenticatedEndpoints: endpoints.filter((e) => e.authRequired === true).map((e) => e.path),
832
+ };
833
+ // Detect auth weaknesses: if newer versions require auth but older don't
834
+ const versionedEndpoints = endpoints.filter((e) => e.path.match(/\/(v\d|api\/v\d)/));
835
+ if (versionedEndpoints.length > 1) {
836
+ const noAuth = versionedEndpoints.filter((e) => e.authRequired === false);
837
+ const withAuth = versionedEndpoints.filter((e) => e.authRequired === true);
838
+ if (noAuth.length > 0 && withAuth.length > 0) {
839
+ authAnalysis.authWeakness = true;
840
+ authAnalysis.weaknessDescription =
841
+ "Some API versions do not require authentication while others do — older/unprotected versions may allow unauthorized access";
842
+ authAnalysis.unprotectedVersions = noAuth.map((e) => e.path);
843
+ authAnalysis.protectedVersions = withAuth.map((e) => e.path);
844
+ }
845
+ }
846
+ // Internal/dev endpoint detection
847
+ const internalEndpoints = endpoints.filter((e) => e.path.includes("internal") ||
848
+ e.path.includes("dev") ||
849
+ e.path.includes("staging") ||
850
+ e.path.includes("legacy") ||
851
+ e.path.includes("beta"));
852
+ const result = {
853
+ target: baseUrl,
854
+ totalChecked: API_VERSION_PATHS.length,
855
+ totalFound: endpoints.length,
856
+ endpoints,
857
+ authAnalysis,
858
+ internalEndpoints: internalEndpoints.length > 0 ? internalEndpoints : undefined,
859
+ summary: {
860
+ apiVersionsFound: endpoints.filter((e) => e.path.match(/\/v\d/)).map((e) => e.path),
861
+ responseFormats: Array.from(new Set(endpoints.map((e) => e.responseFormat))),
862
+ hasGraphQL: endpoints.some((e) => e.path.includes("graphql") || e.graphqlDetected),
863
+ hasREST: endpoints.some((e) => e.path.includes("rest") || e.path.includes("api")),
864
+ },
865
+ };
866
+ cache.set(cacheKey, result);
867
+ return json(result);
868
+ },
869
+ };
870
+ // ─── Export All Path Tools ───
871
+ export const pathTools = [
872
+ pathSensitive,
873
+ pathRobots,
874
+ pathGitLeak,
875
+ pathDebug,
876
+ pathApiVersion,
877
+ ];
878
+ //# sourceMappingURL=index.js.map