pentesting 0.12.4 → 0.12.12

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/dist/index.js CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ctfResearch,
4
+ searchADWriteups,
5
+ searchByScenario,
6
+ searchLinuxPrivesc,
7
+ searchMachineWriteup,
8
+ searchWindowsPrivesc,
9
+ searchWriteups,
10
+ securityResearch
11
+ } from "./chunk-AOJBE232.js";
2
12
  import {
3
13
  AGENT_CONFIG,
4
14
  AGENT_EVENT,
@@ -15,8 +25,9 @@ import {
15
25
  PHASE_ID,
16
26
  PHASE_STATUS,
17
27
  THOUGHT_TYPE,
18
- TOOL_NAME
19
- } from "./chunk-JXR7HH4V.js";
28
+ TOOL_NAME,
29
+ TOOL_TO_APT
30
+ } from "./chunk-6GEXOEUI.js";
20
31
  import {
21
32
  __require
22
33
  } from "./chunk-3RG5ZIWI.js";
@@ -33,7 +44,7 @@ import Spinner from "ink-spinner";
33
44
 
34
45
  // src/core/agent/autonomous-agent.ts
35
46
  import Anthropic from "@anthropic-ai/sdk";
36
- import { EventEmitter as EventEmitter18 } from "events";
47
+ import { EventEmitter as EventEmitter17 } from "events";
37
48
 
38
49
  // src/core/prompts/autonomous-prompt.ts
39
50
  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.
@@ -205,7 +216,16 @@ RIGHT: Use ffuf tool with url=https://domain.com/FUZZ, mode=directory
205
216
 
206
217
  NEVER write manual bash loops when MCP tools exist!
207
218
  ALWAYS prefer MCP tools over bash commands!
208
- </mandatory_autonomous_execution>
219
+
220
+ <explore_and_verify_strategy>
221
+ When performing research or vulnerability analysis:
222
+ 1. **Search**: Start with broad searches (search_writeups, security_research).
223
+ 2. **Explore**: Use fetchUrlContent to read the actual content of promising links.
224
+ 3. **Recursive Search**: If you find specific keywords, CVEs, or tool names in the content, perform NEW searches for those specific items.
225
+ 4. **Verify**: Use browser_automation to interact with target sites and verify if the techniques found in research actually apply to the current target.
226
+ 5. **Trace**: Document the path from initial search to final verification.
227
+ </explore_and_verify_strategy>
228
+ </mandatory_autonomous_execution>
209
229
 
210
230
  <output_format>
211
231
  Always structure your thinking clearly:
@@ -2776,12 +2796,6 @@ var ALL_TOOLS = [
2776
2796
  ...RESEARCH_TOOLS
2777
2797
  ];
2778
2798
 
2779
- // src/core/tools/tool-executor.ts
2780
- import { exec, spawn as spawn2 } from "child_process";
2781
- import { promisify } from "util";
2782
- import * as fs from "fs/promises";
2783
- import * as path from "path";
2784
-
2785
2799
  // src/core/resource/resource-manager.ts
2786
2800
  import { EventEmitter } from "events";
2787
2801
  var RESOURCE_EVENT = {
@@ -3085,343 +3099,234 @@ function getResourceManager(config) {
3085
3099
  return resourceManagerInstance;
3086
3100
  }
3087
3101
 
3088
- // src/core/tools/web-search.ts
3089
- import { spawn } from "child_process";
3090
- var WRITEUP_SOURCES = {
3091
- general: [
3092
- "site:0xdf.gitlab.io",
3093
- // 0xdf's blog
3094
- "site:ippsec.rocks",
3095
- // IppSec video index
3096
- "site:medium.com",
3097
- // Medium writeups
3098
- "site:infosecwriteups.com",
3099
- // InfoSec writeups
3100
- "site:hackingarticles.in"
3101
- // Raj Chandel's blog
3102
- ],
3103
- htb: [
3104
- "site:0xdf.gitlab.io",
3105
- "site:ippsec.rocks",
3106
- "site:app.hackthebox.com",
3107
- '"HackTheBox" OR "HTB"'
3108
- ],
3109
- thm: [
3110
- "site:tryhackme.com",
3111
- '"TryHackMe" OR "THM"'
3112
- ],
3113
- ctf: [
3114
- "site:ctftime.org",
3115
- "site:ctf.zeyu2001.com",
3116
- "site:github.com CTF writeup"
3117
- ],
3118
- vulnhub: [
3119
- "site:vulnhub.com",
3120
- '"VulnHub" writeup'
3121
- ]
3122
- };
3123
- var SCENARIO_KEYWORDS = {
3124
- ad: "Active Directory Kerberos LDAP BloodHound",
3125
- windows: "Windows privilege escalation PowerShell",
3126
- linux: "Linux privilege escalation GTFOBins SUID",
3127
- web: "web exploitation XSS SQLi SSTI RCE",
3128
- crypto: "cryptography RSA AES cipher",
3129
- forensics: "forensics memory dump volatility",
3130
- pwn: "binary exploitation buffer overflow ROP",
3131
- reversing: "reverse engineering IDA Ghidra",
3132
- misc: "miscellaneous steganography OSINT"
3133
- };
3134
- async function searchDuckDuckGo(query, options = {}) {
3135
- const { maxResults = 10, timeout = 3e4 } = options;
3136
- return new Promise((resolve) => {
3137
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
3138
- const proc = spawn("curl", [
3139
- "-s",
3140
- "-A",
3141
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
3142
- url
3143
- ], { timeout });
3144
- let stdout = "";
3145
- proc.stdout.on("data", (data) => {
3146
- stdout += data.toString();
3147
- });
3148
- proc.on("close", () => {
3149
- const results = [];
3150
- const resultRegex = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
3151
- const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([^<]+)<\/a>/g;
3152
- let match;
3153
- const urls = [];
3154
- const titles = [];
3155
- const snippets = [];
3156
- while ((match = resultRegex.exec(stdout)) !== null) {
3157
- urls.push(match[1]);
3158
- titles.push(match[2]);
3159
- }
3160
- while ((match = snippetRegex.exec(stdout)) !== null) {
3161
- snippets.push(match[1]);
3162
- }
3163
- for (let i = 0; i < Math.min(urls.length, maxResults); i++) {
3164
- results.push({
3165
- title: titles[i] || "",
3166
- url: urls[i] || "",
3167
- snippet: snippets[i] || ""
3168
- });
3169
- }
3170
- resolve(results);
3171
- });
3172
- proc.on("error", () => {
3173
- resolve([]);
3174
- });
3175
- });
3176
- }
3177
- async function searchCVE(query) {
3178
- return searchDuckDuckGo(`${query} site:cve.mitre.org OR site:nvd.nist.gov`);
3179
- }
3180
- async function searchExploits(query) {
3181
- return searchDuckDuckGo(`${query} site:exploit-db.com OR site:github.com exploit POC`);
3182
- }
3183
- async function searchWriteups(query, platform3 = "all") {
3184
- const sources = platform3 === "all" ? WRITEUP_SOURCES.general : WRITEUP_SOURCES[platform3] || WRITEUP_SOURCES.general;
3185
- const siteQuery = sources.join(" OR ");
3186
- return searchDuckDuckGo(`${query} writeup walkthrough ${siteQuery}`);
3187
- }
3188
- async function searchMachineWriteup(machineName) {
3189
- const [writeups, videos] = await Promise.all([
3190
- searchDuckDuckGo(`"${machineName}" HackTheBox OR TryHackMe writeup walkthrough site:0xdf.gitlab.io OR site:medium.com`),
3191
- searchDuckDuckGo(`"${machineName}" ippsec site:youtube.com OR site:ippsec.rocks`)
3192
- ]);
3193
- return { writeups, videos };
3102
+ // src/core/tools/tool-utils.ts
3103
+ import { exec, spawn } from "child_process";
3104
+ import { promisify } from "util";
3105
+ import * as fs from "fs/promises";
3106
+ import * as path from "path";
3107
+ var execAsync = promisify(exec);
3108
+ var SEARCHED_PACKAGES = {};
3109
+ var USE_DOCKER = process.env.PENTESTING_DOCKER === "1";
3110
+ var DOCKER_CONTAINER = process.env.PENTESTING_CONTAINER || "pentesting-tools";
3111
+ var autoInstallEnabled = true;
3112
+ async function commandExists(cmd) {
3113
+ try {
3114
+ await execAsync(`which ${cmd}`);
3115
+ return true;
3116
+ } catch {
3117
+ return false;
3118
+ }
3194
3119
  }
3195
- async function searchByScenario(query, scenario) {
3196
- const keywords = SCENARIO_KEYWORDS[scenario] || "";
3197
- return searchDuckDuckGo(`${query} ${keywords} writeup walkthrough exploit`);
3120
+ async function searchForAptPackage(toolName) {
3121
+ if (SEARCHED_PACKAGES[toolName]) return SEARCHED_PACKAGES[toolName];
3122
+ console.log(`
3123
+ \u{1F50D} Searching for apt package name for '${toolName}' on Kali Linux...`);
3124
+ try {
3125
+ const { searchGoogle, deepSearch } = await import("./web-search-XQYEM24B.js");
3126
+ const query = `apt install package name for ${toolName} on Kali Linux`;
3127
+ const searchResults = await deepSearch(query, { maxResults: 3 });
3128
+ for (const res of searchResults) {
3129
+ const content = res.content || res.snippet;
3130
+ const match = content.match(/apt(?:-get)?\s+install\s+(?:-y\s+)?([a-z0-9._-]+)/i);
3131
+ if (match && match[1]) {
3132
+ const pkg = match[1].toLowerCase();
3133
+ console.log(`\u2728 Found potential package: ${pkg}`);
3134
+ SEARCHED_PACKAGES[toolName] = pkg;
3135
+ return pkg;
3136
+ }
3137
+ }
3138
+ } catch (error) {
3139
+ console.error("Failed to search for package:", error);
3140
+ }
3141
+ return null;
3198
3142
  }
3199
- async function searchADWriteups(query) {
3200
- return searchDuckDuckGo(
3201
- `${query} Active Directory Kerberos LDAP BloodHound GetNPUsers AS-REP Roasting Kerberoasting DCSync Pass-the-Hash writeup`
3202
- );
3143
+ async function installTool(toolName, customPackage) {
3144
+ const packageName = customPackage || TOOL_TO_APT[toolName] || toolName;
3145
+ try {
3146
+ console.log(`
3147
+ \u{1F4E6} Installing ${toolName} (${packageName})...`);
3148
+ const { stdout, stderr } = await execAsync(
3149
+ `sudo apt update -qq && sudo apt install -y ${packageName}`,
3150
+ { timeout: 3e5 }
3151
+ // 5 minute timeout for installation
3152
+ );
3153
+ console.log(`\u2705 ${toolName} installed successfully
3154
+ `);
3155
+ return { success: true, output: stdout + stderr, duration: 0 };
3156
+ } catch (error) {
3157
+ const errorMessage = error instanceof Error ? error.message : String(error);
3158
+ console.log(`\u274C Failed to install ${toolName}: ${errorMessage}
3159
+ `);
3160
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3161
+ }
3203
3162
  }
3204
- async function searchLinuxPrivesc(query) {
3205
- return searchDuckDuckGo(
3206
- `${query} Linux privilege escalation SUID GTFOBins sudo capabilities cronjob kernel exploit writeup`
3207
- );
3163
+ async function ensureToolAvailable(toolName) {
3164
+ if (await commandExists(toolName)) {
3165
+ return true;
3166
+ }
3167
+ if (!autoInstallEnabled) {
3168
+ console.log(`\u26A0\uFE0F Tool '${toolName}' is missing but auto-install is disabled.`);
3169
+ return false;
3170
+ }
3171
+ let packageName = TOOL_TO_APT[toolName];
3172
+ if (!packageName) {
3173
+ packageName = await searchForAptPackage(toolName);
3174
+ }
3175
+ const result = await installTool(toolName, packageName);
3176
+ return result.success;
3208
3177
  }
3209
- async function searchWindowsPrivesc(query) {
3210
- return searchDuckDuckGo(
3211
- `${query} Windows privilege escalation SeImpersonate JuicyPotato PrintSpoofer token impersonation UAC bypass writeup`
3212
- );
3178
+ async function isDockerAvailable() {
3179
+ if (!USE_DOCKER) return false;
3180
+ try {
3181
+ const { stdout } = await execAsync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Running}}'`);
3182
+ return stdout.trim() === "true";
3183
+ } catch {
3184
+ return false;
3185
+ }
3213
3186
  }
3214
- async function securityResearch(query) {
3215
- const [general, cves, exploits, writeups] = await Promise.all([
3216
- searchDuckDuckGo(query),
3217
- searchCVE(query),
3218
- searchExploits(query),
3219
- searchWriteups(query)
3220
- ]);
3221
- return { general, cves, exploits, writeups };
3187
+ function wrapForDocker(command) {
3188
+ const cleanCmd = command.replace(/^sudo\s+/, "");
3189
+ return `docker exec ${DOCKER_CONTAINER} sh -c "${cleanCmd.replace(/"/g, '\\"')}"`;
3222
3190
  }
3223
- async function ctfResearch(boxName, scenario) {
3224
- const [machine, scenarioResults, exploits] = await Promise.all([
3225
- searchMachineWriteup(boxName),
3226
- scenario ? searchByScenario(boxName, scenario) : Promise.resolve([]),
3227
- searchExploits(boxName)
3228
- ]);
3229
- return { machine, scenario: scenarioResults, exploits };
3191
+ async function shouldUseDocker(command) {
3192
+ if (!USE_DOCKER) return false;
3193
+ return await isDockerAvailable();
3230
3194
  }
3231
-
3232
- // src/core/tools/tool-executor.ts
3233
- var execAsync = promisify(exec);
3234
- var DOCKER_IMAGE = "agnusdei1207/pentesting:latest";
3235
- var DOCKER_CONTAINER = process.env.PENTESTING_CONTAINER || "pentesting-tools";
3236
- var FORCE_DOCKER = process.env.PENTESTING_DOCKER === "1";
3237
- var dockerStarted = false;
3238
- var DOCKER_TOOLS = [
3239
- // Network scanning
3240
- TOOL_NAME.RUSTSCAN,
3241
- TOOL_NAME.NMAP_SCAN,
3242
- TOOL_NAME.MASSCAN,
3243
- TOOL_NAME.TCPDUMP_CAPTURE,
3244
- TOOL_NAME.PING,
3245
- TOOL_NAME.TRACEROUTE,
3246
- TOOL_NAME.MTR,
3247
- TOOL_NAME.NETCAT,
3248
- TOOL_NAME.TSHARK,
3249
- TOOL_NAME.NGREP,
3250
- TOOL_NAME.ARP_SCAN,
3251
- TOOL_NAME.SOCAT,
3252
- // DNS & Subdomain
3253
- TOOL_NAME.DIG,
3254
- TOOL_NAME.HOST,
3255
- TOOL_NAME.NSLOOKUP,
3256
- TOOL_NAME.WHOIS,
3257
- TOOL_NAME.SUBFINDER,
3258
- TOOL_NAME.AMASS,
3259
- TOOL_NAME.DNSENUM,
3260
- TOOL_NAME.DNSRECON,
3261
- TOOL_NAME.DNSMAP,
3262
- TOOL_NAME.ZONE_TRANSFER,
3263
- // Service Enumeration
3264
- TOOL_NAME.SNMP_WALK,
3265
- TOOL_NAME.SNMP_CHECK,
3266
- TOOL_NAME.ONESIXTYONE,
3267
- TOOL_NAME.FTP_ENUM,
3268
- TOOL_NAME.FTP_ANON,
3269
- TOOL_NAME.NBTSCAN,
3270
- TOOL_NAME.RPC_INFO,
3271
- TOOL_NAME.SHOWMOUNT,
3272
- TOOL_NAME.TELNET,
3273
- // Web tools
3274
- TOOL_NAME.FFUF,
3275
- TOOL_NAME.GOBUSTER,
3276
- TOOL_NAME.DIRB,
3277
- TOOL_NAME.FEROXBUSTER,
3278
- TOOL_NAME.WHATWEB,
3279
- TOOL_NAME.HTTPX,
3280
- TOOL_NAME.NUCLEI,
3281
- TOOL_NAME.NIKTO,
3282
- TOOL_NAME.DIRECTORY_BRUTEFORCE,
3283
- TOOL_NAME.SQL_INJECTION,
3284
- TOOL_NAME.WAYBACKURLS,
3285
- TOOL_NAME.WAFW00F,
3286
- TOOL_NAME.GOWITNESS,
3287
- // Windows/SMB/AD
3288
- TOOL_NAME.SMB_ENUM,
3289
- TOOL_NAME.SMBMAP,
3290
- TOOL_NAME.ENUM4LINUX,
3291
- TOOL_NAME.CRACKMAPEXEC,
3292
- TOOL_NAME.SMBCLIENT,
3293
- TOOL_NAME.RPCCLIENT,
3294
- TOOL_NAME.WINRM,
3295
- TOOL_NAME.RDP_CHECK,
3296
- TOOL_NAME.LDAP_SEARCH,
3297
- TOOL_NAME.KERBRUTE,
3298
- TOOL_NAME.BLOODHOUND,
3299
- // Database
3300
- TOOL_NAME.MSSQL_CLIENT,
3301
- TOOL_NAME.MYSQL_CLIENT,
3302
- TOOL_NAME.PSQL_CLIENT,
3303
- TOOL_NAME.REDIS_CLI,
3304
- TOOL_NAME.MONGO_CLIENT,
3305
- // Bruteforce & Credentials
3306
- TOOL_NAME.HYDRA,
3307
- TOOL_NAME.MEDUSA,
3308
- TOOL_NAME.BRUTEFORCE_LOGIN,
3309
- TOOL_NAME.CRACK_HASH,
3310
- TOOL_NAME.JOHN,
3311
- TOOL_NAME.HASHCAT,
3312
- TOOL_NAME.HASHID,
3313
- // Exploitation
3314
- TOOL_NAME.SEARCHSPLOIT,
3315
- TOOL_NAME.METASPLOIT,
3316
- TOOL_NAME.GENERATE_PAYLOAD,
3317
- TOOL_NAME.MSFVENOM,
3318
- // SSH & Tunneling
3319
- TOOL_NAME.SSH,
3320
- TOOL_NAME.CHISEL,
3321
- TOOL_NAME.PROXYCHAINS,
3322
- TOOL_NAME.SETUP_TUNNEL,
3323
- TOOL_NAME.LATERAL_MOVEMENT,
3324
- TOOL_NAME.REVERSE_SHELL,
3325
- TOOL_NAME.DUMP_CREDENTIALS,
3326
- // Listener & Payload
3327
- TOOL_NAME.NC_LISTENER,
3328
- TOOL_NAME.PYTHON_HTTP_SERVER,
3329
- TOOL_NAME.RLWRAP,
3330
- TOOL_NAME.PWNCAT,
3331
- // Impacket
3332
- TOOL_NAME.IMPACKET_SECRETSDUMP,
3333
- TOOL_NAME.IMPACKET_PSEXEC,
3334
- TOOL_NAME.IMPACKET_WMIEXEC,
3335
- TOOL_NAME.IMPACKET_SMBEXEC,
3336
- TOOL_NAME.IMPACKET_ATEXEC,
3337
- TOOL_NAME.IMPACKET_DCOMEXEC,
3338
- TOOL_NAME.IMPACKET_GETNPUSERS,
3339
- TOOL_NAME.IMPACKET_GETUSERSPNS,
3340
- // Forensics
3341
- TOOL_NAME.BINWALK,
3342
- TOOL_NAME.FOREMOST,
3343
- TOOL_NAME.STEGHIDE,
3344
- TOOL_NAME.EXIFTOOL,
3345
- // Reversing
3346
- TOOL_NAME.GDB,
3347
- TOOL_NAME.RADARE2,
3348
- // Privesc
3349
- TOOL_NAME.RUN_PRIVESC_ENUM,
3350
- TOOL_NAME.CHECK_SUDO,
3351
- TOOL_NAME.FIND_SUID
3352
- ];
3353
- async function ensureDockerContainer() {
3354
- if (dockerStarted) return true;
3195
+ async function executeBash(command, options = {}) {
3196
+ const startTime = Date.now();
3197
+ const timeout = options.timeout || 6e4;
3198
+ const resourceManager = getResourceManager();
3355
3199
  try {
3356
- const { stdout } = await execAsync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Running}}'`);
3357
- if (stdout.trim() === "true") {
3358
- dockerStarted = true;
3359
- return true;
3360
- }
3361
- await execAsync(`docker start ${DOCKER_CONTAINER}`);
3362
- dockerStarted = true;
3363
- console.log(`[Docker] Started container: ${DOCKER_CONTAINER}`);
3364
- return true;
3365
- } catch (err) {
3366
- const errorMsg = err.message || String(err);
3367
- if (errorMsg.includes("Cannot connect to the Docker daemon") || errorMsg.includes("docker.sock")) {
3368
- console.log("\n\u26A0\uFE0F Docker is not running!\n");
3369
- console.log("To use advanced security tools (nmap, rustscan, etc.), please:");
3370
- console.log(" 1. Install Docker: https://docs.docker.com/get-docker/");
3371
- console.log(" 2. Start Docker Desktop (macOS): open -a Docker");
3372
- console.log(" 3. Wait for Docker to fully start, then retry\n");
3373
- console.log("Falling back to local tools (curl, dig, etc.)...\n");
3374
- return false;
3200
+ let finalCommand = command;
3201
+ const useDocker = options.useDocker ?? await shouldUseDocker(command);
3202
+ if (useDocker) {
3203
+ finalCommand = wrapForDocker(command);
3204
+ } else {
3205
+ const baseCmd = command.trim().split(/\s+/)[0].replace(/^sudo\s+/, "");
3206
+ const aptPackage = TOOL_TO_APT[baseCmd];
3207
+ if (aptPackage && !await commandExists(baseCmd)) {
3208
+ const installed = await ensureToolAvailable(baseCmd);
3209
+ if (!installed) {
3210
+ return {
3211
+ success: false,
3212
+ output: "",
3213
+ error: `Tool '${baseCmd}' is not installed and auto-install failed. Run: sudo apt install ${aptPackage}`,
3214
+ duration: 0
3215
+ };
3216
+ }
3217
+ }
3375
3218
  }
3376
- try {
3377
- console.log(`[Docker] Pulling and starting ${DOCKER_IMAGE}...`);
3378
- await execAsync(`docker run -d --name ${DOCKER_CONTAINER} --network host ${DOCKER_IMAGE}`, {
3379
- timeout: 12e4
3380
- // 2 min timeout for pull
3219
+ if (options.background) {
3220
+ const child = spawn("/bin/bash", ["-c", finalCommand], {
3221
+ detached: true,
3222
+ stdio: "ignore",
3223
+ env: { ...process.env, LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" }
3381
3224
  });
3382
- dockerStarted = true;
3383
- console.log(`[Docker] Container ready: ${DOCKER_CONTAINER}`);
3384
- return true;
3385
- } catch (createErr) {
3386
- const createMsg = createErr.message || "";
3387
- if (createMsg.includes("Cannot connect to the Docker daemon")) {
3388
- console.log("\n\u26A0\uFE0F Docker is not running! Start it first:\n");
3389
- console.log(" macOS: open -a Docker");
3390
- console.log(" Linux: sudo systemctl start docker");
3391
- console.log(" Windows: Start Docker Desktop\n");
3392
- } else {
3393
- console.error(`[Docker] Failed to start container: ${createErr}`);
3225
+ if (child.pid) {
3226
+ resourceManager.trackProcess(child.pid);
3227
+ child.on("exit", () => {
3228
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3229
+ });
3230
+ child.on("error", () => {
3231
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3232
+ });
3394
3233
  }
3395
- return false;
3234
+ child.unref();
3235
+ return { success: true, output: `[Background] Command started with PID ${child.pid}`, duration: 0 };
3396
3236
  }
3237
+ const promise = new Promise((resolve, reject) => {
3238
+ const child = exec(finalCommand, {
3239
+ timeout,
3240
+ maxBuffer: 50 * 1024 * 1024,
3241
+ shell: "/bin/bash",
3242
+ encoding: "utf8",
3243
+ env: { ...process.env, LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" }
3244
+ }, (error, stdout2, stderr2) => {
3245
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3246
+ if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
3247
+ else resolve({ stdout: stdout2, stderr: stderr2 });
3248
+ });
3249
+ if (child.pid) resourceManager.trackProcess(child.pid);
3250
+ });
3251
+ const { stdout, stderr } = await promise;
3252
+ return {
3253
+ success: true,
3254
+ output: (useDocker ? "[Docker] " : "") + stdout + (stderr ? `
3255
+ [STDERR]
3256
+ ${stderr}` : ""),
3257
+ duration: Math.round((Date.now() - startTime) / 1e3)
3258
+ };
3259
+ } catch (error) {
3260
+ const err = error;
3261
+ if (err.message?.includes("No such container")) {
3262
+ return { success: false, output: "", error: `Docker container '${DOCKER_CONTAINER}' not running.`, duration: 0 };
3263
+ }
3264
+ return { success: false, output: err.stdout || "", error: err.stderr || err.message, exitCode: err.code, duration: 0 };
3397
3265
  }
3398
3266
  }
3399
- async function isDockerAvailable() {
3400
- return await ensureDockerContainer();
3267
+ var _currentTarget = null;
3268
+ var _targetListeners = [];
3269
+ function onTargetChange(listener) {
3270
+ _targetListeners.push(listener);
3401
3271
  }
3402
- async function commandExists(cmd) {
3272
+ async function setTarget(target) {
3403
3273
  try {
3404
- await execAsync(`which ${cmd}`);
3405
- return true;
3406
- } catch {
3407
- return false;
3274
+ _currentTarget = target;
3275
+ _targetListeners.forEach((listener) => {
3276
+ try {
3277
+ listener(target);
3278
+ } catch (e) {
3279
+ }
3280
+ });
3281
+ return { success: true, output: `\u{1F3AF} Target set: ${target}
3282
+
3283
+ Now beginning reconnaissance...`, duration: 0 };
3284
+ } catch (error) {
3285
+ const errorMessage = error instanceof Error ? error.message : String(error);
3286
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3408
3287
  }
3409
3288
  }
3410
- async function shouldUseDocker(command) {
3411
- if (FORCE_DOCKER) return true;
3412
- const baseCmd = command.trim().split(/\s+/)[0].replace(/^sudo\s+/, "");
3413
- if (!DOCKER_TOOLS.some((tool) => baseCmd.includes(tool))) {
3414
- return false;
3289
+ async function readFile2(filePath, startLine, endLine) {
3290
+ try {
3291
+ const content = await fs.readFile(filePath, "utf-8");
3292
+ let output = content;
3293
+ if (startLine || endLine) {
3294
+ const lines = content.split("\n");
3295
+ output = lines.slice((startLine || 1) - 1, endLine || lines.length).join("\n");
3296
+ }
3297
+ return { success: true, output, duration: 0 };
3298
+ } catch (error) {
3299
+ const errorMessage = error instanceof Error ? error.message : String(error);
3300
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3415
3301
  }
3416
- if (await commandExists(baseCmd)) {
3417
- return false;
3302
+ }
3303
+ async function writeFile2(filePath, content, overwrite = false) {
3304
+ try {
3305
+ const exists = await fs.access(filePath).then(() => true).catch(() => false);
3306
+ if (exists && !overwrite) {
3307
+ return { success: false, output: "", error: "File exists. Set overwrite: true to replace.", duration: 0 };
3308
+ }
3309
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
3310
+ await fs.writeFile(filePath, content, "utf-8");
3311
+ return { success: true, output: `Written to ${filePath}`, duration: 0 };
3312
+ } catch (error) {
3313
+ const errorMessage = error instanceof Error ? error.message : String(error);
3314
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3418
3315
  }
3419
- return await isDockerAvailable();
3420
3316
  }
3421
- function wrapForDocker(command) {
3422
- const cleanCmd = command.replace(/^sudo\s+/, "");
3423
- return `docker exec ${DOCKER_CONTAINER} sh -c "${cleanCmd.replace(/"/g, '\\"')}"`;
3317
+ async function listDirectory(dirPath, recursive = false, hidden = false) {
3318
+ try {
3319
+ const flags = `-l${hidden ? "a" : ""}${recursive ? "R" : ""}`;
3320
+ const { stdout } = await execAsync(`ls ${flags} "${dirPath}"`);
3321
+ return { success: true, output: stdout, duration: 0 };
3322
+ } catch (error) {
3323
+ const errorMessage = error instanceof Error ? error.message : String(error);
3324
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3325
+ }
3424
3326
  }
3327
+
3328
+ // src/core/tools/tool-executor.ts
3329
+ import * as fs2 from "fs/promises";
3425
3330
  async function executeToolCall(toolName, input) {
3426
3331
  const startTime = Date.now();
3427
3332
  const resourceManager = getResourceManager();
@@ -3796,28 +3701,28 @@ async function executeToolCall(toolName, input) {
3796
3701
  result = await takeScreenshot(input);
3797
3702
  break;
3798
3703
  // Research & Writeup Tools
3799
- case "search_writeups":
3704
+ case TOOL_NAME.SEARCH_WRITEUPS:
3800
3705
  result = await executeSearchWriteups(input);
3801
3706
  break;
3802
- case "search_machine":
3707
+ case TOOL_NAME.SEARCH_MACHINE:
3803
3708
  result = await executeSearchMachine(input);
3804
3709
  break;
3805
- case "search_by_scenario":
3710
+ case TOOL_NAME.SEARCH_BY_SCENARIO:
3806
3711
  result = await executeSearchByScenario(input);
3807
3712
  break;
3808
- case "search_ad_writeups":
3713
+ case TOOL_NAME.SEARCH_AD_WRITEUPS:
3809
3714
  result = await executeSearchADWriteups(input);
3810
3715
  break;
3811
- case "search_linux_privesc":
3716
+ case TOOL_NAME.SEARCH_LINUX_PRIVESC:
3812
3717
  result = await executeSearchLinuxPrivesc(input);
3813
3718
  break;
3814
- case "search_windows_privesc":
3719
+ case TOOL_NAME.SEARCH_WINDOWS_PRIVESC:
3815
3720
  result = await executeSearchWindowsPrivesc(input);
3816
3721
  break;
3817
- case "ctf_research":
3722
+ case TOOL_NAME.CTF_RESEARCH:
3818
3723
  result = await executeCtfResearch(input);
3819
3724
  break;
3820
- case "security_research":
3725
+ case TOOL_NAME.SECURITY_RESEARCH:
3821
3726
  result = await executeSecurityResearch(input);
3822
3727
  break;
3823
3728
  default:
@@ -3827,175 +3732,19 @@ async function executeToolCall(toolName, input) {
3827
3732
  error: `Unknown tool: ${toolName}`,
3828
3733
  duration: Date.now() - startTime
3829
3734
  };
3830
- }
3831
- result.duration = Date.now() - startTime;
3832
- return result;
3833
- } catch (error) {
3834
- return {
3835
- success: false,
3836
- output: "",
3837
- error: error.message || String(error),
3838
- duration: Date.now() - startTime
3839
- };
3840
- } finally {
3841
- resourceManager.trackJobEnd();
3842
- }
3843
- }
3844
- async function executeBash(command, options = {}) {
3845
- const startTime = Date.now();
3846
- const timeout = options.timeout || 6e4;
3847
- const resourceManager = getResourceManager();
3848
- try {
3849
- let finalCommand = command;
3850
- const useDocker = options.useDocker ?? await shouldUseDocker(command);
3851
- if (useDocker) {
3852
- finalCommand = wrapForDocker(command);
3853
- console.log(`[Docker] Executing: ${finalCommand}`);
3854
- }
3855
- if (options.background) {
3856
- const child = spawn2("/bin/bash", ["-c", finalCommand], {
3857
- detached: true,
3858
- stdio: "ignore",
3859
- env: {
3860
- ...process.env,
3861
- LANG: "en_US.UTF-8",
3862
- LC_ALL: "en_US.UTF-8"
3863
- }
3864
- });
3865
- if (child.pid) {
3866
- resourceManager.trackProcess(child.pid);
3867
- child.on("exit", () => {
3868
- if (child.pid) resourceManager.untrackProcess(child.pid);
3869
- });
3870
- child.on("error", () => {
3871
- if (child.pid) resourceManager.untrackProcess(child.pid);
3872
- });
3873
- }
3874
- child.unref();
3875
- return {
3876
- success: true,
3877
- output: `[Background] Command started with PID ${child.pid}`,
3878
- duration: 0
3879
- };
3880
- }
3881
- const promise = new Promise((resolve, reject) => {
3882
- const child = exec(finalCommand, {
3883
- timeout,
3884
- maxBuffer: 50 * 1024 * 1024,
3885
- // 50MB
3886
- shell: "/bin/bash",
3887
- encoding: "utf8",
3888
- env: {
3889
- ...process.env,
3890
- LANG: "en_US.UTF-8",
3891
- LC_ALL: "en_US.UTF-8"
3892
- }
3893
- }, (error, stdout2, stderr2) => {
3894
- if (child.pid) resourceManager.untrackProcess(child.pid);
3895
- if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
3896
- else resolve({ stdout: stdout2, stderr: stderr2 });
3897
- });
3898
- if (child.pid) {
3899
- resourceManager.trackProcess(child.pid);
3900
- }
3901
- });
3902
- const { stdout, stderr } = await promise;
3903
- return {
3904
- success: true,
3905
- output: (useDocker ? "[Docker] " : "") + stdout + (stderr ? `
3906
- [STDERR]
3907
- ${stderr}` : ""),
3908
- duration: Math.round((Date.now() - startTime) / 1e3)
3909
- };
3910
- } catch (error) {
3911
- if (error.message?.includes("No such container")) {
3912
- return {
3913
- success: false,
3914
- output: "",
3915
- error: `Docker container '${DOCKER_CONTAINER}' not running. Start it with: cd docker && docker-compose up -d`,
3916
- duration: 0
3917
- };
3918
- }
3919
- return {
3920
- success: false,
3921
- output: error.stdout || "",
3922
- error: error.stderr || error.message,
3923
- exitCode: error.code,
3924
- duration: 0
3925
- };
3926
- }
3927
- }
3928
- var _currentTarget = null;
3929
- var _targetListeners = [];
3930
- function onTargetChange(listener) {
3931
- _targetListeners.push(listener);
3932
- }
3933
- async function setTarget(target) {
3934
- try {
3935
- _currentTarget = target;
3936
- _targetListeners.forEach((listener) => {
3937
- try {
3938
- listener(target);
3939
- } catch (e) {
3940
- console.error("Target listener error:", e);
3941
- }
3942
- });
3943
- return {
3944
- success: true,
3945
- output: `\u{1F3AF} Target set: ${target}
3946
-
3947
- Now beginning reconnaissance...`,
3948
- duration: 0
3949
- };
3735
+ }
3736
+ result.duration = Date.now() - startTime;
3737
+ return result;
3950
3738
  } catch (error) {
3739
+ const errMsg = error instanceof Error ? error.message : String(error);
3951
3740
  return {
3952
3741
  success: false,
3953
3742
  output: "",
3954
- error: error.message || String(error),
3955
- duration: 0
3743
+ error: errMsg,
3744
+ duration: Date.now() - startTime
3956
3745
  };
3957
- }
3958
- }
3959
- async function readFile2(filePath, startLine, endLine) {
3960
- try {
3961
- const content = await fs.readFile(filePath, "utf-8");
3962
- let output = content;
3963
- if (startLine || endLine) {
3964
- const lines = content.split("\n");
3965
- const start = (startLine || 1) - 1;
3966
- const end = endLine || lines.length;
3967
- output = lines.slice(start, end).join("\n");
3968
- }
3969
- return { success: true, output, duration: 0 };
3970
- } catch (error) {
3971
- return { success: false, output: "", error: error.message, duration: 0 };
3972
- }
3973
- }
3974
- async function writeFile2(filePath, content, overwrite = false) {
3975
- try {
3976
- const exists = await fs.access(filePath).then(() => true).catch(() => false);
3977
- if (exists && !overwrite) {
3978
- return {
3979
- success: false,
3980
- output: "",
3981
- error: "File exists. Set overwrite: true to replace.",
3982
- duration: 0
3983
- };
3984
- }
3985
- await fs.mkdir(path.dirname(filePath), { recursive: true });
3986
- await fs.writeFile(filePath, content, "utf-8");
3987
- return { success: true, output: `Written to ${filePath}`, duration: 0 };
3988
- } catch (error) {
3989
- return { success: false, output: "", error: error.message, duration: 0 };
3990
- }
3991
- }
3992
- async function listDirectory(dirPath, recursive = false, hidden = false) {
3993
- try {
3994
- const flags = `-l${hidden ? "a" : ""}${recursive ? "R" : ""}`;
3995
- const { stdout } = await execAsync(`ls ${flags} "${dirPath}"`);
3996
- return { success: true, output: stdout, duration: 0 };
3997
- } catch (error) {
3998
- return { success: false, output: "", error: error.message, duration: 0 };
3746
+ } finally {
3747
+ resourceManager.trackJobEnd();
3999
3748
  }
4000
3749
  }
4001
3750
  async function executeRustscan(input) {
@@ -4134,7 +3883,7 @@ const { chromium } = require('playwright');
4134
3883
  })();
4135
3884
  `;
4136
3885
  const scriptPath = "/tmp/playwright_script.js";
4137
- await fs.writeFile(scriptPath, playwrightScript);
3886
+ await fs2.writeFile(scriptPath, playwrightScript);
4138
3887
  return executeBash(`node ${scriptPath}`, { timeout: 6e4 });
4139
3888
  }
4140
3889
  async function executeSearchsploit(input) {
@@ -4306,12 +4055,12 @@ async function reportFinding(input) {
4306
4055
  const findingsPath = "/tmp/hacker-code-findings.json";
4307
4056
  let findings = [];
4308
4057
  try {
4309
- const existing = await fs.readFile(findingsPath, "utf-8");
4058
+ const existing = await fs2.readFile(findingsPath, "utf-8");
4310
4059
  findings = JSON.parse(existing);
4311
4060
  } catch {
4312
4061
  }
4313
4062
  findings.push(finding);
4314
- await fs.writeFile(findingsPath, JSON.stringify(findings, null, 2));
4063
+ await fs2.writeFile(findingsPath, JSON.stringify(findings, null, 2));
4315
4064
  return {
4316
4065
  success: true,
4317
4066
  output: `Finding recorded: [${severity?.toString().toUpperCase()}] ${title}`,
@@ -4332,7 +4081,7 @@ const { chromium } = require('playwright');
4332
4081
  console.log('Screenshot saved');
4333
4082
  })();
4334
4083
  `;
4335
- await fs.writeFile("/tmp/screenshot.js", script);
4084
+ await fs2.writeFile("/tmp/screenshot.js", script);
4336
4085
  return executeBash("node /tmp/screenshot.js");
4337
4086
  }
4338
4087
  return executeBash(`script -q /dev/null -c "cat" | tee "${filename || "terminal.txt"}"`);
@@ -4930,14 +4679,14 @@ async function executeSearchWriteups(input) {
4930
4679
  const startTime = Date.now();
4931
4680
  try {
4932
4681
  const query = String(input.query || "");
4933
- const platform3 = input.platform || "all";
4682
+ const platform3 = input.platform || "general";
4934
4683
  const results = await searchWriteups(query, platform3);
4935
4684
  const output = results.length > 0 ? results.map((r, i) => `[${i + 1}] ${r.title}
4936
4685
  URL: ${r.url}
4937
4686
  ${r.snippet}`).join("\n\n") : "No writeups found.";
4938
4687
  return { success: true, output, duration: Date.now() - startTime };
4939
4688
  } catch (error) {
4940
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4689
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4941
4690
  }
4942
4691
  }
4943
4692
  async function executeSearchMachine(input) {
@@ -4957,7 +4706,7 @@ async function executeSearchMachine(input) {
4957
4706
  ${r.url}`).join("\n") : "No videos found.";
4958
4707
  return { success: true, output, duration: Date.now() - startTime };
4959
4708
  } catch (error) {
4960
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4709
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4961
4710
  }
4962
4711
  }
4963
4712
  async function executeSearchByScenario(input) {
@@ -4971,7 +4720,7 @@ async function executeSearchByScenario(input) {
4971
4720
  ${r.snippet}`).join("\n\n") : `No ${scenario} writeups found.`;
4972
4721
  return { success: true, output, duration: Date.now() - startTime };
4973
4722
  } catch (error) {
4974
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4723
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4975
4724
  }
4976
4725
  }
4977
4726
  async function executeSearchADWriteups(input) {
@@ -4984,7 +4733,7 @@ async function executeSearchADWriteups(input) {
4984
4733
  ${r.snippet}`).join("\n\n") : "No AD writeups found.";
4985
4734
  return { success: true, output, duration: Date.now() - startTime };
4986
4735
  } catch (error) {
4987
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4736
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4988
4737
  }
4989
4738
  }
4990
4739
  async function executeSearchLinuxPrivesc(input) {
@@ -4997,7 +4746,7 @@ async function executeSearchLinuxPrivesc(input) {
4997
4746
  ${r.snippet}`).join("\n\n") : "No Linux privesc writeups found.";
4998
4747
  return { success: true, output, duration: Date.now() - startTime };
4999
4748
  } catch (error) {
5000
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4749
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5001
4750
  }
5002
4751
  }
5003
4752
  async function executeSearchWindowsPrivesc(input) {
@@ -5010,7 +4759,7 @@ async function executeSearchWindowsPrivesc(input) {
5010
4759
  ${r.snippet}`).join("\n\n") : "No Windows privesc writeups found.";
5011
4760
  return { success: true, output, duration: Date.now() - startTime };
5012
4761
  } catch (error) {
5013
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4762
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5014
4763
  }
5015
4764
  }
5016
4765
  async function executeCtfResearch(input) {
@@ -5041,7 +4790,7 @@ async function executeCtfResearch(input) {
5041
4790
  ${r.url}`).join("\n") : "No exploits found.";
5042
4791
  return { success: true, output, duration: Date.now() - startTime };
5043
4792
  } catch (error) {
5044
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4793
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5045
4794
  }
5046
4795
  }
5047
4796
  async function executeSecurityResearch(input) {
@@ -5066,7 +4815,7 @@ async function executeSecurityResearch(input) {
5066
4815
  ${r.url}`).join("\n") : "No writeups found.";
5067
4816
  return { success: true, output, duration: Date.now() - startTime };
5068
4817
  } catch (error) {
5069
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4818
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5070
4819
  }
5071
4820
  }
5072
4821
 
@@ -6939,12 +6688,14 @@ EFFORT: [low/medium/high]`;
6939
6688
  for (const section of sections.slice(1)) {
6940
6689
  const match = section.match(/(\w+)\s+DESCRIPTION:\s*(.+)\s+REASONING:\s*(.+)\s+IMPACT:\s*([\d.]+)\s+EFFORT:\s*(\w+)/is);
6941
6690
  if (match) {
6691
+ const suggestionType = match[1].toLowerCase().trim();
6692
+ const suggestionEffort = match[5].toLowerCase().trim();
6942
6693
  suggestions.push({
6943
- type: match[1],
6694
+ type: suggestionType,
6944
6695
  description: match[2].trim(),
6945
6696
  reasoning: match[3].trim(),
6946
6697
  estimatedImpact: parseFloat(match[4]) || 0.5,
6947
- effort: match[5].trim()
6698
+ effort: suggestionEffort
6948
6699
  });
6949
6700
  }
6950
6701
  }
@@ -7483,7 +7234,7 @@ import { v4 as uuidv46 } from "uuid";
7483
7234
  import { EventEmitter as EventEmitter8 } from "events";
7484
7235
  import { v4 as uuidv45 } from "uuid";
7485
7236
  import { createHash } from "crypto";
7486
- import { promises as fs2 } from "fs";
7237
+ import { promises as fs3 } from "fs";
7487
7238
  import { join } from "path";
7488
7239
  import { homedir } from "os";
7489
7240
  var AUDIT_EVENT = {
@@ -7509,7 +7260,7 @@ var AuditLogger = class extends EventEmitter8 {
7509
7260
  async initialize() {
7510
7261
  if (this.initialized) return;
7511
7262
  try {
7512
- await fs2.mkdir(this.logDir, { recursive: true });
7263
+ await fs3.mkdir(this.logDir, { recursive: true });
7513
7264
  this.initialized = true;
7514
7265
  } catch (error) {
7515
7266
  console.error("[AuditLogger] Failed to initialize:", error);
@@ -7606,19 +7357,19 @@ var AuditLogger = class extends EventEmitter8 {
7606
7357
  async pruneDiskLogs(maxFiles = 1e3) {
7607
7358
  if (!this.initialized) await this.initialize();
7608
7359
  try {
7609
- const files = await fs2.readdir(this.logDir);
7360
+ const files = await fs3.readdir(this.logDir);
7610
7361
  if (files.length <= maxFiles) return 0;
7611
7362
  const fileStats = await Promise.all(
7612
7363
  files.map(async (file) => {
7613
7364
  const filePath = join(this.logDir, file);
7614
- const stats = await fs2.stat(filePath);
7365
+ const stats = await fs3.stat(filePath);
7615
7366
  return { file, time: stats.mtimeMs, path: filePath };
7616
7367
  })
7617
7368
  );
7618
7369
  fileStats.sort((a, b) => a.time - b.time);
7619
7370
  const toDelete = fileStats.slice(0, files.length - maxFiles);
7620
7371
  for (const item of toDelete) {
7621
- await fs2.unlink(item.path);
7372
+ await fs3.unlink(item.path);
7622
7373
  }
7623
7374
  return toDelete.length;
7624
7375
  } catch (error) {
@@ -7689,7 +7440,7 @@ var AuditLogger = class extends EventEmitter8 {
7689
7440
  this.logDir,
7690
7441
  `audit_${this.sessionId}_${Date.now()}.json`
7691
7442
  );
7692
- await fs2.writeFile(
7443
+ await fs3.writeFile(
7693
7444
  exportPath,
7694
7445
  JSON.stringify({
7695
7446
  sessionId: this.sessionId,
@@ -7750,7 +7501,7 @@ var AuditLogger = class extends EventEmitter8 {
7750
7501
  try {
7751
7502
  const filename = `${entry.timestamp.replace(/[:.]/g, "-")}_${entry.id}.json`;
7752
7503
  const filepath = join(this.logDir, filename);
7753
- await fs2.writeFile(filepath, JSON.stringify(entry, null, 2));
7504
+ await fs3.writeFile(filepath, JSON.stringify(entry, null, 2));
7754
7505
  } catch (error) {
7755
7506
  console.error("[AuditLogger] Failed to persist entry:", error);
7756
7507
  }
@@ -7771,7 +7522,7 @@ var AuditLogger = class extends EventEmitter8 {
7771
7522
  try {
7772
7523
  const filename = `system_${entry.timestamp.replace(/[:.]/g, "-")}_${entry.id}.json`;
7773
7524
  const filepath = join(this.logDir, filename);
7774
- await fs2.writeFile(filepath, JSON.stringify(entry, null, 2));
7525
+ await fs3.writeFile(filepath, JSON.stringify(entry, null, 2));
7775
7526
  } catch (error) {
7776
7527
  console.error("[AuditLogger] Failed to persist system event:", error);
7777
7528
  }
@@ -10139,7 +9890,7 @@ Constraints: ${context.constraints.join(", ")}`;
10139
9890
  // src/core/agent/learning/self-reflection.ts
10140
9891
  import { EventEmitter as EventEmitter12 } from "events";
10141
9892
  import { v4 as uuidv410 } from "uuid";
10142
- import * as fs3 from "fs/promises";
9893
+ import * as fs4 from "fs/promises";
10143
9894
  import * as path2 from "path";
10144
9895
  var SelfReflectionSystem = class extends EventEmitter12 {
10145
9896
  llm;
@@ -10393,10 +10144,10 @@ CONFIDENCE: [0.0-1.0]`;
10393
10144
  */
10394
10145
  async persistExperience(experience) {
10395
10146
  try {
10396
- await fs3.mkdir(this.dataDir, { recursive: true });
10147
+ await fs4.mkdir(this.dataDir, { recursive: true });
10397
10148
  const filename = `${experience.id}.json`;
10398
10149
  const filepath = path2.join(this.dataDir, filename);
10399
- await fs3.writeFile(filepath, JSON.stringify(experience, null, 2));
10150
+ await fs4.writeFile(filepath, JSON.stringify(experience, null, 2));
10400
10151
  } catch (error) {
10401
10152
  this.emit("persist_error", error);
10402
10153
  }
@@ -10406,13 +10157,13 @@ CONFIDENCE: [0.0-1.0]`;
10406
10157
  */
10407
10158
  async loadExperiences() {
10408
10159
  try {
10409
- await fs3.mkdir(this.dataDir, { recursive: true });
10410
- const files = await fs3.readdir(this.dataDir);
10160
+ await fs4.mkdir(this.dataDir, { recursive: true });
10161
+ const files = await fs4.readdir(this.dataDir);
10411
10162
  for (const file of files) {
10412
10163
  if (file.endsWith(".json")) {
10413
10164
  const filepath = path2.join(this.dataDir, file);
10414
10165
  try {
10415
- const content = await fs3.readFile(filepath, "utf-8");
10166
+ const content = await fs4.readFile(filepath, "utf-8");
10416
10167
  const experience = JSON.parse(content);
10417
10168
  this.experiences.push(experience);
10418
10169
  for (const lesson of experience.lessons) {
@@ -10469,7 +10220,7 @@ CONFIDENCE: [0.0-1.0]`;
10469
10220
 
10470
10221
  // src/core/agent/background/job-manager.ts
10471
10222
  import { EventEmitter as EventEmitter13 } from "events";
10472
- import { spawn as spawn3 } from "child_process";
10223
+ import { spawn as spawn2 } from "child_process";
10473
10224
  import { v4 as uuidv411 } from "uuid";
10474
10225
  var BackgroundJobManager = class extends EventEmitter13 {
10475
10226
  jobs = /* @__PURE__ */ new Map();
@@ -10512,7 +10263,7 @@ var BackgroundJobManager = class extends EventEmitter13 {
10512
10263
  this.jobs.set(job.id, job);
10513
10264
  this.incrementResource(config.type);
10514
10265
  try {
10515
- const process2 = spawn3(config.command, config.args || [], {
10266
+ const process2 = spawn2(config.command, config.args || [], {
10516
10267
  cwd: config.cwd,
10517
10268
  shell: true,
10518
10269
  detached: false,
@@ -10900,7 +10651,7 @@ var backgroundJobManager = new BackgroundJobManager();
10900
10651
  // src/core/agent/session/checkpoint-manager.ts
10901
10652
  import { EventEmitter as EventEmitter14 } from "events";
10902
10653
  import { v4 as uuidv412 } from "uuid";
10903
- import * as fs4 from "fs/promises";
10654
+ import * as fs5 from "fs/promises";
10904
10655
  import * as path3 from "path";
10905
10656
  var SessionManager = class extends EventEmitter14 {
10906
10657
  checkpoints = /* @__PURE__ */ new Map();
@@ -11089,10 +10840,10 @@ var SessionManager = class extends EventEmitter14 {
11089
10840
  async persistCheckpoint(checkpoint) {
11090
10841
  try {
11091
10842
  const sessionDir = path3.join(this.checkpointDir, checkpoint.sessionId);
11092
- await fs4.mkdir(sessionDir, { recursive: true });
10843
+ await fs5.mkdir(sessionDir, { recursive: true });
11093
10844
  const filename = `${checkpoint.id}.json`;
11094
10845
  const filepath = path3.join(sessionDir, filename);
11095
- await fs4.writeFile(filepath, JSON.stringify(checkpoint, null, 2));
10846
+ await fs5.writeFile(filepath, JSON.stringify(checkpoint, null, 2));
11096
10847
  } catch (error) {
11097
10848
  this.emit("persist_error", error);
11098
10849
  }
@@ -11103,17 +10854,17 @@ var SessionManager = class extends EventEmitter14 {
11103
10854
  async loadCheckpoints(sessionId) {
11104
10855
  const checkpoints = [];
11105
10856
  try {
11106
- await fs4.mkdir(this.checkpointDir, { recursive: true });
11107
- const sessions = await fs4.readdir(this.checkpointDir);
10857
+ await fs5.mkdir(this.checkpointDir, { recursive: true });
10858
+ const sessions = await fs5.readdir(this.checkpointDir);
11108
10859
  const targetSessions = sessionId ? [sessionId] : sessions;
11109
10860
  for (const session of targetSessions) {
11110
10861
  const sessionDir = path3.join(this.checkpointDir, session);
11111
10862
  try {
11112
- const files = await fs4.readdir(sessionDir);
10863
+ const files = await fs5.readdir(sessionDir);
11113
10864
  for (const file of files) {
11114
10865
  if (file.endsWith(".json")) {
11115
10866
  const filepath = path3.join(sessionDir, file);
11116
- const content = await fs4.readFile(filepath, "utf-8");
10867
+ const content = await fs5.readFile(filepath, "utf-8");
11117
10868
  try {
11118
10869
  const checkpoint = JSON.parse(content);
11119
10870
  checkpoints.push(checkpoint);
@@ -11152,7 +10903,7 @@ var SessionManager = class extends EventEmitter14 {
11152
10903
  checkpoint.sessionId,
11153
10904
  `${checkpointId}.json`
11154
10905
  );
11155
- await fs4.unlink(filepath);
10906
+ await fs5.unlink(filepath);
11156
10907
  this.checkpoints.delete(checkpointId);
11157
10908
  return true;
11158
10909
  } catch {
@@ -11394,8 +11145,8 @@ var ContextManager = class {
11394
11145
  };
11395
11146
 
11396
11147
  // src/core/hooks/hook-executor.ts
11397
- import { spawn as spawn4 } from "child_process";
11398
- import * as fs5 from "fs/promises";
11148
+ import { spawn as spawn3 } from "child_process";
11149
+ import * as fs6 from "fs/promises";
11399
11150
  import * as path4 from "path";
11400
11151
  import { EventEmitter as EventEmitter15 } from "events";
11401
11152
  var HookExecutor = class extends EventEmitter15 {
@@ -11410,7 +11161,7 @@ var HookExecutor = class extends EventEmitter15 {
11410
11161
  async initialize() {
11411
11162
  try {
11412
11163
  const configPath = path4.join(this.hooksDir, "hooks.json");
11413
- const configContent = await fs5.readFile(configPath, "utf-8");
11164
+ const configContent = await fs6.readFile(configPath, "utf-8");
11414
11165
  this.hooksConfig = JSON.parse(configContent);
11415
11166
  this.initialized = true;
11416
11167
  this.emit("initialized", { hooks: Object.keys(this.hooksConfig?.hooks || {}) });
@@ -11452,7 +11203,7 @@ var HookExecutor = class extends EventEmitter15 {
11452
11203
  HOOK_PHASE: context.phase || "",
11453
11204
  HOOK_TARGET: context.target || ""
11454
11205
  };
11455
- const proc = spawn4("bash", ["-c", command], {
11206
+ const proc = spawn3("bash", ["-c", command], {
11456
11207
  env,
11457
11208
  cwd: this.hooksDir,
11458
11209
  timeout
@@ -11524,205 +11275,8 @@ function getHookExecutor() {
11524
11275
  return hookExecutor;
11525
11276
  }
11526
11277
 
11527
- // src/mcp/mcp-client.ts
11528
- import { spawn as spawn5 } from "child_process";
11529
- import { EventEmitter as EventEmitter16 } from "events";
11530
- import * as readline from "readline";
11531
- var MCPClient = class extends EventEmitter16 {
11532
- process = null;
11533
- requestId = 0;
11534
- pendingRequests = /* @__PURE__ */ new Map();
11535
- serverName;
11536
- config;
11537
- connected = false;
11538
- constructor(serverName, config) {
11539
- super();
11540
- this.serverName = serverName;
11541
- this.config = config;
11542
- }
11543
- // Start MCP server
11544
- async connect() {
11545
- return new Promise((resolve, reject) => {
11546
- const { command, args, env } = this.config;
11547
- this.process = spawn5(command, args || [], {
11548
- env: { ...process.env, ...env },
11549
- stdio: ["pipe", "pipe", "pipe"]
11550
- });
11551
- if (this.process.stdout) {
11552
- const rl = readline.createInterface({
11553
- input: this.process.stdout,
11554
- crlfDelay: Infinity
11555
- });
11556
- rl.on("line", (line) => {
11557
- try {
11558
- const response = JSON.parse(line);
11559
- this.handleResponse(response);
11560
- } catch {
11561
- }
11562
- });
11563
- }
11564
- if (this.process.stderr) {
11565
- this.process.stderr.on("data", (data) => {
11566
- this.emit("stderr", data.toString());
11567
- });
11568
- }
11569
- this.process.on("error", (error) => {
11570
- this.emit("error", error);
11571
- reject(error);
11572
- });
11573
- this.process.on("spawn", () => {
11574
- this.connected = true;
11575
- this.emit("connected", { server: this.serverName });
11576
- resolve();
11577
- });
11578
- this.process.on("close", (code) => {
11579
- this.connected = false;
11580
- this.emit("disconnected", { server: this.serverName, code });
11581
- });
11582
- });
11583
- }
11584
- // Send JSON-RPC request
11585
- async sendRequest(method, params) {
11586
- if (!this.process?.stdin || !this.connected) {
11587
- throw new Error("MCP server not connected");
11588
- }
11589
- const id = ++this.requestId;
11590
- const request = {
11591
- jsonrpc: "2.0",
11592
- id,
11593
- method,
11594
- params
11595
- };
11596
- return new Promise((resolve, reject) => {
11597
- this.pendingRequests.set(id, { resolve, reject });
11598
- const line = JSON.stringify(request) + "\n";
11599
- this.process.stdin.write(line, (error) => {
11600
- if (error) {
11601
- this.pendingRequests.delete(id);
11602
- reject(error);
11603
- }
11604
- });
11605
- setTimeout(() => {
11606
- if (this.pendingRequests.has(id)) {
11607
- this.pendingRequests.delete(id);
11608
- reject(new Error("MCP request timeout"));
11609
- }
11610
- }, 3e4);
11611
- });
11612
- }
11613
- // Handle JSON-RPC response
11614
- handleResponse(response) {
11615
- const pending = this.pendingRequests.get(response.id);
11616
- if (!pending) {
11617
- return;
11618
- }
11619
- this.pendingRequests.delete(response.id);
11620
- if (response.error) {
11621
- pending.reject(new Error(response.error.message));
11622
- } else {
11623
- pending.resolve(response.result);
11624
- }
11625
- }
11626
- // Initialize MCP server
11627
- async initialize() {
11628
- return this.sendRequest("initialize", {
11629
- protocolVersion: "2024-11-05",
11630
- capabilities: {},
11631
- clientInfo: {
11632
- name: "pentesting",
11633
- version: "1.0.0"
11634
- }
11635
- });
11636
- }
11637
- // List available tools
11638
- async listTools() {
11639
- const result = await this.sendRequest("tools/list");
11640
- return result.tools || [];
11641
- }
11642
- // Call a tool
11643
- async callTool(name, arguments_) {
11644
- return this.sendRequest("tools/call", { name, arguments: arguments_ });
11645
- }
11646
- // List resources
11647
- async listResources() {
11648
- const result = await this.sendRequest("resources/list");
11649
- return result.resources || [];
11650
- }
11651
- // Read a resource
11652
- async readResource(uri) {
11653
- return this.sendRequest("resources/read", { uri });
11654
- }
11655
- // Disconnect
11656
- async disconnect() {
11657
- if (this.process) {
11658
- this.process.kill();
11659
- this.process = null;
11660
- this.connected = false;
11661
- }
11662
- }
11663
- // Check if connected
11664
- isConnected() {
11665
- return this.connected;
11666
- }
11667
- };
11668
- var MCPManager = class extends EventEmitter16 {
11669
- clients = /* @__PURE__ */ new Map();
11670
- tools = /* @__PURE__ */ new Map();
11671
- // Add and connect to a server
11672
- async addServer(name, config) {
11673
- const client = new MCPClient(name, config);
11674
- client.on("error", (error) => this.emit("error", { server: name, error }));
11675
- client.on("connected", () => this.emit("server_connected", { server: name }));
11676
- client.on("disconnected", (info) => this.emit("server_disconnected", info));
11677
- try {
11678
- await client.connect();
11679
- await client.initialize();
11680
- const tools = await client.listTools();
11681
- for (const tool of tools) {
11682
- this.tools.set(tool.name, { server: name, tool });
11683
- }
11684
- this.clients.set(name, client);
11685
- this.emit("tools_loaded", { server: name, count: tools.length });
11686
- } catch (error) {
11687
- this.emit("error", { server: name, error });
11688
- throw error;
11689
- }
11690
- }
11691
- // Call a tool by name
11692
- async callTool(toolName, args) {
11693
- const entry = this.tools.get(toolName);
11694
- if (!entry) {
11695
- throw new Error(`Unknown MCP tool: ${toolName}`);
11696
- }
11697
- const client = this.clients.get(entry.server);
11698
- if (!client) {
11699
- throw new Error(`MCP server not connected: ${entry.server}`);
11700
- }
11701
- return client.callTool(toolName, args);
11702
- }
11703
- // Get all available tools
11704
- getAvailableTools() {
11705
- return Array.from(this.tools.values()).map((e) => e.tool);
11706
- }
11707
- // Disconnect all
11708
- async disconnectAll() {
11709
- for (const client of this.clients.values()) {
11710
- await client.disconnect();
11711
- }
11712
- this.clients.clear();
11713
- this.tools.clear();
11714
- }
11715
- };
11716
- var mcpManager = null;
11717
- function getMCPManager() {
11718
- if (!mcpManager) {
11719
- mcpManager = new MCPManager();
11720
- }
11721
- return mcpManager;
11722
- }
11723
-
11724
11278
  // src/core/approval/approval-manager.ts
11725
- import { EventEmitter as EventEmitter17 } from "events";
11279
+ import { EventEmitter as EventEmitter16 } from "events";
11726
11280
  var APPROVAL_EVENT = {
11727
11281
  REQUEST: "approval_request",
11728
11282
  RESPONSE: "approval_response",
@@ -11893,7 +11447,7 @@ function assessRisk(toolName, toolInput) {
11893
11447
  function generateRequestId() {
11894
11448
  return `approval_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
11895
11449
  }
11896
- var ApprovalManager = class extends EventEmitter17 {
11450
+ var ApprovalManager = class extends EventEmitter16 {
11897
11451
  pendingRequests = /* @__PURE__ */ new Map();
11898
11452
  autoApprovedTools = /* @__PURE__ */ new Set();
11899
11453
  autoDeniedTools = /* @__PURE__ */ new Set();
@@ -12007,103 +11561,682 @@ var ApprovalManager = class extends EventEmitter17 {
12007
11561
  respondedAt: (/* @__PURE__ */ new Date()).toISOString()
12008
11562
  });
12009
11563
  }
12010
- resolve("deny");
12011
- } else {
12012
- resolve(response.decision);
11564
+ resolve("deny");
11565
+ } else {
11566
+ resolve(response.decision);
11567
+ }
11568
+ }
11569
+ };
11570
+ this.on(APPROVAL_EVENT.RESPONSE, responseHandler);
11571
+ });
11572
+ }
11573
+ /**
11574
+ * Respond to an approval request (called by UI)
11575
+ * Now handles timed YOLO modes
11576
+ */
11577
+ respond(requestId, decision) {
11578
+ if (decision === "yolo_5min") {
11579
+ this.setYoloMode(true, 5 * 60 * 1e3);
11580
+ decision = "approve";
11581
+ } else if (decision === "yolo_15min") {
11582
+ this.setYoloMode(true, 15 * 60 * 1e3);
11583
+ decision = "approve";
11584
+ } else if (decision === "yolo_30min") {
11585
+ this.setYoloMode(true, 30 * 60 * 1e3);
11586
+ decision = "approve";
11587
+ }
11588
+ if (decision === "approve" || decision === "approve_always") {
11589
+ this.stats.approved++;
11590
+ } else if (decision === "deny" || decision === "deny_always") {
11591
+ this.stats.denied++;
11592
+ }
11593
+ const response = {
11594
+ requestId,
11595
+ decision,
11596
+ respondedAt: (/* @__PURE__ */ new Date()).toISOString()
11597
+ };
11598
+ this.emit(APPROVAL_EVENT.RESPONSE, response);
11599
+ }
11600
+ /**
11601
+ * Get pending requests
11602
+ */
11603
+ getPendingRequests() {
11604
+ return Array.from(this.pendingRequests.values());
11605
+ }
11606
+ /**
11607
+ * Get auto-approved tools
11608
+ */
11609
+ getAutoApprovedTools() {
11610
+ return Array.from(this.autoApprovedTools);
11611
+ }
11612
+ /**
11613
+ * Add a tool to the auto-approved list
11614
+ */
11615
+ addAutoApprovedTool(toolName) {
11616
+ this.autoApprovedTools.add(toolName);
11617
+ }
11618
+ /**
11619
+ * Reset all auto decisions
11620
+ */
11621
+ resetAutoDecisions() {
11622
+ this.autoApprovedTools.clear();
11623
+ this.autoDeniedTools.clear();
11624
+ }
11625
+ /**
11626
+ * Get YOLO mode status
11627
+ */
11628
+ getYoloStatus() {
11629
+ const remainingMs = this.yoloExpiresAt ? Math.max(0, this.yoloExpiresAt - Date.now()) : null;
11630
+ return {
11631
+ enabled: this.yoloMode,
11632
+ expiresAt: this.yoloExpiresAt,
11633
+ remainingMs
11634
+ };
11635
+ }
11636
+ /**
11637
+ * Get approval statistics
11638
+ */
11639
+ getStats() {
11640
+ return { ...this.stats };
11641
+ }
11642
+ /**
11643
+ * Cancel YOLO mode
11644
+ */
11645
+ cancelYoloMode() {
11646
+ this.setYoloMode(false);
11647
+ }
11648
+ /**
11649
+ * Dispose of the approval manager
11650
+ */
11651
+ dispose() {
11652
+ if (this.yoloTimer) {
11653
+ clearTimeout(this.yoloTimer);
11654
+ this.yoloTimer = null;
11655
+ }
11656
+ this.pendingRequests.clear();
11657
+ this.removeAllListeners();
11658
+ }
11659
+ };
11660
+ var approvalManager = null;
11661
+ function getApprovalManager(options) {
11662
+ if (!approvalManager) {
11663
+ approvalManager = new ApprovalManager(options);
11664
+ }
11665
+ return approvalManager;
11666
+ }
11667
+
11668
+ // src/core/agent/strategy/attack-planner.ts
11669
+ var SERVICE_TOOL_MAP = {
11670
+ http: {
11671
+ enumTools: [
11672
+ { tool: TOOL_NAME.WHATWEB, description: "Identify web technologies", successIndicators: ["WordPress", "Apache", "nginx", "PHP"], timeout: 3e4, priority: 90 },
11673
+ { tool: TOOL_NAME.NIKTO, description: "Web vulnerability scan", successIndicators: ["OSVDB", "vulnerability", "found"], timeout: 3e5, priority: 80 },
11674
+ { tool: TOOL_NAME.FFUF, description: "Directory/file fuzzing", successIndicators: ["200", "301", "302", "Status"], timeout: 3e5, priority: 85 },
11675
+ { tool: TOOL_NAME.GOBUSTER, description: "Directory bruteforce", successIndicators: ["Status: 200", "Found:"], timeout: 3e5, priority: 70 },
11676
+ { tool: TOOL_NAME.NUCLEI, description: "Vulnerability scanning with templates", successIndicators: ["[critical]", "[high]", "[medium]", "found"], timeout: 6e5, priority: 75 }
11677
+ ],
11678
+ exploitTools: [
11679
+ { tool: TOOL_NAME.SQL_INJECTION, description: "SQL injection testing", successIndicators: ["injectable", "sql", "dump"], timeout: 6e5, priority: 85 },
11680
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search for known exploits", successIndicators: ["Exploit", "exploit/"], timeout: 3e4, priority: 90 }
11681
+ ],
11682
+ vulnChecks: ["sql_injection", "xss", "file_inclusion", "command_injection", "path_traversal"]
11683
+ },
11684
+ https: {
11685
+ enumTools: [
11686
+ { tool: TOOL_NAME.WHATWEB, description: "Identify web technologies", successIndicators: ["WordPress", "Apache", "nginx"], timeout: 3e4, priority: 90 },
11687
+ { tool: TOOL_NAME.NIKTO, description: "Web vulnerability scan", successIndicators: ["OSVDB", "vulnerability"], timeout: 3e5, priority: 80 },
11688
+ { tool: TOOL_NAME.FFUF, description: "Directory fuzzing", successIndicators: ["200", "301", "302"], timeout: 3e5, priority: 85 },
11689
+ { tool: TOOL_NAME.NUCLEI, description: "Template-based vuln scanning", successIndicators: ["[critical]", "[high]"], timeout: 6e5, priority: 75 }
11690
+ ],
11691
+ exploitTools: [
11692
+ { tool: TOOL_NAME.SQL_INJECTION, description: "SQL injection", successIndicators: ["injectable", "dump"], timeout: 6e5, priority: 85 },
11693
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 90 }
11694
+ ],
11695
+ vulnChecks: ["ssl_vulnerability", "sql_injection", "xss"]
11696
+ },
11697
+ ssh: {
11698
+ enumTools: [
11699
+ { tool: TOOL_NAME.BASH, description: "SSH banner grab", successIndicators: ["SSH", "OpenSSH"], timeout: 1e4, priority: 90 }
11700
+ ],
11701
+ exploitTools: [
11702
+ { tool: TOOL_NAME.HYDRA, description: "SSH bruteforce", successIndicators: ["login:", "password:"], timeout: 6e5, priority: 70 },
11703
+ { tool: TOOL_NAME.SSH, description: "SSH with found credentials", successIndicators: ["$", "#", "Last login"], timeout: 3e4, priority: 95 },
11704
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search SSH exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 80 }
11705
+ ],
11706
+ vulnChecks: ["weak_credentials", "old_version"]
11707
+ },
11708
+ smb: {
11709
+ enumTools: [
11710
+ { tool: TOOL_NAME.SMB_ENUM, description: "SMB enumeration", successIndicators: ["share", "IPC$", "users"], timeout: 12e4, priority: 90 },
11711
+ { tool: TOOL_NAME.ENUM4LINUX, description: "Full SMB enum", successIndicators: ["user", "share", "password"], timeout: 3e5, priority: 85 },
11712
+ { tool: TOOL_NAME.SMBMAP, description: "SMB share permissions", successIndicators: ["READ", "WRITE", "Disk"], timeout: 12e4, priority: 88 },
11713
+ { tool: TOOL_NAME.SMBCLIENT, description: "SMB client access", successIndicators: ["smb:", "NT_STATUS"], timeout: 6e4, priority: 80 }
11714
+ ],
11715
+ exploitTools: [
11716
+ { tool: TOOL_NAME.CRACKMAPEXEC, description: "Credential spray SMB", successIndicators: ["Pwn3d!", "+)"], timeout: 3e5, priority: 85 },
11717
+ { tool: TOOL_NAME.IMPACKET_PSEXEC, description: "PsExec remote execution", successIndicators: ["C:\\>", "$"], timeout: 12e4, priority: 90 },
11718
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search SMB exploits", successIndicators: ["Exploit", "EternalBlue"], timeout: 3e4, priority: 95 }
11719
+ ],
11720
+ vulnChecks: ["null_session", "anonymous_access", "eternal_blue", "weak_credentials"]
11721
+ },
11722
+ ftp: {
11723
+ enumTools: [
11724
+ { tool: TOOL_NAME.FTP_ANON, description: "Check anonymous FTP", successIndicators: ["Anonymous", "230", "Login successful"], timeout: 3e4, priority: 95 },
11725
+ { tool: TOOL_NAME.FTP_ENUM, description: "FTP enumeration", successIndicators: ["user", "directory"], timeout: 6e4, priority: 85 }
11726
+ ],
11727
+ exploitTools: [
11728
+ { tool: TOOL_NAME.HYDRA, description: "FTP bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 70 },
11729
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search FTP exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 90 }
11730
+ ],
11731
+ vulnChecks: ["anonymous_access", "weak_credentials", "known_vulnerability"]
11732
+ },
11733
+ mysql: {
11734
+ enumTools: [
11735
+ { tool: TOOL_NAME.MYSQL_CLIENT, description: "MySQL client access", successIndicators: ["mysql>", "Server version"], timeout: 3e4, priority: 90 }
11736
+ ],
11737
+ exploitTools: [
11738
+ { tool: TOOL_NAME.HYDRA, description: "MySQL bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 70 },
11739
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "MySQL exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 85 }
11740
+ ],
11741
+ vulnChecks: ["default_credentials", "udf_exploit"]
11742
+ },
11743
+ snmp: {
11744
+ enumTools: [
11745
+ { tool: TOOL_NAME.SNMP_WALK, description: "SNMP walk", successIndicators: ["STRING:", "INTEGER:", "SNMPv2"], timeout: 12e4, priority: 90 },
11746
+ { tool: TOOL_NAME.SNMP_CHECK, description: "SNMP check", successIndicators: ["system", "user", "process"], timeout: 6e4, priority: 85 },
11747
+ { tool: TOOL_NAME.ONESIXTYONE, description: "SNMP community string bruteforce", successIndicators: ["public", "private"], timeout: 6e4, priority: 88 }
11748
+ ],
11749
+ exploitTools: [],
11750
+ vulnChecks: ["default_community_string", "snmpv1"]
11751
+ },
11752
+ ldap: {
11753
+ enumTools: [
11754
+ { tool: TOOL_NAME.LDAP_SEARCH, description: "LDAP enumeration", successIndicators: ["dn:", "cn=", "dc="], timeout: 12e4, priority: 90 }
11755
+ ],
11756
+ exploitTools: [
11757
+ { tool: TOOL_NAME.KERBRUTE, description: "Kerberos user enum", successIndicators: ["VALID", "user"], timeout: 3e5, priority: 80 },
11758
+ { tool: TOOL_NAME.IMPACKET_GETNPUSERS, description: "AS-REP roasting", successIndicators: ["$krb5asrep$"], timeout: 12e4, priority: 85 }
11759
+ ],
11760
+ vulnChecks: ["anonymous_bind", "kerberoasting", "as-rep_roasting"]
11761
+ },
11762
+ rdp: {
11763
+ enumTools: [
11764
+ { tool: TOOL_NAME.RDP_CHECK, description: "RDP security check", successIndicators: ["RDP", "PROTOCOL_RDP"], timeout: 3e4, priority: 90 }
11765
+ ],
11766
+ exploitTools: [
11767
+ { tool: TOOL_NAME.HYDRA, description: "RDP bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 60 },
11768
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "RDP exploits", successIndicators: ["BlueKeep", "CVE-2019"], timeout: 3e4, priority: 90 }
11769
+ ],
11770
+ vulnChecks: ["bluekeep", "weak_credentials"]
11771
+ },
11772
+ redis: {
11773
+ enumTools: [
11774
+ { tool: TOOL_NAME.REDIS_CLI, description: "Redis client access", successIndicators: ["redis", "PONG", "keys"], timeout: 3e4, priority: 90 }
11775
+ ],
11776
+ exploitTools: [
11777
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Redis exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 85 }
11778
+ ],
11779
+ vulnChecks: ["no_auth", "default_config"]
11780
+ }
11781
+ };
11782
+ var AttackPlanner = class {
11783
+ /**
11784
+ * Generate an initial attack plan based on target and discovered services
11785
+ */
11786
+ generatePlan(target, services) {
11787
+ const plan = {
11788
+ target,
11789
+ phases: [],
11790
+ currentPhaseIndex: 0,
11791
+ adaptations: []
11792
+ };
11793
+ plan.phases.push({
11794
+ name: "initial_recon",
11795
+ steps: [
11796
+ { tool: TOOL_NAME.NMAP_SCAN, description: "Full port scan", successIndicators: ["open", "tcp", "udp"], timeout: 3e5, priority: 100 },
11797
+ { tool: TOOL_NAME.RUSTSCAN, description: "Fast port scan", successIndicators: ["Open"], timeout: 6e4, priority: 95 }
11798
+ ],
11799
+ successCriteria: "At least 1 service discovered",
11800
+ maxAttempts: 5
11801
+ });
11802
+ const enumSteps = [];
11803
+ for (const svc of services) {
11804
+ const normalizedService = this.normalizeServiceName(svc.service);
11805
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11806
+ if (mapping) {
11807
+ enumSteps.push(...mapping.enumTools.map((step) => ({
11808
+ ...step,
11809
+ description: `[${svc.port}/${svc.service}] ${step.description}`
11810
+ })));
11811
+ }
11812
+ }
11813
+ if (enumSteps.length > 0) {
11814
+ enumSteps.sort((a, b) => b.priority - a.priority);
11815
+ plan.phases.push({
11816
+ name: "service_enumeration",
11817
+ steps: enumSteps,
11818
+ successCriteria: "Vulnerabilities or access vectors identified",
11819
+ maxAttempts: enumSteps.length + 5
11820
+ });
11821
+ }
11822
+ const vulnSteps = [
11823
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search for known exploits", successIndicators: ["Exploit", "CVE"], timeout: 3e4, priority: 90 }
11824
+ ];
11825
+ for (const svc of services) {
11826
+ if (svc.version) {
11827
+ vulnSteps.push({
11828
+ tool: TOOL_NAME.SEARCHSPLOIT,
11829
+ description: `Search exploits for ${svc.service} ${svc.version}`,
11830
+ successIndicators: ["Exploit"],
11831
+ timeout: 3e4,
11832
+ priority: 88
11833
+ });
11834
+ }
11835
+ }
11836
+ plan.phases.push({
11837
+ name: "vulnerability_analysis",
11838
+ steps: vulnSteps,
11839
+ successCriteria: "Exploitable vulnerability found",
11840
+ maxAttempts: 10
11841
+ });
11842
+ const exploitSteps = [];
11843
+ for (const svc of services) {
11844
+ const normalizedService = this.normalizeServiceName(svc.service);
11845
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11846
+ if (mapping) {
11847
+ exploitSteps.push(...mapping.exploitTools.map((step) => ({
11848
+ ...step,
11849
+ description: `[${svc.port}/${svc.service}] ${step.description}`
11850
+ })));
11851
+ }
11852
+ }
11853
+ if (exploitSteps.length > 0) {
11854
+ exploitSteps.sort((a, b) => b.priority - a.priority);
11855
+ plan.phases.push({
11856
+ name: "exploitation",
11857
+ steps: exploitSteps,
11858
+ successCriteria: "Shell or admin access obtained",
11859
+ maxAttempts: exploitSteps.length + 10
11860
+ });
11861
+ }
11862
+ plan.phases.push({
11863
+ name: "post_exploitation",
11864
+ steps: [
11865
+ { tool: TOOL_NAME.RUN_PRIVESC_ENUM, description: "Privilege escalation enumeration", successIndicators: ["SUID", "cron", "writable"], timeout: 12e4, priority: 90 },
11866
+ { tool: TOOL_NAME.CHECK_SUDO, description: "Check sudo rights", successIndicators: ["(ALL)", "NOPASSWD", "may run"], timeout: 3e4, priority: 95 },
11867
+ { tool: TOOL_NAME.FIND_SUID, description: "Find SUID binaries", successIndicators: ["/usr/", "/bin/"], timeout: 3e4, priority: 85 },
11868
+ { tool: TOOL_NAME.DUMP_CREDENTIALS, description: "Dump credentials", successIndicators: ["password", "hash", "credential"], timeout: 12e4, priority: 80 }
11869
+ ],
11870
+ successCriteria: "Root/admin access obtained",
11871
+ maxAttempts: 15
11872
+ });
11873
+ return plan;
11874
+ }
11875
+ /**
11876
+ * Adapt the plan based on new discoveries
11877
+ */
11878
+ adaptPlan(plan, newServices, findings) {
11879
+ const adaptation = `Adapting plan: ${newServices.length} new services, ${findings.length} findings`;
11880
+ plan.adaptations.push(adaptation);
11881
+ for (const svc of newServices) {
11882
+ const normalizedService = this.normalizeServiceName(svc.service);
11883
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11884
+ if (mapping) {
11885
+ let enumPhase = plan.phases.find((p) => p.name === "service_enumeration");
11886
+ if (!enumPhase) {
11887
+ enumPhase = {
11888
+ name: "service_enumeration",
11889
+ steps: [],
11890
+ successCriteria: "Vulnerabilities identified",
11891
+ maxAttempts: 10
11892
+ };
11893
+ const reconIdx = plan.phases.findIndex((p) => p.name === "initial_recon");
11894
+ plan.phases.splice(reconIdx + 1, 0, enumPhase);
11895
+ }
11896
+ for (const tool of mapping.enumTools) {
11897
+ const exists = enumPhase.steps.some(
11898
+ (s) => s.tool === tool.tool && s.description.includes(`${svc.port}`)
11899
+ );
11900
+ if (!exists) {
11901
+ enumPhase.steps.push({
11902
+ ...tool,
11903
+ description: `[${svc.port}/${svc.service}] ${tool.description}`
11904
+ });
11905
+ }
11906
+ }
11907
+ }
11908
+ }
11909
+ return plan;
11910
+ }
11911
+ /**
11912
+ * Get suggested next steps based on current state
11913
+ */
11914
+ suggestNextSteps(services, completedTools, hasCredentials, hasShell) {
11915
+ const suggestions = [];
11916
+ if (hasShell) {
11917
+ suggestions.push(
11918
+ { tool: TOOL_NAME.RUN_PRIVESC_ENUM, description: "Run privilege escalation enumeration", successIndicators: ["SUID", "cron"], timeout: 12e4, priority: 95 },
11919
+ { tool: TOOL_NAME.CHECK_SUDO, description: "Check sudo permissions", successIndicators: ["(ALL)", "NOPASSWD"], timeout: 3e4, priority: 90 },
11920
+ { tool: TOOL_NAME.FIND_SUID, description: "Find SUID binaries", successIndicators: ["/usr/", "/bin/"], timeout: 3e4, priority: 85 }
11921
+ );
11922
+ } else if (hasCredentials) {
11923
+ for (const svc of services) {
11924
+ if (["ssh", "smb", "ftp", "rdp", "winrm"].includes(this.normalizeServiceName(svc.service))) {
11925
+ suggestions.push({
11926
+ tool: svc.service === "ssh" ? TOOL_NAME.SSH : TOOL_NAME.CRACKMAPEXEC,
11927
+ description: `Try credentials on ${svc.service}:${svc.port}`,
11928
+ successIndicators: ["$", "#", "Pwn3d!", "Login successful"],
11929
+ timeout: 3e4,
11930
+ priority: 95
11931
+ });
11932
+ }
11933
+ }
11934
+ } else {
11935
+ for (const svc of services) {
11936
+ const normalizedService = this.normalizeServiceName(svc.service);
11937
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11938
+ if (mapping) {
11939
+ for (const tool of mapping.enumTools) {
11940
+ if (!completedTools.includes(tool.tool)) {
11941
+ suggestions.push({
11942
+ ...tool,
11943
+ description: `[${svc.port}/${svc.service}] ${tool.description}`
11944
+ });
11945
+ }
12013
11946
  }
12014
11947
  }
12015
- };
12016
- this.on(APPROVAL_EVENT.RESPONSE, responseHandler);
12017
- });
11948
+ }
11949
+ }
11950
+ suggestions.sort((a, b) => b.priority - a.priority);
11951
+ return suggestions.slice(0, 10);
12018
11952
  }
12019
11953
  /**
12020
- * Respond to an approval request (called by UI)
12021
- * Now handles timed YOLO modes
11954
+ * Get vulnerability checks for discovered services
12022
11955
  */
12023
- respond(requestId, decision) {
12024
- if (decision === "yolo_5min") {
12025
- this.setYoloMode(true, 5 * 60 * 1e3);
12026
- decision = "approve";
12027
- } else if (decision === "yolo_15min") {
12028
- this.setYoloMode(true, 15 * 60 * 1e3);
12029
- decision = "approve";
12030
- } else if (decision === "yolo_30min") {
12031
- this.setYoloMode(true, 30 * 60 * 1e3);
12032
- decision = "approve";
11956
+ getVulnChecks(services) {
11957
+ const checks = [];
11958
+ for (const svc of services) {
11959
+ const normalizedService = this.normalizeServiceName(svc.service);
11960
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11961
+ if (mapping) {
11962
+ checks.push(...mapping.vulnChecks);
11963
+ }
12033
11964
  }
12034
- if (decision === "approve" || decision === "approve_always") {
12035
- this.stats.approved++;
12036
- } else if (decision === "deny" || decision === "deny_always") {
12037
- this.stats.denied++;
11965
+ return [...new Set(checks)];
11966
+ }
11967
+ /**
11968
+ * Normalize service names from nmap output to our mapping keys
11969
+ */
11970
+ normalizeServiceName(service) {
11971
+ const lower = service.toLowerCase();
11972
+ if (lower.includes("http") && !lower.includes("https")) return "http";
11973
+ if (lower.includes("https") || lower.includes("ssl/http")) return "https";
11974
+ if (lower.includes("ssh")) return "ssh";
11975
+ if (lower.includes("smb") || lower.includes("microsoft-ds") || lower.includes("netbios")) return "smb";
11976
+ if (lower.includes("ftp")) return "ftp";
11977
+ if (lower.includes("mysql") || lower.includes("mariadb")) return "mysql";
11978
+ if (lower.includes("mssql") || lower.includes("ms-sql")) return "mssql";
11979
+ if (lower.includes("postgresql") || lower.includes("postgres")) return "postgresql";
11980
+ if (lower.includes("redis")) return "redis";
11981
+ if (lower.includes("mongo")) return "mongodb";
11982
+ if (lower.includes("snmp")) return "snmp";
11983
+ if (lower.includes("ldap")) return "ldap";
11984
+ if (lower.includes("rdp") || lower.includes("ms-wbt-server")) return "rdp";
11985
+ return lower;
11986
+ }
11987
+ };
11988
+ var attackPlannerInstance = null;
11989
+ function getAttackPlanner() {
11990
+ if (!attackPlannerInstance) {
11991
+ attackPlannerInstance = new AttackPlanner();
11992
+ }
11993
+ return attackPlannerInstance;
11994
+ }
11995
+
11996
+ // src/core/agent/strategy/stuck-detector.ts
11997
+ var StuckDetector = class {
11998
+ actionHistory = [];
11999
+ progressEvents = [];
12000
+ maxHistorySize = 100;
12001
+ // Thresholds
12002
+ SAME_TOOL_THRESHOLD = 3;
12003
+ // Same tool+target 3x = stuck
12004
+ CYCLE_MIN_LENGTH = 2;
12005
+ // Minimum cycle length
12006
+ CYCLE_REPETITIONS = 2;
12007
+ // Cycle seen 2x = stuck
12008
+ NO_PROGRESS_TIMEOUT = 3e5;
12009
+ // 5 minutes without progress
12010
+ NO_NEW_INFO_THRESHOLD = 8;
12011
+ // 8 actions without new info
12012
+ /**
12013
+ * Record a tool execution
12014
+ */
12015
+ recordAction(tool, target, success, hasNewInfo) {
12016
+ this.actionHistory.push({
12017
+ tool,
12018
+ target,
12019
+ timestamp: Date.now(),
12020
+ success,
12021
+ hasNewInfo
12022
+ });
12023
+ if (this.actionHistory.length > this.maxHistorySize) {
12024
+ this.actionHistory = this.actionHistory.slice(-this.maxHistorySize);
12038
12025
  }
12039
- const response = {
12040
- requestId,
12041
- decision,
12042
- respondedAt: (/* @__PURE__ */ new Date()).toISOString()
12043
- };
12044
- this.emit(APPROVAL_EVENT.RESPONSE, response);
12045
12026
  }
12046
12027
  /**
12047
- * Get pending requests
12028
+ * Record a progress event
12048
12029
  */
12049
- getPendingRequests() {
12050
- return Array.from(this.pendingRequests.values());
12030
+ recordProgress(type, details) {
12031
+ this.progressEvents.push({
12032
+ type,
12033
+ timestamp: Date.now(),
12034
+ details
12035
+ });
12051
12036
  }
12052
12037
  /**
12053
- * Get auto-approved tools
12038
+ * Analyze whether the agent is stuck
12054
12039
  */
12055
- getAutoApprovedTools() {
12056
- return Array.from(this.autoApprovedTools);
12040
+ analyze() {
12041
+ const checks = [
12042
+ this.checkSameToolRepetition(),
12043
+ this.checkCycleDetection(),
12044
+ this.checkProgressTimeout(),
12045
+ this.checkNoNewInfoStreak(),
12046
+ this.checkAllFailures()
12047
+ ];
12048
+ const stuckChecks = checks.filter((c) => c.isStuck);
12049
+ if (stuckChecks.length === 0) {
12050
+ return {
12051
+ isStuck: false,
12052
+ reason: "",
12053
+ suggestion: "",
12054
+ confidence: 0
12055
+ };
12056
+ }
12057
+ stuckChecks.sort((a, b) => b.confidence - a.confidence);
12058
+ return stuckChecks[0];
12057
12059
  }
12058
12060
  /**
12059
- * Reset all auto decisions
12061
+ * Check 1: Same tool+target repeated too many times
12060
12062
  */
12061
- resetAutoDecisions() {
12062
- this.autoApprovedTools.clear();
12063
- this.autoDeniedTools.clear();
12063
+ checkSameToolRepetition() {
12064
+ if (this.actionHistory.length < this.SAME_TOOL_THRESHOLD) {
12065
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12066
+ }
12067
+ const recent = this.actionHistory.slice(-10);
12068
+ const comboCounts = /* @__PURE__ */ new Map();
12069
+ for (const action of recent) {
12070
+ const key = `${action.tool}:${action.target}`;
12071
+ comboCounts.set(key, (comboCounts.get(key) || 0) + 1);
12072
+ }
12073
+ for (const [combo, count] of comboCounts) {
12074
+ if (count >= this.SAME_TOOL_THRESHOLD) {
12075
+ const [tool] = combo.split(":");
12076
+ return {
12077
+ isStuck: true,
12078
+ reason: `Same tool "${tool}" executed ${count} times with same target`,
12079
+ suggestion: `Try a different tool or target. Consider switching approach entirely.`,
12080
+ confidence: Math.min(0.9, 0.5 + count * 0.1)
12081
+ };
12082
+ }
12083
+ }
12084
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12064
12085
  }
12065
12086
  /**
12066
- * Get YOLO mode status
12087
+ * Check 2: Cycle detection (A→B→C→A→B→C pattern)
12067
12088
  */
12068
- getYoloStatus() {
12069
- const remainingMs = this.yoloExpiresAt ? Math.max(0, this.yoloExpiresAt - Date.now()) : null;
12070
- return {
12071
- enabled: this.yoloMode,
12072
- expiresAt: this.yoloExpiresAt,
12073
- remainingMs
12074
- };
12089
+ checkCycleDetection() {
12090
+ if (this.actionHistory.length < this.CYCLE_MIN_LENGTH * this.CYCLE_REPETITIONS) {
12091
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12092
+ }
12093
+ const recent = this.actionHistory.slice(-20).map((a) => a.tool);
12094
+ for (let cycleLen = this.CYCLE_MIN_LENGTH; cycleLen <= 5; cycleLen++) {
12095
+ if (recent.length < cycleLen * this.CYCLE_REPETITIONS) continue;
12096
+ const lastCycle = recent.slice(-cycleLen);
12097
+ let repetitions = 0;
12098
+ for (let i = recent.length - cycleLen; i >= 0; i -= cycleLen) {
12099
+ const segment = recent.slice(i, i + cycleLen);
12100
+ if (segment.length === cycleLen && segment.every((s, idx) => s === lastCycle[idx])) {
12101
+ repetitions++;
12102
+ } else {
12103
+ break;
12104
+ }
12105
+ }
12106
+ if (repetitions >= this.CYCLE_REPETITIONS) {
12107
+ return {
12108
+ isStuck: true,
12109
+ reason: `Cycle detected: [${lastCycle.join(" \u2192 ")}] repeated ${repetitions + 1} times`,
12110
+ suggestion: `Break the cycle. Try a completely different approach or tool category.`,
12111
+ confidence: Math.min(0.95, 0.6 + repetitions * 0.1)
12112
+ };
12113
+ }
12114
+ }
12115
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12075
12116
  }
12076
12117
  /**
12077
- * Get approval statistics
12118
+ * Check 3: No progress for too long
12078
12119
  */
12079
- getStats() {
12080
- return { ...this.stats };
12120
+ checkProgressTimeout() {
12121
+ if (this.progressEvents.length === 0 && this.actionHistory.length > 5) {
12122
+ const oldestAction = this.actionHistory[0];
12123
+ const elapsed = Date.now() - oldestAction.timestamp;
12124
+ if (elapsed > this.NO_PROGRESS_TIMEOUT) {
12125
+ return {
12126
+ isStuck: true,
12127
+ reason: `No progress events recorded in ${Math.round(elapsed / 6e4)} minutes`,
12128
+ suggestion: `Consider scanning more broadly or trying a different attack vector.`,
12129
+ confidence: 0.7
12130
+ };
12131
+ }
12132
+ }
12133
+ if (this.progressEvents.length > 0) {
12134
+ const lastProgress = this.progressEvents[this.progressEvents.length - 1];
12135
+ const elapsed = Date.now() - lastProgress.timestamp;
12136
+ if (elapsed > this.NO_PROGRESS_TIMEOUT) {
12137
+ return {
12138
+ isStuck: true,
12139
+ reason: `No progress for ${Math.round(elapsed / 6e4)} minutes (last: ${lastProgress.details})`,
12140
+ suggestion: `Last progress was "${lastProgress.type}". Try a different approach.`,
12141
+ confidence: Math.min(0.85, 0.5 + elapsed / this.NO_PROGRESS_TIMEOUT * 0.3)
12142
+ };
12143
+ }
12144
+ }
12145
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12081
12146
  }
12082
12147
  /**
12083
- * Cancel YOLO mode
12148
+ * Check 4: Streak of actions with no new information
12084
12149
  */
12085
- cancelYoloMode() {
12086
- this.setYoloMode(false);
12150
+ checkNoNewInfoStreak() {
12151
+ if (this.actionHistory.length < this.NO_NEW_INFO_THRESHOLD) {
12152
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12153
+ }
12154
+ const recent = this.actionHistory.slice(-this.NO_NEW_INFO_THRESHOLD);
12155
+ const noNewInfoCount = recent.filter((a) => !a.hasNewInfo).length;
12156
+ if (noNewInfoCount >= this.NO_NEW_INFO_THRESHOLD) {
12157
+ return {
12158
+ isStuck: true,
12159
+ reason: `${noNewInfoCount} consecutive actions produced no new information`,
12160
+ suggestion: `All recent actions returned known information. Try deeper enumeration or a different service.`,
12161
+ confidence: 0.75
12162
+ };
12163
+ }
12164
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12087
12165
  }
12088
12166
  /**
12089
- * Dispose of the approval manager
12167
+ * Check 5: All recent actions failed
12090
12168
  */
12091
- dispose() {
12092
- if (this.yoloTimer) {
12093
- clearTimeout(this.yoloTimer);
12094
- this.yoloTimer = null;
12169
+ checkAllFailures() {
12170
+ if (this.actionHistory.length < 5) {
12171
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12095
12172
  }
12096
- this.pendingRequests.clear();
12097
- this.removeAllListeners();
12173
+ const recent = this.actionHistory.slice(-5);
12174
+ const failureCount = recent.filter((a) => !a.success).length;
12175
+ if (failureCount >= 5) {
12176
+ return {
12177
+ isStuck: true,
12178
+ reason: `Last ${failureCount} actions all failed`,
12179
+ suggestion: `Multiple failures detected. Check if the target is reachable, or try different tools.`,
12180
+ confidence: 0.8
12181
+ };
12182
+ }
12183
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12098
12184
  }
12099
- };
12100
- var approvalManager = null;
12101
- function getApprovalManager(options) {
12102
- if (!approvalManager) {
12103
- approvalManager = new ApprovalManager(options);
12185
+ /**
12186
+ * Get suggested alternative approaches
12187
+ */
12188
+ getSuggestedPivots(completedTools, availableServices) {
12189
+ const suggestions = [];
12190
+ const toolCategories = {
12191
+ "web_scanning": ["ffuf", "gobuster", "nikto", "nuclei", "whatweb"],
12192
+ "credential_attack": ["hydra", "medusa", "john", "hashcat"],
12193
+ "smb_attack": ["smbclient", "smbmap", "enum4linux", "crackmapexec"],
12194
+ "ssh_attack": ["ssh", "hydra"],
12195
+ "exploit_search": ["searchsploit", "metasploit"],
12196
+ "dns_recon": ["dig", "dnsenum", "dnsrecon", "subfinder"],
12197
+ "privesc": ["run_privesc_enum", "check_sudo", "find_suid"]
12198
+ };
12199
+ for (const [category, tools] of Object.entries(toolCategories)) {
12200
+ const usedCount = tools.filter((t) => completedTools.includes(t)).length;
12201
+ const totalCount = tools.length;
12202
+ if (usedCount < totalCount / 2) {
12203
+ suggestions.push(`Try ${category} tools (${totalCount - usedCount} unused)`);
12204
+ }
12205
+ }
12206
+ for (const service of availableServices) {
12207
+ const normalizedService = service.toLowerCase();
12208
+ if (normalizedService.includes("http") && !completedTools.includes("ffuf")) {
12209
+ suggestions.push("Try directory fuzzing on web services");
12210
+ }
12211
+ if (normalizedService.includes("smb") && !completedTools.includes("enum4linux")) {
12212
+ suggestions.push("Try full SMB enumeration");
12213
+ }
12214
+ }
12215
+ return suggestions.slice(0, 5);
12104
12216
  }
12105
- return approvalManager;
12106
- }
12217
+ /**
12218
+ * Reset detector state
12219
+ */
12220
+ reset() {
12221
+ this.actionHistory = [];
12222
+ this.progressEvents = [];
12223
+ }
12224
+ /**
12225
+ * Get statistics about current state
12226
+ */
12227
+ getStats() {
12228
+ const total = this.actionHistory.length;
12229
+ const successes = this.actionHistory.filter((a) => a.success).length;
12230
+ const newInfo = this.actionHistory.filter((a) => a.hasNewInfo).length;
12231
+ const lastProgress = this.progressEvents.length > 0 ? Date.now() - this.progressEvents[this.progressEvents.length - 1].timestamp : null;
12232
+ return {
12233
+ totalActions: total,
12234
+ successRate: total > 0 ? successes / total : 0,
12235
+ newInfoRate: total > 0 ? newInfo / total : 0,
12236
+ lastProgressAge: lastProgress
12237
+ };
12238
+ }
12239
+ };
12107
12240
 
12108
12241
  // src/core/agent/autonomous-agent.ts
12109
12242
  function toContentBlockParam(block) {
@@ -12147,7 +12280,7 @@ var DEFAULT_PHASES = [
12147
12280
  { id: PHASE_ID.EXFIL, name: "Data Exfiltration", shortName: "Exfil", status: PHASE_STATUS.PENDING, attempts: 0 },
12148
12281
  { id: PHASE_ID.REPORT, name: "Reporting", shortName: "Report", status: PHASE_STATUS.PENDING, attempts: 0 }
12149
12282
  ];
12150
- var AutonomousHackingAgent = class extends EventEmitter18 {
12283
+ var AutonomousHackingAgent = class extends EventEmitter17 {
12151
12284
  // LLM Client
12152
12285
  client;
12153
12286
  // Core State
@@ -12163,12 +12296,15 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12163
12296
  sessionManager;
12164
12297
  // Utility Systems
12165
12298
  hookExecutor;
12166
- mcpManager;
12167
12299
  contextManager;
12168
12300
  approvalManager;
12169
- // v0.12.0 New Systems
12301
+ // v0.12.7 New Systems
12170
12302
  resourceManager;
12171
12303
  auditLogger;
12304
+ // Strategy modules
12305
+ attackPlanner;
12306
+ stuckDetector;
12307
+ currentAttackPlan;
12172
12308
  // Token usage tracking
12173
12309
  tokenUsage = { input: 0, output: 0, total: 0 };
12174
12310
  // Execution control flags
@@ -12181,7 +12317,6 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12181
12317
  enableAutoCheckpoint = true;
12182
12318
  checkpointIntervalMs = 6e4;
12183
12319
  enableAutonomousOrchestrator = true;
12184
- // New: Use enhanced autonomous orchestrator
12185
12320
  // Rabbit hole detection settings
12186
12321
  STUCK_THRESHOLD = 5;
12187
12322
  STUCK_TIME_THRESHOLD = 3e5;
@@ -12194,6 +12329,8 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12194
12329
  });
12195
12330
  this.config = { ...AGENT_CONFIG, ...config };
12196
12331
  this.tools = ALL_TOOLS;
12332
+ this.attackPlanner = getAttackPlanner();
12333
+ this.stuckDetector = new StuckDetector();
12197
12334
  this.state = this.createInitialState();
12198
12335
  this.sharedMemory = new SharedMemory();
12199
12336
  this.sharedMemory.on("finding_added", (finding) => {
@@ -12248,7 +12385,7 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12248
12385
  this.think(THOUGHT_TYPE.PLANNING, "[coordinator] Replanning attack strategy...");
12249
12386
  });
12250
12387
  this.coordinator.on("thought", (data) => {
12251
- this.think(THOUGHT_TYPE.PLANNING, data.message);
12388
+ this.think(THOUGHT_TYPE.PLANNING, data.content);
12252
12389
  });
12253
12390
  if (this.enableAutonomousOrchestrator) {
12254
12391
  const autonomousOrch = this.coordinator;
@@ -12264,7 +12401,6 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12264
12401
  }
12265
12402
  this.sessionManager = new SessionManager(".pentesting/sessions");
12266
12403
  this.hookExecutor = getHookExecutor();
12267
- this.mcpManager = getMCPManager();
12268
12404
  this.contextManager = new ContextManager(this.client);
12269
12405
  this.approvalManager = getApprovalManager({ yoloMode: config?.autoApprove });
12270
12406
  this.resourceManager = getResourceManager();
@@ -12394,6 +12530,16 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12394
12530
  this.emit(AGENT_EVENT.TARGET_SET, { target, action: "added" });
12395
12531
  return true;
12396
12532
  }
12533
+ /**
12534
+ * Add an MCP server for extended tool capabilities
12535
+ */
12536
+ async addMCPServer(name, command, args) {
12537
+ this.think(THOUGHT_TYPE.PLANNING, `[mcp] Registering MCP server: ${name} (${command})`);
12538
+ this.emit(AGENT_EVENT.THOUGHT, {
12539
+ type: THOUGHT_TYPE.PLANNING,
12540
+ content: `Connected to MCP server: ${name}`
12541
+ });
12542
+ }
12397
12543
  // ===== MAIN AUTONOMOUS EXECUTION =====
12398
12544
  async runAutonomous(objective) {
12399
12545
  if (!this.state.target.primary) {
@@ -12411,6 +12557,17 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12411
12557
  role: "user",
12412
12558
  content: mainObjective
12413
12559
  });
12560
+ this.currentAttackPlan = this.attackPlanner.generatePlan(
12561
+ this.state.target.primary,
12562
+ this.state.target.services.map((s) => ({
12563
+ host: s.host,
12564
+ port: s.port,
12565
+ protocol: s.protocol,
12566
+ service: s.service,
12567
+ version: s.version
12568
+ }))
12569
+ );
12570
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Generated attack plan with ${this.currentAttackPlan.phases.length} phases`);
12414
12571
  if (this.enableAutoCheckpoint) {
12415
12572
  this.sessionManager.enableAutoCheckpoint(this.checkpointIntervalMs, async () => {
12416
12573
  return await this.getCurrentState();
@@ -12425,8 +12582,14 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12425
12582
  this.state.fullAttempts++;
12426
12583
  this.emit(AGENT_EVENT.ITERATION, { current: iteration, max: maxIterations, phase: this.state.currentPhase });
12427
12584
  try {
12428
- if (this.checkIfStuck()) {
12585
+ const stuckState = this.stuckDetector.analyze();
12586
+ const legacyStuck = this.checkIfStuck();
12587
+ if (stuckState.isStuck || legacyStuck) {
12429
12588
  this.state.status = AGENT_STATUS.STUCK;
12589
+ if (stuckState.isStuck) {
12590
+ this.think(THOUGHT_TYPE.STUCK, `[stuck] ${stuckState.reason} (confidence: ${(stuckState.confidence * 100).toFixed(0)}%)`);
12591
+ this.think(THOUGHT_TYPE.PLANNING, `[stuck] Suggestion: ${stuckState.suggestion}`);
12592
+ }
12430
12593
  if (this.learningSystem) {
12431
12594
  const reflection = await this.analyzeFailureWithLearning();
12432
12595
  this.think(THOUGHT_TYPE.REFLECTION, `[learning] ${reflection}`);
@@ -12435,6 +12598,37 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12435
12598
  this.think(THOUGHT_TYPE.PLANNING, `[learning] Trying alternative: ${alternative}`);
12436
12599
  }
12437
12600
  }
12601
+ const completedTools = [...this.state.repeatedActions.keys()].map((k) => k.split(":")[0]);
12602
+ const serviceNames = this.state.target.services.map((s) => s.service);
12603
+ const pivots = this.stuckDetector.getSuggestedPivots(completedTools, serviceNames);
12604
+ if (pivots.length > 0) {
12605
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Pivot suggestions: ${pivots.join(", ")}`);
12606
+ }
12607
+ if (this.currentAttackPlan) {
12608
+ const serviceInfos = this.state.target.services.map((s) => ({
12609
+ host: s.host,
12610
+ port: s.port,
12611
+ protocol: s.protocol,
12612
+ service: s.service,
12613
+ version: s.version
12614
+ }));
12615
+ const hasCredentials = this.state.target.credentials.length > 0;
12616
+ const hasShell = this.state.target.compromised.length > 0;
12617
+ const nextSteps = this.attackPlanner.suggestNextSteps(
12618
+ serviceInfos,
12619
+ completedTools,
12620
+ hasCredentials,
12621
+ hasShell
12622
+ );
12623
+ if (nextSteps.length > 0) {
12624
+ const stepDescriptions = nextSteps.slice(0, 3).map((s) => `${s.tool}: ${s.description}`).join("; ");
12625
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Suggested: ${stepDescriptions}`);
12626
+ this.state.history.push({
12627
+ role: "user",
12628
+ content: `[System] You are stuck. Try these alternative approaches: ${stepDescriptions}. Avoid repeating the same tools/commands.`
12629
+ });
12630
+ }
12631
+ }
12438
12632
  const shouldSkip = await this.decideNextPhase();
12439
12633
  if (shouldSkip) {
12440
12634
  this.setPhaseStatus(this.state.currentPhase, PHASE_STATUS.SKIPPED);
@@ -12497,7 +12691,7 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12497
12691
  try {
12498
12692
  await this.coordinator.initializeAttack(target, attackGoal);
12499
12693
  let findings = [];
12500
- if (this.enableAutonomousOrchestrator && this.coordinator.runAutonomous) {
12694
+ if (this.enableAutonomousOrchestrator && "runAutonomous" in this.coordinator) {
12501
12695
  findings = await this.coordinator.runAutonomous(attackGoal);
12502
12696
  } else {
12503
12697
  findings = await this.coordinator.run();
@@ -12702,32 +12896,46 @@ Leverage shared memory findings from other agents.
12702
12896
  }
12703
12897
  async processResponse(response) {
12704
12898
  const contentBlocks = [];
12705
- const toolResults = [] = [];
12899
+ const toolUseBlocks = [];
12900
+ const toolResults = [];
12706
12901
  let textResponse = "";
12707
- let hasToolUse = false;
12708
- const processTool = async (block) => {
12709
- hasToolUse = true;
12902
+ for (const block of response.content) {
12903
+ if (block.type === "text") {
12904
+ textResponse += block.text;
12905
+ contentBlocks.push({ type: "text", text: block.text });
12906
+ this.think(THOUGHT_TYPE.OBSERVATION, block.text.slice(0, 500));
12907
+ this.emit(AGENT_EVENT.RESPONSE, block.text);
12908
+ } else if (block.type === "tool_use") {
12909
+ contentBlocks.push({
12910
+ type: "tool_use",
12911
+ id: block.id,
12912
+ name: block.name,
12913
+ input: block.input
12914
+ });
12915
+ toolUseBlocks.push(block);
12916
+ }
12917
+ }
12918
+ if (contentBlocks.length > 0) {
12919
+ this.state.history.push({
12920
+ role: "assistant",
12921
+ content: contentBlocks
12922
+ });
12923
+ }
12924
+ for (const block of toolUseBlocks) {
12710
12925
  const toolName = block.name;
12711
12926
  const toolInput = block.input;
12712
- contentBlocks.push({
12713
- type: "tool_use",
12714
- id: block.id,
12715
- name: toolName,
12716
- input: toolInput
12717
- });
12718
12927
  const actionKey = `${toolName}:${JSON.stringify(toolInput).slice(0, 100)}`;
12719
- this.trackAction(actionKey);
12720
12928
  this.think(THOUGHT_TYPE.ACTION, `[tool] ${toolName}`);
12721
12929
  this.emit(AGENT_EVENT.TOOL_CALL, { id: block.id, name: toolName, input: toolInput });
12722
12930
  const hookCheck = await this.hookExecutor.checkPreToolUse(toolName, toolInput);
12723
12931
  if (hookCheck.decision === "block") {
12724
12932
  this.think(THOUGHT_TYPE.STUCK, `Tool blocked: ${hookCheck.output}`);
12725
12933
  toolResults.push({ id: block.id, content: `Blocked: ${hookCheck.output}`, is_error: true });
12726
- return;
12934
+ continue;
12727
12935
  }
12728
12936
  if (this.shouldStopLoop()) {
12729
12937
  this.think(THOUGHT_TYPE.OBSERVATION, "Execution paused");
12730
- return;
12938
+ break;
12731
12939
  }
12732
12940
  if (this.approvalManager.requiresApproval(toolName, toolInput)) {
12733
12941
  const risk = assessRisk(toolName, toolInput);
@@ -12738,14 +12946,14 @@ Leverage shared memory findings from other agents.
12738
12946
  riskLevel: risk
12739
12947
  });
12740
12948
  const decision = await new Promise((resolve) => {
12741
- const handler = (response2) => {
12742
- if (response2.requestId === block.id) {
12949
+ const handler = (resp) => {
12950
+ if (resp.requestId === block.id) {
12743
12951
  this.approvalManager.removeListener("approval_response", handler);
12744
- if (response2.decision === "approve_always") {
12745
- this.approvalManager.autoApprovedTools?.add(toolName);
12952
+ if (resp.decision === "approve_always") {
12953
+ this.approvalManager.addAutoApprovedTool(toolName);
12746
12954
  resolve("approve");
12747
12955
  } else {
12748
- resolve(response2.decision);
12956
+ resolve(resp.decision);
12749
12957
  }
12750
12958
  }
12751
12959
  };
@@ -12754,7 +12962,7 @@ Leverage shared memory findings from other agents.
12754
12962
  if (decision === "deny") {
12755
12963
  this.think(THOUGHT_TYPE.STUCK, `Tool denied: ${toolName}`);
12756
12964
  toolResults.push({ id: block.id, content: "Denied", is_error: true });
12757
- return;
12965
+ continue;
12758
12966
  }
12759
12967
  }
12760
12968
  const result = await executeToolCall(toolName, toolInput);
@@ -12765,26 +12973,12 @@ Leverage shared memory findings from other agents.
12765
12973
  );
12766
12974
  this.emit(AGENT_EVENT.TOOL_RESULT, { id: block.id, name: toolName, result });
12767
12975
  this.extractIntelligence(toolName, result);
12976
+ this.trackAction(actionKey, result);
12768
12977
  toolResults.push({
12769
12978
  id: block.id,
12770
12979
  content: result.output || result.error || "No output",
12771
12980
  is_error: !result.success
12772
12981
  });
12773
- };
12774
- const toolsToProcess = response.content.filter((b) => b.type === "tool_use").map(processTool);
12775
- for (const block of response.content) {
12776
- if (block.type === "text") {
12777
- textResponse += block.text;
12778
- contentBlocks.push({ type: "text", text: block.text });
12779
- this.think(THOUGHT_TYPE.OBSERVATION, block.text.slice(0, 500));
12780
- this.emit(AGENT_EVENT.RESPONSE, block.text);
12781
- }
12782
- }
12783
- if (contentBlocks.length > 0) {
12784
- this.state.history.push({
12785
- role: "assistant",
12786
- content: contentBlocks
12787
- });
12788
12982
  }
12789
12983
  if (toolResults.length > 0) {
12790
12984
  this.state.history.push({
@@ -12797,11 +12991,8 @@ Leverage shared memory findings from other agents.
12797
12991
  }))
12798
12992
  });
12799
12993
  }
12800
- await Promise.all(toolsToProcess);
12801
- if (response.stop_reason === "tool_use" && hasToolUse && !this.shouldStopLoop()) {
12802
- setImmediate(() => this.executeStep().catch((err) => {
12803
- this.think(THOUGHT_TYPE.STUCK, `Error in next step: ${err}`);
12804
- }));
12994
+ if (response.stop_reason === "tool_use" && toolUseBlocks.length > 0 && !this.shouldStopLoop()) {
12995
+ return await this.executeStep();
12805
12996
  }
12806
12997
  return textResponse;
12807
12998
  }
@@ -12809,7 +13000,7 @@ Leverage shared memory findings from other agents.
12809
13000
  extractIntelligence(toolName, result) {
12810
13001
  if (!result.success) return;
12811
13002
  const output = result.output;
12812
- if (toolName === "nmap_scan" || toolName === "bash") {
13003
+ if (toolName === TOOL_NAME.NMAP_SCAN || toolName === TOOL_NAME.BASH) {
12813
13004
  const portMatches = output.match(/(\d+)\/(tcp|udp)\s+open\s+(\S+)/gi);
12814
13005
  if (portMatches) {
12815
13006
  portMatches.forEach((match) => {
@@ -12838,10 +13029,41 @@ Leverage shared memory findings from other agents.
12838
13029
  }
12839
13030
  });
12840
13031
  }
13032
+ if (portMatches && this.currentAttackPlan) {
13033
+ const currentServices = this.state.target.services.map((s) => ({
13034
+ host: s.host,
13035
+ port: s.port,
13036
+ protocol: s.protocol,
13037
+ service: s.service,
13038
+ version: s.version
13039
+ }));
13040
+ const findingTitles = this.state.findings.map((f) => f.title);
13041
+ this.currentAttackPlan = this.attackPlanner.adaptPlan(
13042
+ this.currentAttackPlan,
13043
+ currentServices,
13044
+ findingTitles
13045
+ );
13046
+ if (this.currentAttackPlan.adaptations.length > 0) {
13047
+ const latest = this.currentAttackPlan.adaptations[this.currentAttackPlan.adaptations.length - 1];
13048
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Plan adapted: ${latest}`);
13049
+ }
13050
+ }
12841
13051
  }
12842
- if (output.includes("meterpreter") || output.includes("shell") || output.includes("www-data") || output.includes("uid=")) {
13052
+ if (output.includes("meterpreter") || output.includes("www-data") || output.includes("uid=")) {
12843
13053
  this.addCompromisedHost(this.state.target.primary);
12844
13054
  }
13055
+ const credPatterns = [
13056
+ /(?:user|login|username)[:\s=]+([\w.@-]+)/gi,
13057
+ /(?:pass|password|pwd)[:\s=]+([\S]+)/gi
13058
+ ];
13059
+ for (const pattern of credPatterns) {
13060
+ const matches = output.matchAll(pattern);
13061
+ for (const match of matches) {
13062
+ if (match[1] && match[1].length > 2 && match[1].length < 100) {
13063
+ this.stuckDetector.recordProgress("credential_found", `Potential cred: ${match[1].substring(0, 20)}`);
13064
+ }
13065
+ }
13066
+ }
12845
13067
  }
12846
13068
  // ===== LEARNING SYSTEM INTEGRATION =====
12847
13069
  async analyzeFailureWithLearning() {
@@ -13021,13 +13243,19 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13021
13243
  this.state.stuckCounter = 0;
13022
13244
  this.state.lastProgressTime = /* @__PURE__ */ new Date();
13023
13245
  this.state.repeatedActions.clear();
13246
+ this.stuckDetector.recordProgress("phase_advanced", `Reset at phase ${this.state.currentPhase}`);
13024
13247
  }
13025
- trackAction(action) {
13248
+ trackAction(action, result) {
13026
13249
  const count = this.state.repeatedActions.get(action) || 0;
13027
13250
  this.state.repeatedActions.set(action, count + 1);
13028
13251
  if (count + 1 >= this.STUCK_THRESHOLD) {
13029
13252
  this.state.stuckCounter++;
13030
13253
  }
13254
+ const [tool, ...targetParts] = action.split(":");
13255
+ const target = targetParts.join(":") || this.state.target.primary;
13256
+ const success = result ? result.success : true;
13257
+ const hasNewInfo = result ? result.output.length > 50 && success : false;
13258
+ this.stuckDetector.recordAction(tool, target, success, hasNewInfo);
13031
13259
  }
13032
13260
  // ===== DECISION HELPERS =====
13033
13261
  shouldAdvancePhase() {
@@ -13063,6 +13291,11 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13063
13291
  const hasSuccess = successKeywords.some((kw) => response.toLowerCase().includes(kw));
13064
13292
  if (hasSuccess) {
13065
13293
  this.resetStuckCounter();
13294
+ if (response.toLowerCase().includes("shell") || response.toLowerCase().includes("root")) {
13295
+ this.stuckDetector.recordProgress("shell_obtained", "Shell access detected in response");
13296
+ } else if (response.toLowerCase().includes("discovered") || response.toLowerCase().includes("found")) {
13297
+ this.stuckDetector.recordProgress("service_discovered", "Discovery detected in response");
13298
+ }
13066
13299
  }
13067
13300
  }
13068
13301
  async attemptRecovery(error) {
@@ -13122,6 +13355,13 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13122
13355
  recordProgress(type) {
13123
13356
  this.resetStuckCounter();
13124
13357
  this.state.lastProgressTime = /* @__PURE__ */ new Date();
13358
+ const progressTypeMap = {
13359
+ discovery: "service_discovered",
13360
+ credential: "credential_found",
13361
+ access: "shell_obtained",
13362
+ exploit: "vulnerability_found"
13363
+ };
13364
+ this.stuckDetector.recordProgress(progressTypeMap[type] || "service_discovered", type);
13125
13365
  let message = "";
13126
13366
  let importance = 60;
13127
13367
  switch (type) {
@@ -13243,25 +13483,6 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13243
13483
  this.sharedMemory.clear();
13244
13484
  this.emit(AGENT_EVENT.RESET);
13245
13485
  }
13246
- // ===== MCP INTEGRATION =====
13247
- async addMCPServer(name, command, args) {
13248
- await this.mcpManager.addServer(name, { command, args });
13249
- const mcpTools = this.mcpManager.getAvailableTools();
13250
- for (const mcpTool of mcpTools) {
13251
- const anthropicTool = {
13252
- name: mcpTool.name,
13253
- description: mcpTool.description,
13254
- input_schema: mcpTool.inputSchema
13255
- };
13256
- if (!this.tools.find((t) => t.name === mcpTool.name)) {
13257
- this.tools.push(anthropicTool);
13258
- }
13259
- }
13260
- this.emit(AGENT_EVENT.MCP_SERVER_ADDED, { name, toolCount: mcpTools.length });
13261
- }
13262
- getMCPTools() {
13263
- return this.mcpManager.getAvailableTools().map((t) => t.name);
13264
- }
13265
13486
  // ===== PUBLIC ACCESSORS =====
13266
13487
  getSystemPrompt() {
13267
13488
  return AUTONOMOUS_HACKING_PROMPT;
@@ -13333,7 +13554,42 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13333
13554
  } else if (block.type === "tool_use") {
13334
13555
  hasToolCalls = true;
13335
13556
  this.emit(AGENT_EVENT.TOOL_CALL, { name: block.name, input: block.input });
13336
- const result = await executeToolCall(block.name, block.input);
13557
+ const toolInput = block.input;
13558
+ if (this.approvalManager.requiresApproval(block.name, toolInput)) {
13559
+ const risk = assessRisk(block.name, toolInput);
13560
+ this.emit(AGENT_EVENT.APPROVAL_NEEDED, {
13561
+ id: block.id,
13562
+ toolName: block.name,
13563
+ toolInput,
13564
+ riskLevel: risk
13565
+ });
13566
+ const decision = await new Promise((resolve) => {
13567
+ const handler = (resp) => {
13568
+ if (resp.requestId === block.id) {
13569
+ this.approvalManager.removeListener("approval_response", handler);
13570
+ resolve(resp.decision);
13571
+ }
13572
+ };
13573
+ this.approvalManager.on("approval_response", handler);
13574
+ });
13575
+ if (decision === "deny") {
13576
+ this.state.history.push({
13577
+ role: "assistant",
13578
+ content: response.content
13579
+ });
13580
+ this.state.history.push({
13581
+ role: "user",
13582
+ content: [{
13583
+ type: "tool_result",
13584
+ tool_use_id: block.id,
13585
+ content: "Tool execution denied by user",
13586
+ is_error: true
13587
+ }]
13588
+ });
13589
+ continue;
13590
+ }
13591
+ }
13592
+ const result = await executeToolCall(block.name, toolInput);
13337
13593
  this.emit(AGENT_EVENT.TOOL_RESULT, { name: block.name, result });
13338
13594
  this.state.history.push({
13339
13595
  role: "assistant",
@@ -13567,8 +13823,8 @@ Respond helpfully. If asked to perform security testing, use appropriate tools.`
13567
13823
  this.resourceManager.stopMonitoring();
13568
13824
  await this.resourceManager.performCleanup();
13569
13825
  this.resourceManager.dispose();
13570
- this.removeAllListeners();
13571
13826
  this.emit("shutdown");
13827
+ this.removeAllListeners();
13572
13828
  }
13573
13829
  // ===== UTILITY: Execute Tool with Audit =====
13574
13830
  async executeToolWithAudit(toolName, toolInput) {
@@ -13610,16 +13866,16 @@ Respond helpfully. If asked to perform security testing, use appropriate tools.`
13610
13866
  };
13611
13867
 
13612
13868
  // src/core/session/session-manager.ts
13613
- import * as fs6 from "fs/promises";
13869
+ import * as fs7 from "fs/promises";
13614
13870
  import * as path5 from "path";
13615
- import { EventEmitter as EventEmitter19 } from "events";
13871
+ import { EventEmitter as EventEmitter18 } from "events";
13616
13872
  var SESSIONS_DIR = ".pentesting/sessions";
13617
13873
  function generateSessionId() {
13618
13874
  const timestamp = Date.now().toString(36);
13619
13875
  const random = Math.random().toString(36).substring(2, 8);
13620
13876
  return `session_${timestamp}_${random}`;
13621
13877
  }
13622
- var SessionManager2 = class extends EventEmitter19 {
13878
+ var SessionManager2 = class extends EventEmitter18 {
13623
13879
  sessionsDir;
13624
13880
  currentSession = null;
13625
13881
  constructor(baseDir) {
@@ -13631,7 +13887,7 @@ var SessionManager2 = class extends EventEmitter19 {
13631
13887
  */
13632
13888
  async initialize() {
13633
13889
  try {
13634
- await fs6.mkdir(this.sessionsDir, { recursive: true });
13890
+ await fs7.mkdir(this.sessionsDir, { recursive: true });
13635
13891
  } catch {
13636
13892
  }
13637
13893
  }
@@ -13652,7 +13908,7 @@ var SessionManager2 = class extends EventEmitter19 {
13652
13908
  };
13653
13909
  this.currentSession = metadata;
13654
13910
  const sessionDir = path5.join(this.sessionsDir, metadata.id);
13655
- await fs6.mkdir(sessionDir, { recursive: true });
13911
+ await fs7.mkdir(sessionDir, { recursive: true });
13656
13912
  await this.saveMetadata(metadata);
13657
13913
  this.emit("session_created", metadata);
13658
13914
  return metadata;
@@ -13662,7 +13918,7 @@ var SessionManager2 = class extends EventEmitter19 {
13662
13918
  */
13663
13919
  async saveMetadata(metadata) {
13664
13920
  const metadataPath = path5.join(this.sessionsDir, metadata.id, "metadata.json");
13665
- await fs6.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
13921
+ await fs7.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
13666
13922
  }
13667
13923
  /**
13668
13924
  * Save full session snapshot
@@ -13676,16 +13932,16 @@ var SessionManager2 = class extends EventEmitter19 {
13676
13932
  this.currentSession.currentPhase = snapshot.state.currentPhase;
13677
13933
  await this.saveMetadata(this.currentSession);
13678
13934
  const statePath = path5.join(sessionDir, "state.json");
13679
- await fs6.writeFile(statePath, JSON.stringify(snapshot.state, null, 2));
13935
+ await fs7.writeFile(statePath, JSON.stringify(snapshot.state, null, 2));
13680
13936
  const configPath = path5.join(sessionDir, "config.json");
13681
- await fs6.writeFile(configPath, JSON.stringify(snapshot.config, null, 2));
13937
+ await fs7.writeFile(configPath, JSON.stringify(snapshot.config, null, 2));
13682
13938
  const historyPath = path5.join(sessionDir, "history.jsonl");
13683
13939
  const historyLine = JSON.stringify({
13684
13940
  timestamp: this.currentSession.updatedAt,
13685
13941
  iteration: snapshot.state.iteration,
13686
13942
  phase: snapshot.state.currentPhase
13687
13943
  }) + "\n";
13688
- await fs6.appendFile(historyPath, historyLine);
13944
+ await fs7.appendFile(historyPath, historyLine);
13689
13945
  this.emit("snapshot_saved", this.currentSession.id);
13690
13946
  }
13691
13947
  /**
@@ -13695,15 +13951,15 @@ var SessionManager2 = class extends EventEmitter19 {
13695
13951
  try {
13696
13952
  const sessionDir = path5.join(this.sessionsDir, sessionId);
13697
13953
  const metadataPath = path5.join(sessionDir, "metadata.json");
13698
- const metadataContent = await fs6.readFile(metadataPath, "utf-8");
13954
+ const metadataContent = await fs7.readFile(metadataPath, "utf-8");
13699
13955
  const metadata = JSON.parse(metadataContent);
13700
13956
  const statePath = path5.join(sessionDir, "state.json");
13701
- const stateContent = await fs6.readFile(statePath, "utf-8");
13957
+ const stateContent = await fs7.readFile(statePath, "utf-8");
13702
13958
  const state = JSON.parse(stateContent);
13703
13959
  const configPath = path5.join(sessionDir, "config.json");
13704
13960
  let config = {};
13705
13961
  try {
13706
- const configContent = await fs6.readFile(configPath, "utf-8");
13962
+ const configContent = await fs7.readFile(configPath, "utf-8");
13707
13963
  config = JSON.parse(configContent);
13708
13964
  } catch {
13709
13965
  }
@@ -13720,13 +13976,13 @@ var SessionManager2 = class extends EventEmitter19 {
13720
13976
  async listSessions() {
13721
13977
  await this.initialize();
13722
13978
  try {
13723
- const entries = await fs6.readdir(this.sessionsDir, { withFileTypes: true });
13979
+ const entries = await fs7.readdir(this.sessionsDir, { withFileTypes: true });
13724
13980
  const sessions = [];
13725
13981
  for (const entry of entries) {
13726
13982
  if (entry.isDirectory()) {
13727
13983
  try {
13728
13984
  const metadataPath = path5.join(this.sessionsDir, entry.name, "metadata.json");
13729
- const content = await fs6.readFile(metadataPath, "utf-8");
13985
+ const content = await fs7.readFile(metadataPath, "utf-8");
13730
13986
  const metadata = JSON.parse(content);
13731
13987
  sessions.push({
13732
13988
  id: metadata.id,
@@ -13776,14 +14032,14 @@ var SessionManager2 = class extends EventEmitter19 {
13776
14032
  await this.initialize();
13777
14033
  let deletedCount = 0;
13778
14034
  try {
13779
- const entries = await fs6.readdir(this.sessionsDir, { withFileTypes: true });
14035
+ const entries = await fs7.readdir(this.sessionsDir, { withFileTypes: true });
13780
14036
  for (const entry of entries) {
13781
14037
  if (entry.isDirectory()) {
13782
14038
  const metadataPath = path5.join(this.sessionsDir, entry.name, "metadata.json");
13783
14039
  try {
13784
- await fs6.access(metadataPath);
14040
+ await fs7.access(metadataPath);
13785
14041
  } catch {
13786
- await fs6.rm(path5.join(this.sessionsDir, entry.name), { recursive: true });
14042
+ await fs7.rm(path5.join(this.sessionsDir, entry.name), { recursive: true });
13787
14043
  deletedCount++;
13788
14044
  }
13789
14045
  }
@@ -13806,7 +14062,7 @@ var SessionManager2 = class extends EventEmitter19 {
13806
14062
  async deleteSession(sessionId) {
13807
14063
  try {
13808
14064
  const sessionDir = path5.join(this.sessionsDir, sessionId);
13809
- await fs6.rm(sessionDir, { recursive: true });
14065
+ await fs7.rm(sessionDir, { recursive: true });
13810
14066
  this.emit("session_deleted", sessionId);
13811
14067
  return true;
13812
14068
  } catch {
@@ -13938,7 +14194,7 @@ function getSlashCommandRegistry() {
13938
14194
  // src/core/context/context-manager.ts
13939
14195
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
13940
14196
  import { join as join6, dirname as dirname2 } from "path";
13941
- import { EventEmitter as EventEmitter20 } from "events";
14197
+ import { EventEmitter as EventEmitter19 } from "events";
13942
14198
  var CONTEXT_EVENT = {
13943
14199
  MESSAGE_ADDED: "message_added",
13944
14200
  CHECKPOINT_CREATED: "checkpoint_created",
@@ -13946,7 +14202,7 @@ var CONTEXT_EVENT = {
13946
14202
  CLEARED: "cleared",
13947
14203
  COMPACTED: "compacted"
13948
14204
  };
13949
- var ContextManager2 = class extends EventEmitter20 {
14205
+ var ContextManager2 = class extends EventEmitter19 {
13950
14206
  filePath;
13951
14207
  state;
13952
14208
  maxMessages;
@@ -14728,7 +14984,7 @@ import { homedir as homedir2 } from "os";
14728
14984
  import { join as join9 } from "path";
14729
14985
 
14730
14986
  // src/utils/input-queue.ts
14731
- import { EventEmitter as EventEmitter21 } from "events";
14987
+ import { EventEmitter as EventEmitter20 } from "events";
14732
14988
  var INPUT_QUEUE_EVENT = {
14733
14989
  QUEUED: "queued",
14734
14990
  // Message added to queue
@@ -14741,7 +14997,7 @@ var INPUT_QUEUE_EVENT = {
14741
14997
  SHUTDOWN: "shutdown"
14742
14998
  // Queue shutdown
14743
14999
  };
14744
- var InputQueue = class extends EventEmitter21 {
15000
+ var InputQueue = class extends EventEmitter20 {
14745
15001
  queue = [];
14746
15002
  isShutdown = false;
14747
15003
  isPaused = false;
@@ -14888,7 +15144,6 @@ var App = ({ autoApprove = false, target }) => {
14888
15144
  const [mode, setMode] = useState("agent");
14889
15145
  const [checkpointCount, setCheckpointCount] = useState(0);
14890
15146
  const [pendingTargetConfirmation, setPendingTargetConfirmation] = useState(null);
14891
- const [spinnerHue, setSpinnerHue] = useState(0);
14892
15147
  const [queuedCount, setQueuedCount] = useState(0);
14893
15148
  const [, forceUpdate] = useState(0);
14894
15149
  const updateProcessing = useCallback((val) => {
@@ -14932,7 +15187,7 @@ var App = ({ autoApprove = false, target }) => {
14932
15187
  setCheckpointCount(contextManagerRef.current?.getCheckpoints().length || 0);
14933
15188
  }
14934
15189
  });
14935
- import("./auto-update-GKO64QMO.js").then(({ checkForUpdateAsync, formatUpdateNotification }) => {
15190
+ import("./auto-update-QJJRAZRM.js").then(({ checkForUpdateAsync, formatUpdateNotification }) => {
14936
15191
  checkForUpdateAsync().then((result) => {
14937
15192
  if (result.hasUpdate) {
14938
15193
  const notification = formatUpdateNotification(result);
@@ -14958,6 +15213,20 @@ var App = ({ autoApprove = false, target }) => {
14958
15213
  }
14959
15214
  });
14960
15215
  }, [sessionManager2]);
15216
+ useEffect(() => {
15217
+ const queue = getInputQueue();
15218
+ const syncQueueState = () => {
15219
+ setQueuedCount(queue.length);
15220
+ };
15221
+ queue.on(INPUT_QUEUE_EVENT.QUEUED, syncQueueState);
15222
+ queue.on(INPUT_QUEUE_EVENT.DEQUEUED, syncQueueState);
15223
+ queue.on(INPUT_QUEUE_EVENT.CLEARED, syncQueueState);
15224
+ return () => {
15225
+ queue.off(INPUT_QUEUE_EVENT.QUEUED, syncQueueState);
15226
+ queue.off(INPUT_QUEUE_EVENT.DEQUEUED, syncQueueState);
15227
+ queue.off(INPUT_QUEUE_EVENT.CLEARED, syncQueueState);
15228
+ };
15229
+ }, []);
14961
15230
  const startTimeRef = useRef(0);
14962
15231
  const timerRef = useRef(null);
14963
15232
  const addMessage = useCallback((type, content, extraOrDuration) => {
@@ -14980,9 +15249,8 @@ var App = ({ autoApprove = false, target }) => {
14980
15249
  const startTimer = useCallback(() => {
14981
15250
  startTimeRef.current = Date.now();
14982
15251
  timerRef.current = setInterval(() => {
14983
- setElapsedTime(Math.floor((Date.now() - startTimeRef.current) / 100) / 10);
14984
- setSpinnerHue((h) => (h + 15) % 360);
14985
- }, 100);
15252
+ setElapsedTime(Math.floor((Date.now() - startTimeRef.current) / 1e3));
15253
+ }, 1e3);
14986
15254
  }, []);
14987
15255
  const stopTimer = useCallback(() => {
14988
15256
  if (timerRef.current) {
@@ -15159,10 +15427,24 @@ var App = ({ autoApprove = false, target }) => {
15159
15427
  }, [agent, target, addMessage, stopTimer, autoApprove, approvalManager2]);
15160
15428
  const handleExit = useCallback(async () => {
15161
15429
  setCurrentStatus("Exiting...");
15162
- await agent.shutdown();
15163
- setTimeout(() => {
15430
+ try {
15431
+ if (timerRef.current) {
15432
+ clearInterval(timerRef.current);
15433
+ timerRef.current = null;
15434
+ }
15435
+ const queue = getInputQueue();
15436
+ queue.shutdown();
15437
+ await Promise.race([
15438
+ agent.shutdown(),
15439
+ new Promise((resolve) => setTimeout(resolve, 3e3))
15440
+ ]);
15441
+ const rm2 = getResourceManager();
15442
+ await rm2.performCleanup();
15443
+ exit();
15444
+ } catch (err) {
15445
+ console.error("Shutdown error:", err);
15164
15446
  exit();
15165
- }, 150);
15447
+ }
15166
15448
  }, [agent, exit]);
15167
15449
  useEffect(() => {
15168
15450
  const onExit = () => {
@@ -15228,10 +15510,7 @@ var App = ({ autoApprove = false, target }) => {
15228
15510
  }
15229
15511
  }
15230
15512
  if (trimmed === "/exit" || trimmed === "/quit" || trimmed === "/q") {
15231
- if (isProcessing && agent) {
15232
- agent.pause();
15233
- }
15234
- exit();
15513
+ await handleExit();
15235
15514
  return;
15236
15515
  }
15237
15516
  if (!isProcessing && !trimmed.startsWith("/") && !pendingTargetConfirmation && !pendingApproval) {
@@ -15526,7 +15805,7 @@ ${list}`);
15526
15805
  case CLI_COMMAND.EXIT:
15527
15806
  case "quit":
15528
15807
  case "q":
15529
- exit();
15808
+ await handleExit();
15530
15809
  return;
15531
15810
  case "approve":
15532
15811
  case "y":
@@ -15703,7 +15982,7 @@ ${list}`);
15703
15982
  return;
15704
15983
  case "update":
15705
15984
  try {
15706
- const { checkForUpdate, formatUpdateNotification, doUpdate } = await import("./update-UMRID565.js");
15985
+ const { checkForUpdate, formatUpdateNotification, doUpdate } = await import("./update-7HXYJ62R.js");
15707
15986
  const result = checkForUpdate(true);
15708
15987
  if (result.hasUpdate) {
15709
15988
  const notification = formatUpdateNotification(result);
@@ -15761,7 +16040,7 @@ ${list}`);
15761
16040
  }, (error, stdout2, stderr2) => {
15762
16041
  if (child.pid) resourceManager.untrackProcess(child.pid);
15763
16042
  if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
15764
- else resolve({ stdout: stdout2.toString(), stderr: stderr2.toString() });
16043
+ else resolve({ stdout: String(stdout2), stderr: String(stderr2) });
15765
16044
  });
15766
16045
  if (child.pid) resourceManager.trackProcess(child.pid);
15767
16046
  });
@@ -15931,10 +16210,25 @@ ${list}`);
15931
16210
  }
15932
16211
  )
15933
16212
  ] }),
15934
- isProcessing && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
15935
- "ESC to interrupt \u2502 ",
15936
- queuedCount > 0 ? `\u{1F4E5} ${queuedCount} messages queued` : "Chatting enabled during execution"
15937
- ] }) })
16213
+ isProcessing && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
16214
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
16215
+ "ESC to interrupt \u2502 ",
16216
+ queuedCount > 0 ? `\u{1F4E5} ${queuedCount} messages queued` : "Chatting enabled during execution"
16217
+ ] }),
16218
+ queuedCount > 0 && (() => {
16219
+ try {
16220
+ const pending = getInputQueue().getPending().slice(0, 3);
16221
+ return pending.map((item, i) => /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
16222
+ " ",
16223
+ i + 1,
16224
+ ". ",
16225
+ String(item.content || item).slice(0, 60)
16226
+ ] }, i));
16227
+ } catch {
16228
+ return null;
16229
+ }
16230
+ })()
16231
+ ] })
15938
16232
  ] }),
15939
16233
  /* @__PURE__ */ jsx3(
15940
16234
  status_bar_default,
@@ -16022,8 +16316,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
16022
16316
  const summary = agent.getSummary();
16023
16317
  console.log(JSON.stringify(summary, null, 2));
16024
16318
  if (options.output) {
16025
- const fs7 = await import("fs/promises");
16026
- await fs7.writeFile(options.output, JSON.stringify(summary, null, 2));
16319
+ const fs8 = await import("fs/promises");
16320
+ await fs8.writeFile(options.output, JSON.stringify(summary, null, 2));
16027
16321
  console.log(chalk.hex(THEME.text.accent)(`
16028
16322
  [+] Report saved to: ${options.output}`));
16029
16323
  }