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.
- package/.env.example +2 -0
- package/dist/agents/automation-planner.agent.d.ts +108 -0
- package/dist/agents/automation-planner.agent.d.ts.map +1 -1
- package/dist/agents/automation-planner.agent.js +13 -4
- package/dist/agents/automation-planner.agent.js.map +1 -1
- package/dist/agents/coding.agent.d.ts +352 -0
- package/dist/agents/coding.agent.d.ts.map +1 -0
- package/dist/agents/coding.agent.js +34 -0
- package/dist/agents/coding.agent.js.map +1 -0
- package/dist/agents/goal-manager.agent.d.ts +125 -0
- package/dist/agents/goal-manager.agent.d.ts.map +1 -0
- package/dist/agents/goal-manager.agent.js +32 -0
- package/dist/agents/goal-manager.agent.js.map +1 -0
- package/dist/agents/manager.d.ts +378 -0
- package/dist/agents/manager.d.ts.map +1 -1
- package/dist/agents/manager.js +113 -18
- package/dist/agents/manager.js.map +1 -1
- package/dist/agents/security.agent.d.ts +340 -0
- package/dist/agents/security.agent.d.ts.map +1 -0
- package/dist/agents/security.agent.js +76 -0
- package/dist/agents/security.agent.js.map +1 -0
- package/dist/cli/ask.d.ts +5 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +33 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/confirm.d.ts.map +1 -1
- package/dist/cli/confirm.js +8 -14
- package/dist/cli/confirm.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +61 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/format.d.ts +1 -0
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +41 -2
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +6 -2
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli/jobs.d.ts +4 -0
- package/dist/cli/jobs.d.ts.map +1 -1
- package/dist/cli/jobs.js +26 -0
- package/dist/cli/jobs.js.map +1 -1
- package/dist/cli/prompt.d.ts +9 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +116 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +95 -20
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/spinner.d.ts +6 -0
- package/dist/cli/spinner.d.ts.map +1 -0
- package/dist/cli/spinner.js +39 -0
- package/dist/cli/spinner.js.map +1 -0
- package/dist/cli/triggers.d.ts.map +1 -1
- package/dist/cli/triggers.js +12 -4
- package/dist/cli/triggers.js.map +1 -1
- package/dist/cli/tunnel.d.ts +12 -0
- package/dist/cli/tunnel.d.ts.map +1 -0
- package/dist/cli/tunnel.js +64 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/voice.d.ts.map +1 -1
- package/dist/cli/voice.js +13 -2
- package/dist/cli/voice.js.map +1 -1
- package/dist/config/env.d.ts +5 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +7 -2
- package/dist/config/env.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +1 -0
- package/dist/config/models.js.map +1 -1
- package/dist/documents/pdf.d.ts +11 -0
- package/dist/documents/pdf.d.ts.map +1 -0
- package/dist/documents/pdf.js +64 -0
- package/dist/documents/pdf.js.map +1 -0
- package/dist/documents/slides.d.ts +13 -0
- package/dist/documents/slides.d.ts.map +1 -0
- package/dist/documents/slides.js +82 -0
- package/dist/documents/slides.js.map +1 -0
- package/dist/index.js +72 -3
- package/dist/index.js.map +1 -1
- package/dist/jobs/anomaly.d.ts +10 -0
- package/dist/jobs/anomaly.d.ts.map +1 -0
- package/dist/jobs/anomaly.js +52 -0
- package/dist/jobs/anomaly.js.map +1 -0
- package/dist/jobs/runner.d.ts.map +1 -1
- package/dist/jobs/runner.js +6 -0
- package/dist/jobs/runner.js.map +1 -1
- package/dist/jobs/trigger-orchestrator.d.ts +26 -0
- package/dist/jobs/trigger-orchestrator.d.ts.map +1 -0
- package/dist/jobs/trigger-orchestrator.js +252 -0
- package/dist/jobs/trigger-orchestrator.js.map +1 -0
- package/dist/jobs/trigger-policies.d.ts +36 -0
- package/dist/jobs/trigger-policies.d.ts.map +1 -0
- package/dist/jobs/trigger-policies.js +68 -0
- package/dist/jobs/trigger-policies.js.map +1 -0
- package/dist/jobs/trigger-router.d.ts +14 -0
- package/dist/jobs/trigger-router.d.ts.map +1 -0
- package/dist/jobs/trigger-router.js +114 -0
- package/dist/jobs/trigger-router.js.map +1 -0
- package/dist/jobs/webhook-server.d.ts +7 -0
- package/dist/jobs/webhook-server.d.ts.map +1 -0
- package/dist/jobs/webhook-server.js +56 -0
- package/dist/jobs/webhook-server.js.map +1 -0
- package/dist/jobs/workflows.d.ts +4 -1
- package/dist/jobs/workflows.d.ts.map +1 -1
- package/dist/jobs/workflows.js +18 -42
- package/dist/jobs/workflows.js.map +1 -1
- package/dist/memory/heal.d.ts +19 -0
- package/dist/memory/heal.d.ts.map +1 -0
- package/dist/memory/heal.js +253 -0
- package/dist/memory/heal.js.map +1 -0
- package/dist/memory/knowledge-graph.d.ts +32 -0
- package/dist/memory/knowledge-graph.d.ts.map +1 -0
- package/dist/memory/knowledge-graph.js +103 -0
- package/dist/memory/knowledge-graph.js.map +1 -0
- package/dist/memory/local-store.d.ts +1 -0
- package/dist/memory/local-store.d.ts.map +1 -1
- package/dist/memory/local-store.js +41 -8
- package/dist/memory/local-store.js.map +1 -1
- package/dist/memory/notebook.d.ts +29 -0
- package/dist/memory/notebook.d.ts.map +1 -0
- package/dist/memory/notebook.js +64 -0
- package/dist/memory/notebook.js.map +1 -0
- package/dist/memory/personal-context.d.ts +45 -0
- package/dist/memory/personal-context.d.ts.map +1 -0
- package/dist/memory/personal-context.js +103 -0
- package/dist/memory/personal-context.js.map +1 -0
- package/dist/memory/scratchpad.d.ts.map +1 -1
- package/dist/memory/scratchpad.js +30 -4
- package/dist/memory/scratchpad.js.map +1 -1
- package/dist/runtime/ask.d.ts +15 -0
- package/dist/runtime/ask.d.ts.map +1 -0
- package/dist/runtime/ask.js +23 -0
- package/dist/runtime/ask.js.map +1 -0
- package/dist/runtime/confirm.d.ts +3 -0
- package/dist/runtime/confirm.d.ts.map +1 -1
- package/dist/runtime/confirm.js +38 -12
- package/dist/runtime/confirm.js.map +1 -1
- package/dist/runtime/progress.d.ts +2 -1
- package/dist/runtime/progress.d.ts.map +1 -1
- package/dist/runtime/progress.js +3 -0
- package/dist/runtime/progress.js.map +1 -1
- package/dist/runtime/voice-confirm.d.ts +8 -0
- package/dist/runtime/voice-confirm.d.ts.map +1 -0
- package/dist/runtime/voice-confirm.js +67 -0
- package/dist/runtime/voice-confirm.js.map +1 -0
- package/dist/safety/trust-log.d.ts +23 -0
- package/dist/safety/trust-log.d.ts.map +1 -0
- package/dist/safety/trust-log.js +57 -0
- package/dist/safety/trust-log.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/skills/loader.d.ts +14 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +108 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/registry.d.ts +22 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +120 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/tools/ask.tool.d.ts +23 -0
- package/dist/tools/ask.tool.d.ts.map +1 -0
- package/dist/tools/ask.tool.js +47 -0
- package/dist/tools/ask.tool.js.map +1 -0
- package/dist/tools/cli-runner.d.ts +13 -0
- package/dist/tools/cli-runner.d.ts.map +1 -0
- package/dist/tools/cli-runner.js +75 -0
- package/dist/tools/cli-runner.js.map +1 -0
- package/dist/tools/computer-use.tool.d.ts +159 -0
- package/dist/tools/computer-use.tool.d.ts.map +1 -0
- package/dist/tools/computer-use.tool.js +357 -0
- package/dist/tools/computer-use.tool.js.map +1 -0
- package/dist/tools/desktop.tool.d.ts.map +1 -1
- package/dist/tools/desktop.tool.js +3 -2
- package/dist/tools/desktop.tool.js.map +1 -1
- package/dist/tools/documents.tool.d.ts +31 -0
- package/dist/tools/documents.tool.d.ts.map +1 -0
- package/dist/tools/documents.tool.js +62 -0
- package/dist/tools/documents.tool.js.map +1 -0
- package/dist/tools/git.tool.d.ts +77 -0
- package/dist/tools/git.tool.d.ts.map +1 -0
- package/dist/tools/git.tool.js +189 -0
- package/dist/tools/git.tool.js.map +1 -0
- package/dist/tools/heal.tool.d.ts +13 -0
- package/dist/tools/heal.tool.d.ts.map +1 -0
- package/dist/tools/heal.tool.js +34 -0
- package/dist/tools/heal.tool.js.map +1 -0
- package/dist/tools/image-generate.tool.d.ts.map +1 -1
- package/dist/tools/image-generate.tool.js +2 -1
- package/dist/tools/image-generate.tool.js.map +1 -1
- package/dist/tools/knowledge.tool.d.ts +24 -0
- package/dist/tools/knowledge.tool.d.ts.map +1 -0
- package/dist/tools/knowledge.tool.js +52 -0
- package/dist/tools/knowledge.tool.js.map +1 -0
- package/dist/tools/notebook.tool.d.ts +32 -0
- package/dist/tools/notebook.tool.d.ts.map +1 -0
- package/dist/tools/notebook.tool.js +50 -0
- package/dist/tools/notebook.tool.js.map +1 -0
- package/dist/tools/notify.tool.d.ts +26 -0
- package/dist/tools/notify.tool.d.ts.map +1 -0
- package/dist/tools/notify.tool.js +65 -0
- package/dist/tools/notify.tool.js.map +1 -0
- package/dist/tools/orchestration.tool.d.ts +83 -0
- package/dist/tools/orchestration.tool.d.ts.map +1 -0
- package/dist/tools/orchestration.tool.js +154 -0
- package/dist/tools/orchestration.tool.js.map +1 -0
- package/dist/tools/osint-install.tool.d.ts +35 -0
- package/dist/tools/osint-install.tool.d.ts.map +1 -0
- package/dist/tools/osint-install.tool.js +334 -0
- package/dist/tools/osint-install.tool.js.map +1 -0
- package/dist/tools/osint.tool.d.ts +445 -0
- package/dist/tools/osint.tool.d.ts.map +1 -0
- package/dist/tools/osint.tool.js +633 -0
- package/dist/tools/osint.tool.js.map +1 -0
- package/dist/tools/pentest-install.tool.d.ts +32 -0
- package/dist/tools/pentest-install.tool.d.ts.map +1 -0
- package/dist/tools/pentest-install.tool.js +201 -0
- package/dist/tools/pentest-install.tool.js.map +1 -0
- package/dist/tools/pentest.tool.d.ts +595 -0
- package/dist/tools/pentest.tool.d.ts.map +1 -0
- package/dist/tools/pentest.tool.js +844 -0
- package/dist/tools/pentest.tool.js.map +1 -0
- package/dist/tools/personal-context.tool.d.ts +32 -0
- package/dist/tools/personal-context.tool.d.ts.map +1 -0
- package/dist/tools/personal-context.tool.js +76 -0
- package/dist/tools/personal-context.tool.js.map +1 -0
- package/dist/tools/setup-assistant.tool.d.ts +49 -0
- package/dist/tools/setup-assistant.tool.d.ts.map +1 -0
- package/dist/tools/setup-assistant.tool.js +139 -0
- package/dist/tools/setup-assistant.tool.js.map +1 -0
- package/dist/tools/skills.tool.d.ts +38 -0
- package/dist/tools/skills.tool.d.ts.map +1 -0
- package/dist/tools/skills.tool.js +77 -0
- package/dist/tools/skills.tool.js.map +1 -0
- package/dist/tools/trust.tool.d.ts +16 -0
- package/dist/tools/trust.tool.d.ts.map +1 -0
- package/dist/tools/trust.tool.js +34 -0
- package/dist/tools/trust.tool.js.map +1 -0
- package/dist/tools/update.tool.d.ts +25 -0
- package/dist/tools/update.tool.d.ts.map +1 -0
- package/dist/tools/update.tool.js +64 -0
- package/dist/tools/update.tool.js.map +1 -0
- package/dist/tools/workspace.tool.d.ts +24 -0
- package/dist/tools/workspace.tool.d.ts.map +1 -0
- package/dist/tools/workspace.tool.js +64 -0
- package/dist/tools/workspace.tool.js.map +1 -0
- package/dist/voice/cascade.d.ts.map +1 -1
- package/dist/voice/cascade.js +75 -24
- package/dist/voice/cascade.js.map +1 -1
- package/dist/voice/deepgram.d.ts.map +1 -1
- package/dist/voice/deepgram.js +8 -1
- package/dist/voice/deepgram.js.map +1 -1
- package/dist/voice/types.d.ts +4 -0
- package/dist/voice/types.d.ts.map +1 -1
- package/dist/workspace/init.d.ts +3 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/init.js +75 -0
- package/dist/workspace/init.js.map +1 -0
- package/dist/workspace/output-paths.d.ts +3 -0
- package/dist/workspace/output-paths.d.ts.map +1 -0
- package/dist/workspace/output-paths.js +18 -0
- package/dist/workspace/output-paths.js.map +1 -0
- package/dist/workspace/paths.d.ts +26 -0
- package/dist/workspace/paths.d.ts.map +1 -0
- package/dist/workspace/paths.js +48 -0
- package/dist/workspace/paths.js.map +1 -0
- 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
|