pentesting 0.8.40 → 0.8.44
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/README.md +115 -18
- package/dist/index.js +1451 -17
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -32,8 +32,8 @@ import TextInput from "ink-text-input";
|
|
|
32
32
|
import Spinner from "ink-spinner";
|
|
33
33
|
|
|
34
34
|
// src/core/agent/autonomous-agent.ts
|
|
35
|
-
import
|
|
36
|
-
import { EventEmitter as
|
|
35
|
+
import Anthropic3 from "@anthropic-ai/sdk";
|
|
36
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
37
37
|
|
|
38
38
|
// src/core/prompts/autonomous-prompt.ts
|
|
39
39
|
var AUTONOMOUS_HACKING_PROMPT = `You are Pentesting, an elite autonomous penetration testing AI designed for CTF competitions and professional security assessments. You operate with minimal human intervention, making intelligent decisions, adapting to obstacles, and persistently pursuing objectives until complete system compromise.
|
|
@@ -140,13 +140,61 @@ If you notice you're repeating the same actions without progress:
|
|
|
140
140
|
|
|
141
141
|
Key tools for each phase:
|
|
142
142
|
- Recon: whois, dig, nslookup, theHarvester, amass
|
|
143
|
+
- Subdomain: ffuf, subfinder, assetfinder, amass, gobuster dns
|
|
143
144
|
- Scan: nmap, masscan, rustscan
|
|
144
|
-
-
|
|
145
|
+
- Directory: ffuf, gobuster, dirsearch, feroxbuster
|
|
146
|
+
- Web: nikto, sqlmap, wpscan, nuclei
|
|
145
147
|
- Exploit: metasploit, searchsploit, custom scripts
|
|
146
148
|
- PrivEsc: linpeas, winpeas, GTFOBins
|
|
147
149
|
- Post: mimikatz, bloodhound, chisel
|
|
148
150
|
</tool_usage_guidelines>
|
|
149
151
|
|
|
152
|
+
<mandatory_autonomous_execution>
|
|
153
|
+
CRITICAL: You MUST execute tools immediately without asking. DO NOT:
|
|
154
|
+
- Ask "should I run ffuf?" - JUST RUN IT
|
|
155
|
+
- Say "I can use gobuster" - JUST USE IT
|
|
156
|
+
- Explain what you could do - JUST DO IT
|
|
157
|
+
- Wait for user confirmation - ACT NOW
|
|
158
|
+
- Write bash scripts manually when MCP tools exist - USE THE TOOLS
|
|
159
|
+
|
|
160
|
+
YOU HAVE THESE MCP TOOLS - USE THEM DIRECTLY:
|
|
161
|
+
- ffuf \u2192 subdomain and directory fuzzing
|
|
162
|
+
- subfinder \u2192 passive subdomain discovery
|
|
163
|
+
- amass \u2192 comprehensive subdomain enum
|
|
164
|
+
- nuclei \u2192 vulnerability scanning
|
|
165
|
+
- nikto \u2192 web server scanning
|
|
166
|
+
- whatweb \u2192 technology fingerprinting
|
|
167
|
+
- httpx \u2192 HTTP probing
|
|
168
|
+
- waybackurls \u2192 historical URL discovery
|
|
169
|
+
- feroxbuster \u2192 recursive directory scanning
|
|
170
|
+
- rustscan \u2192 fast port scanning
|
|
171
|
+
- nmap_scan \u2192 detailed port/service scan
|
|
172
|
+
- hydra \u2192 credential bruteforce
|
|
173
|
+
- sqlmap (sql_injection) \u2192 SQL injection
|
|
174
|
+
- gobuster (directory_bruteforce) \u2192 directory enumeration
|
|
175
|
+
|
|
176
|
+
TRIGGER \u2192 ACTION MAPPING:
|
|
177
|
+
"\uC11C\uBE0C\uB3C4\uBA54\uC778" or "subdomain" \u2192 IMMEDIATELY use ffuf tool with mode=subdomain
|
|
178
|
+
"\uB514\uB809\uD1A0\uB9AC" or "directory" or "path" \u2192 IMMEDIATELY use ffuf tool with mode=directory
|
|
179
|
+
"\uC2A4\uCE94" or "scan" \u2192 IMMEDIATELY use rustscan then nmap_scan
|
|
180
|
+
"\uCDE8\uC57D\uC810" or "vuln" \u2192 IMMEDIATELY use nuclei tool
|
|
181
|
+
"\uAE30\uC220\uC2A4\uD0DD" or "tech" \u2192 IMMEDIATELY use whatweb tool
|
|
182
|
+
"\uD788\uC2A4\uD1A0\uB9AC" or "wayback" \u2192 IMMEDIATELY use waybackurls tool
|
|
183
|
+
"\uBE0C\uB8E8\uD2B8\uD3EC\uC2A4" or "bruteforce" \u2192 IMMEDIATELY use hydra tool
|
|
184
|
+
"SQL" or "\uC778\uC81D\uC158" \u2192 IMMEDIATELY use sql_injection tool
|
|
185
|
+
|
|
186
|
+
EXAMPLE - User says "\uC11C\uBE0C\uB3C4\uBA54\uC778 \uCC3E\uC544":
|
|
187
|
+
WRONG: for sub in www mail ftp; do host $sub.domain.com; done
|
|
188
|
+
RIGHT: Use ffuf tool with url=https://FUZZ.domain.com, mode=subdomain
|
|
189
|
+
|
|
190
|
+
EXAMPLE - User says "\uB514\uB809\uD1A0\uB9AC \uC2A4\uCE94\uD574":
|
|
191
|
+
WRONG: curl https://domain.com/admin
|
|
192
|
+
RIGHT: Use ffuf tool with url=https://domain.com/FUZZ, mode=directory
|
|
193
|
+
|
|
194
|
+
NEVER write manual bash loops when MCP tools exist!
|
|
195
|
+
ALWAYS prefer MCP tools over bash commands!
|
|
196
|
+
</mandatory_autonomous_execution>
|
|
197
|
+
|
|
150
198
|
<output_format>
|
|
151
199
|
Always structure your thinking clearly:
|
|
152
200
|
|
|
@@ -220,6 +268,115 @@ Analyze your situation honestly:
|
|
|
220
268
|
- Manual testing vs. automated?
|
|
221
269
|
|
|
222
270
|
Based on this reflection, propose 3 completely different approaches to try next.`;
|
|
271
|
+
var STRATEGY_PLANNING_PROMPT = `You are the strategic coordinator for this penetration test. Before executing ANY action, validate the strategy.
|
|
272
|
+
|
|
273
|
+
## STRATEGIC VALIDATION PROCESS
|
|
274
|
+
|
|
275
|
+
### 1. INTENT VERIFICATION
|
|
276
|
+
\`\`\`
|
|
277
|
+
[INTENT CHECK]
|
|
278
|
+
- What is the user's objective?
|
|
279
|
+
- What phase are we in?
|
|
280
|
+
- What have we accomplished so far?
|
|
281
|
+
- What is the immediate goal?
|
|
282
|
+
\`\`\`
|
|
283
|
+
|
|
284
|
+
### 2. PLAN FORMULATION
|
|
285
|
+
\`\`\`
|
|
286
|
+
[PLAN PROPOSAL]
|
|
287
|
+
Given the current state, I propose:
|
|
288
|
+
1. [Primary approach] - Expected outcome: X
|
|
289
|
+
2. [Backup approach] - If primary fails: Y
|
|
290
|
+
3. [Alternative] - If we need to pivot: Z
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
293
|
+
### 3. PLAN VALIDATION (Self-Check)
|
|
294
|
+
\`\`\`
|
|
295
|
+
[VALIDATION]
|
|
296
|
+
\u25A1 Is this aligned with BFS (surface mapping first)?
|
|
297
|
+
\u25A1 Have I completed reconnaissance before deep testing?
|
|
298
|
+
\u25A1 Is this the highest ROI action right now?
|
|
299
|
+
\u25A1 Am I avoiding rabbit holes (SSL testing too early, etc.)?
|
|
300
|
+
\u25A1 Is the tool/command correct for this task?
|
|
301
|
+
\`\`\`
|
|
302
|
+
|
|
303
|
+
### 4. AGENT COORDINATION
|
|
304
|
+
When switching agents or techniques:
|
|
305
|
+
\`\`\`
|
|
306
|
+
[HANDOFF]
|
|
307
|
+
From: [current agent/approach]
|
|
308
|
+
To: [next agent/approach]
|
|
309
|
+
Reason: [why this switch makes sense]
|
|
310
|
+
Context passed: [what the next agent needs to know]
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
### 5. DECISION LOG
|
|
314
|
+
After each significant decision:
|
|
315
|
+
\`\`\`
|
|
316
|
+
[DECISION]
|
|
317
|
+
Action: [what was done]
|
|
318
|
+
Result: [what happened]
|
|
319
|
+
Learning: [what we now know]
|
|
320
|
+
Next: [logical next step]
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
323
|
+
## ANTI-PATTERNS TO DETECT AND PREVENT
|
|
324
|
+
|
|
325
|
+
- \u274C SSL/TLS testing before full surface mapping
|
|
326
|
+
- \u274C Deep diving one endpoint before discovering others
|
|
327
|
+
- \u274C Brute force without any intelligence
|
|
328
|
+
- \u274C Repeating failed commands with minor changes
|
|
329
|
+
- \u274C Ignoring discovered information
|
|
330
|
+
- \u274C Skipping subdomain/directory enumeration
|
|
331
|
+
|
|
332
|
+
## SUCCESS PATTERNS TO FOLLOW
|
|
333
|
+
|
|
334
|
+
- \u2705 Port scan \u2192 Web discovery (subdomain + directory) \u2192 Technology detection \u2192 CVE check
|
|
335
|
+
- \u2705 Find all endpoints first, then prioritize by value
|
|
336
|
+
- \u2705 Use discovered info (usernames, versions) in subsequent attacks
|
|
337
|
+
- \u2705 Pivot quickly when stuck (max 3 attempts per approach)
|
|
338
|
+
- \u2705 Document findings as we go`;
|
|
339
|
+
var AGENT_COLLABORATION_PROMPT = `When collaborating with other specialized agents:
|
|
340
|
+
|
|
341
|
+
## AGENT ROLES
|
|
342
|
+
- **Recon Agent**: Surface discovery, OSINT, subdomain/directory enumeration
|
|
343
|
+
- **Exploit Agent**: Vulnerability exploitation, payload delivery
|
|
344
|
+
- **PrivEsc Agent**: Privilege escalation on compromised hosts
|
|
345
|
+
- **Web Agent**: Web application testing, injection attacks
|
|
346
|
+
- **Crypto Agent**: Cryptographic analysis, hash cracking
|
|
347
|
+
|
|
348
|
+
## COLLABORATION PROTOCOL
|
|
349
|
+
|
|
350
|
+
### 1. TASK DELEGATION
|
|
351
|
+
\`\`\`
|
|
352
|
+
[DELEGATE]
|
|
353
|
+
From: [current agent]
|
|
354
|
+
To: [specialized agent]
|
|
355
|
+
Task: [specific task]
|
|
356
|
+
Expected output: [what we need back]
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
### 2. RESULTS HANDOFF
|
|
360
|
+
\`\`\`
|
|
361
|
+
[HANDOFF]
|
|
362
|
+
Agent: [which agent completed]
|
|
363
|
+
Findings: [key discoveries]
|
|
364
|
+
Recommended next: [what should happen next]
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
### 3. CONFLICT RESOLUTION
|
|
368
|
+
When agents disagree on approach:
|
|
369
|
+
- Consider ROI of each approach
|
|
370
|
+
- Check if one requires prerequisites the other provides
|
|
371
|
+
- Default to BFS (broader coverage) over DFS (deep focus)
|
|
372
|
+
- Escalate to user only if truly ambiguous
|
|
373
|
+
|
|
374
|
+
### 4. KNOWLEDGE SHARING
|
|
375
|
+
All agents maintain shared context:
|
|
376
|
+
- Discovered hosts and services
|
|
377
|
+
- Obtained credentials
|
|
378
|
+
- Failed approaches (don't repeat)
|
|
379
|
+
- Current attack surface map`;
|
|
223
380
|
|
|
224
381
|
// src/core/tools/tool-definitions.ts
|
|
225
382
|
var SYSTEM_TOOLS = [
|
|
@@ -387,6 +544,134 @@ Use for:
|
|
|
387
544
|
}
|
|
388
545
|
}
|
|
389
546
|
];
|
|
547
|
+
var DNS_TOOLS = [
|
|
548
|
+
{
|
|
549
|
+
name: TOOL_NAME.FFUF,
|
|
550
|
+
description: `FFUF - Fast web fuzzer. USE THIS for subdomain and directory enumeration.
|
|
551
|
+
|
|
552
|
+
SUBDOMAIN ENUMERATION:
|
|
553
|
+
ffuf -u https://FUZZ.domain.com -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc 200,301,302,403
|
|
554
|
+
|
|
555
|
+
DIRECTORY ENUMERATION:
|
|
556
|
+
ffuf -u https://domain.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
|
|
557
|
+
|
|
558
|
+
VHOST DISCOVERY:
|
|
559
|
+
ffuf -u https://domain.com -H "Host: FUZZ.domain.com" -w wordlist.txt
|
|
560
|
+
|
|
561
|
+
PARAMETER FUZZING:
|
|
562
|
+
ffuf -u https://domain.com/page?FUZZ=value -w params.txt
|
|
563
|
+
|
|
564
|
+
OPTIONS:
|
|
565
|
+
- -mc: Match HTTP status codes
|
|
566
|
+
- -fc: Filter HTTP status codes
|
|
567
|
+
- -fs: Filter response size
|
|
568
|
+
- -fw: Filter word count
|
|
569
|
+
- -t: Threads (default 40)
|
|
570
|
+
- -recursion: Enable recursion
|
|
571
|
+
- -e: Extensions (.php,.html,.txt)
|
|
572
|
+
|
|
573
|
+
CRITICAL: This is your PRIMARY tool for web enumeration. USE IT IMMEDIATELY when asked to find subdomains or directories.`,
|
|
574
|
+
input_schema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
url: { type: "string", description: "Target URL with FUZZ keyword" },
|
|
578
|
+
wordlist: { type: "string", description: "Wordlist path (default: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt)" },
|
|
579
|
+
mode: { type: "string", enum: ["subdomain", "directory", "vhost", "parameter"], description: "Fuzzing mode" },
|
|
580
|
+
match_codes: { type: "string", description: 'Status codes to match (e.g., "200,301,302,403")' },
|
|
581
|
+
filter_codes: { type: "string", description: 'Status codes to filter (e.g., "404")' },
|
|
582
|
+
filter_size: { type: "string", description: "Response size to filter" },
|
|
583
|
+
threads: { type: "number", description: "Threads (default: 40)" },
|
|
584
|
+
extensions: { type: "string", description: 'Extensions to append (e.g., "php,html,txt")' },
|
|
585
|
+
headers: { type: "string", description: "Custom headers" },
|
|
586
|
+
recursion: { type: "boolean", description: "Enable recursion" }
|
|
587
|
+
},
|
|
588
|
+
required: ["url"]
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: TOOL_NAME.SUBFINDER,
|
|
593
|
+
description: `Subfinder - Passive subdomain discovery tool.
|
|
594
|
+
|
|
595
|
+
USAGE:
|
|
596
|
+
subfinder -d domain.com -o subdomains.txt
|
|
597
|
+
|
|
598
|
+
OPTIONS:
|
|
599
|
+
- -d: Domain to find subdomains for
|
|
600
|
+
- -o: Output file
|
|
601
|
+
- -all: Use all sources
|
|
602
|
+
- -silent: Silent mode (only subdomains)
|
|
603
|
+
- -recursive: Recursive subdomain discovery
|
|
604
|
+
|
|
605
|
+
Great for OSINT-based subdomain discovery without active scanning.`,
|
|
606
|
+
input_schema: {
|
|
607
|
+
type: "object",
|
|
608
|
+
properties: {
|
|
609
|
+
domain: { type: "string", description: "Target domain" },
|
|
610
|
+
output: { type: "string", description: "Output file path" },
|
|
611
|
+
all_sources: { type: "boolean", description: "Use all sources" },
|
|
612
|
+
recursive: { type: "boolean", description: "Recursive discovery" },
|
|
613
|
+
silent: { type: "boolean", description: "Silent mode" }
|
|
614
|
+
},
|
|
615
|
+
required: ["domain"]
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: TOOL_NAME.AMASS,
|
|
620
|
+
description: `Amass - In-depth subdomain enumeration.
|
|
621
|
+
|
|
622
|
+
MODES:
|
|
623
|
+
- enum: Subdomain enumeration
|
|
624
|
+
- intel: Gather intel on organization
|
|
625
|
+
- track: Track changes over time
|
|
626
|
+
|
|
627
|
+
USAGE:
|
|
628
|
+
amass enum -d domain.com -o output.txt
|
|
629
|
+
amass enum -passive -d domain.com (passive only)
|
|
630
|
+
amass enum -active -d domain.com (active probing)
|
|
631
|
+
|
|
632
|
+
Most comprehensive but slower than subfinder.`,
|
|
633
|
+
input_schema: {
|
|
634
|
+
type: "object",
|
|
635
|
+
properties: {
|
|
636
|
+
mode: { type: "string", enum: ["enum", "intel", "track"], description: "Amass mode" },
|
|
637
|
+
domain: { type: "string", description: "Target domain" },
|
|
638
|
+
passive: { type: "boolean", description: "Passive enumeration only" },
|
|
639
|
+
active: { type: "boolean", description: "Active DNS resolution" },
|
|
640
|
+
output: { type: "string", description: "Output file" }
|
|
641
|
+
},
|
|
642
|
+
required: ["domain"]
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: TOOL_NAME.FEROXBUSTER,
|
|
647
|
+
description: `Feroxbuster - Fast, recursive content discovery tool.
|
|
648
|
+
|
|
649
|
+
USAGE:
|
|
650
|
+
feroxbuster -u https://domain.com -w wordlist.txt
|
|
651
|
+
|
|
652
|
+
OPTIONS:
|
|
653
|
+
- -u: Target URL
|
|
654
|
+
- -w: Wordlist
|
|
655
|
+
- -x: Extensions (php,html,txt)
|
|
656
|
+
- -t: Threads
|
|
657
|
+
- -d: Recursion depth
|
|
658
|
+
- --auto-tune: Automatic rate limiting
|
|
659
|
+
|
|
660
|
+
Faster than gobuster with built-in recursion.`,
|
|
661
|
+
input_schema: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
url: { type: "string", description: "Target URL" },
|
|
665
|
+
wordlist: { type: "string", description: "Wordlist path" },
|
|
666
|
+
extensions: { type: "string", description: 'Extensions (e.g., "php,html")' },
|
|
667
|
+
threads: { type: "number", description: "Threads" },
|
|
668
|
+
depth: { type: "number", description: "Recursion depth" },
|
|
669
|
+
status_codes: { type: "string", description: "Status codes to include" }
|
|
670
|
+
},
|
|
671
|
+
required: ["url"]
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
];
|
|
390
675
|
var SERVICE_TOOLS = [
|
|
391
676
|
{
|
|
392
677
|
name: TOOL_NAME.ZONE_TRANSFER,
|
|
@@ -1060,6 +1345,150 @@ Use for:
|
|
|
1060
1345
|
},
|
|
1061
1346
|
required: ["url", "action"]
|
|
1062
1347
|
}
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: TOOL_NAME.NUCLEI,
|
|
1351
|
+
description: `Nuclei - Fast vulnerability scanner with templates.
|
|
1352
|
+
|
|
1353
|
+
CRITICAL: Use this for automated vulnerability scanning.
|
|
1354
|
+
|
|
1355
|
+
USAGE:
|
|
1356
|
+
nuclei -u https://target.com -t cves/
|
|
1357
|
+
nuclei -u https://target.com -t exposures/
|
|
1358
|
+
nuclei -l urls.txt -t technologies/
|
|
1359
|
+
|
|
1360
|
+
TEMPLATE CATEGORIES:
|
|
1361
|
+
- cves: Known CVE exploits
|
|
1362
|
+
- vulnerabilities: Generic vulns
|
|
1363
|
+
- exposures: Sensitive file exposure
|
|
1364
|
+
- misconfigurations: Config issues
|
|
1365
|
+
- technologies: Tech detection
|
|
1366
|
+
- default-logins: Default credentials
|
|
1367
|
+
|
|
1368
|
+
OPTIONS:
|
|
1369
|
+
- -t: Template path/directory
|
|
1370
|
+
- -severity: Filter by severity (critical,high,medium,low)
|
|
1371
|
+
- -o: Output file
|
|
1372
|
+
- -silent: Silent mode`,
|
|
1373
|
+
input_schema: {
|
|
1374
|
+
type: "object",
|
|
1375
|
+
properties: {
|
|
1376
|
+
target: { type: "string", description: "Target URL or file with URLs" },
|
|
1377
|
+
templates: { type: "string", description: "Template path (e.g., cves/, exposures/)" },
|
|
1378
|
+
severity: { type: "string", description: "Severity filter (critical,high,medium,low)" },
|
|
1379
|
+
output: { type: "string", description: "Output file" },
|
|
1380
|
+
silent: { type: "boolean", description: "Silent mode" }
|
|
1381
|
+
},
|
|
1382
|
+
required: ["target"]
|
|
1383
|
+
}
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
name: TOOL_NAME.NIKTO,
|
|
1387
|
+
description: `Nikto - Web server vulnerability scanner.
|
|
1388
|
+
|
|
1389
|
+
Scans for:
|
|
1390
|
+
- Dangerous files/CGIs
|
|
1391
|
+
- Outdated software versions
|
|
1392
|
+
- Server configuration issues
|
|
1393
|
+
- Default files and programs
|
|
1394
|
+
|
|
1395
|
+
USAGE:
|
|
1396
|
+
nikto -h https://target.com
|
|
1397
|
+
nikto -h target.com -port 8080
|
|
1398
|
+
|
|
1399
|
+
OPTIONS:
|
|
1400
|
+
- -h: Target host
|
|
1401
|
+
- -port: Port (default 80)
|
|
1402
|
+
- -ssl: Force SSL
|
|
1403
|
+
- -Tuning: Scan tuning (1-9, x)`,
|
|
1404
|
+
input_schema: {
|
|
1405
|
+
type: "object",
|
|
1406
|
+
properties: {
|
|
1407
|
+
target: { type: "string", description: "Target URL/IP" },
|
|
1408
|
+
port: { type: "number", description: "Port number" },
|
|
1409
|
+
ssl: { type: "boolean", description: "Force SSL" },
|
|
1410
|
+
tuning: { type: "string", description: "Scan tuning options" },
|
|
1411
|
+
output: { type: "string", description: "Output file" }
|
|
1412
|
+
},
|
|
1413
|
+
required: ["target"]
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
name: TOOL_NAME.WHATWEB,
|
|
1418
|
+
description: `WhatWeb - Web technology fingerprinting.
|
|
1419
|
+
|
|
1420
|
+
Identifies:
|
|
1421
|
+
- CMS (WordPress, Joomla, Drupal)
|
|
1422
|
+
- Web frameworks
|
|
1423
|
+
- JavaScript libraries
|
|
1424
|
+
- Web servers
|
|
1425
|
+
- Plugins and versions
|
|
1426
|
+
|
|
1427
|
+
USAGE:
|
|
1428
|
+
whatweb https://target.com
|
|
1429
|
+
whatweb -a 3 https://target.com # Aggressive mode
|
|
1430
|
+
|
|
1431
|
+
AGGRESSION LEVELS:
|
|
1432
|
+
- 1: Stealthy (default)
|
|
1433
|
+
- 3: Aggressive
|
|
1434
|
+
- 4: Heavy`,
|
|
1435
|
+
input_schema: {
|
|
1436
|
+
type: "object",
|
|
1437
|
+
properties: {
|
|
1438
|
+
target: { type: "string", description: "Target URL" },
|
|
1439
|
+
aggression: { type: "number", description: "Aggression level (1-4)" },
|
|
1440
|
+
verbose: { type: "boolean", description: "Verbose output" }
|
|
1441
|
+
},
|
|
1442
|
+
required: ["target"]
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: TOOL_NAME.HTTPX,
|
|
1447
|
+
description: `httpx - Fast HTTP toolkit for probing.
|
|
1448
|
+
|
|
1449
|
+
USAGE:
|
|
1450
|
+
echo "subdomain.target.com" | httpx
|
|
1451
|
+
httpx -l urls.txt -status-code -title
|
|
1452
|
+
|
|
1453
|
+
OPTIONS:
|
|
1454
|
+
- -status-code: Show status codes
|
|
1455
|
+
- -title: Extract page titles
|
|
1456
|
+
- -tech-detect: Technology detection
|
|
1457
|
+
- -follow-redirects: Follow redirects
|
|
1458
|
+
- -threads: Concurrent threads`,
|
|
1459
|
+
input_schema: {
|
|
1460
|
+
type: "object",
|
|
1461
|
+
properties: {
|
|
1462
|
+
target: { type: "string", description: "Target URL or file" },
|
|
1463
|
+
status_code: { type: "boolean", description: "Show status codes" },
|
|
1464
|
+
title: { type: "boolean", description: "Extract titles" },
|
|
1465
|
+
tech_detect: { type: "boolean", description: "Tech detection" },
|
|
1466
|
+
follow_redirects: { type: "boolean", description: "Follow redirects" }
|
|
1467
|
+
},
|
|
1468
|
+
required: ["target"]
|
|
1469
|
+
}
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: TOOL_NAME.WAYBACKURLS,
|
|
1473
|
+
description: `Waybackurls - Fetch URLs from Wayback Machine.
|
|
1474
|
+
|
|
1475
|
+
Reveals:
|
|
1476
|
+
- Historical endpoints
|
|
1477
|
+
- Hidden parameters
|
|
1478
|
+
- Old files and paths
|
|
1479
|
+
- API endpoints
|
|
1480
|
+
|
|
1481
|
+
USAGE:
|
|
1482
|
+
echo "target.com" | waybackurls
|
|
1483
|
+
waybackurls target.com | grep -E "\\.js$" # Find JS files`,
|
|
1484
|
+
input_schema: {
|
|
1485
|
+
type: "object",
|
|
1486
|
+
properties: {
|
|
1487
|
+
domain: { type: "string", description: "Target domain" },
|
|
1488
|
+
output: { type: "string", description: "Output file" }
|
|
1489
|
+
},
|
|
1490
|
+
required: ["domain"]
|
|
1491
|
+
}
|
|
1063
1492
|
}
|
|
1064
1493
|
];
|
|
1065
1494
|
var EXPLOIT_TOOLS = [
|
|
@@ -1302,6 +1731,7 @@ var REPORT_TOOLS = [
|
|
|
1302
1731
|
var ALL_TOOLS = [
|
|
1303
1732
|
...SYSTEM_TOOLS,
|
|
1304
1733
|
...NETWORK_TOOLS,
|
|
1734
|
+
...DNS_TOOLS,
|
|
1305
1735
|
...SERVICE_TOOLS,
|
|
1306
1736
|
...WINDOWS_TOOLS,
|
|
1307
1737
|
...WEB_TOOLS,
|
|
@@ -4150,6 +4580,868 @@ function buildAgentSystemPrompt(basePrompt, agent) {
|
|
|
4150
4580
|
${agent.systemPrompt}`;
|
|
4151
4581
|
}
|
|
4152
4582
|
|
|
4583
|
+
// src/core/agent/agent-orchestrator.ts
|
|
4584
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
4585
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
4586
|
+
var ORCHESTRATOR_EVENT = {
|
|
4587
|
+
AGENT_START: "agent_start",
|
|
4588
|
+
AGENT_COMPLETE: "agent_complete",
|
|
4589
|
+
AGENT_ERROR: "agent_error",
|
|
4590
|
+
ALL_COMPLETE: "all_complete",
|
|
4591
|
+
FINDING: "finding"
|
|
4592
|
+
};
|
|
4593
|
+
var AgentOrchestrator = class extends EventEmitter4 {
|
|
4594
|
+
client;
|
|
4595
|
+
agents = /* @__PURE__ */ new Map();
|
|
4596
|
+
initialized = false;
|
|
4597
|
+
constructor(apiKey) {
|
|
4598
|
+
super();
|
|
4599
|
+
this.client = new Anthropic({
|
|
4600
|
+
apiKey: apiKey || LLM_API_KEY || process.env.PENTEST_API_KEY,
|
|
4601
|
+
baseURL: LLM_BASE_URL
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4604
|
+
/**
|
|
4605
|
+
* Initialize with built-in agents
|
|
4606
|
+
*/
|
|
4607
|
+
async initialize() {
|
|
4608
|
+
if (this.initialized) return;
|
|
4609
|
+
for (const agent of BUILTIN_AGENTS) {
|
|
4610
|
+
this.agents.set(agent.name, agent);
|
|
4611
|
+
}
|
|
4612
|
+
this.initialized = true;
|
|
4613
|
+
}
|
|
4614
|
+
/**
|
|
4615
|
+
* Launch multiple agents in parallel
|
|
4616
|
+
*/
|
|
4617
|
+
async launchParallel(tasks) {
|
|
4618
|
+
await this.initialize();
|
|
4619
|
+
console.log(`\u{1F680} Launching ${tasks.length} agents in parallel...`);
|
|
4620
|
+
const startTime = Date.now();
|
|
4621
|
+
const promises = tasks.map((task) => this.executeAgent(task));
|
|
4622
|
+
const results = await Promise.allSettled(promises);
|
|
4623
|
+
const duration = Date.now() - startTime;
|
|
4624
|
+
console.log(`\u2705 All agents completed in ${(duration / 1e3).toFixed(1)}s`);
|
|
4625
|
+
const finalResults = results.map((result, index) => {
|
|
4626
|
+
if (result.status === "fulfilled") {
|
|
4627
|
+
return result.value;
|
|
4628
|
+
} else {
|
|
4629
|
+
return {
|
|
4630
|
+
agent: tasks[index].agent,
|
|
4631
|
+
success: false,
|
|
4632
|
+
output: "",
|
|
4633
|
+
findings: [],
|
|
4634
|
+
duration: 0,
|
|
4635
|
+
error: result.reason?.message || "Agent failed"
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
});
|
|
4639
|
+
this.emit(ORCHESTRATOR_EVENT.ALL_COMPLETE, {
|
|
4640
|
+
results: finalResults,
|
|
4641
|
+
duration
|
|
4642
|
+
});
|
|
4643
|
+
return finalResults;
|
|
4644
|
+
}
|
|
4645
|
+
/**
|
|
4646
|
+
* Execute a single agent
|
|
4647
|
+
*/
|
|
4648
|
+
async executeAgent(task) {
|
|
4649
|
+
const startTime = Date.now();
|
|
4650
|
+
this.emit(ORCHESTRATOR_EVENT.AGENT_START, { agent: task.agent, prompt: task.prompt });
|
|
4651
|
+
try {
|
|
4652
|
+
const agentDef = this.agents.get(task.agent);
|
|
4653
|
+
const systemPrompt = agentDef ? buildAgentSystemPrompt("You are a security expert. Report findings clearly.", agentDef) : `You are a security expert focused on ${task.agent}. Be thorough and report findings in JSON format.`;
|
|
4654
|
+
const response = await withRetry(
|
|
4655
|
+
() => this.client.messages.create({
|
|
4656
|
+
model: LLM_MODEL,
|
|
4657
|
+
max_tokens: LLM_MAX_TOKENS,
|
|
4658
|
+
system: systemPrompt,
|
|
4659
|
+
messages: [
|
|
4660
|
+
{ role: "user", content: task.prompt }
|
|
4661
|
+
]
|
|
4662
|
+
}),
|
|
4663
|
+
{ maxRetries: 2 }
|
|
4664
|
+
);
|
|
4665
|
+
const output = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
4666
|
+
const findings = this.parseFindings(output, task.agent);
|
|
4667
|
+
const duration = Date.now() - startTime;
|
|
4668
|
+
const result = {
|
|
4669
|
+
agent: task.agent,
|
|
4670
|
+
success: true,
|
|
4671
|
+
output,
|
|
4672
|
+
findings,
|
|
4673
|
+
duration
|
|
4674
|
+
};
|
|
4675
|
+
this.emit(ORCHESTRATOR_EVENT.AGENT_COMPLETE, result);
|
|
4676
|
+
for (const finding of findings) {
|
|
4677
|
+
this.emit(ORCHESTRATOR_EVENT.FINDING, finding);
|
|
4678
|
+
}
|
|
4679
|
+
return result;
|
|
4680
|
+
} catch (error) {
|
|
4681
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4682
|
+
this.emit(ORCHESTRATOR_EVENT.AGENT_ERROR, { agent: task.agent, error: errorMsg });
|
|
4683
|
+
return {
|
|
4684
|
+
agent: task.agent,
|
|
4685
|
+
success: false,
|
|
4686
|
+
output: "",
|
|
4687
|
+
findings: [],
|
|
4688
|
+
duration: Date.now() - startTime,
|
|
4689
|
+
error: errorMsg
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Parse findings from agent output
|
|
4695
|
+
*/
|
|
4696
|
+
parseFindings(output, agentName) {
|
|
4697
|
+
const findings = [];
|
|
4698
|
+
const jsonMatch = output.match(/```json\n?([\s\S]*?)\n?```/);
|
|
4699
|
+
if (jsonMatch) {
|
|
4700
|
+
try {
|
|
4701
|
+
const parsed = JSON.parse(jsonMatch[1]);
|
|
4702
|
+
if (Array.isArray(parsed)) {
|
|
4703
|
+
return parsed.map((f) => this.normalizeFinding(f, agentName));
|
|
4704
|
+
} else if (parsed.findings) {
|
|
4705
|
+
return parsed.findings.map((f) => this.normalizeFinding(f, agentName));
|
|
4706
|
+
}
|
|
4707
|
+
} catch {
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
const patterns = [
|
|
4711
|
+
{ regex: /CVE-\d{4}-\d+/gi, type: "vulnerability", severity: "high" },
|
|
4712
|
+
{ regex: /open port[s]?[:\s]+(\d+)/gi, type: "info", severity: "info" },
|
|
4713
|
+
{ regex: /admin|root|password/gi, type: "credential", severity: "high" },
|
|
4714
|
+
{ regex: /shell access|RCE|command injection/gi, type: "vulnerability", severity: "critical" },
|
|
4715
|
+
{ regex: /SQL injection|XSS|SSRF/gi, type: "vulnerability", severity: "high" }
|
|
4716
|
+
];
|
|
4717
|
+
for (const pattern of patterns) {
|
|
4718
|
+
const matches = output.match(pattern.regex);
|
|
4719
|
+
if (matches) {
|
|
4720
|
+
for (const match of [...new Set(matches)]) {
|
|
4721
|
+
findings.push({
|
|
4722
|
+
id: `finding_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
|
|
4723
|
+
type: pattern.type,
|
|
4724
|
+
title: match,
|
|
4725
|
+
description: `Found by ${agentName}: ${match}`,
|
|
4726
|
+
confidence: 60,
|
|
4727
|
+
severity: pattern.severity,
|
|
4728
|
+
evidence: [match],
|
|
4729
|
+
exploitability: "possible"
|
|
4730
|
+
});
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
return findings;
|
|
4735
|
+
}
|
|
4736
|
+
/**
|
|
4737
|
+
* Normalize a finding object
|
|
4738
|
+
*/
|
|
4739
|
+
normalizeFinding(raw, agentName) {
|
|
4740
|
+
const f = raw;
|
|
4741
|
+
return {
|
|
4742
|
+
id: f.id || `finding_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
|
|
4743
|
+
type: f.type || "info",
|
|
4744
|
+
title: f.title || "Unknown finding",
|
|
4745
|
+
description: f.description || `Found by ${agentName}`,
|
|
4746
|
+
confidence: f.confidence || 50,
|
|
4747
|
+
severity: f.severity || "medium",
|
|
4748
|
+
evidence: f.evidence || [],
|
|
4749
|
+
exploitability: f.exploitability || "possible",
|
|
4750
|
+
nextSteps: f.nextSteps
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
/**
|
|
4754
|
+
* Get available agent names
|
|
4755
|
+
*/
|
|
4756
|
+
getAgentNames() {
|
|
4757
|
+
return Array.from(this.agents.keys());
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4760
|
+
function consolidateFindings(results) {
|
|
4761
|
+
const allFindings = [];
|
|
4762
|
+
for (const result of results) {
|
|
4763
|
+
allFindings.push(...result.findings);
|
|
4764
|
+
}
|
|
4765
|
+
const findingMap = /* @__PURE__ */ new Map();
|
|
4766
|
+
for (const finding of allFindings) {
|
|
4767
|
+
const key = `${finding.type}:${finding.title}`;
|
|
4768
|
+
if (findingMap.has(key)) {
|
|
4769
|
+
const existing = findingMap.get(key);
|
|
4770
|
+
existing.confidence = Math.min(100, existing.confidence + 10);
|
|
4771
|
+
existing.evidence.push(...finding.evidence);
|
|
4772
|
+
} else {
|
|
4773
|
+
findingMap.set(key, { ...finding });
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
return Array.from(findingMap.values()).sort((a, b) => b.confidence - a.confidence);
|
|
4777
|
+
}
|
|
4778
|
+
var orchestratorInstance = null;
|
|
4779
|
+
function getOrchestrator(apiKey) {
|
|
4780
|
+
if (!orchestratorInstance) {
|
|
4781
|
+
orchestratorInstance = new AgentOrchestrator(apiKey);
|
|
4782
|
+
}
|
|
4783
|
+
return orchestratorInstance;
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
// src/core/agent/agent-memory.ts
|
|
4787
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
4788
|
+
var MEMORY_EVENT = {
|
|
4789
|
+
ENTRY_ADDED: "entry_added",
|
|
4790
|
+
STATE_UPDATED: "state_updated",
|
|
4791
|
+
HANDOFF: "handoff",
|
|
4792
|
+
COMPACTION: "compaction"
|
|
4793
|
+
};
|
|
4794
|
+
var AgentMemory = class _AgentMemory extends EventEmitter5 {
|
|
4795
|
+
// Short-term memory (current session)
|
|
4796
|
+
shortTermMemory = [];
|
|
4797
|
+
// Long-term memory (persisted across sessions)
|
|
4798
|
+
longTermMemory = [];
|
|
4799
|
+
// Episodic memory (specific events/interactions)
|
|
4800
|
+
episodicMemory = /* @__PURE__ */ new Map();
|
|
4801
|
+
// Shared state across all agents
|
|
4802
|
+
sharedState;
|
|
4803
|
+
// Agent contexts
|
|
4804
|
+
agentContexts = /* @__PURE__ */ new Map();
|
|
4805
|
+
// Memory limits
|
|
4806
|
+
SHORT_TERM_LIMIT = 100;
|
|
4807
|
+
LONG_TERM_LIMIT = 1e3;
|
|
4808
|
+
COMPACTION_THRESHOLD = 80;
|
|
4809
|
+
// % of limit
|
|
4810
|
+
constructor(target = "") {
|
|
4811
|
+
super();
|
|
4812
|
+
this.sharedState = this.initializeState(target);
|
|
4813
|
+
}
|
|
4814
|
+
// ===== State Management =====
|
|
4815
|
+
initializeState(target) {
|
|
4816
|
+
return {
|
|
4817
|
+
target,
|
|
4818
|
+
discoveredHosts: [],
|
|
4819
|
+
discoveredServices: /* @__PURE__ */ new Map(),
|
|
4820
|
+
credentials: [],
|
|
4821
|
+
currentPhase: "recon",
|
|
4822
|
+
completedPhases: [],
|
|
4823
|
+
attackSurface: /* @__PURE__ */ new Map(),
|
|
4824
|
+
vulnerabilities: [],
|
|
4825
|
+
failedApproaches: []
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
getSharedState() {
|
|
4829
|
+
return this.sharedState;
|
|
4830
|
+
}
|
|
4831
|
+
updateState(updates) {
|
|
4832
|
+
this.sharedState = { ...this.sharedState, ...updates };
|
|
4833
|
+
this.emit(MEMORY_EVENT.STATE_UPDATED, this.sharedState);
|
|
4834
|
+
}
|
|
4835
|
+
// ===== Memory Operations =====
|
|
4836
|
+
addMemory(entry) {
|
|
4837
|
+
const fullEntry = {
|
|
4838
|
+
...entry,
|
|
4839
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,
|
|
4840
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4841
|
+
};
|
|
4842
|
+
this.shortTermMemory.push(fullEntry);
|
|
4843
|
+
if (entry.importance >= 70) {
|
|
4844
|
+
this.longTermMemory.push(fullEntry);
|
|
4845
|
+
}
|
|
4846
|
+
if (!this.episodicMemory.has(entry.agent)) {
|
|
4847
|
+
this.episodicMemory.set(entry.agent, []);
|
|
4848
|
+
}
|
|
4849
|
+
this.episodicMemory.get(entry.agent).push(fullEntry);
|
|
4850
|
+
this.emit(MEMORY_EVENT.ENTRY_ADDED, fullEntry);
|
|
4851
|
+
this.checkCompaction();
|
|
4852
|
+
return fullEntry;
|
|
4853
|
+
}
|
|
4854
|
+
// ===== Memory Retrieval =====
|
|
4855
|
+
getRecentMemories(count = 10) {
|
|
4856
|
+
return this.shortTermMemory.slice(-count);
|
|
4857
|
+
}
|
|
4858
|
+
getMemoriesByAgent(agentName) {
|
|
4859
|
+
return this.episodicMemory.get(agentName) || [];
|
|
4860
|
+
}
|
|
4861
|
+
getMemoriesByType(type) {
|
|
4862
|
+
return [...this.shortTermMemory, ...this.longTermMemory].filter((m) => m.type === type);
|
|
4863
|
+
}
|
|
4864
|
+
getImportantMemories(threshold = 70) {
|
|
4865
|
+
return this.longTermMemory.filter((m) => m.importance >= threshold);
|
|
4866
|
+
}
|
|
4867
|
+
searchMemories(query) {
|
|
4868
|
+
const lowerQuery = query.toLowerCase();
|
|
4869
|
+
return [...this.shortTermMemory, ...this.longTermMemory].filter((m) => m.content.toLowerCase().includes(lowerQuery)).sort((a, b) => b.importance - a.importance);
|
|
4870
|
+
}
|
|
4871
|
+
// ===== Agent Context Management =====
|
|
4872
|
+
setAgentContext(context) {
|
|
4873
|
+
this.agentContexts.set(context.agentName, context);
|
|
4874
|
+
}
|
|
4875
|
+
getAgentContext(agentName) {
|
|
4876
|
+
return this.agentContexts.get(agentName);
|
|
4877
|
+
}
|
|
4878
|
+
getAllAgentContexts() {
|
|
4879
|
+
return Array.from(this.agentContexts.values());
|
|
4880
|
+
}
|
|
4881
|
+
// ===== Agent Handoff =====
|
|
4882
|
+
/**
|
|
4883
|
+
* Create a handoff package for transferring context between agents
|
|
4884
|
+
*/
|
|
4885
|
+
createHandoff(fromAgent, toAgent, task, relevantContext) {
|
|
4886
|
+
const handoffId = `handoff_${Date.now()}`;
|
|
4887
|
+
const relevantMemories = this.shortTermMemory.filter(
|
|
4888
|
+
(m) => m.agent === fromAgent || relevantContext.some((c) => m.content.includes(c))
|
|
4889
|
+
).slice(-20);
|
|
4890
|
+
const contextSummary = this.createContextSummary(fromAgent, task);
|
|
4891
|
+
this.addMemory({
|
|
4892
|
+
type: "handoff",
|
|
4893
|
+
agent: "orchestrator",
|
|
4894
|
+
content: `Handoff: ${fromAgent} \u2192 ${toAgent} for task: ${task}`,
|
|
4895
|
+
importance: 80,
|
|
4896
|
+
metadata: { fromAgent, toAgent, task, handoffId }
|
|
4897
|
+
});
|
|
4898
|
+
this.emit(MEMORY_EVENT.HANDOFF, { fromAgent, toAgent, task, handoffId });
|
|
4899
|
+
return {
|
|
4900
|
+
handoffId,
|
|
4901
|
+
context: contextSummary,
|
|
4902
|
+
sharedState: this.sharedState,
|
|
4903
|
+
relevantMemories
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
/**
|
|
4907
|
+
* Create a summarized context for handoff
|
|
4908
|
+
*/
|
|
4909
|
+
createContextSummary(agentName, task) {
|
|
4910
|
+
const agentMemories = this.getMemoriesByAgent(agentName).slice(-10);
|
|
4911
|
+
const findings = this.getMemoriesByType("finding").slice(-5);
|
|
4912
|
+
const errors = this.getMemoriesByType("error").slice(-3);
|
|
4913
|
+
return `
|
|
4914
|
+
## Context Summary for: ${task}
|
|
4915
|
+
|
|
4916
|
+
### Target
|
|
4917
|
+
- Primary: ${this.sharedState.target}
|
|
4918
|
+
- Phase: ${this.sharedState.currentPhase}
|
|
4919
|
+
- Hosts discovered: ${this.sharedState.discoveredHosts.length}
|
|
4920
|
+
|
|
4921
|
+
### Recent Actions by ${agentName}
|
|
4922
|
+
${agentMemories.map((m) => `- ${m.content.slice(0, 100)}`).join("\n")}
|
|
4923
|
+
|
|
4924
|
+
### Key Findings
|
|
4925
|
+
${findings.map((f) => `- [${f.agent}] ${f.content.slice(0, 100)}`).join("\n")}
|
|
4926
|
+
|
|
4927
|
+
### Failed Approaches (avoid these)
|
|
4928
|
+
${this.sharedState.failedApproaches.slice(-5).map((f) => `- ${f.approach}: ${f.reason}`).join("\n")}
|
|
4929
|
+
|
|
4930
|
+
### Credentials Found
|
|
4931
|
+
${this.sharedState.credentials.map((c) => `- ${c.username}:${c.password} (${c.source})`).join("\n") || "None"}
|
|
4932
|
+
`;
|
|
4933
|
+
}
|
|
4934
|
+
// ===== Attack Surface Tracking =====
|
|
4935
|
+
addToAttackSurface(category, items) {
|
|
4936
|
+
const existing = this.sharedState.attackSurface.get(category) || [];
|
|
4937
|
+
const newItems = items.filter((i) => !existing.includes(i));
|
|
4938
|
+
this.sharedState.attackSurface.set(category, [...existing, ...newItems]);
|
|
4939
|
+
if (newItems.length > 0) {
|
|
4940
|
+
this.addMemory({
|
|
4941
|
+
type: "discovery",
|
|
4942
|
+
agent: "orchestrator",
|
|
4943
|
+
content: `Attack surface expanded [${category}]: ${newItems.join(", ")}`,
|
|
4944
|
+
importance: 60
|
|
4945
|
+
});
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
getAttackSurface() {
|
|
4949
|
+
return this.sharedState.attackSurface;
|
|
4950
|
+
}
|
|
4951
|
+
getAttackSurfaceSummary() {
|
|
4952
|
+
const summary = [];
|
|
4953
|
+
for (const [category, items] of this.sharedState.attackSurface) {
|
|
4954
|
+
summary.push(`${category}: ${items.length} items`);
|
|
4955
|
+
}
|
|
4956
|
+
return summary.join(", ");
|
|
4957
|
+
}
|
|
4958
|
+
// ===== Failed Approach Tracking =====
|
|
4959
|
+
recordFailedApproach(approach, reason) {
|
|
4960
|
+
this.sharedState.failedApproaches.push({
|
|
4961
|
+
approach,
|
|
4962
|
+
reason,
|
|
4963
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
hasFailedBefore(approach) {
|
|
4967
|
+
return this.sharedState.failedApproaches.some(
|
|
4968
|
+
(f) => f.approach.toLowerCase().includes(approach.toLowerCase())
|
|
4969
|
+
);
|
|
4970
|
+
}
|
|
4971
|
+
// ===== Memory Compaction =====
|
|
4972
|
+
checkCompaction() {
|
|
4973
|
+
if (this.shortTermMemory.length >= this.SHORT_TERM_LIMIT * (this.COMPACTION_THRESHOLD / 100)) {
|
|
4974
|
+
this.compactShortTermMemory();
|
|
4975
|
+
}
|
|
4976
|
+
if (this.longTermMemory.length >= this.LONG_TERM_LIMIT * (this.COMPACTION_THRESHOLD / 100)) {
|
|
4977
|
+
this.compactLongTermMemory();
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
compactShortTermMemory() {
|
|
4981
|
+
const sorted = [...this.shortTermMemory].sort((a, b) => {
|
|
4982
|
+
const importanceDiff = b.importance - a.importance;
|
|
4983
|
+
if (importanceDiff !== 0) return importanceDiff;
|
|
4984
|
+
return b.timestamp.getTime() - a.timestamp.getTime();
|
|
4985
|
+
});
|
|
4986
|
+
this.shortTermMemory = sorted.slice(0, Math.floor(this.SHORT_TERM_LIMIT * 0.5));
|
|
4987
|
+
this.emit(MEMORY_EVENT.COMPACTION, { type: "short-term", remaining: this.shortTermMemory.length });
|
|
4988
|
+
}
|
|
4989
|
+
compactLongTermMemory() {
|
|
4990
|
+
this.longTermMemory = this.longTermMemory.sort((a, b) => b.importance - a.importance).slice(0, Math.floor(this.LONG_TERM_LIMIT * 0.7));
|
|
4991
|
+
this.emit(MEMORY_EVENT.COMPACTION, { type: "long-term", remaining: this.longTermMemory.length });
|
|
4992
|
+
}
|
|
4993
|
+
// ===== Serialization =====
|
|
4994
|
+
toJSON() {
|
|
4995
|
+
return {
|
|
4996
|
+
shortTermMemory: this.shortTermMemory,
|
|
4997
|
+
longTermMemory: this.longTermMemory,
|
|
4998
|
+
sharedState: {
|
|
4999
|
+
...this.sharedState,
|
|
5000
|
+
discoveredServices: Object.fromEntries(this.sharedState.discoveredServices),
|
|
5001
|
+
attackSurface: Object.fromEntries(this.sharedState.attackSurface)
|
|
5002
|
+
},
|
|
5003
|
+
agentContexts: Object.fromEntries(this.agentContexts)
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
static fromJSON(data) {
|
|
5007
|
+
const memory = new _AgentMemory();
|
|
5008
|
+
const parsed = data;
|
|
5009
|
+
memory.shortTermMemory = parsed.shortTermMemory || [];
|
|
5010
|
+
memory.longTermMemory = parsed.longTermMemory || [];
|
|
5011
|
+
if (parsed.sharedState) {
|
|
5012
|
+
memory.sharedState = {
|
|
5013
|
+
...parsed.sharedState,
|
|
5014
|
+
discoveredServices: new Map(Object.entries(parsed.sharedState.discoveredServices || {})),
|
|
5015
|
+
attackSurface: new Map(Object.entries(parsed.sharedState.attackSurface || {}))
|
|
5016
|
+
};
|
|
5017
|
+
}
|
|
5018
|
+
if (parsed.agentContexts) {
|
|
5019
|
+
memory.agentContexts = new Map(Object.entries(parsed.agentContexts));
|
|
5020
|
+
}
|
|
5021
|
+
return memory;
|
|
5022
|
+
}
|
|
5023
|
+
};
|
|
5024
|
+
var memoryInstance = null;
|
|
5025
|
+
function getAgentMemory(target) {
|
|
5026
|
+
if (!memoryInstance) {
|
|
5027
|
+
memoryInstance = new AgentMemory(target);
|
|
5028
|
+
}
|
|
5029
|
+
return memoryInstance;
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
// src/core/agent/supervisor-agent.ts
|
|
5033
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
5034
|
+
import { EventEmitter as EventEmitter6 } from "events";
|
|
5035
|
+
var SUPERVISOR_EVENT = {
|
|
5036
|
+
PLAN_CREATED: "plan_created",
|
|
5037
|
+
PHASE_STARTED: "phase_started",
|
|
5038
|
+
PHASE_COMPLETED: "phase_completed",
|
|
5039
|
+
TASK_DELEGATED: "task_delegated",
|
|
5040
|
+
TASK_COMPLETED: "task_completed",
|
|
5041
|
+
STRATEGY_ADJUSTED: "strategy_adjusted",
|
|
5042
|
+
DECISION_MADE: "decision_made"
|
|
5043
|
+
};
|
|
5044
|
+
var SupervisorAgent = class extends EventEmitter6 {
|
|
5045
|
+
client;
|
|
5046
|
+
orchestrator;
|
|
5047
|
+
memory;
|
|
5048
|
+
currentPlan = null;
|
|
5049
|
+
// Agent capability mapping
|
|
5050
|
+
agentCapabilities = /* @__PURE__ */ new Map([
|
|
5051
|
+
["target-explorer", ["recon", "scanning", "enumeration", "osint", "subdomain", "directory"]],
|
|
5052
|
+
["exploit-researcher", ["cve", "exploit", "vulnerability", "payload", "metasploit"]],
|
|
5053
|
+
["privesc-master", ["privilege", "escalation", "root", "system", "linux", "windows"]],
|
|
5054
|
+
["web-hacker", ["web", "sql", "xss", "injection", "http", "api", "form"]],
|
|
5055
|
+
["crypto-solver", ["crypto", "hash", "password", "decrypt", "encode"]],
|
|
5056
|
+
["forensics-analyst", ["forensics", "memory", "file", "log", "steganography"]],
|
|
5057
|
+
["reverse-engineer", ["binary", "reverse", "debug", "exploit", "buffer"]],
|
|
5058
|
+
["attack-architect", ["strategy", "plan", "attack", "chain", "prioritize"]],
|
|
5059
|
+
["finding-reviewer", ["validate", "verify", "review", "confidence", "false positive"]]
|
|
5060
|
+
]);
|
|
5061
|
+
constructor(apiKey) {
|
|
5062
|
+
super();
|
|
5063
|
+
this.client = new Anthropic2({
|
|
5064
|
+
apiKey: apiKey || LLM_API_KEY || process.env.PENTEST_API_KEY,
|
|
5065
|
+
baseURL: LLM_BASE_URL
|
|
5066
|
+
});
|
|
5067
|
+
this.orchestrator = getOrchestrator(apiKey);
|
|
5068
|
+
this.memory = getAgentMemory();
|
|
5069
|
+
}
|
|
5070
|
+
// ===== Task Planning =====
|
|
5071
|
+
/**
|
|
5072
|
+
* Create an execution plan for the given objective
|
|
5073
|
+
*/
|
|
5074
|
+
async createPlan(objective, target) {
|
|
5075
|
+
const planPrompt = `
|
|
5076
|
+
${STRATEGY_PLANNING_PROMPT}
|
|
5077
|
+
|
|
5078
|
+
Create an execution plan for:
|
|
5079
|
+
Objective: ${objective}
|
|
5080
|
+
Target: ${target}
|
|
5081
|
+
|
|
5082
|
+
Respond with a JSON plan:
|
|
5083
|
+
{
|
|
5084
|
+
"phases": [
|
|
5085
|
+
{
|
|
5086
|
+
"name": "Phase name",
|
|
5087
|
+
"description": "What this phase accomplishes",
|
|
5088
|
+
"agents": ["agent-name"],
|
|
5089
|
+
"tasks": [
|
|
5090
|
+
{
|
|
5091
|
+
"description": "Task description",
|
|
5092
|
+
"assignedAgent": "agent-name"
|
|
5093
|
+
}
|
|
5094
|
+
]
|
|
5095
|
+
}
|
|
5096
|
+
]
|
|
5097
|
+
}
|
|
5098
|
+
|
|
5099
|
+
Rules:
|
|
5100
|
+
1. Start with reconnaissance (BFS - map attack surface first)
|
|
5101
|
+
2. Discovery before exploitation
|
|
5102
|
+
3. Use specialized agents for their strengths
|
|
5103
|
+
4. Include validation steps
|
|
5104
|
+
5. Plan for fallbacks
|
|
5105
|
+
`;
|
|
5106
|
+
try {
|
|
5107
|
+
const response = await this.client.messages.create({
|
|
5108
|
+
model: LLM_MODEL,
|
|
5109
|
+
max_tokens: 2048,
|
|
5110
|
+
messages: [{ role: "user", content: planPrompt }]
|
|
5111
|
+
});
|
|
5112
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
5113
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
5114
|
+
if (jsonMatch) {
|
|
5115
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
5116
|
+
this.currentPlan = this.buildPlan(objective, parsed);
|
|
5117
|
+
} else {
|
|
5118
|
+
this.currentPlan = this.createDefaultPlan(objective, target);
|
|
5119
|
+
}
|
|
5120
|
+
} catch {
|
|
5121
|
+
this.currentPlan = this.createDefaultPlan(objective, target);
|
|
5122
|
+
}
|
|
5123
|
+
this.emit(SUPERVISOR_EVENT.PLAN_CREATED, this.currentPlan);
|
|
5124
|
+
this.memory.addMemory({
|
|
5125
|
+
type: "decision",
|
|
5126
|
+
agent: "supervisor",
|
|
5127
|
+
content: `Plan created: ${this.currentPlan.phases.length} phases for ${objective}`,
|
|
5128
|
+
importance: 90
|
|
5129
|
+
});
|
|
5130
|
+
return this.currentPlan;
|
|
5131
|
+
}
|
|
5132
|
+
buildPlan(objective, parsed) {
|
|
5133
|
+
return {
|
|
5134
|
+
id: `plan_${Date.now()}`,
|
|
5135
|
+
objective,
|
|
5136
|
+
status: "planning",
|
|
5137
|
+
currentPhaseIndex: 0,
|
|
5138
|
+
phases: parsed.phases.map((phase, idx) => ({
|
|
5139
|
+
id: `phase_${idx}`,
|
|
5140
|
+
name: phase.name,
|
|
5141
|
+
description: phase.description,
|
|
5142
|
+
agents: phase.agents || [],
|
|
5143
|
+
status: "pending",
|
|
5144
|
+
findings: [],
|
|
5145
|
+
tasks: (phase.tasks || []).map((task, tidx) => ({
|
|
5146
|
+
id: `task_${idx}_${tidx}`,
|
|
5147
|
+
description: task.description,
|
|
5148
|
+
assignedAgent: task.assignedAgent || this.selectBestAgent(task.description),
|
|
5149
|
+
status: "pending"
|
|
5150
|
+
}))
|
|
5151
|
+
}))
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
createDefaultPlan(objective, target) {
|
|
5155
|
+
return {
|
|
5156
|
+
id: `plan_${Date.now()}`,
|
|
5157
|
+
objective,
|
|
5158
|
+
status: "planning",
|
|
5159
|
+
currentPhaseIndex: 0,
|
|
5160
|
+
phases: [
|
|
5161
|
+
{
|
|
5162
|
+
id: "phase_0",
|
|
5163
|
+
name: "Reconnaissance",
|
|
5164
|
+
description: "Map attack surface - ports, subdomains, directories",
|
|
5165
|
+
agents: ["target-explorer"],
|
|
5166
|
+
status: "pending",
|
|
5167
|
+
findings: [],
|
|
5168
|
+
tasks: [
|
|
5169
|
+
{ id: "task_0_0", description: `Port scan ${target}`, assignedAgent: "target-explorer", status: "pending" },
|
|
5170
|
+
{ id: "task_0_1", description: `Subdomain enumeration for ${target}`, assignedAgent: "target-explorer", status: "pending" },
|
|
5171
|
+
{ id: "task_0_2", description: `Directory bruteforce on web services`, assignedAgent: "web-hacker", status: "pending" }
|
|
5172
|
+
]
|
|
5173
|
+
},
|
|
5174
|
+
{
|
|
5175
|
+
id: "phase_1",
|
|
5176
|
+
name: "Analysis",
|
|
5177
|
+
description: "Analyze findings and identify vulnerabilities",
|
|
5178
|
+
agents: ["attack-architect", "finding-reviewer"],
|
|
5179
|
+
status: "pending",
|
|
5180
|
+
findings: [],
|
|
5181
|
+
tasks: [
|
|
5182
|
+
{ id: "task_1_0", description: "Analyze attack surface and prioritize targets", assignedAgent: "attack-architect", status: "pending" },
|
|
5183
|
+
{ id: "task_1_1", description: "CVE research for discovered services", assignedAgent: "exploit-researcher", status: "pending" }
|
|
5184
|
+
]
|
|
5185
|
+
},
|
|
5186
|
+
{
|
|
5187
|
+
id: "phase_2",
|
|
5188
|
+
name: "Exploitation",
|
|
5189
|
+
description: "Attempt exploitation of identified vulnerabilities",
|
|
5190
|
+
agents: ["exploit-researcher", "web-hacker"],
|
|
5191
|
+
status: "pending",
|
|
5192
|
+
findings: [],
|
|
5193
|
+
tasks: [
|
|
5194
|
+
{ id: "task_2_0", description: "Exploit high-priority vulnerabilities", assignedAgent: "exploit-researcher", status: "pending" }
|
|
5195
|
+
]
|
|
5196
|
+
}
|
|
5197
|
+
]
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5200
|
+
// ===== Agent Selection =====
|
|
5201
|
+
/**
|
|
5202
|
+
* Select the best agent for a given task
|
|
5203
|
+
*/
|
|
5204
|
+
selectBestAgent(taskDescription) {
|
|
5205
|
+
const lowerTask = taskDescription.toLowerCase();
|
|
5206
|
+
let bestAgent = "target-explorer";
|
|
5207
|
+
let bestScore = 0;
|
|
5208
|
+
for (const [agent, capabilities] of this.agentCapabilities) {
|
|
5209
|
+
const score = capabilities.filter((cap) => lowerTask.includes(cap)).length;
|
|
5210
|
+
if (score > bestScore) {
|
|
5211
|
+
bestScore = score;
|
|
5212
|
+
bestAgent = agent;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
return bestAgent;
|
|
5216
|
+
}
|
|
5217
|
+
/**
|
|
5218
|
+
* Get agents for a specific phase
|
|
5219
|
+
*/
|
|
5220
|
+
getAgentsForPhase(phaseName) {
|
|
5221
|
+
const phaseMapping = {
|
|
5222
|
+
"recon": ["target-explorer"],
|
|
5223
|
+
"reconnaissance": ["target-explorer"],
|
|
5224
|
+
"scan": ["target-explorer"],
|
|
5225
|
+
"enum": ["target-explorer", "web-hacker"],
|
|
5226
|
+
"enumeration": ["target-explorer", "web-hacker"],
|
|
5227
|
+
"analysis": ["attack-architect", "finding-reviewer"],
|
|
5228
|
+
"vuln": ["exploit-researcher", "web-hacker"],
|
|
5229
|
+
"vulnerability": ["exploit-researcher", "web-hacker"],
|
|
5230
|
+
"exploit": ["exploit-researcher", "web-hacker"],
|
|
5231
|
+
"exploitation": ["exploit-researcher", "web-hacker"],
|
|
5232
|
+
"privesc": ["privesc-master"],
|
|
5233
|
+
"privilege": ["privesc-master"],
|
|
5234
|
+
"post": ["privesc-master", "forensics-analyst"]
|
|
5235
|
+
};
|
|
5236
|
+
const lowerPhase = phaseName.toLowerCase();
|
|
5237
|
+
for (const [key, agents] of Object.entries(phaseMapping)) {
|
|
5238
|
+
if (lowerPhase.includes(key)) {
|
|
5239
|
+
return agents;
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
return ["target-explorer"];
|
|
5243
|
+
}
|
|
5244
|
+
// ===== Task Execution =====
|
|
5245
|
+
/**
|
|
5246
|
+
* Execute the current plan
|
|
5247
|
+
*/
|
|
5248
|
+
async executePlan() {
|
|
5249
|
+
if (!this.currentPlan) {
|
|
5250
|
+
throw new Error("No plan created. Call createPlan() first.");
|
|
5251
|
+
}
|
|
5252
|
+
this.currentPlan.status = "executing";
|
|
5253
|
+
const allFindings = [];
|
|
5254
|
+
for (let i = 0; i < this.currentPlan.phases.length; i++) {
|
|
5255
|
+
this.currentPlan.currentPhaseIndex = i;
|
|
5256
|
+
const phase = this.currentPlan.phases[i];
|
|
5257
|
+
this.emit(SUPERVISOR_EVENT.PHASE_STARTED, phase);
|
|
5258
|
+
phase.status = "in_progress";
|
|
5259
|
+
try {
|
|
5260
|
+
const phaseFindings = await this.executePhase(phase);
|
|
5261
|
+
phase.findings = phaseFindings;
|
|
5262
|
+
allFindings.push(...phaseFindings);
|
|
5263
|
+
phase.status = "completed";
|
|
5264
|
+
this.emit(SUPERVISOR_EVENT.PHASE_COMPLETED, phase);
|
|
5265
|
+
await this.evaluateProgress(phaseFindings);
|
|
5266
|
+
} catch (error) {
|
|
5267
|
+
phase.status = "failed";
|
|
5268
|
+
this.memory.recordFailedApproach(
|
|
5269
|
+
phase.name,
|
|
5270
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
5271
|
+
);
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
this.currentPlan.status = "completed";
|
|
5275
|
+
return consolidateFindings(allFindings.map((f) => ({
|
|
5276
|
+
agent: "supervisor",
|
|
5277
|
+
success: true,
|
|
5278
|
+
output: "",
|
|
5279
|
+
findings: [f],
|
|
5280
|
+
duration: 0
|
|
5281
|
+
})));
|
|
5282
|
+
}
|
|
5283
|
+
/**
|
|
5284
|
+
* Execute a single phase
|
|
5285
|
+
*/
|
|
5286
|
+
async executePhase(phase) {
|
|
5287
|
+
const tasks = phase.tasks.map((task) => ({
|
|
5288
|
+
agent: task.assignedAgent,
|
|
5289
|
+
prompt: this.buildTaskPrompt(task, phase),
|
|
5290
|
+
priority: 1
|
|
5291
|
+
}));
|
|
5292
|
+
await this.orchestrator.initialize();
|
|
5293
|
+
const results = await this.orchestrator.launchParallel(tasks);
|
|
5294
|
+
for (let i = 0; i < phase.tasks.length; i++) {
|
|
5295
|
+
const task = phase.tasks[i];
|
|
5296
|
+
const result = results[i];
|
|
5297
|
+
task.status = result.success ? "completed" : "failed";
|
|
5298
|
+
task.result = result.output;
|
|
5299
|
+
task.error = result.error;
|
|
5300
|
+
this.emit(SUPERVISOR_EVENT.TASK_COMPLETED, { task, result });
|
|
5301
|
+
this.memory.addMemory({
|
|
5302
|
+
type: result.success ? "action" : "error",
|
|
5303
|
+
agent: task.assignedAgent,
|
|
5304
|
+
content: result.success ? `Completed: ${task.description}` : `Failed: ${task.description} - ${result.error}`,
|
|
5305
|
+
importance: result.success ? 60 : 70
|
|
5306
|
+
});
|
|
5307
|
+
}
|
|
5308
|
+
return consolidateFindings(results);
|
|
5309
|
+
}
|
|
5310
|
+
buildTaskPrompt(task, phase) {
|
|
5311
|
+
const handoff = this.memory.createHandoff(
|
|
5312
|
+
"supervisor",
|
|
5313
|
+
task.assignedAgent,
|
|
5314
|
+
task.description,
|
|
5315
|
+
[phase.name, task.description]
|
|
5316
|
+
);
|
|
5317
|
+
return `
|
|
5318
|
+
${AGENT_COLLABORATION_PROMPT}
|
|
5319
|
+
|
|
5320
|
+
## Your Task
|
|
5321
|
+
${task.description}
|
|
5322
|
+
|
|
5323
|
+
## Phase Context
|
|
5324
|
+
Phase: ${phase.name}
|
|
5325
|
+
Description: ${phase.description}
|
|
5326
|
+
|
|
5327
|
+
## Shared Context
|
|
5328
|
+
${handoff.context}
|
|
5329
|
+
|
|
5330
|
+
## Instructions
|
|
5331
|
+
1. Complete the task thoroughly
|
|
5332
|
+
2. Report all findings in structured format
|
|
5333
|
+
3. Suggest next steps based on discoveries
|
|
5334
|
+
4. Be efficient - BFS over DFS
|
|
5335
|
+
|
|
5336
|
+
Provide your findings in JSON format when possible.
|
|
5337
|
+
`;
|
|
5338
|
+
}
|
|
5339
|
+
// ===== Strategy Evaluation =====
|
|
5340
|
+
/**
|
|
5341
|
+
* Evaluate progress and adjust strategy if needed
|
|
5342
|
+
*/
|
|
5343
|
+
async evaluateProgress(findings) {
|
|
5344
|
+
const criticalFindings = findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
5345
|
+
const failedApproaches = this.memory.getSharedState().failedApproaches;
|
|
5346
|
+
if (criticalFindings.length > 0) {
|
|
5347
|
+
this.memory.addMemory({
|
|
5348
|
+
type: "decision",
|
|
5349
|
+
agent: "supervisor",
|
|
5350
|
+
content: `Strategy adjustment: Found ${criticalFindings.length} critical/high findings. Prioritizing exploitation.`,
|
|
5351
|
+
importance: 85
|
|
5352
|
+
});
|
|
5353
|
+
this.emit(SUPERVISOR_EVENT.STRATEGY_ADJUSTED, {
|
|
5354
|
+
reason: "Critical findings discovered",
|
|
5355
|
+
adjustment: "Prioritize exploitation",
|
|
5356
|
+
findings: criticalFindings
|
|
5357
|
+
});
|
|
5358
|
+
}
|
|
5359
|
+
if (failedApproaches.length >= 3) {
|
|
5360
|
+
this.memory.addMemory({
|
|
5361
|
+
type: "decision",
|
|
5362
|
+
agent: "supervisor",
|
|
5363
|
+
content: `Strategy adjustment: ${failedApproaches.length} failed approaches. Recommending pivot.`,
|
|
5364
|
+
importance: 80
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
// ===== Decision Making =====
|
|
5369
|
+
/**
|
|
5370
|
+
* Make a decision about next action
|
|
5371
|
+
*/
|
|
5372
|
+
async makeDecision(situation) {
|
|
5373
|
+
const decisionPrompt = `
|
|
5374
|
+
${STRATEGY_PLANNING_PROMPT}
|
|
5375
|
+
|
|
5376
|
+
Current situation:
|
|
5377
|
+
${situation}
|
|
5378
|
+
|
|
5379
|
+
Shared state:
|
|
5380
|
+
- Target: ${this.memory.getSharedState().target}
|
|
5381
|
+
- Phase: ${this.memory.getSharedState().currentPhase}
|
|
5382
|
+
- Attack surface: ${this.memory.getAttackSurfaceSummary()}
|
|
5383
|
+
- Failed approaches: ${this.memory.getSharedState().failedApproaches.map((f) => f.approach).join(", ")}
|
|
5384
|
+
|
|
5385
|
+
Make a decision:
|
|
5386
|
+
1. What action should we take?
|
|
5387
|
+
2. Which agent should handle it?
|
|
5388
|
+
3. What's your reasoning?
|
|
5389
|
+
4. What are alternatives if this fails?
|
|
5390
|
+
|
|
5391
|
+
Respond with JSON:
|
|
5392
|
+
{
|
|
5393
|
+
"action": "description of action",
|
|
5394
|
+
"agent": "agent-name",
|
|
5395
|
+
"reasoning": "why this is the best choice",
|
|
5396
|
+
"confidence": 0-100,
|
|
5397
|
+
"alternatives": ["alt1", "alt2"]
|
|
5398
|
+
}
|
|
5399
|
+
`;
|
|
5400
|
+
try {
|
|
5401
|
+
const response = await this.client.messages.create({
|
|
5402
|
+
model: LLM_MODEL,
|
|
5403
|
+
max_tokens: 1024,
|
|
5404
|
+
messages: [{ role: "user", content: decisionPrompt }]
|
|
5405
|
+
});
|
|
5406
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
5407
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
5408
|
+
if (jsonMatch) {
|
|
5409
|
+
const decision = JSON.parse(jsonMatch[0]);
|
|
5410
|
+
this.emit(SUPERVISOR_EVENT.DECISION_MADE, decision);
|
|
5411
|
+
this.memory.addMemory({
|
|
5412
|
+
type: "decision",
|
|
5413
|
+
agent: "supervisor",
|
|
5414
|
+
content: `Decision: ${decision.action} (${decision.agent}, ${decision.confidence}% confidence)`,
|
|
5415
|
+
importance: 75
|
|
5416
|
+
});
|
|
5417
|
+
return decision;
|
|
5418
|
+
}
|
|
5419
|
+
} catch {
|
|
5420
|
+
}
|
|
5421
|
+
return {
|
|
5422
|
+
action: "Continue with reconnaissance",
|
|
5423
|
+
agent: "target-explorer",
|
|
5424
|
+
reasoning: "Default action when unsure",
|
|
5425
|
+
confidence: 50,
|
|
5426
|
+
alternatives: ["web-hacker", "exploit-researcher"]
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
// ===== Getters =====
|
|
5430
|
+
getCurrentPlan() {
|
|
5431
|
+
return this.currentPlan;
|
|
5432
|
+
}
|
|
5433
|
+
getMemory() {
|
|
5434
|
+
return this.memory;
|
|
5435
|
+
}
|
|
5436
|
+
};
|
|
5437
|
+
var supervisorInstance = null;
|
|
5438
|
+
function getSupervisor(apiKey) {
|
|
5439
|
+
if (!supervisorInstance) {
|
|
5440
|
+
supervisorInstance = new SupervisorAgent(apiKey);
|
|
5441
|
+
}
|
|
5442
|
+
return supervisorInstance;
|
|
5443
|
+
}
|
|
5444
|
+
|
|
4153
5445
|
// src/commands/index.ts
|
|
4154
5446
|
var SCAN_COMMAND = {
|
|
4155
5447
|
name: "scan",
|
|
@@ -4466,7 +5758,7 @@ var DEFAULT_PHASES = [
|
|
|
4466
5758
|
{ id: PHASE_ID.EXFIL, name: "Data Exfiltration", shortName: "Exfil", status: PHASE_STATUS.PENDING, attempts: 0 },
|
|
4467
5759
|
{ id: PHASE_ID.REPORT, name: "Reporting", shortName: "Report", status: PHASE_STATUS.PENDING, attempts: 0 }
|
|
4468
5760
|
];
|
|
4469
|
-
var AutonomousHackingAgent = class extends
|
|
5761
|
+
var AutonomousHackingAgent = class extends EventEmitter7 {
|
|
4470
5762
|
client;
|
|
4471
5763
|
state;
|
|
4472
5764
|
config;
|
|
@@ -4481,6 +5773,9 @@ var AutonomousHackingAgent = class extends EventEmitter4 {
|
|
|
4481
5773
|
mcpManager;
|
|
4482
5774
|
contextManager;
|
|
4483
5775
|
approvalManager;
|
|
5776
|
+
orchestrator;
|
|
5777
|
+
agentMemory;
|
|
5778
|
+
supervisor;
|
|
4484
5779
|
// Token usage tracking
|
|
4485
5780
|
tokenUsage = {
|
|
4486
5781
|
input: 0,
|
|
@@ -4499,7 +5794,7 @@ var AutonomousHackingAgent = class extends EventEmitter4 {
|
|
|
4499
5794
|
// Max attempts per phase
|
|
4500
5795
|
constructor(apiKey, config) {
|
|
4501
5796
|
super();
|
|
4502
|
-
this.client = new
|
|
5797
|
+
this.client = new Anthropic3({
|
|
4503
5798
|
apiKey: apiKey || LLM_API_KEY || process.env.PENTEST_API_KEY,
|
|
4504
5799
|
baseURL: LLM_BASE_URL
|
|
4505
5800
|
});
|
|
@@ -4509,6 +5804,9 @@ var AutonomousHackingAgent = class extends EventEmitter4 {
|
|
|
4509
5804
|
this.mcpManager = getMCPManager();
|
|
4510
5805
|
this.contextManager = new ContextManager(this.client);
|
|
4511
5806
|
this.approvalManager = getApprovalManager({ yoloMode: config?.autoApprove });
|
|
5807
|
+
this.orchestrator = getOrchestrator(apiKey);
|
|
5808
|
+
this.agentMemory = getAgentMemory();
|
|
5809
|
+
this.supervisor = getSupervisor(apiKey);
|
|
4512
5810
|
this.state = this.createInitialState();
|
|
4513
5811
|
this.initSystems();
|
|
4514
5812
|
}
|
|
@@ -4919,25 +6217,161 @@ What went wrong and what different approach should be tried?
|
|
|
4919
6217
|
this.think(THOUGHT_TYPE.REFLECTION, reflection);
|
|
4920
6218
|
return reflection;
|
|
4921
6219
|
}
|
|
6220
|
+
// ===== Strategy Validation =====
|
|
6221
|
+
async validateStrategy(proposedAction) {
|
|
6222
|
+
this.think(THOUGHT_TYPE.PLANNING, "[strategy] Validating action strategy...");
|
|
6223
|
+
const validationPrompt = `
|
|
6224
|
+
${STRATEGY_PLANNING_PROMPT}
|
|
6225
|
+
|
|
6226
|
+
Current situation:
|
|
6227
|
+
- Target: ${this.state.target.primary}
|
|
6228
|
+
- Current phase: ${this.getCurrentPhase().shortName}
|
|
6229
|
+
- Discovered services: ${this.state.target.services.map((s) => `${s.host}:${s.port} (${s.service})`).join(", ") || "none"}
|
|
6230
|
+
- Attack surface mapped: ${this.state.target.discovered.length} hosts, ${this.state.target.services.length} services
|
|
6231
|
+
- Subdomains found: ${this.state.target.discovered.filter((d) => d.includes(".")).length}
|
|
6232
|
+
|
|
6233
|
+
Proposed action: ${proposedAction}
|
|
6234
|
+
|
|
6235
|
+
Validate this action:
|
|
6236
|
+
1. Is this aligned with BFS (surface mapping first)?
|
|
6237
|
+
2. Have we completed reconnaissance before deep testing?
|
|
6238
|
+
3. Is this the highest ROI action right now?
|
|
6239
|
+
4. Should we adjust the approach?
|
|
6240
|
+
|
|
6241
|
+
Respond with JSON:
|
|
6242
|
+
{
|
|
6243
|
+
"valid": true/false,
|
|
6244
|
+
"adjustedAction": "if invalid, suggest better action",
|
|
6245
|
+
"reasoning": "brief explanation"
|
|
6246
|
+
}
|
|
6247
|
+
`;
|
|
6248
|
+
try {
|
|
6249
|
+
const response = await this.client.messages.create({
|
|
6250
|
+
model: LLM_MODEL,
|
|
6251
|
+
max_tokens: 1024,
|
|
6252
|
+
messages: [{ role: "user", content: validationPrompt }]
|
|
6253
|
+
});
|
|
6254
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
6255
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
6256
|
+
if (jsonMatch) {
|
|
6257
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
6258
|
+
if (!result.valid) {
|
|
6259
|
+
this.think(THOUGHT_TYPE.REFLECTION, `[strategy] Action adjusted: ${result.reasoning}`);
|
|
6260
|
+
}
|
|
6261
|
+
return result;
|
|
6262
|
+
}
|
|
6263
|
+
} catch {
|
|
6264
|
+
}
|
|
6265
|
+
return { valid: true, reasoning: "Validation skipped" };
|
|
6266
|
+
}
|
|
6267
|
+
// ===== Agent Collaboration =====
|
|
6268
|
+
/**
|
|
6269
|
+
* Delegate a task to a specialist agent and get results
|
|
6270
|
+
*/
|
|
6271
|
+
async delegateToSpecialist(agentName, task, context) {
|
|
6272
|
+
this.think(THOUGHT_TYPE.PLANNING, `[delegate] Delegating to ${agentName}: ${task.slice(0, 50)}...`);
|
|
6273
|
+
await this.orchestrator.initialize();
|
|
6274
|
+
const agentTask = {
|
|
6275
|
+
agent: agentName,
|
|
6276
|
+
prompt: `${AGENT_COLLABORATION_PROMPT}
|
|
6277
|
+
|
|
6278
|
+
Context from main agent:
|
|
6279
|
+
${context}
|
|
6280
|
+
|
|
6281
|
+
Your task:
|
|
6282
|
+
${task}
|
|
6283
|
+
|
|
6284
|
+
Provide your analysis and findings. Include:
|
|
6285
|
+
1. Key discoveries
|
|
6286
|
+
2. Recommended next actions
|
|
6287
|
+
3. Any concerns or issues found`,
|
|
6288
|
+
priority: 1
|
|
6289
|
+
};
|
|
6290
|
+
try {
|
|
6291
|
+
const results = await this.orchestrator.launchParallel([agentTask]);
|
|
6292
|
+
const result = results[0];
|
|
6293
|
+
if (result.success) {
|
|
6294
|
+
const consolidatedFindings = consolidateFindings(results);
|
|
6295
|
+
const findingsSummary = consolidatedFindings.map((f) => `- [${f.severity.toUpperCase()}] ${f.title} (${f.confidence}% confidence)`).join("\n");
|
|
6296
|
+
this.think(THOUGHT_TYPE.REFLECTION, `[delegate] ${agentName} completed: ${result.findings.length} findings`);
|
|
6297
|
+
return {
|
|
6298
|
+
success: true,
|
|
6299
|
+
findings: findingsSummary || result.output.slice(0, 500),
|
|
6300
|
+
recommendation: result.findings[0]?.nextSteps?.join(", ") || "Continue with main approach"
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6303
|
+
return {
|
|
6304
|
+
success: false,
|
|
6305
|
+
findings: result.error || "Agent failed",
|
|
6306
|
+
recommendation: "Try alternative approach"
|
|
6307
|
+
};
|
|
6308
|
+
} catch (error) {
|
|
6309
|
+
return {
|
|
6310
|
+
success: false,
|
|
6311
|
+
findings: error instanceof Error ? error.message : "Unknown error",
|
|
6312
|
+
recommendation: "Fallback to main agent"
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
/**
|
|
6317
|
+
* Consult multiple agents for strategy validation
|
|
6318
|
+
*/
|
|
6319
|
+
async consultAgents(question) {
|
|
6320
|
+
this.think(THOUGHT_TYPE.PLANNING, "[consult] Consulting specialist agents...");
|
|
6321
|
+
const tasks = [
|
|
6322
|
+
{
|
|
6323
|
+
agent: "attack-architect",
|
|
6324
|
+
prompt: `Strategic question: ${question}
|
|
6325
|
+
|
|
6326
|
+
Provide your expert opinion on the best approach.`,
|
|
6327
|
+
priority: 1
|
|
6328
|
+
},
|
|
6329
|
+
{
|
|
6330
|
+
agent: "finding-reviewer",
|
|
6331
|
+
prompt: `Review this approach: ${question}
|
|
6332
|
+
|
|
6333
|
+
Validate if this is the right strategy.`,
|
|
6334
|
+
priority: 1
|
|
6335
|
+
}
|
|
6336
|
+
];
|
|
6337
|
+
await this.orchestrator.initialize();
|
|
6338
|
+
const results = await this.orchestrator.launchParallel(tasks);
|
|
6339
|
+
const opinions = results.filter((r) => r.success).map((r) => `**${r.agent}**: ${r.output.slice(0, 200)}...`).join("\n\n");
|
|
6340
|
+
this.think(THOUGHT_TYPE.REFLECTION, "[consult] Received opinions from specialists");
|
|
6341
|
+
return opinions || "No specialist opinions available";
|
|
6342
|
+
}
|
|
4922
6343
|
// ===== Progress Detection =====
|
|
4923
6344
|
recordProgress(type) {
|
|
4924
6345
|
this.resetStuckCounter();
|
|
4925
6346
|
this.state.lastProgressTime = /* @__PURE__ */ new Date();
|
|
6347
|
+
let message = "";
|
|
6348
|
+
let importance = 60;
|
|
4926
6349
|
switch (type) {
|
|
4927
6350
|
case "discovery":
|
|
4928
|
-
|
|
6351
|
+
message = "[target] New target discovered!";
|
|
6352
|
+
importance = 65;
|
|
4929
6353
|
break;
|
|
4930
6354
|
case "credential":
|
|
4931
|
-
|
|
6355
|
+
message = "[cred] Credential obtained!";
|
|
6356
|
+
importance = 85;
|
|
4932
6357
|
break;
|
|
4933
6358
|
case "access":
|
|
4934
|
-
|
|
6359
|
+
message = "[access] Access obtained!";
|
|
6360
|
+
importance = 90;
|
|
4935
6361
|
break;
|
|
4936
6362
|
case "exploit":
|
|
4937
|
-
|
|
6363
|
+
message = "[exploit] Exploit successful!";
|
|
6364
|
+
importance = 95;
|
|
4938
6365
|
this.state.successfulExploits++;
|
|
4939
6366
|
break;
|
|
4940
6367
|
}
|
|
6368
|
+
this.think(THOUGHT_TYPE.BREAKTHROUGH, message);
|
|
6369
|
+
this.agentMemory.addMemory({
|
|
6370
|
+
type: type === "exploit" ? "action" : type === "credential" ? "credential" : "discovery",
|
|
6371
|
+
agent: this.currentAgent?.name || "autonomous-agent",
|
|
6372
|
+
content: message,
|
|
6373
|
+
importance
|
|
6374
|
+
});
|
|
4941
6375
|
}
|
|
4942
6376
|
// ===== Finding Management =====
|
|
4943
6377
|
addFinding(finding) {
|
|
@@ -5686,14 +7120,14 @@ Respond helpfully to the user's message. If they ask to perform security testing
|
|
|
5686
7120
|
// src/core/session/session-manager.ts
|
|
5687
7121
|
import * as fs4 from "fs/promises";
|
|
5688
7122
|
import * as path4 from "path";
|
|
5689
|
-
import { EventEmitter as
|
|
7123
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
5690
7124
|
var SESSIONS_DIR = ".pentesting/sessions";
|
|
5691
7125
|
function generateSessionId() {
|
|
5692
7126
|
const timestamp = Date.now().toString(36);
|
|
5693
7127
|
const random = Math.random().toString(36).substring(2, 8);
|
|
5694
7128
|
return `session_${timestamp}_${random}`;
|
|
5695
7129
|
}
|
|
5696
|
-
var SessionManager = class extends
|
|
7130
|
+
var SessionManager = class extends EventEmitter8 {
|
|
5697
7131
|
sessionsDir;
|
|
5698
7132
|
currentSession = null;
|
|
5699
7133
|
constructor(baseDir) {
|
|
@@ -6012,7 +7446,7 @@ function getSlashCommandRegistry() {
|
|
|
6012
7446
|
// src/core/context/context-manager.ts
|
|
6013
7447
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
|
|
6014
7448
|
import { join as join4, dirname as dirname3 } from "path";
|
|
6015
|
-
import { EventEmitter as
|
|
7449
|
+
import { EventEmitter as EventEmitter9 } from "events";
|
|
6016
7450
|
var CONTEXT_EVENT = {
|
|
6017
7451
|
MESSAGE_ADDED: "message_added",
|
|
6018
7452
|
CHECKPOINT_CREATED: "checkpoint_created",
|
|
@@ -6020,7 +7454,7 @@ var CONTEXT_EVENT = {
|
|
|
6020
7454
|
CLEARED: "cleared",
|
|
6021
7455
|
COMPACTED: "compacted"
|
|
6022
7456
|
};
|
|
6023
|
-
var ContextManager2 = class extends
|
|
7457
|
+
var ContextManager2 = class extends EventEmitter9 {
|
|
6024
7458
|
filePath;
|
|
6025
7459
|
state;
|
|
6026
7460
|
maxMessages;
|
|
@@ -6731,9 +8165,9 @@ import { homedir } from "os";
|
|
|
6731
8165
|
import { join as join7 } from "path";
|
|
6732
8166
|
|
|
6733
8167
|
// src/cli/utils/keyboard-listener.ts
|
|
6734
|
-
import { EventEmitter as
|
|
8168
|
+
import { EventEmitter as EventEmitter10 } from "events";
|
|
6735
8169
|
import * as readline2 from "readline";
|
|
6736
|
-
var KeyboardListener = class extends
|
|
8170
|
+
var KeyboardListener = class extends EventEmitter10 {
|
|
6737
8171
|
isListening = false;
|
|
6738
8172
|
isPaused = false;
|
|
6739
8173
|
stdin = process.stdin;
|
|
@@ -6958,7 +8392,7 @@ function getKeyboardListener() {
|
|
|
6958
8392
|
}
|
|
6959
8393
|
|
|
6960
8394
|
// src/utils/input-queue.ts
|
|
6961
|
-
import { EventEmitter as
|
|
8395
|
+
import { EventEmitter as EventEmitter11 } from "events";
|
|
6962
8396
|
var INPUT_QUEUE_EVENT = {
|
|
6963
8397
|
QUEUED: "queued",
|
|
6964
8398
|
// Message added to queue
|
|
@@ -6971,7 +8405,7 @@ var INPUT_QUEUE_EVENT = {
|
|
|
6971
8405
|
SHUTDOWN: "shutdown"
|
|
6972
8406
|
// Queue shutdown
|
|
6973
8407
|
};
|
|
6974
|
-
var InputQueue = class extends
|
|
8408
|
+
var InputQueue = class extends EventEmitter11 {
|
|
6975
8409
|
queue = [];
|
|
6976
8410
|
isShutdown = false;
|
|
6977
8411
|
isPaused = false;
|