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,818 @@
1
+ import { z } from "zod";
2
+ import { RateLimiter } from "../utils/rate-limiter.js";
3
+ import { TTLCache } from "../utils/cache.js";
4
+ import { requireApiKey } from "../utils/require-key.js";
5
+ import { json } from "../types/index.js";
6
+ import * as dns from "node:dns/promises";
7
+ import { randomUUID } from "node:crypto";
8
+ const limiter = new RateLimiter(500);
9
+ const cache = new TTLCache(600_000);
10
+ // ─── Helpers ───
11
+ const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
12
+ function categorizeHostname(hostname) {
13
+ const h = hostname.toLowerCase();
14
+ if (/^(dev|development)[\.-]/.test(h) || /[\.-](dev|development)\./.test(h))
15
+ return "development";
16
+ if (/^(staging|stage|stg)[\.-]/.test(h) || /[\.-](staging|stage|stg)\./.test(h))
17
+ return "staging";
18
+ if (/^(admin|administrator|panel)[\.-]/.test(h))
19
+ return "admin";
20
+ if (/^(api|rest|graphql)[\.-]/.test(h))
21
+ return "api";
22
+ if (/^(vpn|gateway|gw|tunnel)[\.-]/.test(h))
23
+ return "vpn";
24
+ if (/^(mail|smtp|pop|imap|mx|webmail)[\.-]/.test(h))
25
+ return "mail";
26
+ if (/^(internal|intranet|corp)[\.-]/.test(h))
27
+ return "internal";
28
+ if (/^(test|testing|qa|uat)[\.-]/.test(h))
29
+ return "testing";
30
+ if (/^(cdn|static|assets|media|img|images)[\.-]/.test(h))
31
+ return "cdn";
32
+ if (/^(ci|cd|jenkins|gitlab|github|build|deploy)[\.-]/.test(h))
33
+ return "ci-cd";
34
+ if (/^(db|database|mysql|postgres|redis|mongo|elastic)[\.-]/.test(h))
35
+ return "database";
36
+ if (/^(ns\d*|dns\d*)\./.test(h))
37
+ return "nameserver";
38
+ if (/^(www|web)\./.test(h))
39
+ return "web";
40
+ return "other";
41
+ }
42
+ // ─── Tool 1: enum_subdomains ───
43
+ async function enumSubdomains(args, ctx) {
44
+ const domain = args.domain;
45
+ const source = args.source ?? "auto";
46
+ const cacheKey = `enum_subdomains:${domain}:${source}`;
47
+ const cached = cache.get(cacheKey);
48
+ if (cached)
49
+ return json(cached);
50
+ let subdomainList = [];
51
+ let usedSource;
52
+ // Determine which source to use
53
+ const useSecurityTrails = source === "securitytrails" || (source === "auto" && ctx.config.securitytrailsApiKey);
54
+ if (useSecurityTrails) {
55
+ const apiKey = requireApiKey(ctx.config.securitytrailsApiKey, "SecurityTrails", "SECURITYTRAILS_API_KEY");
56
+ usedSource = "securitytrails";
57
+ await limiter.acquire();
58
+ try {
59
+ const response = await fetch(`https://api.securitytrails.com/v1/domain/${encodeURIComponent(domain)}/subdomains`, {
60
+ headers: { APIKEY: apiKey },
61
+ signal: AbortSignal.timeout(15_000),
62
+ });
63
+ if (!response.ok) {
64
+ const errText = await response.text();
65
+ return json({
66
+ error: `SecurityTrails API error: ${response.status}`,
67
+ details: errText,
68
+ });
69
+ }
70
+ const data = await response.json();
71
+ const subs = (data.subdomains ?? []);
72
+ subdomainList = subs.map((sub) => `${sub}.${domain}`);
73
+ }
74
+ catch (err) {
75
+ return json({
76
+ error: `SecurityTrails request failed: ${err.message}`,
77
+ });
78
+ }
79
+ }
80
+ else {
81
+ // HackerTarget fallback
82
+ usedSource = "hackertarget";
83
+ await limiter.acquire();
84
+ try {
85
+ const response = await fetch(`https://api.hackertarget.com/hostsearch/?q=${encodeURIComponent(domain)}`, {
86
+ signal: AbortSignal.timeout(15_000),
87
+ });
88
+ if (!response.ok) {
89
+ return json({
90
+ error: `HackerTarget API error: ${response.status}`,
91
+ });
92
+ }
93
+ const text = await response.text();
94
+ if (text.startsWith("error") || text.startsWith("API count exceeded")) {
95
+ return json({
96
+ domain,
97
+ error: text.trim(),
98
+ subdomains: [],
99
+ total: 0,
100
+ });
101
+ }
102
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
103
+ for (const line of lines) {
104
+ const parts = line.split(",");
105
+ if (parts.length >= 1 && parts[0]) {
106
+ subdomainList.push(parts[0].trim());
107
+ }
108
+ }
109
+ }
110
+ catch (err) {
111
+ return json({
112
+ error: `HackerTarget request failed: ${err.message}`,
113
+ });
114
+ }
115
+ }
116
+ // Resolve IPs and categorize each subdomain
117
+ const subdomains = [];
118
+ const categories = {};
119
+ for (const hostname of subdomainList) {
120
+ let ips = [];
121
+ try {
122
+ await limiter.acquire();
123
+ ips = await dns.resolve4(hostname);
124
+ }
125
+ catch {
126
+ // DNS resolution failed — no IPs
127
+ }
128
+ const category = categorizeHostname(hostname);
129
+ categories[category] = (categories[category] ?? 0) + 1;
130
+ subdomains.push({ hostname, ips, category });
131
+ }
132
+ const result = {
133
+ domain,
134
+ source: usedSource,
135
+ subdomains,
136
+ total: subdomains.length,
137
+ categories,
138
+ intelligence: {
139
+ note: `Discovered ${subdomains.length} subdomains via ${usedSource}.`,
140
+ interestingCategories: Object.entries(categories)
141
+ .filter(([cat]) => ["admin", "staging", "development", "internal", "database", "vpn", "ci-cd", "testing"].includes(cat))
142
+ .map(([cat, count]) => `${cat}: ${count}`),
143
+ },
144
+ };
145
+ cache.set(cacheKey, result);
146
+ return json(result);
147
+ }
148
+ // ─── Tool 2: enum_asn_neighbors ───
149
+ async function enumAsnNeighbors(args) {
150
+ const target = args.target;
151
+ const limit = args.limit ?? 100;
152
+ const cacheKey = `enum_asn_neighbors:${target}:${limit}`;
153
+ const cached = cache.get(cacheKey);
154
+ if (cached)
155
+ return json(cached);
156
+ // Resolve domain to IP if needed
157
+ let ip;
158
+ if (IP_REGEX.test(target)) {
159
+ ip = target;
160
+ }
161
+ else {
162
+ try {
163
+ await limiter.acquire();
164
+ const ips = await dns.resolve4(target);
165
+ if (!ips || ips.length === 0) {
166
+ return json({ error: `Could not resolve ${target} to an IP address` });
167
+ }
168
+ ip = ips[0];
169
+ }
170
+ catch (err) {
171
+ return json({ error: `DNS resolution failed for ${target}: ${err.message}` });
172
+ }
173
+ }
174
+ // Get ASN for this IP
175
+ await limiter.acquire();
176
+ try {
177
+ const response = await fetch(`https://api.hackertarget.com/aslookup/?q=${encodeURIComponent(ip)}`, {
178
+ signal: AbortSignal.timeout(15_000),
179
+ });
180
+ if (!response.ok) {
181
+ return json({ error: `HackerTarget ASN lookup error: ${response.status}` });
182
+ }
183
+ const text = await response.text();
184
+ if (text.startsWith("error") || text.startsWith("API count exceeded")) {
185
+ return json({ target, ip, error: text.trim() });
186
+ }
187
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
188
+ if (lines.length === 0) {
189
+ return json({ target, ip, error: "No ASN data returned" });
190
+ }
191
+ // First line: "ip,asn_number,org_name" or similar
192
+ const firstLineParts = lines[0].split(",");
193
+ let asnNumber = null;
194
+ let orgName = null;
195
+ if (firstLineParts.length >= 2) {
196
+ // Extract ASN number — might be like "AS12345" or just "12345"
197
+ const rawAsn = firstLineParts[1].trim().replace(/^AS/i, "");
198
+ asnNumber = rawAsn;
199
+ orgName = firstLineParts.slice(2).join(",").trim() || null;
200
+ }
201
+ if (!asnNumber) {
202
+ return json({ target, ip, error: "Could not parse ASN number from response" });
203
+ }
204
+ // Get all prefixes for this ASN
205
+ await limiter.acquire();
206
+ const asnResponse = await fetch(`https://api.hackertarget.com/aslookup/?q=AS${asnNumber}`, {
207
+ signal: AbortSignal.timeout(15_000),
208
+ });
209
+ if (!asnResponse.ok) {
210
+ return json({ error: `HackerTarget ASN prefix lookup error: ${asnResponse.status}` });
211
+ }
212
+ const asnText = await asnResponse.text();
213
+ const asnLines = asnText.split("\n").map((l) => l.trim()).filter(Boolean);
214
+ // Parse CIDR ranges from the response
215
+ const prefixes = [];
216
+ for (const line of asnLines) {
217
+ // CIDR pattern: x.x.x.x/xx or x:x:x::/xx
218
+ const cidrMatch = line.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/);
219
+ if (cidrMatch) {
220
+ prefixes.push(cidrMatch[1]);
221
+ }
222
+ // Also check for IPv6 CIDR
223
+ const ipv6CidrMatch = line.match(/([0-9a-fA-F:]+\/\d{1,3})/);
224
+ if (ipv6CidrMatch && !cidrMatch) {
225
+ prefixes.push(ipv6CidrMatch[1]);
226
+ }
227
+ }
228
+ const limitedPrefixes = prefixes.slice(0, limit);
229
+ const result = {
230
+ target,
231
+ ip,
232
+ asn: {
233
+ number: `AS${asnNumber}`,
234
+ org: orgName,
235
+ },
236
+ prefixes: limitedPrefixes,
237
+ totalPrefixes: prefixes.length,
238
+ intelligence: {
239
+ note: `ASN AS${asnNumber} (${orgName ?? "unknown"}) announces ${prefixes.length} prefix(es).`,
240
+ networkSize: prefixes.length > 50
241
+ ? "Large network — likely a hosting provider, ISP, or major organization."
242
+ : prefixes.length > 10
243
+ ? "Medium-sized network — could be a mid-size company or regional ISP."
244
+ : "Small network — likely a single organization with limited IP space.",
245
+ },
246
+ };
247
+ cache.set(cacheKey, result);
248
+ return json(result);
249
+ }
250
+ catch (err) {
251
+ return json({ error: `ASN neighbor lookup failed: ${err.message}` });
252
+ }
253
+ }
254
+ // ─── Tool 3: enum_passive_dns ───
255
+ async function enumPassiveDns(args, ctx) {
256
+ const domain = args.domain;
257
+ const type = args.type ?? "a";
258
+ const cacheKey = `enum_passive_dns:${domain}:${type}`;
259
+ const cached = cache.get(cacheKey);
260
+ if (cached)
261
+ return json(cached);
262
+ let apiKey;
263
+ try {
264
+ apiKey = requireApiKey(ctx.config.securitytrailsApiKey, "SecurityTrails", "SECURITYTRAILS_API_KEY");
265
+ }
266
+ catch {
267
+ return json({
268
+ error: "SecurityTrails API key required for passive DNS history.",
269
+ hint: "Set SECURITYTRAILS_API_KEY environment variable",
270
+ });
271
+ }
272
+ await limiter.acquire();
273
+ try {
274
+ const response = await fetch(`https://api.securitytrails.com/v1/history/${encodeURIComponent(domain)}/dns/${type}`, {
275
+ headers: { APIKEY: apiKey },
276
+ signal: AbortSignal.timeout(15_000),
277
+ });
278
+ if (!response.ok) {
279
+ const errText = await response.text();
280
+ return json({
281
+ error: `SecurityTrails API error: ${response.status}`,
282
+ details: errText,
283
+ });
284
+ }
285
+ const data = await response.json();
286
+ const records = [];
287
+ for (const record of data.records ?? []) {
288
+ records.push({
289
+ first_seen: record.first_seen ?? "unknown",
290
+ last_seen: record.last_seen ?? "unknown",
291
+ values: (record.values ?? []).map((v) => ({
292
+ ip: v.ip ?? v.host ?? v.ip_count ?? null,
293
+ ip_count: v.ip_count ?? null,
294
+ host: v.host ?? null,
295
+ })),
296
+ });
297
+ }
298
+ // Detect infrastructure changes
299
+ const changes = [];
300
+ if (records.length > 1) {
301
+ changes.push(`${records.length} historical DNS record sets found — infrastructure has changed over time.`);
302
+ // Check for IP changes in A records
303
+ if (type === "a") {
304
+ const uniqueIps = new Set();
305
+ for (const rec of records) {
306
+ for (const v of rec.values) {
307
+ if (v.ip)
308
+ uniqueIps.add(v.ip);
309
+ }
310
+ }
311
+ if (uniqueIps.size > 1) {
312
+ changes.push(`${uniqueIps.size} unique IP addresses observed — domain has migrated between hosts.`);
313
+ }
314
+ }
315
+ }
316
+ const result = {
317
+ domain,
318
+ type,
319
+ records,
320
+ total_records: records.length,
321
+ intelligence: {
322
+ note: records.length > 0
323
+ ? `Found ${records.length} historical ${type.toUpperCase()} record(s) for ${domain}.`
324
+ : `No historical ${type.toUpperCase()} records found for ${domain}.`,
325
+ changes,
326
+ },
327
+ };
328
+ cache.set(cacheKey, result);
329
+ return json(result);
330
+ }
331
+ catch (err) {
332
+ return json({ error: `Passive DNS lookup failed: ${err.message}` });
333
+ }
334
+ }
335
+ // ─── Tool 4: enum_tld_expansion ───
336
+ async function enumTldExpansion(args) {
337
+ const basename = args.basename;
338
+ const tlds = args.tlds ?? [
339
+ "com", "net", "org", "io", "dev", "co", "app", "ai", "xyz", "info",
340
+ "biz", "us", "uk", "de", "fr", "eu", "ca", "au", "nl", "ru",
341
+ "cn", "jp", "in", "com.tr", "me",
342
+ ];
343
+ const cacheKey = `enum_tld_expansion:${basename}:${tlds.join(",")}`;
344
+ const cached = cache.get(cacheKey);
345
+ if (cached)
346
+ return json(cached);
347
+ const results = [];
348
+ for (const tld of tlds) {
349
+ const domain = `${basename}.${tld}`;
350
+ await limiter.acquire();
351
+ let ips = [];
352
+ let active = false;
353
+ try {
354
+ ips = await dns.resolve4(domain);
355
+ active = ips.length > 0;
356
+ }
357
+ catch {
358
+ // Domain does not resolve — inactive
359
+ }
360
+ let nameservers = [];
361
+ if (active) {
362
+ try {
363
+ await limiter.acquire();
364
+ nameservers = await dns.resolveNs(domain);
365
+ }
366
+ catch {
367
+ // NS resolution failed
368
+ }
369
+ }
370
+ results.push({ domain, tld, active, ips, nameservers });
371
+ }
372
+ const activeResults = results.filter((r) => r.active);
373
+ const inactiveResults = results.filter((r) => !r.active);
374
+ // Group by shared NS
375
+ const nsGroups = {};
376
+ for (const r of activeResults) {
377
+ if (r.nameservers.length > 0) {
378
+ const nsKey = [...r.nameservers].sort().join(",");
379
+ if (!nsGroups[nsKey])
380
+ nsGroups[nsKey] = [];
381
+ nsGroups[nsKey].push(r.domain);
382
+ }
383
+ }
384
+ // Filter groups with more than one domain (same owner signals)
385
+ const sameNsGroups = {};
386
+ for (const [ns, domains] of Object.entries(nsGroups)) {
387
+ if (domains.length > 1) {
388
+ sameNsGroups[ns] = domains;
389
+ }
390
+ }
391
+ const result = {
392
+ basename,
393
+ results,
394
+ active_count: activeResults.length,
395
+ inactive_count: inactiveResults.length,
396
+ same_ns_groups: sameNsGroups,
397
+ intelligence: {
398
+ note: `${activeResults.length} of ${tlds.length} TLD variations are active for "${basename}".`,
399
+ sameOwnerHint: Object.keys(sameNsGroups).length > 0
400
+ ? "Domains sharing the same nameservers likely belong to the same organization."
401
+ : "No domains share the same nameservers — ownership may differ.",
402
+ activeTlds: activeResults.map((r) => r.tld),
403
+ },
404
+ };
405
+ cache.set(cacheKey, result);
406
+ return json(result);
407
+ }
408
+ // ─── Tool 5: enum_related_domains ───
409
+ async function enumRelatedDomains(args) {
410
+ const domain = args.domain;
411
+ const analyticsId = args.analyticsId;
412
+ const nameserversParam = args.nameservers;
413
+ const cacheKey = `enum_related_domains:${domain}:${analyticsId ?? ""}:${(nameserversParam ?? []).join(",")}`;
414
+ const cached = cache.get(cacheKey);
415
+ if (cached)
416
+ return json(cached);
417
+ await limiter.acquire();
418
+ // Query crt.sh for certificate SANs
419
+ let certSANs = [];
420
+ let relatedRootDomains = [];
421
+ try {
422
+ const response = await fetch(`https://crt.sh/?q=${encodeURIComponent(domain)}&output=json`, {
423
+ signal: AbortSignal.timeout(20_000),
424
+ });
425
+ if (response.ok) {
426
+ const data = await response.json();
427
+ const allNames = new Set();
428
+ for (const entry of data) {
429
+ const nameValue = entry.name_value ?? "";
430
+ const names = nameValue.split("\n").map((n) => n.trim().toLowerCase()).filter(Boolean);
431
+ for (const name of names) {
432
+ // Remove wildcard prefix
433
+ const cleanName = name.replace(/^\*\./, "");
434
+ allNames.add(cleanName);
435
+ }
436
+ }
437
+ certSANs = [...allNames];
438
+ // Extract unique root domains
439
+ const rootDomains = new Set();
440
+ for (const name of allNames) {
441
+ const parts = name.split(".");
442
+ if (parts.length >= 2) {
443
+ // Handle multi-level TLDs like co.uk, com.tr
444
+ const possibleRoots = [];
445
+ if (parts.length >= 3) {
446
+ // Try 2-level root: domain.tld
447
+ possibleRoots.push(parts.slice(-2).join("."));
448
+ // Try 3-level root: domain.co.uk
449
+ possibleRoots.push(parts.slice(-3).join("."));
450
+ }
451
+ else {
452
+ possibleRoots.push(parts.join("."));
453
+ }
454
+ // Use the 2-level root domain as default
455
+ rootDomains.add(parts.slice(-2).join("."));
456
+ }
457
+ }
458
+ relatedRootDomains = [...rootDomains].filter((d) => d !== domain);
459
+ }
460
+ }
461
+ catch {
462
+ // crt.sh query failed
463
+ }
464
+ // For each unique root domain, get NS records
465
+ const relatedDomains = [];
466
+ // Get NS for primary domain
467
+ let primaryNs = [];
468
+ try {
469
+ await limiter.acquire();
470
+ primaryNs = await dns.resolveNs(domain);
471
+ }
472
+ catch {
473
+ // NS resolution failed
474
+ }
475
+ const nsToMatch = nameserversParam ?? primaryNs;
476
+ for (const relDomain of relatedRootDomains) {
477
+ await limiter.acquire();
478
+ let relNs = [];
479
+ try {
480
+ relNs = await dns.resolveNs(relDomain);
481
+ }
482
+ catch {
483
+ // NS resolution failed
484
+ }
485
+ const sharedNs = nsToMatch.length > 0 &&
486
+ relNs.length > 0 &&
487
+ relNs.some((ns) => nsToMatch.includes(ns));
488
+ relatedDomains.push({
489
+ domain: relDomain,
490
+ source: "certificate_transparency",
491
+ sharedNs,
492
+ });
493
+ }
494
+ const intelligenceNotes = [];
495
+ intelligenceNotes.push(`Found ${certSANs.length} unique names in certificate transparency logs.`);
496
+ intelligenceNotes.push(`${relatedRootDomains.length} unique root domain(s) discovered beyond ${domain}.`);
497
+ if (analyticsId) {
498
+ intelligenceNotes.push(`Analytics ID "${analyticsId}" provided — cross-referencing requires fetching page content (not performed here). Use tools like BuiltWith or PublicWWW for verification.`);
499
+ }
500
+ const sharedNsDomains = relatedDomains.filter((d) => d.sharedNs);
501
+ if (sharedNsDomains.length > 0) {
502
+ intelligenceNotes.push(`${sharedNsDomains.length} related domain(s) share nameservers with ${domain} — likely same owner.`);
503
+ }
504
+ const result = {
505
+ domain,
506
+ relatedDomains,
507
+ totalFound: relatedDomains.length,
508
+ certSANs,
509
+ intelligence: {
510
+ notes: intelligenceNotes,
511
+ },
512
+ };
513
+ cache.set(cacheKey, result);
514
+ return json(result);
515
+ }
516
+ // ─── Tool 6: enum_wildcard_detect ───
517
+ async function enumWildcardDetect(args) {
518
+ const domain = args.domain;
519
+ const cacheKey = `enum_wildcard_detect:${domain}`;
520
+ const cached = cache.get(cacheKey);
521
+ if (cached)
522
+ return json(cached);
523
+ // Generate 5 random subdomain names
524
+ const randomSubs = Array.from({ length: 5 }, () => `${randomUUID().slice(0, 8)}.${domain}`);
525
+ const resolvedResults = [];
526
+ for (const sub of randomSubs) {
527
+ await limiter.acquire();
528
+ try {
529
+ const ips = await dns.resolve4(sub);
530
+ resolvedResults.push({ subdomain: sub, ips });
531
+ }
532
+ catch {
533
+ resolvedResults.push({ subdomain: sub, ips: null });
534
+ }
535
+ }
536
+ // Analyze results
537
+ const resolvedCount = resolvedResults.filter((r) => r.ips !== null && r.ips.length > 0).length;
538
+ const unresolvedCount = resolvedResults.filter((r) => r.ips === null || r.ips.length === 0).length;
539
+ let wildcard = false;
540
+ let wildcardIps = [];
541
+ if (resolvedCount === randomSubs.length) {
542
+ // All resolved — check if they all point to the same IP(s)
543
+ const allIps = resolvedResults
544
+ .filter((r) => r.ips !== null)
545
+ .map((r) => r.ips.sort().join(","));
546
+ const uniqueIpSets = new Set(allIps);
547
+ if (uniqueIpSets.size === 1) {
548
+ wildcard = true;
549
+ wildcardIps = resolvedResults[0].ips ?? [];
550
+ }
551
+ else {
552
+ wildcard = "partial";
553
+ const ipSet = new Set();
554
+ for (const r of resolvedResults) {
555
+ if (r.ips) {
556
+ for (const ip of r.ips)
557
+ ipSet.add(ip);
558
+ }
559
+ }
560
+ wildcardIps = [...ipSet];
561
+ }
562
+ }
563
+ else if (resolvedCount > 0 && unresolvedCount > 0) {
564
+ wildcard = "partial";
565
+ const ipSet = new Set();
566
+ for (const r of resolvedResults) {
567
+ if (r.ips) {
568
+ for (const ip of r.ips)
569
+ ipSet.add(ip);
570
+ }
571
+ }
572
+ wildcardIps = [...ipSet];
573
+ }
574
+ // Check for wildcard CNAME
575
+ let wildcardCname = null;
576
+ try {
577
+ await limiter.acquire();
578
+ const cnames = await dns.resolveCname(`*.${domain}`);
579
+ if (cnames && cnames.length > 0) {
580
+ wildcardCname = cnames[0];
581
+ }
582
+ }
583
+ catch {
584
+ // No wildcard CNAME
585
+ }
586
+ const result = {
587
+ domain,
588
+ wildcard,
589
+ wildcardIps,
590
+ wildcardCname,
591
+ testedSubdomains: resolvedResults.map((r) => ({
592
+ subdomain: r.subdomain,
593
+ resolved: r.ips !== null && r.ips.length > 0,
594
+ ips: r.ips ?? [],
595
+ })),
596
+ intelligence: {
597
+ note: wildcard === true
598
+ ? `Wildcard DNS confirmed for ${domain} — all random subdomains resolve to ${wildcardIps.join(", ")}. Subdomain brute-forcing will produce false positives unless wildcard IPs are filtered.`
599
+ : wildcard === "partial"
600
+ ? `Partial wildcard DNS detected for ${domain} — some random subdomains resolve. Results may be inconsistent; consider filtering known wildcard IPs during enumeration.`
601
+ : `No wildcard DNS detected for ${domain} — subdomain enumeration results should be reliable.`,
602
+ wildcardCname: wildcardCname
603
+ ? `Wildcard CNAME record found pointing to ${wildcardCname}.`
604
+ : null,
605
+ recommendation: wildcard
606
+ ? "Filter wildcard IPs from subdomain enumeration results to avoid false positives."
607
+ : "No filtering needed — proceed with standard subdomain enumeration.",
608
+ },
609
+ };
610
+ cache.set(cacheKey, result);
611
+ return json(result);
612
+ }
613
+ // ─── Tool 7: enum_scope_summary ───
614
+ async function enumScopeSummary(args) {
615
+ const domain = args.domain;
616
+ const subdomains = args.subdomains ?? [];
617
+ const ips = args.ips ?? [];
618
+ const asns = args.asns ?? [];
619
+ const wildcardZones = args.wildcardZones ?? [];
620
+ // Categorize subdomains
621
+ const categories = {};
622
+ for (const sub of subdomains) {
623
+ const category = categorizeHostname(sub);
624
+ if (!categories[category])
625
+ categories[category] = [];
626
+ categories[category].push(sub);
627
+ }
628
+ // Group IPs into /24 ranges
629
+ const ipRanges = {};
630
+ for (const ip of ips) {
631
+ if (IP_REGEX.test(ip)) {
632
+ const octets = ip.split(".");
633
+ const range = `${octets[0]}.${octets[1]}.${octets[2]}.0/24`;
634
+ if (!ipRanges[range])
635
+ ipRanges[range] = [];
636
+ ipRanges[range].push(ip);
637
+ }
638
+ }
639
+ // Identify interesting findings
640
+ const interestingCategories = [
641
+ "admin", "staging", "development", "internal", "database", "vpn", "ci-cd", "testing",
642
+ ];
643
+ const interestingFindings = [];
644
+ for (const cat of interestingCategories) {
645
+ if (categories[cat] && categories[cat].length > 0) {
646
+ interestingFindings.push({
647
+ category: cat,
648
+ hosts: categories[cat],
649
+ });
650
+ }
651
+ }
652
+ // Generate recommended actions
653
+ const recommendedActions = [];
654
+ if (interestingFindings.length > 0) {
655
+ recommendedActions.push(`Investigate ${interestingFindings.length} interesting subdomain categories: ${interestingFindings.map((f) => f.category).join(", ")}.`);
656
+ }
657
+ if (categories["admin"]) {
658
+ recommendedActions.push("Admin panels found — test for default credentials, brute-force protection, and authentication bypasses.");
659
+ }
660
+ if (categories["staging"] || categories["development"]) {
661
+ recommendedActions.push("Staging/dev environments found — these often have weaker security controls. Check for debug modes, exposed source, and unpatched software.");
662
+ }
663
+ if (categories["internal"]) {
664
+ recommendedActions.push("Internal/intranet subdomains found — verify they are not publicly accessible and test for information leakage.");
665
+ }
666
+ if (categories["database"]) {
667
+ recommendedActions.push("Database-related subdomains found — check for exposed database management interfaces (phpMyAdmin, Adminer, etc.).");
668
+ }
669
+ if (categories["vpn"]) {
670
+ recommendedActions.push("VPN endpoints found — test for known VPN vulnerabilities (CVEs for Fortinet, Pulse Secure, etc.).");
671
+ }
672
+ if (categories["ci-cd"]) {
673
+ recommendedActions.push("CI/CD systems found — check for unauthenticated access to Jenkins, GitLab, or build pipelines.");
674
+ }
675
+ if (wildcardZones.length > 0) {
676
+ recommendedActions.push(`${wildcardZones.length} wildcard DNS zone(s) detected — filter wildcard IPs during enumeration to avoid false positives.`);
677
+ }
678
+ if (Object.keys(ipRanges).length > 3) {
679
+ recommendedActions.push(`Infrastructure spans ${Object.keys(ipRanges).length} /24 ranges — consider port scanning key ranges for additional service discovery.`);
680
+ }
681
+ if (asns.length > 1) {
682
+ recommendedActions.push(`${asns.length} ASNs discovered — multi-ASN presence suggests cloud/CDN usage or geographically distributed infrastructure.`);
683
+ }
684
+ if (recommendedActions.length === 0) {
685
+ recommendedActions.push("Run enum_subdomains, enum_tld_expansion, and enum_asn_neighbors to expand scope before summarizing.");
686
+ }
687
+ const result = {
688
+ domain,
689
+ summary: {
690
+ totalSubdomains: subdomains.length,
691
+ totalIps: ips.length,
692
+ totalAsns: asns.length,
693
+ wildcardZones: wildcardZones.length,
694
+ },
695
+ categories,
696
+ ipRanges,
697
+ interestingFindings,
698
+ recommendedActions,
699
+ intelligence: {
700
+ attackSurface: {
701
+ uniqueHosts: subdomains.length,
702
+ ipDiversity: Object.keys(ipRanges).length,
703
+ asnDiversity: asns.length,
704
+ interestingCategoryCount: interestingFindings.length,
705
+ },
706
+ riskLevel: interestingFindings.length > 3
707
+ ? "high"
708
+ : interestingFindings.length > 0
709
+ ? "medium"
710
+ : "low",
711
+ note: `Attack surface includes ${subdomains.length} hosts across ${Object.keys(ipRanges).length} /24 range(s) and ${asns.length} ASN(s). ${interestingFindings.length} interesting subdomain categories identified for further investigation.`,
712
+ },
713
+ };
714
+ return json(result);
715
+ }
716
+ // ─── Tool Definitions ───
717
+ export const enumTools = [
718
+ {
719
+ name: "enum_subdomains",
720
+ description: "Subdomain enumeration via SecurityTrails or HackerTarget. Discovers subdomains, resolves their current IPs, and categorizes each hostname (admin, staging, api, vpn, database, ci-cd, etc.). Uses SecurityTrails when API key is available, falls back to HackerTarget (free, no key required).",
721
+ schema: {
722
+ domain: z.string().describe("Target domain for subdomain enumeration"),
723
+ source: z
724
+ .enum(["securitytrails", "hackertarget", "auto"])
725
+ .optional()
726
+ .default("auto")
727
+ .describe("Enumeration source (auto = SecurityTrails if key available, else HackerTarget)"),
728
+ },
729
+ execute: enumSubdomains,
730
+ },
731
+ {
732
+ name: "enum_asn_neighbors",
733
+ description: "ASN neighbor discovery. Finds the ASN for a given IP or domain, then enumerates all IP prefixes (CIDR ranges) announced by that ASN. Reveals the full network footprint of an organization and helps identify other IP ranges that may host related services.",
734
+ schema: {
735
+ target: z.string().describe("IP address or domain to find ASN neighbors"),
736
+ limit: z
737
+ .number()
738
+ .optional()
739
+ .default(100)
740
+ .describe("Max results to return (default: 100)"),
741
+ },
742
+ execute: (args) => enumAsnNeighbors(args),
743
+ },
744
+ {
745
+ name: "enum_passive_dns",
746
+ description: "Passive DNS history via SecurityTrails. Retrieves historical DNS records showing infrastructure changes over time — IP migrations, hosting provider switches, and DNS configuration evolution. Requires SecurityTrails API key.",
747
+ schema: {
748
+ domain: z.string().describe("Domain for passive DNS history"),
749
+ type: z
750
+ .enum(["a", "aaaa", "mx", "ns", "soa"])
751
+ .optional()
752
+ .default("a")
753
+ .describe("Record type (default: a)"),
754
+ },
755
+ execute: enumPassiveDns,
756
+ },
757
+ {
758
+ name: "enum_tld_expansion",
759
+ description: "TLD/ccTLD expansion check. Tests whether the same base domain name exists on other TLDs (.com, .net, .org, .io, .dev, .ai, etc.). Identifies active variations, compares nameservers to detect same-owner domains, and flags domains that may be related or brand-squatted.",
760
+ schema: {
761
+ basename: z.string().describe("Base domain name without TLD (e.g., 'example')"),
762
+ tlds: z
763
+ .array(z.string())
764
+ .optional()
765
+ .describe("TLDs to check (default: 25 common TLDs including com, net, org, io, dev, co, app, ai, xyz, info, biz, us, uk, de, fr, eu, ca, au, nl, ru, cn, jp, in, com.tr, me)"),
766
+ },
767
+ execute: (args) => enumTldExpansion(args),
768
+ },
769
+ {
770
+ name: "enum_related_domains",
771
+ description: "Find domains related by shared infrastructure signals. Queries certificate transparency logs (crt.sh) for certificate SANs, extracts unique root domains, and compares nameservers to identify domains likely owned by the same organization. Optionally accepts analytics IDs for cross-reference hints.",
772
+ schema: {
773
+ domain: z.string().describe("Primary domain to find related domains"),
774
+ analyticsId: z
775
+ .string()
776
+ .optional()
777
+ .describe("Google Analytics/GTM ID for cross-reference"),
778
+ nameservers: z
779
+ .array(z.string())
780
+ .optional()
781
+ .describe("Known NS records to match against"),
782
+ },
783
+ execute: (args) => enumRelatedDomains(args),
784
+ },
785
+ {
786
+ name: "enum_wildcard_detect",
787
+ description: "Detect wildcard DNS records. Tests random non-existent subdomains to determine if a domain has wildcard DNS configured. Reports whether wildcard is confirmed, partial, or absent, along with wildcard IPs and CNAME targets. Essential before subdomain brute-forcing to avoid false positives.",
788
+ schema: {
789
+ domain: z.string().describe("Domain to check for wildcard DNS"),
790
+ },
791
+ execute: (args) => enumWildcardDetect(args),
792
+ },
793
+ {
794
+ name: "enum_scope_summary",
795
+ description: "Aggregate scope expansion data into an attack surface summary. Categorizes subdomains, groups IPs into /24 ranges, identifies interesting targets (admin panels, staging/dev environments, databases, VPNs, CI/CD), and generates prioritized recommended actions. Pure local computation, no network requests.",
796
+ schema: {
797
+ domain: z.string().describe("Primary target domain"),
798
+ subdomains: z
799
+ .array(z.string())
800
+ .optional()
801
+ .describe("Discovered subdomains"),
802
+ ips: z
803
+ .array(z.string())
804
+ .optional()
805
+ .describe("Discovered IP addresses"),
806
+ asns: z
807
+ .array(z.string())
808
+ .optional()
809
+ .describe("Discovered ASN numbers"),
810
+ wildcardZones: z
811
+ .array(z.string())
812
+ .optional()
813
+ .describe("Zones with wildcard DNS"),
814
+ },
815
+ execute: (args) => enumScopeSummary(args),
816
+ },
817
+ ];
818
+ //# sourceMappingURL=index.js.map