zilmate 1.3.5 → 1.6.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 (270) hide show
  1. package/.env.example +2 -0
  2. package/dist/agents/automation-planner.agent.d.ts +108 -0
  3. package/dist/agents/automation-planner.agent.d.ts.map +1 -1
  4. package/dist/agents/automation-planner.agent.js +13 -4
  5. package/dist/agents/automation-planner.agent.js.map +1 -1
  6. package/dist/agents/coding.agent.d.ts +352 -0
  7. package/dist/agents/coding.agent.d.ts.map +1 -0
  8. package/dist/agents/coding.agent.js +34 -0
  9. package/dist/agents/coding.agent.js.map +1 -0
  10. package/dist/agents/goal-manager.agent.d.ts +125 -0
  11. package/dist/agents/goal-manager.agent.d.ts.map +1 -0
  12. package/dist/agents/goal-manager.agent.js +32 -0
  13. package/dist/agents/goal-manager.agent.js.map +1 -0
  14. package/dist/agents/manager.d.ts +378 -0
  15. package/dist/agents/manager.d.ts.map +1 -1
  16. package/dist/agents/manager.js +113 -18
  17. package/dist/agents/manager.js.map +1 -1
  18. package/dist/agents/security.agent.d.ts +340 -0
  19. package/dist/agents/security.agent.d.ts.map +1 -0
  20. package/dist/agents/security.agent.js +76 -0
  21. package/dist/agents/security.agent.js.map +1 -0
  22. package/dist/cli/ask.d.ts +5 -0
  23. package/dist/cli/ask.d.ts.map +1 -0
  24. package/dist/cli/ask.js +33 -0
  25. package/dist/cli/ask.js.map +1 -0
  26. package/dist/cli/confirm.d.ts.map +1 -1
  27. package/dist/cli/confirm.js +8 -14
  28. package/dist/cli/confirm.js.map +1 -1
  29. package/dist/cli/doctor.d.ts.map +1 -1
  30. package/dist/cli/doctor.js +61 -1
  31. package/dist/cli/doctor.js.map +1 -1
  32. package/dist/cli/format.d.ts +1 -0
  33. package/dist/cli/format.d.ts.map +1 -1
  34. package/dist/cli/format.js +41 -2
  35. package/dist/cli/format.js.map +1 -1
  36. package/dist/cli/interactive.d.ts.map +1 -1
  37. package/dist/cli/interactive.js +6 -2
  38. package/dist/cli/interactive.js.map +1 -1
  39. package/dist/cli/jobs.d.ts +4 -0
  40. package/dist/cli/jobs.d.ts.map +1 -1
  41. package/dist/cli/jobs.js +26 -0
  42. package/dist/cli/jobs.js.map +1 -1
  43. package/dist/cli/prompt.d.ts +9 -0
  44. package/dist/cli/prompt.d.ts.map +1 -0
  45. package/dist/cli/prompt.js +116 -0
  46. package/dist/cli/prompt.js.map +1 -0
  47. package/dist/cli/setup.d.ts.map +1 -1
  48. package/dist/cli/setup.js +95 -20
  49. package/dist/cli/setup.js.map +1 -1
  50. package/dist/cli/spinner.d.ts +6 -0
  51. package/dist/cli/spinner.d.ts.map +1 -0
  52. package/dist/cli/spinner.js +39 -0
  53. package/dist/cli/spinner.js.map +1 -0
  54. package/dist/cli/triggers.d.ts.map +1 -1
  55. package/dist/cli/triggers.js +12 -4
  56. package/dist/cli/triggers.js.map +1 -1
  57. package/dist/cli/tunnel.d.ts +12 -0
  58. package/dist/cli/tunnel.d.ts.map +1 -0
  59. package/dist/cli/tunnel.js +64 -0
  60. package/dist/cli/tunnel.js.map +1 -0
  61. package/dist/cli/voice.d.ts.map +1 -1
  62. package/dist/cli/voice.js +13 -2
  63. package/dist/cli/voice.js.map +1 -1
  64. package/dist/config/env.d.ts +5 -0
  65. package/dist/config/env.d.ts.map +1 -1
  66. package/dist/config/env.js +7 -2
  67. package/dist/config/env.js.map +1 -1
  68. package/dist/config/models.d.ts +1 -0
  69. package/dist/config/models.d.ts.map +1 -1
  70. package/dist/config/models.js +1 -0
  71. package/dist/config/models.js.map +1 -1
  72. package/dist/documents/pdf.d.ts +11 -0
  73. package/dist/documents/pdf.d.ts.map +1 -0
  74. package/dist/documents/pdf.js +64 -0
  75. package/dist/documents/pdf.js.map +1 -0
  76. package/dist/documents/slides.d.ts +13 -0
  77. package/dist/documents/slides.d.ts.map +1 -0
  78. package/dist/documents/slides.js +82 -0
  79. package/dist/documents/slides.js.map +1 -0
  80. package/dist/index.js +72 -3
  81. package/dist/index.js.map +1 -1
  82. package/dist/jobs/anomaly.d.ts +10 -0
  83. package/dist/jobs/anomaly.d.ts.map +1 -0
  84. package/dist/jobs/anomaly.js +52 -0
  85. package/dist/jobs/anomaly.js.map +1 -0
  86. package/dist/jobs/runner.d.ts.map +1 -1
  87. package/dist/jobs/runner.js +6 -0
  88. package/dist/jobs/runner.js.map +1 -1
  89. package/dist/jobs/trigger-orchestrator.d.ts +26 -0
  90. package/dist/jobs/trigger-orchestrator.d.ts.map +1 -0
  91. package/dist/jobs/trigger-orchestrator.js +252 -0
  92. package/dist/jobs/trigger-orchestrator.js.map +1 -0
  93. package/dist/jobs/trigger-policies.d.ts +36 -0
  94. package/dist/jobs/trigger-policies.d.ts.map +1 -0
  95. package/dist/jobs/trigger-policies.js +68 -0
  96. package/dist/jobs/trigger-policies.js.map +1 -0
  97. package/dist/jobs/trigger-router.d.ts +14 -0
  98. package/dist/jobs/trigger-router.d.ts.map +1 -0
  99. package/dist/jobs/trigger-router.js +114 -0
  100. package/dist/jobs/trigger-router.js.map +1 -0
  101. package/dist/jobs/webhook-server.d.ts +7 -0
  102. package/dist/jobs/webhook-server.d.ts.map +1 -0
  103. package/dist/jobs/webhook-server.js +56 -0
  104. package/dist/jobs/webhook-server.js.map +1 -0
  105. package/dist/jobs/workflows.d.ts +4 -1
  106. package/dist/jobs/workflows.d.ts.map +1 -1
  107. package/dist/jobs/workflows.js +18 -42
  108. package/dist/jobs/workflows.js.map +1 -1
  109. package/dist/memory/heal.d.ts +19 -0
  110. package/dist/memory/heal.d.ts.map +1 -0
  111. package/dist/memory/heal.js +253 -0
  112. package/dist/memory/heal.js.map +1 -0
  113. package/dist/memory/knowledge-graph.d.ts +32 -0
  114. package/dist/memory/knowledge-graph.d.ts.map +1 -0
  115. package/dist/memory/knowledge-graph.js +103 -0
  116. package/dist/memory/knowledge-graph.js.map +1 -0
  117. package/dist/memory/local-store.d.ts +1 -0
  118. package/dist/memory/local-store.d.ts.map +1 -1
  119. package/dist/memory/local-store.js +41 -8
  120. package/dist/memory/local-store.js.map +1 -1
  121. package/dist/memory/notebook.d.ts +29 -0
  122. package/dist/memory/notebook.d.ts.map +1 -0
  123. package/dist/memory/notebook.js +64 -0
  124. package/dist/memory/notebook.js.map +1 -0
  125. package/dist/memory/personal-context.d.ts +45 -0
  126. package/dist/memory/personal-context.d.ts.map +1 -0
  127. package/dist/memory/personal-context.js +103 -0
  128. package/dist/memory/personal-context.js.map +1 -0
  129. package/dist/memory/scratchpad.d.ts.map +1 -1
  130. package/dist/memory/scratchpad.js +30 -4
  131. package/dist/memory/scratchpad.js.map +1 -1
  132. package/dist/runtime/ask.d.ts +15 -0
  133. package/dist/runtime/ask.d.ts.map +1 -0
  134. package/dist/runtime/ask.js +23 -0
  135. package/dist/runtime/ask.js.map +1 -0
  136. package/dist/runtime/confirm.d.ts +3 -0
  137. package/dist/runtime/confirm.d.ts.map +1 -1
  138. package/dist/runtime/confirm.js +38 -12
  139. package/dist/runtime/confirm.js.map +1 -1
  140. package/dist/runtime/progress.d.ts +2 -1
  141. package/dist/runtime/progress.d.ts.map +1 -1
  142. package/dist/runtime/progress.js +3 -0
  143. package/dist/runtime/progress.js.map +1 -1
  144. package/dist/runtime/voice-confirm.d.ts +8 -0
  145. package/dist/runtime/voice-confirm.d.ts.map +1 -0
  146. package/dist/runtime/voice-confirm.js +67 -0
  147. package/dist/runtime/voice-confirm.js.map +1 -0
  148. package/dist/safety/trust-log.d.ts +23 -0
  149. package/dist/safety/trust-log.d.ts.map +1 -0
  150. package/dist/safety/trust-log.js +57 -0
  151. package/dist/safety/trust-log.js.map +1 -0
  152. package/dist/server.d.ts +2 -0
  153. package/dist/server.d.ts.map +1 -1
  154. package/dist/server.js +2 -0
  155. package/dist/server.js.map +1 -1
  156. package/dist/skills/loader.d.ts +14 -0
  157. package/dist/skills/loader.d.ts.map +1 -0
  158. package/dist/skills/loader.js +108 -0
  159. package/dist/skills/loader.js.map +1 -0
  160. package/dist/skills/registry.d.ts +22 -0
  161. package/dist/skills/registry.d.ts.map +1 -0
  162. package/dist/skills/registry.js +120 -0
  163. package/dist/skills/registry.js.map +1 -0
  164. package/dist/tools/ask.tool.d.ts +23 -0
  165. package/dist/tools/ask.tool.d.ts.map +1 -0
  166. package/dist/tools/ask.tool.js +47 -0
  167. package/dist/tools/ask.tool.js.map +1 -0
  168. package/dist/tools/cli-runner.d.ts +13 -0
  169. package/dist/tools/cli-runner.d.ts.map +1 -0
  170. package/dist/tools/cli-runner.js +75 -0
  171. package/dist/tools/cli-runner.js.map +1 -0
  172. package/dist/tools/computer-use.tool.d.ts +159 -0
  173. package/dist/tools/computer-use.tool.d.ts.map +1 -0
  174. package/dist/tools/computer-use.tool.js +357 -0
  175. package/dist/tools/computer-use.tool.js.map +1 -0
  176. package/dist/tools/desktop.tool.d.ts.map +1 -1
  177. package/dist/tools/desktop.tool.js +3 -2
  178. package/dist/tools/desktop.tool.js.map +1 -1
  179. package/dist/tools/documents.tool.d.ts +31 -0
  180. package/dist/tools/documents.tool.d.ts.map +1 -0
  181. package/dist/tools/documents.tool.js +62 -0
  182. package/dist/tools/documents.tool.js.map +1 -0
  183. package/dist/tools/git.tool.d.ts +77 -0
  184. package/dist/tools/git.tool.d.ts.map +1 -0
  185. package/dist/tools/git.tool.js +189 -0
  186. package/dist/tools/git.tool.js.map +1 -0
  187. package/dist/tools/heal.tool.d.ts +13 -0
  188. package/dist/tools/heal.tool.d.ts.map +1 -0
  189. package/dist/tools/heal.tool.js +34 -0
  190. package/dist/tools/heal.tool.js.map +1 -0
  191. package/dist/tools/image-generate.tool.d.ts.map +1 -1
  192. package/dist/tools/image-generate.tool.js +2 -1
  193. package/dist/tools/image-generate.tool.js.map +1 -1
  194. package/dist/tools/knowledge.tool.d.ts +24 -0
  195. package/dist/tools/knowledge.tool.d.ts.map +1 -0
  196. package/dist/tools/knowledge.tool.js +52 -0
  197. package/dist/tools/knowledge.tool.js.map +1 -0
  198. package/dist/tools/notebook.tool.d.ts +32 -0
  199. package/dist/tools/notebook.tool.d.ts.map +1 -0
  200. package/dist/tools/notebook.tool.js +50 -0
  201. package/dist/tools/notebook.tool.js.map +1 -0
  202. package/dist/tools/notify.tool.d.ts +26 -0
  203. package/dist/tools/notify.tool.d.ts.map +1 -0
  204. package/dist/tools/notify.tool.js +65 -0
  205. package/dist/tools/notify.tool.js.map +1 -0
  206. package/dist/tools/orchestration.tool.d.ts +83 -0
  207. package/dist/tools/orchestration.tool.d.ts.map +1 -0
  208. package/dist/tools/orchestration.tool.js +154 -0
  209. package/dist/tools/orchestration.tool.js.map +1 -0
  210. package/dist/tools/osint-install.tool.d.ts +35 -0
  211. package/dist/tools/osint-install.tool.d.ts.map +1 -0
  212. package/dist/tools/osint-install.tool.js +334 -0
  213. package/dist/tools/osint-install.tool.js.map +1 -0
  214. package/dist/tools/osint.tool.d.ts +445 -0
  215. package/dist/tools/osint.tool.d.ts.map +1 -0
  216. package/dist/tools/osint.tool.js +633 -0
  217. package/dist/tools/osint.tool.js.map +1 -0
  218. package/dist/tools/pentest-install.tool.d.ts +32 -0
  219. package/dist/tools/pentest-install.tool.d.ts.map +1 -0
  220. package/dist/tools/pentest-install.tool.js +201 -0
  221. package/dist/tools/pentest-install.tool.js.map +1 -0
  222. package/dist/tools/pentest.tool.d.ts +595 -0
  223. package/dist/tools/pentest.tool.d.ts.map +1 -0
  224. package/dist/tools/pentest.tool.js +844 -0
  225. package/dist/tools/pentest.tool.js.map +1 -0
  226. package/dist/tools/personal-context.tool.d.ts +32 -0
  227. package/dist/tools/personal-context.tool.d.ts.map +1 -0
  228. package/dist/tools/personal-context.tool.js +76 -0
  229. package/dist/tools/personal-context.tool.js.map +1 -0
  230. package/dist/tools/setup-assistant.tool.d.ts +49 -0
  231. package/dist/tools/setup-assistant.tool.d.ts.map +1 -0
  232. package/dist/tools/setup-assistant.tool.js +139 -0
  233. package/dist/tools/setup-assistant.tool.js.map +1 -0
  234. package/dist/tools/skills.tool.d.ts +38 -0
  235. package/dist/tools/skills.tool.d.ts.map +1 -0
  236. package/dist/tools/skills.tool.js +77 -0
  237. package/dist/tools/skills.tool.js.map +1 -0
  238. package/dist/tools/trust.tool.d.ts +16 -0
  239. package/dist/tools/trust.tool.d.ts.map +1 -0
  240. package/dist/tools/trust.tool.js +34 -0
  241. package/dist/tools/trust.tool.js.map +1 -0
  242. package/dist/tools/update.tool.d.ts +25 -0
  243. package/dist/tools/update.tool.d.ts.map +1 -0
  244. package/dist/tools/update.tool.js +64 -0
  245. package/dist/tools/update.tool.js.map +1 -0
  246. package/dist/tools/workspace.tool.d.ts +24 -0
  247. package/dist/tools/workspace.tool.d.ts.map +1 -0
  248. package/dist/tools/workspace.tool.js +64 -0
  249. package/dist/tools/workspace.tool.js.map +1 -0
  250. package/dist/voice/cascade.d.ts.map +1 -1
  251. package/dist/voice/cascade.js +75 -24
  252. package/dist/voice/cascade.js.map +1 -1
  253. package/dist/voice/deepgram.d.ts.map +1 -1
  254. package/dist/voice/deepgram.js +8 -1
  255. package/dist/voice/deepgram.js.map +1 -1
  256. package/dist/voice/types.d.ts +4 -0
  257. package/dist/voice/types.d.ts.map +1 -1
  258. package/dist/workspace/init.d.ts +3 -0
  259. package/dist/workspace/init.d.ts.map +1 -0
  260. package/dist/workspace/init.js +75 -0
  261. package/dist/workspace/init.js.map +1 -0
  262. package/dist/workspace/output-paths.d.ts +3 -0
  263. package/dist/workspace/output-paths.d.ts.map +1 -0
  264. package/dist/workspace/output-paths.js +18 -0
  265. package/dist/workspace/output-paths.js.map +1 -0
  266. package/dist/workspace/paths.d.ts +26 -0
  267. package/dist/workspace/paths.d.ts.map +1 -0
  268. package/dist/workspace/paths.js +48 -0
  269. package/dist/workspace/paths.js.map +1 -0
  270. package/package.json +5 -1
@@ -0,0 +1,844 @@
1
+ import { mkdir, writeFile, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { tool } from 'ai';
4
+ import { z } from 'zod';
5
+ import { requestConfirmation } from '../runtime/confirm.js';
6
+ import { emitProgress } from '../runtime/progress.js';
7
+ import { runCliTool } from './cli-runner.js';
8
+ import { getOutputDir } from '../workspace/output-paths.js';
9
+ // ─── Constants & helpers ─────────────────────────────────────────────────────
10
+ const IS_WIN = process.platform === 'win32';
11
+ function pentestOutputDir() {
12
+ return getOutputDir('pentest');
13
+ }
14
+ async function confirmPentestAction(action, details) {
15
+ return requestConfirmation({
16
+ toolkitSlug: 'ZILMATE',
17
+ toolSlug: 'PENTEST',
18
+ action,
19
+ access: 'Read-only',
20
+ targetTools: ['ZILMATE_PENTEST'],
21
+ details,
22
+ summary: details.join('; '),
23
+ });
24
+ }
25
+ async function ensureDir(subdir) {
26
+ const dir = subdir ? path.join(pentestOutputDir(), subdir) : pentestOutputDir();
27
+ await mkdir(dir, { recursive: true });
28
+ return dir;
29
+ }
30
+ function ts() {
31
+ return new Date().toISOString().replace(/[:.]/g, '-');
32
+ }
33
+ function safe(s) {
34
+ return s.replace(/[^a-z0-9_.-]/gi, '_').slice(0, 60);
35
+ }
36
+ /**
37
+ * Run a CLI tool and stream stdout+stderr.
38
+ * Resolves even on non-zero exit — many pentest tools exit 1 on partial results.
39
+ */
40
+ function runTool(command, args, timeoutMs = 120_000) {
41
+ return runCliTool(command, args, { timeoutMs });
42
+ }
43
+ async function save(subdir, filename, content) {
44
+ const dir = await ensureDir(subdir);
45
+ const p = path.join(dir, filename);
46
+ await writeFile(p, content, 'utf8');
47
+ return p;
48
+ }
49
+ // ─── 1. Nmap — Network Scanner & NSE Vulnerability Engine ────────────────────
50
+ export const nmapTool = {
51
+ /**
52
+ * nmap [scan type] [timing] [port range] [script] [-oA output] <target>
53
+ *
54
+ * Key flags (from nmap.org & offseckit.com cheat sheet):
55
+ * -sS SYN stealth scan (default, requires root/admin)
56
+ * -sV Service/version detection
57
+ * -O OS fingerprinting
58
+ * -sC Default NSE scripts (equiv. --script=default)
59
+ * --script NSE script or category: vuln, safe, default, auth, brute, discovery
60
+ * --script-args mincvss=7.0 filter CVE results by minimum CVSS score
61
+ * -p- All 65535 ports
62
+ * -F Fast: top 100 ports
63
+ * -T0..T5 Timing: T1=sneaky, T2=polite, T3=normal, T4=aggressive, T5=insane
64
+ * -Pn Skip host discovery (treat all hosts as online)
65
+ * -f Fragment packets (evade some firewalls)
66
+ * --open Show only open ports in output
67
+ * -oA <base> Output all formats: .nmap .xml .gnmap
68
+ * -iL <file> Input target list from file
69
+ * -sU UDP scan
70
+ * -sn Host discovery only (ping sweep), no port scan
71
+ */
72
+ runNmap: tool({
73
+ description: 'Network scanner and vulnerability enumerator. Discovers open ports, services, versions, OS, and runs NSE scripts including vuln category to detect CVEs. The mandatory first recon step for any pentest.',
74
+ inputSchema: z.object({
75
+ target: z.string().min(1).describe('IP, hostname, CIDR range (192.168.1.0/24), or file path prefixed with @.'),
76
+ scanType: z
77
+ .enum(['quick', 'full', 'udp', 'stealth', 'discovery'])
78
+ .optional()
79
+ .default('quick')
80
+ .describe('quick=top 1000 ports + version (-sS -sV -T4); full=all 65535 ports (-sS -sV -p- -T4); udp=top 100 UDP; stealth=slow fragmented (-sS -T2 -f); discovery=host sweep only (-sn)'),
81
+ scripts: z
82
+ .array(z.enum(['vuln', 'safe', 'default', 'auth', 'brute', 'discovery', 'vulners']))
83
+ .optional()
84
+ .describe('NSE script categories to run. "vuln" catches Heartbleed, EternalBlue, Shellshock, etc.'),
85
+ minCvss: z
86
+ .number()
87
+ .min(0)
88
+ .max(10)
89
+ .optional()
90
+ .describe('Filter CVE results to this minimum CVSS score when using vulners script. Recommended: 7.0.'),
91
+ ports: z.string().optional().describe('Specific ports or ranges, e.g. "22,80,443" or "1-1024". Overrides scanType port range.'),
92
+ osDetect: z.boolean().optional().default(false).describe('Enable OS fingerprinting (-O). Requires root/admin.'),
93
+ timing: z
94
+ .number()
95
+ .int()
96
+ .min(0)
97
+ .max(5)
98
+ .optional()
99
+ .default(4)
100
+ .describe('Timing template 0-5. 4=aggressive (fast), 2=polite (quiet), 1=sneaky (IDS evasion).'),
101
+ skipHostDiscovery: z.boolean().optional().default(false).describe('Add -Pn to treat all hosts as online (useful when ICMP is blocked).'),
102
+ }),
103
+ execute: async ({ target, scanType, scripts, minCvss, ports, osDetect, timing, skipHostDiscovery }) => {
104
+ const approved = await confirmPentestAction('Run Nmap scan', [
105
+ `Target: ${target}`,
106
+ `Scan type: ${scanType}`,
107
+ scripts?.length ? `NSE scripts: ${scripts.join(', ')}` : 'No NSE scripts',
108
+ osDetect ? 'OS detection enabled (requires root)' : '',
109
+ `Timing: T${timing}`,
110
+ ].filter(Boolean));
111
+ if (!approved)
112
+ throw new Error('Blocked Nmap scan. Ask user to approve.');
113
+ emitProgress({ type: 'tool:start', label: 'Nmap scanning', detail: target });
114
+ const dir = await ensureDir('nmap');
115
+ const outBase = path.join(dir, `nmap-${safe(target)}-${ts()}`);
116
+ const args = [];
117
+ // Scan type → flag bundle
118
+ switch (scanType) {
119
+ case 'quick':
120
+ args.push('-sS', '-sV', `--open`);
121
+ break;
122
+ case 'full':
123
+ args.push('-sS', '-sV', '-p-', '--open');
124
+ break;
125
+ case 'udp':
126
+ args.push('-sU', '-sV', '--top-ports', '100');
127
+ break;
128
+ case 'stealth':
129
+ args.push('-sS', '-f', '--source-port', '53');
130
+ break;
131
+ case 'discovery':
132
+ args.push('-sn');
133
+ break;
134
+ }
135
+ // Port override
136
+ if (ports)
137
+ args.push('-p', ports);
138
+ // Timing
139
+ args.push(`-T${timing}`);
140
+ // NSE scripts
141
+ if (scripts?.length) {
142
+ const scriptArg = scripts.join(',');
143
+ args.push(`--script`, scriptArg);
144
+ if (minCvss !== undefined && scripts.includes('vulners')) {
145
+ args.push('--script-args', `mincvss=${minCvss}`);
146
+ }
147
+ }
148
+ // OS detection
149
+ if (osDetect)
150
+ args.push('-O');
151
+ // Skip host discovery
152
+ if (skipHostDiscovery)
153
+ args.push('-Pn');
154
+ // No color + all output formats
155
+ args.push('--no-stylesheet', '-oA', outBase);
156
+ // Target (handle file input)
157
+ if (target.startsWith('@')) {
158
+ args.push('-iL', target.slice(1));
159
+ }
160
+ else {
161
+ args.push(target);
162
+ }
163
+ const raw = await runTool('nmap', args, scanType === 'full' ? 1_800_000 : 300_000);
164
+ // Quick parse: open ports from normal output
165
+ const openPorts = [...raw.matchAll(/(\d+)\/(?:tcp|udp)\s+open\s+(\S+)/g)]
166
+ .map((m) => ({ port: Number(m[1]), service: m[2] }));
167
+ // CVEs from vulners script
168
+ const cves = [...raw.matchAll(/(CVE-\d{4}-\d+)\s+([\d.]+)/g)]
169
+ .map((m) => ({ cve: m[1], cvss: parseFloat(m[2]) }))
170
+ .filter((c) => !minCvss || c.cvss >= minCvss)
171
+ .sort((a, b) => b.cvss - a.cvss);
172
+ emitProgress({ type: 'tool:end', label: 'Nmap complete', detail: `${openPorts.length} open ports, ${cves.length} CVEs` });
173
+ return {
174
+ target,
175
+ openPorts,
176
+ cves,
177
+ outputFiles: { normal: `${outBase}.nmap`, xml: `${outBase}.xml`, grepable: `${outBase}.gnmap` },
178
+ raw: raw.slice(0, 5000),
179
+ };
180
+ },
181
+ }),
182
+ };
183
+ // ─── 2. Nuclei — Template-Based Vulnerability Scanner ────────────────────────
184
+ export const nucleiTool = {
185
+ /**
186
+ * nuclei [flags]
187
+ *
188
+ * Key flags (from github.com/projectdiscovery/nuclei README):
189
+ * -u <url> Single target URL/host
190
+ * -l <file> Target list file
191
+ * -t <dir/file> Template path (default: ~/.local/nuclei-templates)
192
+ * -tags <csv> Filter by tag: cve,exposures,misconfigurations,default-logins,kev,vkev
193
+ * -severity <csv> Filter: info,low,medium,high,critical
194
+ * -exclude-tags Tags to skip
195
+ * -rl <int> Rate limit (requests/sec)
196
+ * -c <int> Concurrency
197
+ * -o <file> Output file
198
+ * -json JSON output
199
+ * -je <file> JSON-lines export
200
+ * -nc No colour
201
+ * -silent Only findings
202
+ * -update-templates Auto-update community templates
203
+ *
204
+ * Special tag combos:
205
+ * -tags kev CISA Known Exploited Vulnerabilities (1496+ templates)
206
+ * -tags vkev Vendor-confirmed KEV
207
+ */
208
+ runNuclei: tool({
209
+ description: 'Fast, template-based vulnerability scanner covering CVEs, misconfigurations, exposed panels, default logins, and CISA Known Exploited Vulnerabilities (1,496+ KEV templates). Pipe Nmap/subfinder results into it for full-chain recon→scan automation.',
210
+ inputSchema: z.object({
211
+ target: z.string().min(1).describe('URL, hostname, IP, CIDR, or @/path/to/list.txt for bulk.'),
212
+ severity: z
213
+ .array(z.enum(['info', 'low', 'medium', 'high', 'critical']))
214
+ .optional()
215
+ .default(['medium', 'high', 'critical'])
216
+ .describe('Only run templates matching these severities.'),
217
+ tags: z
218
+ .array(z.string())
219
+ .optional()
220
+ .describe('Template tags to include. Examples: "cve", "kev" (CISA exploited), "exposures", "misconfigurations", "default-logins", "panel", "takeover".'),
221
+ excludeTags: z
222
+ .array(z.string())
223
+ .optional()
224
+ .describe('Tags to skip (e.g. "dos", "fuzz" for non-destructive scans).'),
225
+ rateLimit: z.number().int().min(1).max(500).optional().default(150).describe('Max requests per second.'),
226
+ concurrency: z.number().int().min(1).max(100).optional().default(25).describe('Concurrent template executions.'),
227
+ updateTemplates: z.boolean().optional().default(false).describe('Update community templates before scanning (-update-templates).'),
228
+ }),
229
+ execute: async ({ target, severity, tags, excludeTags, rateLimit, concurrency, updateTemplates }) => {
230
+ const approved = await confirmPentestAction('Run Nuclei vulnerability scan', [
231
+ `Target: ${target}`,
232
+ `Severity: ${severity.join(', ')}`,
233
+ tags?.length ? `Tags: ${tags.join(', ')}` : 'No tag filter (all templates for severity)',
234
+ excludeTags?.length ? `Excluding: ${excludeTags.join(', ')}` : '',
235
+ `Rate limit: ${rateLimit} req/s`,
236
+ ].filter(Boolean));
237
+ if (!approved)
238
+ throw new Error('Blocked Nuclei scan. Ask user to approve.');
239
+ // Update templates first if requested
240
+ if (updateTemplates) {
241
+ emitProgress({ type: 'tool:start', label: 'Updating Nuclei templates' });
242
+ await runTool('nuclei', ['-update-templates'], 120_000).catch(() => { });
243
+ emitProgress({ type: 'tool:end', label: 'Templates updated' });
244
+ }
245
+ emitProgress({ type: 'tool:start', label: 'Nuclei scanning', detail: target });
246
+ const dir = await ensureDir('nuclei');
247
+ const outFile = path.join(dir, `nuclei-${safe(target)}-${ts()}.jsonl`);
248
+ const args = ['-nc', '-silent'];
249
+ // Target
250
+ if (target.startsWith('@')) {
251
+ args.push('-l', target.slice(1));
252
+ }
253
+ else {
254
+ args.push('-u', target);
255
+ }
256
+ // Severity filter
257
+ if (severity?.length)
258
+ args.push('-severity', severity.join(','));
259
+ // Tag filters
260
+ if (tags?.length)
261
+ args.push('-tags', tags.join(','));
262
+ if (excludeTags?.length)
263
+ args.push('-exclude-tags', excludeTags.join(','));
264
+ // Performance
265
+ args.push('-rl', String(rateLimit), '-c', String(concurrency));
266
+ // JSON lines output
267
+ args.push('-je', outFile);
268
+ const raw = await runTool('nuclei', args, 900_000);
269
+ // Parse JSONL findings
270
+ let findings = [];
271
+ try {
272
+ const content = await readFile(outFile, 'utf8');
273
+ findings = content
274
+ .split('\n')
275
+ .filter(Boolean)
276
+ .map((line) => {
277
+ const j = JSON.parse(line);
278
+ return {
279
+ templateId: j['template-id'],
280
+ name: j['info']?.name ?? '',
281
+ severity: j['info']?.severity ?? '',
282
+ host: j['host'],
283
+ matched: j['matched-at'],
284
+ };
285
+ });
286
+ }
287
+ catch { /* JSONL parse failed — fall through to raw */ }
288
+ const bySeverity = findings.reduce((acc, f) => {
289
+ acc[f.severity] = (acc[f.severity] ?? 0) + 1;
290
+ return acc;
291
+ }, {});
292
+ emitProgress({ type: 'tool:end', label: 'Nuclei complete', detail: `${findings.length} findings` });
293
+ return { target, findingCount: findings.length, bySeverity, findings: findings.slice(0, 50), outputFile: outFile, raw: raw.slice(0, 3000) };
294
+ },
295
+ }),
296
+ };
297
+ // ─── 3. Subfinder — Passive Subdomain Enumeration ────────────────────────────
298
+ export const subfinderTool = {
299
+ /**
300
+ * subfinder [flags]
301
+ *
302
+ * Key flags (docs.projectdiscovery.io/opensource/subfinder/usage):
303
+ * -d <domain> Single domain
304
+ * -dL <file> Domain list file
305
+ * -s <csv> Specific sources: crtsh,github,virustotal,...
306
+ * -all Use all passive sources (slow)
307
+ * -recursive Recursive subdomain resolution
308
+ * -o <file> Output file
309
+ * -oJ JSON lines output
310
+ * -silent Subdomains only (clean output for piping)
311
+ * -t <int> Goroutines for resolution (default 10)
312
+ * -rl <int> Rate limit req/s
313
+ *
314
+ * API keys stored in: ~/.config/subfinder/provider-config.yaml
315
+ * Without keys: crtsh, dnsdumpster, waybackarchive, hackertarget still work.
316
+ */
317
+ runSubfinder: tool({
318
+ description: 'Fast passive subdomain enumeration using Subfinder. Discovers valid subdomains without active scanning. Chain output directly into Nuclei or httpx for full attack surface coverage.',
319
+ inputSchema: z.object({
320
+ domain: z.string().min(3).describe('Root domain to enumerate, e.g. example.com.'),
321
+ allSources: z.boolean().optional().default(false).describe('Use all available passive sources (slower, more thorough).'),
322
+ sources: z
323
+ .array(z.string())
324
+ .optional()
325
+ .describe('Specific sources: crtsh, github, virustotal, shodan, hackertarget, etc. Omit to use defaults.'),
326
+ recursive: z.boolean().optional().default(false).describe('Recursively discover subdomains of subdomains.'),
327
+ }),
328
+ execute: async ({ domain, allSources, sources, recursive }) => {
329
+ const approved = await confirmPentestAction('Run Subfinder passive subdomain enumeration', [
330
+ `Domain: ${domain}`,
331
+ allSources ? 'Using ALL passive sources' : sources?.length ? `Sources: ${sources.join(', ')}` : 'Using default sources (crtsh, dnsdumpster, hackertarget...)',
332
+ recursive ? 'Recursive enumeration enabled' : '',
333
+ ].filter(Boolean));
334
+ if (!approved)
335
+ throw new Error('Blocked Subfinder. Ask user to approve.');
336
+ emitProgress({ type: 'tool:start', label: 'Subfinder enumerating', detail: domain });
337
+ const dir = await ensureDir('subfinder');
338
+ const outFile = path.join(dir, `subfinder-${safe(domain)}-${ts()}.txt`);
339
+ const jsonFile = path.join(dir, `subfinder-${safe(domain)}-${ts()}.json`);
340
+ // -silent for clean subdomain-only output, -oJ for machine-readable
341
+ const args = ['-d', domain, '-silent', '-o', outFile, '-oJ'];
342
+ if (allSources)
343
+ args.push('-all');
344
+ if (sources?.length)
345
+ args.push('-s', sources.join(','));
346
+ if (recursive)
347
+ args.push('-recursive');
348
+ const raw = await runTool('subfinder', args, 300_000);
349
+ const subdomains = raw
350
+ .split('\n')
351
+ .map((l) => l.trim())
352
+ .filter((l) => l.includes(domain));
353
+ emitProgress({ type: 'tool:end', label: 'Subfinder complete', detail: `${subdomains.length} subdomains` });
354
+ return { domain, subdomainCount: subdomains.length, subdomains: subdomains.slice(0, 200), outputFile: outFile };
355
+ },
356
+ }),
357
+ };
358
+ // ─── 4. SQLMap — Automated SQL Injection Detection & Exploitation ─────────────
359
+ export const sqlmapTool = {
360
+ /**
361
+ * sqlmap [flags]
362
+ *
363
+ * Key flags (hacktricks.wiki, stationx.net/sqlmap-cheat-sheet):
364
+ * -u <url> Target URL with injectable parameter (e.g. "http://site/?id=1")
365
+ * -r <file> Load raw HTTP request from file (Burp export)
366
+ * --data <str> POST data string
367
+ * --cookie <str> Session cookies
368
+ * -p <param> Force parameter to test
369
+ * --dbms <db> Hint DBMS type: mysql, postgresql, mssql, oracle, sqlite
370
+ * --level <1-5> Test depth (1=basic, 5=exhaustive); default 1
371
+ * --risk <1-3> Risk level (3 includes heavy queries); default 1
372
+ * --technique <str> Injection technique: B=boolean, E=error, U=union, S=stacked, T=time, Q=inline
373
+ * --batch Non-interactive (auto-accept defaults)
374
+ * --threads <n> Concurrent requests
375
+ * --random-agent Random User-Agent
376
+ * --tamper <csv> WAF bypass scripts: apostrophemask, randomcase, space2comment...
377
+ * --dbs Enumerate databases
378
+ * --tables Enumerate tables (-D <db>)
379
+ * --columns Enumerate columns (-D <db> -T <table>)
380
+ * --dump Dump table data
381
+ * --current-user Get DB user
382
+ * --is-dba Check if user is DBA
383
+ * --os-cmd <cmd> Execute OS command (if stacked injection possible)
384
+ * --forms Auto-detect and test forms on the page
385
+ * --crawl <depth> Crawl site for injectable params
386
+ */
387
+ runSqlmap: tool({
388
+ description: 'Automated SQL injection detection and exploitation. Tests GET/POST params, cookies, headers. Supports WAF bypass tamper scripts, all major DBMS types, and can extract DB contents or run OS commands when injection allows.',
389
+ inputSchema: z.object({
390
+ target: z.string().min(1).describe('Target URL with a parameter, e.g. "http://site/page?id=1". Or @/path/to/request.txt to load a raw Burp request.'),
391
+ postData: z.string().optional().describe('POST body string, e.g. "user=foo&pass=bar". Use for POST endpoints.'),
392
+ cookie: z.string().optional().describe('Session cookie string, e.g. "PHPSESSID=abc123".'),
393
+ dbms: z.enum(['mysql', 'postgresql', 'mssql', 'oracle', 'sqlite', 'db2']).optional().describe('DBMS hint — speeds up testing significantly if known.'),
394
+ level: z.number().int().min(1).max(5).optional().default(1).describe('Test depth 1-5. 3+ tests headers/cookies. 5 is exhaustive.'),
395
+ risk: z.number().int().min(1).max(3).optional().default(1).describe('Risk 1-3. 2+ adds heavy time-based tests. 3 may modify data.'),
396
+ techniques: z.string().optional().default('BEUST').describe('Injection techniques: B=boolean, E=error, U=union, S=stacked, T=time. Default: BEUST (all).'),
397
+ goal: z
398
+ .enum(['detect', 'enumerate-dbs', 'enumerate-tables', 'dump', 'os-shell'])
399
+ .optional()
400
+ .default('detect')
401
+ .describe('What to do: detect=find injection only; enumerate-dbs=list databases; enumerate-tables=list tables; dump=extract data; os-shell=try OS command execution.'),
402
+ database: z.string().optional().describe('Target database name (required for enumerate-tables and dump).'),
403
+ table: z.string().optional().describe('Target table name (required for dump with specific table).'),
404
+ tamper: z
405
+ .array(z.string())
406
+ .optional()
407
+ .describe('WAF bypass tamper scripts. Common: apostrophemask, randomcase, space2comment, between, charunicodeencode.'),
408
+ threads: z.number().int().min(1).max(10).optional().default(4),
409
+ forms: z.boolean().optional().default(false).describe('Auto-detect and test all forms on the target page.'),
410
+ }),
411
+ execute: async ({ target, postData, cookie, dbms, level, risk, techniques, goal, database, table, tamper, threads, forms }) => {
412
+ const approved = await confirmPentestAction('Run SQLMap injection test', [
413
+ `Target: ${target}`,
414
+ `Goal: ${goal}`,
415
+ `Level: ${level}, Risk: ${risk}`,
416
+ `Techniques: ${techniques}`,
417
+ dbms ? `DBMS hint: ${dbms}` : '',
418
+ tamper?.length ? `Tamper scripts: ${tamper.join(', ')}` : '',
419
+ goal === 'dump' ? '⚠️ Will extract database contents' : '',
420
+ goal === 'os-shell' ? '⚠️ Will attempt OS command execution' : '',
421
+ ].filter(Boolean));
422
+ if (!approved)
423
+ throw new Error('Blocked SQLMap. Ask user to approve.');
424
+ emitProgress({ type: 'tool:start', label: 'SQLMap testing', detail: target });
425
+ const dir = await ensureDir('sqlmap');
426
+ const outDir = path.join(dir, `sqlmap-${safe(target)}-${ts()}`);
427
+ const args = ['--batch', '--no-logging', `--output-dir=${outDir}`, '--no-cast'];
428
+ // Target: file or URL
429
+ if (target.startsWith('@')) {
430
+ args.push('-r', target.slice(1));
431
+ }
432
+ else {
433
+ args.push('-u', target);
434
+ }
435
+ if (postData)
436
+ args.push('--data', postData);
437
+ if (cookie)
438
+ args.push('--cookie', cookie);
439
+ if (dbms)
440
+ args.push('--dbms', dbms);
441
+ args.push('--level', String(level), '--risk', String(risk));
442
+ args.push('--technique', techniques);
443
+ args.push('--threads', String(threads));
444
+ args.push('--random-agent');
445
+ if (forms)
446
+ args.push('--forms');
447
+ if (tamper?.length)
448
+ args.push('--tamper', tamper.join(','));
449
+ // Goal-specific flags
450
+ switch (goal) {
451
+ case 'enumerate-dbs':
452
+ args.push('--dbs', '--current-user', '--is-dba', '--hostname');
453
+ break;
454
+ case 'enumerate-tables':
455
+ if (database)
456
+ args.push('-D', database);
457
+ args.push('--tables');
458
+ break;
459
+ case 'dump':
460
+ if (database)
461
+ args.push('-D', database);
462
+ if (table)
463
+ args.push('-T', table);
464
+ args.push('--dump');
465
+ break;
466
+ case 'os-shell':
467
+ args.push('--os-cmd', 'whoami');
468
+ break;
469
+ // 'detect' → no extra flags
470
+ }
471
+ const raw = await runTool('sqlmap', args, 600_000);
472
+ // Parse injectable parameters
473
+ const injectable = [...raw.matchAll(/Parameter:\s+'?([^'\n]+)'?\s+is\s+(?:injectable|vulnerable)/gi)].map((m) => m[1].trim());
474
+ const isVulnerable = injectable.length > 0 || /\[CRITICAL\].*sql injection/i.test(raw);
475
+ emitProgress({ type: 'tool:end', label: 'SQLMap complete', detail: isVulnerable ? `VULNERABLE: ${injectable.join(', ')}` : 'No injection found' });
476
+ return { target, goal, isVulnerable, injectableParams: injectable, outputDir: outDir, raw: raw.slice(0, 5000) };
477
+ },
478
+ }),
479
+ };
480
+ // ─── 5. ffuf — Fast Web Fuzzer (Dirs, Vhosts, Parameters) ────────────────────
481
+ export const ffufTool = {
482
+ /**
483
+ * ffuf [flags]
484
+ *
485
+ * Key flags (github.com/ffuf/ffuf):
486
+ * -u <url> URL with FUZZ keyword, e.g. http://site/FUZZ or http://FUZZ.site.com
487
+ * -w <wordlist> Wordlist path (use - for stdin)
488
+ * -H <header> Additional header, e.g. "Host: FUZZ.site.com"
489
+ * -X <method> HTTP method (default GET)
490
+ * -d <data> POST body
491
+ * -mc <csv> Match HTTP status codes (default: 200,204,301,302,307,401,403,405,500)
492
+ * -fc <csv> Filter out status codes
493
+ * -ms <int> Match response size
494
+ * -fs <csv> Filter out response sizes
495
+ * -fw <int> Filter by word count
496
+ * -fl <int> Filter by line count
497
+ * -t <int> Threads (default 40)
498
+ * -rate <int> Rate limit (req/s)
499
+ * -o <file> Output file
500
+ * -of <fmt> Output format: json, ejson, html, md, csv, ecsv (default json)
501
+ * -c Colorize output
502
+ * -v Verbose (show full URLs)
503
+ * -s Silent (only results)
504
+ * -recursion Recursive directory fuzzing
505
+ * -recursion-depth Max recursion depth
506
+ * -e <csv> Extensions to append: .php,.html,.txt
507
+ * -ic Ignore wordlist comments
508
+ *
509
+ * Common wordlists (SecLists):
510
+ * /usr/share/seclists/Discovery/Web-Content/common.txt
511
+ * /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
512
+ * /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
513
+ */
514
+ runFfuf: tool({
515
+ description: 'Fast web fuzzer for directory/file brute-force, vhost discovery, parameter mining, and backup file hunting. Pairs perfectly with Subfinder (feed subdomains) and Nuclei (feed discovered paths).',
516
+ inputSchema: z.object({
517
+ url: z.string().url().describe('Target URL with FUZZ keyword, e.g. "https://site.com/FUZZ" or "https://FUZZ.site.com/".'),
518
+ wordlist: z
519
+ .string()
520
+ .optional()
521
+ .describe('Path to wordlist. Defaults to /usr/share/seclists/Discovery/Web-Content/common.txt. Use /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt for vhost fuzzing.'),
522
+ mode: z
523
+ .enum(['directory', 'vhost', 'parameter', 'backup'])
524
+ .optional()
525
+ .default('directory')
526
+ .describe('"directory" = path brute-force; "vhost" = virtual host discovery (set Host header); "parameter" = GET param fuzzing; "backup" = hunt for .bak/.old/.orig/.zip files.'),
527
+ extensions: z
528
+ .array(z.string())
529
+ .optional()
530
+ .describe('File extensions to append to each word, e.g. ["php","html","txt","bak"]. Most useful in directory mode.'),
531
+ matchCodes: z
532
+ .array(z.number().int())
533
+ .optional()
534
+ .default([200, 204, 301, 302, 307, 401, 403])
535
+ .describe('HTTP status codes to show as results.'),
536
+ filterCodes: z.array(z.number().int()).optional().describe('HTTP status codes to hide.'),
537
+ filterSize: z.array(z.number().int()).optional().describe('Response sizes (bytes) to filter out — useful to suppress uniform 404 pages.'),
538
+ threads: z.number().int().min(1).max(200).optional().default(40),
539
+ rateLimit: z.number().int().min(0).optional().describe('Max requests per second. 0 = unlimited.'),
540
+ recursive: z.boolean().optional().default(false).describe('Recursively fuzz discovered directories.'),
541
+ recursionDepth: z.number().int().min(1).max(5).optional().default(2),
542
+ cookie: z.string().optional().describe('Cookie header value for authenticated fuzzing.'),
543
+ }),
544
+ execute: async ({ url, wordlist, mode, extensions, matchCodes, filterCodes, filterSize, threads, rateLimit, recursive, recursionDepth, cookie }) => {
545
+ const approved = await confirmPentestAction('Run ffuf web fuzzing', [
546
+ `Target: ${url}`,
547
+ `Mode: ${mode}`,
548
+ `Wordlist: ${wordlist ?? 'default (common.txt)'}`,
549
+ extensions?.length ? `Extensions: ${extensions.join(', ')}` : '',
550
+ `Threads: ${threads}`,
551
+ recursive ? `Recursive depth: ${recursionDepth}` : '',
552
+ ].filter(Boolean));
553
+ if (!approved)
554
+ throw new Error('Blocked ffuf. Ask user to approve.');
555
+ emitProgress({ type: 'tool:start', label: 'ffuf fuzzing', detail: url });
556
+ const dir = await ensureDir('ffuf');
557
+ const outFile = path.join(dir, `ffuf-${safe(url)}-${ts()}.json`);
558
+ // Default wordlists by mode
559
+ const wl = wordlist ?? (mode === 'vhost'
560
+ ? '/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt'
561
+ : mode === 'backup'
562
+ ? '/usr/share/seclists/Discovery/Web-Content/common.txt'
563
+ : '/usr/share/seclists/Discovery/Web-Content/common.txt');
564
+ const args = ['-s', '-ic', '-u', url, '-w', wl];
565
+ // Mode-specific setup
566
+ if (mode === 'vhost') {
567
+ // Override Host header for virtual host discovery
568
+ args.push('-H', `Host: FUZZ.${new URL(url).hostname}`);
569
+ }
570
+ else if (mode === 'parameter') {
571
+ // Fuzz GET param value: assume URL has FUZZ already placed by user
572
+ }
573
+ else if (mode === 'backup') {
574
+ args.push('-e', '.bak,.old,.orig,.backup,.zip,.tar.gz,.sql,.swp,.~');
575
+ }
576
+ if (extensions?.length && mode === 'directory') {
577
+ args.push('-e', extensions.map((e) => (e.startsWith('.') ? e : `.${e}`)).join(','));
578
+ }
579
+ // Filters & matches
580
+ args.push('-mc', matchCodes.join(','));
581
+ if (filterCodes?.length)
582
+ args.push('-fc', filterCodes.join(','));
583
+ if (filterSize?.length)
584
+ args.push('-fs', filterSize.join(','));
585
+ // Performance
586
+ args.push('-t', String(threads));
587
+ if (rateLimit)
588
+ args.push('-rate', String(rateLimit));
589
+ // Recursion
590
+ if (recursive) {
591
+ args.push('-recursion', '-recursion-depth', String(recursionDepth));
592
+ }
593
+ // Auth
594
+ if (cookie)
595
+ args.push('-H', `Cookie: ${cookie}`);
596
+ // Output
597
+ args.push('-o', outFile, '-of', 'json');
598
+ const raw = await runTool('ffuf', args, 900_000);
599
+ // Parse JSON output
600
+ let results = [];
601
+ try {
602
+ const j = JSON.parse(await readFile(outFile, 'utf8'));
603
+ results = j.results.map((r) => ({
604
+ input: r.input['FUZZ'] ?? '',
605
+ status: r.status,
606
+ length: r.length,
607
+ words: r.words,
608
+ }));
609
+ }
610
+ catch { /* JSON parse failed */ }
611
+ emitProgress({ type: 'tool:end', label: 'ffuf complete', detail: `${results.length} results` });
612
+ return { url, mode, resultCount: results.length, results: results.slice(0, 100), outputFile: outFile, raw: raw.slice(0, 2000) };
613
+ },
614
+ }),
615
+ };
616
+ // ─── 6. httpx — HTTP Probing & Fingerprinting ────────────────────────────────
617
+ export const httpxTool = {
618
+ /**
619
+ * httpx [flags]
620
+ *
621
+ * Key flags (github.com/projectdiscovery/httpx):
622
+ * -l <file> Input list of hosts/URLs
623
+ * -u <url> Single target
624
+ * -title Extract page title
625
+ * -tech-detect Technology fingerprinting (Wappalyzer-based)
626
+ * -status-code Show HTTP status codes
627
+ * -content-length Show content length
628
+ * -follow-redirects Follow HTTP redirects
629
+ * -tls-probe Probe TLS info (certs, expiry, SANs)
630
+ * -tls-grab Grab all TLS data
631
+ * -web-server Show web server header
632
+ * -ip Resolve and show IPs
633
+ * -cdn Detect CDN
634
+ * -probe Show probe result
635
+ * -threads <n> Concurrent probers
636
+ * -rate-limit <n> Max requests/second
637
+ * -o <file> Output file
638
+ * -json JSON output
639
+ * -silent Only show live hosts
640
+ * -nc No colour
641
+ */
642
+ runHttpx: tool({
643
+ description: 'HTTP probing and fingerprinting. Feed it a list of subdomains (from Subfinder) to instantly filter live hosts and identify technologies, TLS issues, web servers, and CDN providers. The essential bridge between recon and scanning.',
644
+ inputSchema: z.object({
645
+ targets: z.string().describe('Newline-separated list of hosts/URLs, or @/path/to/list.txt for file input.'),
646
+ techDetect: z.boolean().optional().default(true).describe('Fingerprint technologies using Wappalyzer rules.'),
647
+ tlsProbe: z.boolean().optional().default(true).describe('Extract TLS certificate info, expiry, and SANs.'),
648
+ followRedirects: z.boolean().optional().default(true),
649
+ threads: z.number().int().min(1).max(300).optional().default(50),
650
+ rateLimit: z.number().int().min(1).max(500).optional().default(100),
651
+ }),
652
+ execute: async ({ targets, techDetect, tlsProbe, followRedirects, threads, rateLimit }) => {
653
+ const approved = await confirmPentestAction('Run httpx HTTP probing', [
654
+ `Targets: ${targets.startsWith('@') ? targets : `${targets.split('\n').length} hosts`}`,
655
+ techDetect ? 'Technology detection enabled' : '',
656
+ tlsProbe ? 'TLS probing enabled' : '',
657
+ `Threads: ${threads}, Rate: ${rateLimit}/s`,
658
+ ].filter(Boolean));
659
+ if (!approved)
660
+ throw new Error('Blocked httpx. Ask user to approve.');
661
+ emitProgress({ type: 'tool:start', label: 'httpx probing', detail: `${threads} threads` });
662
+ const dir = await ensureDir('httpx');
663
+ const outFile = path.join(dir, `httpx-${ts()}.json`);
664
+ let inputArg;
665
+ if (targets.startsWith('@')) {
666
+ inputArg = ['-l', targets.slice(1)];
667
+ }
668
+ else {
669
+ // Write targets to a temp file
670
+ const tmpFile = path.join(dir, `httpx-input-${ts()}.txt`);
671
+ await writeFile(tmpFile, targets, 'utf8');
672
+ inputArg = ['-l', tmpFile];
673
+ }
674
+ const args = [
675
+ ...inputArg,
676
+ '-status-code', '-title', '-web-server', '-content-length', '-ip', '-cdn', '-probe',
677
+ '-nc', '-silent', '-json', '-o', outFile,
678
+ '-threads', String(threads),
679
+ '-rate-limit', String(rateLimit),
680
+ ];
681
+ if (techDetect)
682
+ args.push('-tech-detect');
683
+ if (tlsProbe)
684
+ args.push('-tls-probe', '-tls-grab');
685
+ if (followRedirects)
686
+ args.push('-follow-redirects');
687
+ const raw = await runTool('httpx', args, 600_000);
688
+ // Parse JSON lines
689
+ let probes = [];
690
+ try {
691
+ const content = await readFile(outFile, 'utf8');
692
+ probes = content
693
+ .split('\n')
694
+ .filter(Boolean)
695
+ .map((line) => {
696
+ const j = JSON.parse(line);
697
+ return {
698
+ url: j['url'],
699
+ statusCode: j['status-code'],
700
+ title: j['title'] ?? '',
701
+ tech: j['tech'] ?? [],
702
+ webServer: j['webserver'] ?? '',
703
+ ip: j['host'] ?? '',
704
+ };
705
+ });
706
+ }
707
+ catch { /* JSONL parse failed */ }
708
+ emitProgress({ type: 'tool:end', label: 'httpx complete', detail: `${probes.length} live hosts` });
709
+ return { liveHostCount: probes.length, probes: probes.slice(0, 100), outputFile: outFile, raw: raw.slice(0, 2000) };
710
+ },
711
+ }),
712
+ };
713
+ // ─── 7. Meta: Full Pentest Chain ──────────────────────────────────────────────
714
+ export const pentestChainTool = {
715
+ /**
716
+ * Orchestrates the full kill chain:
717
+ * 1. Subfinder → passive subdomain discovery
718
+ * 2. httpx → probe live hosts, fingerprint tech
719
+ * 3. Nmap → port scan live IPs
720
+ * 4. Nuclei → template-based vuln scan on live URLs
721
+ *
722
+ * Results at each stage feed into the next.
723
+ */
724
+ runPentestChain: tool({
725
+ description: 'Full automated pentest kill chain: Subfinder → httpx → Nmap → Nuclei. Give it a domain and depth level, it discovers subdomains, probes live hosts, port-scans, and runs vulnerability templates. Returns a consolidated findings report.',
726
+ inputSchema: z.object({
727
+ domain: z.string().min(3).describe('Root domain to pentest, e.g. example.com.'),
728
+ depth: z
729
+ .enum(['surface', 'standard', 'deep'])
730
+ .optional()
731
+ .default('standard')
732
+ .describe('surface=quick Nmap (top 1000 ports) + critical CVEs only; standard=full recon + high/critical; deep=all ports + all severity + vuln NSE scripts.'),
733
+ includeKev: z.boolean().optional().default(true).describe('Always include CISA Known Exploited Vulnerabilities templates (recommended).'),
734
+ nmapScripts: z.boolean().optional().default(false).describe('Run Nmap NSE vuln scripts alongside Nuclei (slower, more thorough).'),
735
+ }),
736
+ execute: async ({ domain, depth, includeKev, nmapScripts }) => {
737
+ const approved = await confirmPentestAction('Run full pentest kill chain', [
738
+ `Target domain: ${domain}`,
739
+ `Depth: ${depth}`,
740
+ 'Chain: Subfinder → httpx → Nmap → Nuclei',
741
+ includeKev ? 'CISA KEV templates included' : '',
742
+ nmapScripts ? 'Nmap NSE vuln scripts enabled' : '',
743
+ '⚠️ This will make active network connections to the target and its infrastructure',
744
+ ].filter(Boolean));
745
+ if (!approved)
746
+ throw new Error('Blocked pentest chain. Ask user to approve.');
747
+ const results = { domain, depth, startedAt: new Date().toISOString() };
748
+ const dir = await ensureDir('chain');
749
+ const subFile = path.join(dir, `${safe(domain)}-subdomains.txt`);
750
+ const liveFile = path.join(dir, `${safe(domain)}-live.txt`);
751
+ // ── Step 1: Subfinder ─────────────────────────────────────────────────
752
+ emitProgress({ type: 'tool:start', label: 'Step 1/4: Subfinder', detail: domain });
753
+ try {
754
+ const subArgs = ['-d', domain, '-silent', '-o', subFile];
755
+ if (depth === 'deep')
756
+ subArgs.push('-all');
757
+ const subRaw = await runTool('subfinder', subArgs, 300_000);
758
+ const subdomains = subRaw.split('\n').map((l) => l.trim()).filter(Boolean);
759
+ results['subfinder'] = { count: subdomains.length, sample: subdomains.slice(0, 20) };
760
+ emitProgress({ type: 'tool:end', label: `Subfinder: ${subdomains.length} subdomains found` });
761
+ }
762
+ catch (err) {
763
+ results['subfinder'] = { error: err instanceof Error ? err.message : String(err) };
764
+ }
765
+ // ── Step 2: httpx ────────────────────────────────────────────────────
766
+ emitProgress({ type: 'tool:start', label: 'Step 2/4: httpx probing live hosts' });
767
+ try {
768
+ const httpxArgs = [
769
+ '-l', subFile, '-silent', '-nc', '-json', '-o', liveFile,
770
+ '-status-code', '-title', '-tech-detect', '-web-server', '-ip',
771
+ '-threads', '50', '-rate-limit', '100', '-follow-redirects',
772
+ ];
773
+ const httpxRaw = await runTool('httpx', httpxArgs, 600_000);
774
+ const liveHosts = httpxRaw.split('\n').filter(Boolean).length;
775
+ results['httpx'] = { liveHosts };
776
+ emitProgress({ type: 'tool:end', label: `httpx: ${liveHosts} live hosts` });
777
+ }
778
+ catch (err) {
779
+ results['httpx'] = { error: err instanceof Error ? err.message : String(err) };
780
+ }
781
+ // ── Step 3: Nmap ─────────────────────────────────────────────────────
782
+ emitProgress({ type: 'tool:start', label: 'Step 3/4: Nmap port scanning' });
783
+ try {
784
+ const nmapBase = path.join(dir, `nmap-${safe(domain)}`);
785
+ const nmapArgs = ['-sV', '--open', '-T4', '--no-stylesheet', '-oA', nmapBase];
786
+ if (depth === 'deep') {
787
+ nmapArgs.push('-p-');
788
+ }
789
+ if (nmapScripts) {
790
+ nmapArgs.push('--script', 'vuln');
791
+ }
792
+ nmapArgs.push(domain);
793
+ const nmapRaw = await runTool('nmap', nmapArgs, depth === 'deep' ? 1_800_000 : 300_000);
794
+ const openPorts = [...nmapRaw.matchAll(/(\d+)\/tcp\s+open\s+(\S+)/g)].map((m) => ({ port: m[1], service: m[2] }));
795
+ results['nmap'] = { openPortCount: openPorts.length, ports: openPorts, outputBase: nmapBase };
796
+ emitProgress({ type: 'tool:end', label: `Nmap: ${openPorts.length} open ports` });
797
+ }
798
+ catch (err) {
799
+ results['nmap'] = { error: err instanceof Error ? err.message : String(err) };
800
+ }
801
+ // ── Step 4: Nuclei ───────────────────────────────────────────────────
802
+ emitProgress({ type: 'tool:start', label: 'Step 4/4: Nuclei vulnerability scan' });
803
+ try {
804
+ const nucleiOut = path.join(dir, `nuclei-${safe(domain)}-${ts()}.jsonl`);
805
+ const severity = depth === 'surface' ? 'critical' : depth === 'deep' ? 'low,medium,high,critical' : 'high,critical';
806
+ const nucleiArgs = [
807
+ '-l', liveFile, '-severity', severity, '-nc', '-silent', '-je', nucleiOut,
808
+ '-rl', '150', '-c', '25',
809
+ ];
810
+ if (includeKev)
811
+ nucleiArgs.push('-tags', 'kev,vkev');
812
+ const nucleiRaw = await runTool('nuclei', nucleiArgs, 900_000);
813
+ let findings = [];
814
+ try {
815
+ const content = await readFile(nucleiOut, 'utf8');
816
+ findings = content.split('\n').filter(Boolean).map((l) => JSON.parse(l));
817
+ }
818
+ catch { /* no-op */ }
819
+ results['nuclei'] = { findingCount: findings.length, outputFile: nucleiOut };
820
+ emitProgress({ type: 'tool:end', label: `Nuclei: ${findings.length} findings` });
821
+ }
822
+ catch (err) {
823
+ results['nuclei'] = { error: err instanceof Error ? err.message : String(err) };
824
+ }
825
+ // ── Save consolidated report ─────────────────────────────────────────
826
+ results['completedAt'] = new Date().toISOString();
827
+ const reportFile = path.join(dir, `pentest-report-${safe(domain)}-${ts()}.json`);
828
+ await writeFile(reportFile, JSON.stringify(results, null, 2), 'utf8');
829
+ emitProgress({ type: 'tool:end', label: 'Pentest chain complete', detail: reportFile });
830
+ return { ...results, reportFile };
831
+ },
832
+ }),
833
+ };
834
+ // ─── Barrel export ────────────────────────────────────────────────────────────
835
+ export const pentestTools = {
836
+ ...nmapTool,
837
+ ...nucleiTool,
838
+ ...subfinderTool,
839
+ ...sqlmapTool,
840
+ ...ffufTool,
841
+ ...httpxTool,
842
+ ...pentestChainTool,
843
+ };
844
+ //# sourceMappingURL=pentest.tool.js.map