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,687 @@
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
+ const limiter = new RateLimiter(1000); // respect API rate limits
7
+ const cache = new TTLCache(600_000); // 10min cache
8
+ // ─── Helpers ───
9
+ const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
10
+ function isIpAddress(value) {
11
+ return IP_REGEX.test(value);
12
+ }
13
+ // ─── Tool 1: osint_shodan ───
14
+ async function osintShodan(args, ctx) {
15
+ const ip = args.ip;
16
+ const query = args.query;
17
+ const page = args.page ?? 1;
18
+ if (!ip && !query) {
19
+ return json({ error: "Either 'ip' or 'query' parameter is required" });
20
+ }
21
+ const apiKey = requireApiKey(ctx.config.shodanApiKey, "Shodan", "SHODAN_API_KEY");
22
+ // Search mode
23
+ if (query) {
24
+ const cacheKey = `osint_shodan:search:${query}:${page}`;
25
+ const cached = cache.get(cacheKey);
26
+ if (cached)
27
+ return json(cached);
28
+ await limiter.acquire();
29
+ try {
30
+ const url = `https://api.shodan.io/shodan/host/search?key=${encodeURIComponent(apiKey)}&query=${encodeURIComponent(query)}&page=${page}`;
31
+ const response = await fetch(url, {
32
+ signal: AbortSignal.timeout(15000),
33
+ });
34
+ if (!response.ok) {
35
+ const errText = await response.text();
36
+ return json({
37
+ error: `Shodan API error: ${response.status}`,
38
+ details: errText,
39
+ });
40
+ }
41
+ const data = await response.json();
42
+ const result = {
43
+ query,
44
+ page,
45
+ total: data.total ?? 0,
46
+ matches: (data.matches ?? []).map((match) => ({
47
+ ip: match.ip_str,
48
+ port: match.port,
49
+ transport: match.transport,
50
+ product: match.product ?? null,
51
+ version: match.version ?? null,
52
+ os: match.os ?? null,
53
+ hostnames: match.hostnames ?? [],
54
+ org: match.org ?? null,
55
+ isp: match.isp ?? null,
56
+ asn: match.asn ?? null,
57
+ country: match.location?.country_name ?? null,
58
+ city: match.location?.city ?? null,
59
+ banner: match.data?.substring(0, 500) ?? null,
60
+ vulns: match.vulns ? Object.keys(match.vulns) : [],
61
+ tags: match.tags ?? [],
62
+ timestamp: match.timestamp ?? null,
63
+ })),
64
+ };
65
+ cache.set(cacheKey, result);
66
+ return json(result);
67
+ }
68
+ catch (err) {
69
+ return json({ error: `Shodan search failed: ${err.message}` });
70
+ }
71
+ }
72
+ // Host lookup mode
73
+ const cacheKey = `osint_shodan:host:${ip}`;
74
+ const cached = cache.get(cacheKey);
75
+ if (cached)
76
+ return json(cached);
77
+ await limiter.acquire();
78
+ try {
79
+ const url = `https://api.shodan.io/shodan/host/${encodeURIComponent(ip)}?key=${encodeURIComponent(apiKey)}`;
80
+ const response = await fetch(url, {
81
+ signal: AbortSignal.timeout(15000),
82
+ });
83
+ if (!response.ok) {
84
+ const errText = await response.text();
85
+ return json({
86
+ error: `Shodan API error: ${response.status}`,
87
+ details: errText,
88
+ });
89
+ }
90
+ const data = await response.json();
91
+ const result = {
92
+ ip: data.ip_str,
93
+ hostnames: data.hostnames ?? [],
94
+ os: data.os ?? null,
95
+ org: data.org ?? null,
96
+ isp: data.isp ?? null,
97
+ asn: data.asn ?? null,
98
+ country: data.country_name ?? null,
99
+ city: data.city ?? null,
100
+ lastUpdate: data.last_update ?? null,
101
+ vulns: data.vulns ? Object.keys(data.vulns) : [],
102
+ tags: data.tags ?? [],
103
+ ports: data.ports ?? [],
104
+ services: (data.data ?? []).map((svc) => ({
105
+ port: svc.port,
106
+ transport: svc.transport,
107
+ product: svc.product ?? null,
108
+ version: svc.version ?? null,
109
+ cpe: svc.cpe ?? [],
110
+ banner: svc.data?.substring(0, 500) ?? null,
111
+ ssl: svc.ssl
112
+ ? {
113
+ jarm: svc.ssl.jarm ?? null,
114
+ ja3s: svc.ssl.ja3s ?? null,
115
+ cert: svc.ssl.cert
116
+ ? {
117
+ subject: svc.ssl.cert.subject ?? null,
118
+ issuer: svc.ssl.cert.issuer ?? null,
119
+ expires: svc.ssl.cert.expires ?? null,
120
+ fingerprint: svc.ssl.cert.fingerprint?.sha256 ?? null,
121
+ }
122
+ : null,
123
+ }
124
+ : null,
125
+ http: svc.http
126
+ ? {
127
+ title: svc.http.title ?? null,
128
+ server: svc.http.server ?? null,
129
+ status: svc.http.status ?? null,
130
+ redirects: svc.http.redirects ?? [],
131
+ }
132
+ : null,
133
+ })),
134
+ totalServices: (data.data ?? []).length,
135
+ };
136
+ cache.set(cacheKey, result);
137
+ return json(result);
138
+ }
139
+ catch (err) {
140
+ return json({ error: `Shodan host lookup failed: ${err.message}` });
141
+ }
142
+ }
143
+ // ─── Tool 2: osint_censys ───
144
+ async function osintCensys(args, ctx) {
145
+ const ip = args.ip;
146
+ const query = args.query;
147
+ const perPage = args.perPage ?? 25;
148
+ if (!ip && !query) {
149
+ return json({ error: "Either 'ip' or 'query' parameter is required" });
150
+ }
151
+ const apiId = requireApiKey(ctx.config.censysApiId, "Censys", "CENSYS_API_ID");
152
+ const apiSecret = requireApiKey(ctx.config.censysApiSecret, "Censys", "CENSYS_API_SECRET");
153
+ const authHeader = `Basic ${btoa(`${apiId}:${apiSecret}`)}`;
154
+ // Search mode
155
+ if (query) {
156
+ const cacheKey = `osint_censys:search:${query}:${perPage}`;
157
+ const cached = cache.get(cacheKey);
158
+ if (cached)
159
+ return json(cached);
160
+ await limiter.acquire();
161
+ try {
162
+ const response = await fetch("https://search.censys.io/api/v2/hosts/search", {
163
+ method: "POST",
164
+ headers: {
165
+ Authorization: authHeader,
166
+ "Content-Type": "application/json",
167
+ },
168
+ body: JSON.stringify({ q: query, per_page: perPage }),
169
+ signal: AbortSignal.timeout(15000),
170
+ });
171
+ if (!response.ok) {
172
+ const errText = await response.text();
173
+ return json({
174
+ error: `Censys API error: ${response.status}`,
175
+ details: errText,
176
+ });
177
+ }
178
+ const data = await response.json();
179
+ const hits = data.result?.hits ?? [];
180
+ const result = {
181
+ query,
182
+ totalHits: data.result?.total ?? 0,
183
+ hits: hits.map((hit) => ({
184
+ ip: hit.ip,
185
+ services: (hit.services ?? []).map((svc) => ({
186
+ port: svc.port,
187
+ serviceName: svc.service_name ?? null,
188
+ transportProtocol: svc.transport_protocol ?? null,
189
+ certificate: svc.certificate ?? null,
190
+ jarm: svc.jarm?.fingerprint ?? null,
191
+ software: svc.software ?? [],
192
+ banner: svc.banner ?? null,
193
+ })),
194
+ autonomousSystem: hit.autonomous_system ?? null,
195
+ location: hit.location ?? null,
196
+ operatingSystem: hit.operating_system ?? null,
197
+ lastUpdatedAt: hit.last_updated_at ?? null,
198
+ })),
199
+ };
200
+ cache.set(cacheKey, result);
201
+ return json(result);
202
+ }
203
+ catch (err) {
204
+ return json({ error: `Censys search failed: ${err.message}` });
205
+ }
206
+ }
207
+ // Host lookup mode
208
+ const cacheKey = `osint_censys:host:${ip}`;
209
+ const cached = cache.get(cacheKey);
210
+ if (cached)
211
+ return json(cached);
212
+ await limiter.acquire();
213
+ try {
214
+ const response = await fetch(`https://search.censys.io/api/v2/hosts/${encodeURIComponent(ip)}`, {
215
+ headers: {
216
+ Authorization: authHeader,
217
+ },
218
+ signal: AbortSignal.timeout(15000),
219
+ });
220
+ if (!response.ok) {
221
+ const errText = await response.text();
222
+ return json({
223
+ error: `Censys API error: ${response.status}`,
224
+ details: errText,
225
+ });
226
+ }
227
+ const data = await response.json();
228
+ const host = data.result ?? {};
229
+ const result = {
230
+ ip: host.ip,
231
+ services: (host.services ?? []).map((svc) => ({
232
+ port: svc.port,
233
+ serviceName: svc.service_name ?? null,
234
+ transportProtocol: svc.transport_protocol ?? null,
235
+ certificate: svc.certificate ?? null,
236
+ jarm: svc.jarm?.fingerprint ?? null,
237
+ software: svc.software ?? [],
238
+ banner: svc.banner ?? null,
239
+ tls: svc.tls
240
+ ? {
241
+ version: svc.tls.version_selected ?? null,
242
+ cipherSuite: svc.tls.cipher_selected ?? null,
243
+ certificate: svc.tls.certificates?.leaf_data
244
+ ? {
245
+ subject: svc.tls.certificates.leaf_data.subject ?? null,
246
+ issuer: svc.tls.certificates.leaf_data.issuer ?? null,
247
+ fingerprint: svc.tls.certificates.leaf_data.fingerprint ?? null,
248
+ sans: svc.tls.certificates.leaf_data.names ?? [],
249
+ }
250
+ : null,
251
+ }
252
+ : null,
253
+ })),
254
+ autonomousSystem: host.autonomous_system ?? null,
255
+ location: host.location ?? null,
256
+ operatingSystem: host.operating_system ?? null,
257
+ lastUpdatedAt: host.last_updated_at ?? null,
258
+ labels: host.labels ?? [],
259
+ };
260
+ cache.set(cacheKey, result);
261
+ return json(result);
262
+ }
263
+ catch (err) {
264
+ return json({ error: `Censys host lookup failed: ${err.message}` });
265
+ }
266
+ }
267
+ // ─── Tool 3: osint_reverse_ip ───
268
+ async function osintReverseIp(args) {
269
+ const ip = args.ip;
270
+ const cacheKey = `osint_reverse_ip:${ip}`;
271
+ const cached = cache.get(cacheKey);
272
+ if (cached)
273
+ return json(cached);
274
+ await limiter.acquire();
275
+ try {
276
+ const url = `https://api.hackertarget.com/reverseiplookup/?q=${encodeURIComponent(ip)}`;
277
+ const response = await fetch(url, {
278
+ signal: AbortSignal.timeout(15000),
279
+ });
280
+ if (!response.ok) {
281
+ return json({
282
+ error: `HackerTarget API error: ${response.status}`,
283
+ });
284
+ }
285
+ const text = await response.text();
286
+ // HackerTarget returns "error ..." for errors and plain-text domain list
287
+ if (text.startsWith("error") || text.startsWith("API count exceeded")) {
288
+ return json({
289
+ ip,
290
+ error: text.trim(),
291
+ domains: [],
292
+ totalDomains: 0,
293
+ });
294
+ }
295
+ const domains = text
296
+ .split("\n")
297
+ .map((line) => line.trim())
298
+ .filter((line) => line.length > 0 && line !== ip);
299
+ const result = {
300
+ ip,
301
+ domains,
302
+ totalDomains: domains.length,
303
+ sharedHosting: domains.length > 1,
304
+ intelligence: {
305
+ isSharedHosting: domains.length > 5,
306
+ note: domains.length > 5
307
+ ? "Multiple domains on same IP suggest shared hosting — may indicate lower-value targets or shared infrastructure."
308
+ : domains.length > 1
309
+ ? "A few co-hosted domains — could be related properties or small shared hosting."
310
+ : "Single domain on IP — likely dedicated hosting.",
311
+ },
312
+ };
313
+ cache.set(cacheKey, result);
314
+ return json(result);
315
+ }
316
+ catch (err) {
317
+ return json({ error: `Reverse IP lookup failed: ${err.message}` });
318
+ }
319
+ }
320
+ // ─── Tool 4: osint_whois ───
321
+ async function osintWhois(args) {
322
+ const target = args.target;
323
+ const cacheKey = `osint_whois:${target}`;
324
+ const cached = cache.get(cacheKey);
325
+ if (cached)
326
+ return json(cached);
327
+ await limiter.acquire();
328
+ try {
329
+ const url = `https://api.hackertarget.com/whois/?q=${encodeURIComponent(target)}`;
330
+ const response = await fetch(url, {
331
+ signal: AbortSignal.timeout(15000),
332
+ });
333
+ if (!response.ok) {
334
+ return json({
335
+ error: `HackerTarget WHOIS API error: ${response.status}`,
336
+ });
337
+ }
338
+ const rawText = await response.text();
339
+ if (rawText.startsWith("error") || rawText.startsWith("API count exceeded")) {
340
+ return json({
341
+ target,
342
+ error: rawText.trim(),
343
+ });
344
+ }
345
+ // Parse WHOIS fields from raw text
346
+ const parsed = {};
347
+ const lines = rawText.split("\n");
348
+ for (const line of lines) {
349
+ const colonIdx = line.indexOf(":");
350
+ if (colonIdx > 0) {
351
+ const key = line.substring(0, colonIdx).trim().toLowerCase();
352
+ const value = line.substring(colonIdx + 1).trim();
353
+ if (key && value) {
354
+ // Store first occurrence (WHOIS often has duplicates)
355
+ if (!parsed[key]) {
356
+ parsed[key] = value;
357
+ }
358
+ }
359
+ }
360
+ }
361
+ // Extract key fields
362
+ const registrantName = parsed["registrant name"] ??
363
+ parsed["registrant"] ??
364
+ null;
365
+ const registrantOrg = parsed["registrant organization"] ??
366
+ parsed["registrant org"] ??
367
+ parsed["org-name"] ??
368
+ parsed["organization"] ??
369
+ null;
370
+ const registrantEmail = parsed["registrant email"] ??
371
+ parsed["abuse-mailbox"] ??
372
+ null;
373
+ const creationDate = parsed["creation date"] ??
374
+ parsed["created"] ??
375
+ parsed["registration date"] ??
376
+ null;
377
+ const expirationDate = parsed["registry expiry date"] ??
378
+ parsed["expiration date"] ??
379
+ parsed["expires"] ??
380
+ null;
381
+ const registrar = parsed["registrar"] ??
382
+ null;
383
+ const dnssec = parsed["dnssec"] ??
384
+ null;
385
+ // Extract nameservers
386
+ const nameservers = [];
387
+ for (const line of lines) {
388
+ const lower = line.toLowerCase().trim();
389
+ if (lower.startsWith("name server:") || lower.startsWith("nserver:")) {
390
+ const ns = line.substring(line.indexOf(":") + 1).trim();
391
+ if (ns)
392
+ nameservers.push(ns.toLowerCase());
393
+ }
394
+ }
395
+ // Intelligence analysis
396
+ let domainAgeWarning = null;
397
+ if (creationDate) {
398
+ const created = new Date(creationDate);
399
+ const now = new Date();
400
+ const ageMonths = (now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24 * 30);
401
+ if (ageMonths < 6) {
402
+ domainAgeWarning = `Domain is less than 6 months old (created ${creationDate}) — potentially suspicious for phishing or fraud.`;
403
+ }
404
+ }
405
+ const result = {
406
+ target,
407
+ registrant: {
408
+ name: registrantName,
409
+ organization: registrantOrg,
410
+ email: registrantEmail,
411
+ },
412
+ dates: {
413
+ created: creationDate,
414
+ expires: expirationDate,
415
+ },
416
+ registrar,
417
+ nameservers,
418
+ dnssec,
419
+ intelligence: {
420
+ domainAgeWarning,
421
+ hostingProvider: nameservers.length > 0
422
+ ? `Nameservers suggest hosting with: ${nameservers[0]}`
423
+ : null,
424
+ },
425
+ raw: rawText,
426
+ };
427
+ cache.set(cacheKey, result);
428
+ return json(result);
429
+ }
430
+ catch (err) {
431
+ return json({ error: `WHOIS lookup failed: ${err.message}` });
432
+ }
433
+ }
434
+ // ─── Tool 5: osint_web_archive ───
435
+ async function osintWebArchive(args) {
436
+ const url = args.url;
437
+ const limit = args.limit ?? 50;
438
+ const cacheKey = `osint_web_archive:${url}:${limit}`;
439
+ const cached = cache.get(cacheKey);
440
+ if (cached)
441
+ return json(cached);
442
+ await limiter.acquire();
443
+ try {
444
+ const apiUrl = `https://web.archive.org/web/timemap/json?url=${encodeURIComponent(url)}&limit=${limit}&output=json`;
445
+ const response = await fetch(apiUrl, {
446
+ signal: AbortSignal.timeout(15000),
447
+ });
448
+ if (!response.ok) {
449
+ return json({
450
+ error: `Wayback Machine API error: ${response.status}`,
451
+ });
452
+ }
453
+ const data = await response.json();
454
+ // First row is headers: ["urlkey","timestamp","original","mimetype","statuscode","digest","length"]
455
+ if (!Array.isArray(data) || data.length < 2) {
456
+ return json({
457
+ url,
458
+ found: false,
459
+ snapshots: [],
460
+ totalSnapshots: 0,
461
+ message: "No archived snapshots found for this URL.",
462
+ });
463
+ }
464
+ const headers = data[0];
465
+ const rows = data.slice(1);
466
+ const timestampIdx = headers.indexOf("timestamp");
467
+ const originalIdx = headers.indexOf("original");
468
+ const mimetypeIdx = headers.indexOf("mimetype");
469
+ const statusIdx = headers.indexOf("statuscode");
470
+ const snapshots = rows.map((row) => ({
471
+ timestamp: row[timestampIdx] ?? null,
472
+ original: row[originalIdx] ?? null,
473
+ mimetype: row[mimetypeIdx] ?? null,
474
+ statusCode: row[statusIdx] ?? null,
475
+ archiveUrl: row[timestampIdx]
476
+ ? `https://web.archive.org/web/${row[timestampIdx]}/${row[originalIdx] ?? url}`
477
+ : null,
478
+ }));
479
+ const timestamps = snapshots
480
+ .map((s) => s.timestamp)
481
+ .filter((t) => t !== null)
482
+ .sort();
483
+ const result = {
484
+ url,
485
+ found: true,
486
+ totalSnapshots: snapshots.length,
487
+ oldestCapture: timestamps[0] ?? null,
488
+ newestCapture: timestamps[timestamps.length - 1] ?? null,
489
+ snapshots,
490
+ intelligence: {
491
+ note: "Historical snapshots may reveal removed pages, old technology stacks, exposed credentials, or development/staging content that was once public.",
492
+ oldestArchiveUrl: timestamps[0]
493
+ ? `https://web.archive.org/web/${timestamps[0]}/${url}`
494
+ : null,
495
+ newestArchiveUrl: timestamps.length > 0
496
+ ? `https://web.archive.org/web/${timestamps[timestamps.length - 1]}/${url}`
497
+ : null,
498
+ },
499
+ };
500
+ cache.set(cacheKey, result);
501
+ return json(result);
502
+ }
503
+ catch (err) {
504
+ return json({ error: `Wayback Machine lookup failed: ${err.message}` });
505
+ }
506
+ }
507
+ // ─── Tool 6: osint_virustotal ───
508
+ async function osintVirustotal(args, ctx) {
509
+ const target = args.target;
510
+ let type = args.type;
511
+ // Auto-detect type if not specified
512
+ if (!type) {
513
+ type = isIpAddress(target) ? "ip" : "domain";
514
+ }
515
+ const apiKey = requireApiKey(ctx.config.virustotalApiKey, "VirusTotal", "VIRUSTOTAL_API_KEY");
516
+ const cacheKey = `osint_virustotal:${type}:${target}`;
517
+ const cached = cache.get(cacheKey);
518
+ if (cached)
519
+ return json(cached);
520
+ await limiter.acquire();
521
+ try {
522
+ const endpoint = type === "ip"
523
+ ? `https://www.virustotal.com/api/v3/ip_addresses/${encodeURIComponent(target)}`
524
+ : `https://www.virustotal.com/api/v3/domains/${encodeURIComponent(target)}`;
525
+ const response = await fetch(endpoint, {
526
+ headers: {
527
+ "x-apikey": apiKey,
528
+ },
529
+ signal: AbortSignal.timeout(15000),
530
+ });
531
+ if (!response.ok) {
532
+ const errText = await response.text();
533
+ return json({
534
+ error: `VirusTotal API error: ${response.status}`,
535
+ details: errText,
536
+ });
537
+ }
538
+ const data = await response.json();
539
+ const attributes = data.data?.attributes ?? {};
540
+ if (type === "domain") {
541
+ const result = {
542
+ target,
543
+ type: "domain",
544
+ reputation: attributes.reputation ?? null,
545
+ categories: attributes.categories ?? {},
546
+ lastAnalysisStats: attributes.last_analysis_stats ?? {},
547
+ lastAnalysisDate: attributes.last_analysis_date
548
+ ? new Date(attributes.last_analysis_date * 1000).toISOString()
549
+ : null,
550
+ whois: attributes.whois ?? null,
551
+ lastDnsRecords: (attributes.last_dns_records ?? []).map((rec) => ({
552
+ type: rec.type,
553
+ value: rec.value,
554
+ ttl: rec.ttl ?? null,
555
+ })),
556
+ lastHttpsCertificate: attributes.last_https_certificate
557
+ ? {
558
+ subject: attributes.last_https_certificate.subject ?? null,
559
+ issuer: attributes.last_https_certificate.issuer ?? null,
560
+ validity: attributes.last_https_certificate.validity ?? null,
561
+ extensions: attributes.last_https_certificate.extensions
562
+ ? {
563
+ subjectAlternativeName: attributes.last_https_certificate.extensions.subject_alternative_name ?? [],
564
+ }
565
+ : null,
566
+ }
567
+ : null,
568
+ registrar: attributes.registrar ?? null,
569
+ creationDate: attributes.creation_date
570
+ ? new Date(attributes.creation_date * 1000).toISOString()
571
+ : null,
572
+ totalVotes: attributes.total_votes ?? {},
573
+ tags: attributes.tags ?? [],
574
+ popularity: attributes.popularity_ranks ?? {},
575
+ intelligence: {
576
+ malicious: (attributes.last_analysis_stats?.malicious ?? 0) > 0
577
+ ? `${attributes.last_analysis_stats.malicious} security vendors flagged this domain as malicious.`
578
+ : "No security vendors flagged this domain.",
579
+ suspicious: (attributes.last_analysis_stats?.suspicious ?? 0) > 0
580
+ ? `${attributes.last_analysis_stats.suspicious} vendors flagged as suspicious.`
581
+ : null,
582
+ },
583
+ };
584
+ cache.set(cacheKey, result);
585
+ return json(result);
586
+ }
587
+ // IP address result
588
+ const result = {
589
+ target,
590
+ type: "ip",
591
+ reputation: attributes.reputation ?? null,
592
+ asOwner: attributes.as_owner ?? null,
593
+ asn: attributes.asn ?? null,
594
+ continent: attributes.continent ?? null,
595
+ country: attributes.country ?? null,
596
+ network: attributes.network ?? null,
597
+ lastAnalysisStats: attributes.last_analysis_stats ?? {},
598
+ lastAnalysisDate: attributes.last_analysis_date
599
+ ? new Date(attributes.last_analysis_date * 1000).toISOString()
600
+ : null,
601
+ whois: attributes.whois ?? null,
602
+ totalVotes: attributes.total_votes ?? {},
603
+ tags: attributes.tags ?? [],
604
+ intelligence: {
605
+ malicious: (attributes.last_analysis_stats?.malicious ?? 0) > 0
606
+ ? `${attributes.last_analysis_stats.malicious} security vendors flagged this IP as malicious.`
607
+ : "No security vendors flagged this IP.",
608
+ suspicious: (attributes.last_analysis_stats?.suspicious ?? 0) > 0
609
+ ? `${attributes.last_analysis_stats.suspicious} vendors flagged as suspicious.`
610
+ : null,
611
+ },
612
+ };
613
+ cache.set(cacheKey, result);
614
+ return json(result);
615
+ }
616
+ catch (err) {
617
+ return json({ error: `VirusTotal lookup failed: ${err.message}` });
618
+ }
619
+ }
620
+ // ─── Tool Definitions ───
621
+ export const osintTools = [
622
+ {
623
+ name: "osint_shodan",
624
+ description: "Shodan host lookup and search. Queries Shodan for open ports, services, banners, OS, hostnames, ASN, org, ISP, country, vulnerabilities, and tags. Supports both IP lookup and Shodan dork search queries (e.g., http.favicon.hash, ssl.jarm, ssh.fingerprint, http.title, ssl.cert.subject.cn).",
625
+ schema: {
626
+ ip: z.string().optional().describe("IP address to look up"),
627
+ query: z
628
+ .string()
629
+ .optional()
630
+ .describe("Shodan search query (e.g., http.favicon.hash:-586023785)"),
631
+ page: z.number().optional().default(1).describe("Search results page"),
632
+ },
633
+ execute: osintShodan,
634
+ },
635
+ {
636
+ name: "osint_censys",
637
+ description: "Censys host lookup and search. Returns services with protocol details, certificates, JARM fingerprints, detected software, autonomous system info, and location data. Supports both direct IP lookup and Censys search queries (e.g., services.jarm.fingerprint:HASH).",
638
+ schema: {
639
+ ip: z.string().optional().describe("IP address to look up"),
640
+ query: z.string().optional().describe("Censys search query"),
641
+ perPage: z.number().optional().default(25).describe("Results per page"),
642
+ },
643
+ execute: osintCensys,
644
+ },
645
+ {
646
+ name: "osint_reverse_ip",
647
+ description: "Reverse IP lookup via HackerTarget. Finds all domains hosted on the same IP address for shared hosting discovery, related target identification, and co-hosted application enumeration. Free API, no key required.",
648
+ schema: {
649
+ ip: z.string().describe("IP address for reverse lookup"),
650
+ },
651
+ execute: (args) => osintReverseIp(args),
652
+ },
653
+ {
654
+ name: "osint_whois",
655
+ description: "WHOIS lookup via HackerTarget. Extracts registrant name, organization, email, creation/expiration dates, nameservers, registrar, and DNSSEC status. Flags domains created less than 6 months ago as potentially suspicious. Free API, no key required.",
656
+ schema: {
657
+ target: z.string().describe("Domain or IP for WHOIS lookup"),
658
+ },
659
+ execute: (args) => osintWhois(args),
660
+ },
661
+ {
662
+ name: "osint_web_archive",
663
+ description: "Wayback Machine historical snapshot lookup. Returns archived page captures with timestamps showing oldest/newest snapshots and direct archive URLs. Reveals technology changes over time, removed pages, and content that was once publicly accessible.",
664
+ schema: {
665
+ url: z.string().describe("URL to check in Wayback Machine"),
666
+ limit: z
667
+ .number()
668
+ .optional()
669
+ .default(50)
670
+ .describe("Max snapshots to return"),
671
+ },
672
+ execute: (args) => osintWebArchive(args),
673
+ },
674
+ {
675
+ name: "osint_virustotal",
676
+ description: "VirusTotal domain and IP intelligence. Returns subdomains, DNS resolution history, WHOIS data, communicating files (malware associations), detection ratio across security vendors, categories, reputation score, and certificate details. Auto-detects domain vs IP if type is omitted.",
677
+ schema: {
678
+ target: z.string().describe("Domain or IP for VirusTotal lookup"),
679
+ type: z
680
+ .enum(["domain", "ip"])
681
+ .optional()
682
+ .describe("Target type (auto-detected if omitted)"),
683
+ },
684
+ execute: osintVirustotal,
685
+ },
686
+ ];
687
+ //# sourceMappingURL=index.js.map