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