pentesting 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js
CHANGED
|
@@ -90,7 +90,21 @@ var DISPLAY_LIMITS = {
|
|
|
90
90
|
/** Max endpoints to sample */
|
|
91
91
|
ENDPOINT_SAMPLE: 5,
|
|
92
92
|
/** Purpose truncation length */
|
|
93
|
-
PURPOSE_TRUNCATE: 27
|
|
93
|
+
PURPOSE_TRUNCATE: 27,
|
|
94
|
+
/** Max characters for command description in background process */
|
|
95
|
+
COMMAND_DESCRIPTION_PREVIEW: 80,
|
|
96
|
+
/** Max characters for hash preview in tool output */
|
|
97
|
+
HASH_PREVIEW_LENGTH: 20,
|
|
98
|
+
/** Max characters for loot detail preview */
|
|
99
|
+
LOOT_DETAIL_PREVIEW: 30,
|
|
100
|
+
/** Max characters for link text preview in browser */
|
|
101
|
+
LINK_TEXT_PREVIEW: 100,
|
|
102
|
+
/** Prefix length for API key redaction in logs */
|
|
103
|
+
API_KEY_PREFIX: 8,
|
|
104
|
+
/** Prefix length for sensitive value redaction */
|
|
105
|
+
REDACT_PREFIX: 4,
|
|
106
|
+
/** Max characters for raw JSON error preview */
|
|
107
|
+
RAW_JSON_ERROR_PREVIEW: 500
|
|
94
108
|
};
|
|
95
109
|
var AGENT_LIMITS = {
|
|
96
110
|
/** Maximum agent loop iterations — generous to allow complex tasks.
|
|
@@ -113,17 +127,27 @@ var AGENT_LIMITS = {
|
|
|
113
127
|
/** ID radix for generation */
|
|
114
128
|
ID_RADIX: 36,
|
|
115
129
|
/** Maximum token budget for LLM response (matches LLM_LIMITS.streamMaxTokens) */
|
|
116
|
-
MAX_TOKENS:
|
|
130
|
+
MAX_TOKENS: 32768,
|
|
117
131
|
/** Maximum consecutive idle iterations before nudging agent (deadlock prevention) */
|
|
118
132
|
MAX_CONSECUTIVE_IDLE: 3,
|
|
119
133
|
/** Maximum tool output length before truncation (context hygiene) */
|
|
120
|
-
MAX_TOOL_OUTPUT_LENGTH:
|
|
134
|
+
MAX_TOOL_OUTPUT_LENGTH: 2e4,
|
|
121
135
|
/** Max chars to include in blocked pattern tracking key (loop detection) */
|
|
122
136
|
BLOCKED_PATTERN_KEY_SLICE: 80,
|
|
123
137
|
/** Max chars of error text to include in web_search suggestion */
|
|
124
138
|
ERROR_SEARCH_PREVIEW_SLICE: 50,
|
|
125
139
|
/** How many consecutive identical blocks before warning + forcing alternative */
|
|
126
|
-
MAX_BLOCKED_BEFORE_WARN: 2
|
|
140
|
+
MAX_BLOCKED_BEFORE_WARN: 2,
|
|
141
|
+
/** Maximum action log entries to retain in memory.
|
|
142
|
+
* WHY: actionLog grows unboundedly during long engagements.
|
|
143
|
+
* Older entries are pruned to prevent memory bloat and oversized session files.
|
|
144
|
+
*/
|
|
145
|
+
MAX_ACTION_LOG: 200,
|
|
146
|
+
/** Maximum session files to keep on disk.
|
|
147
|
+
* WHY: Each save creates a timestamped .json in .pentesting/sessions/.
|
|
148
|
+
* Without rotation, disk usage grows unboundedly across engagements.
|
|
149
|
+
*/
|
|
150
|
+
MAX_SESSION_FILES: 10
|
|
127
151
|
};
|
|
128
152
|
|
|
129
153
|
// src/shared/constants/patterns.ts
|
|
@@ -150,14 +174,16 @@ var EXIT_CODES = {
|
|
|
150
174
|
/** Process killed by SIGINT (Ctrl+C) */
|
|
151
175
|
SIGINT: 130,
|
|
152
176
|
/** Process killed by SIGTERM */
|
|
153
|
-
SIGTERM: 143
|
|
177
|
+
SIGTERM: 143,
|
|
178
|
+
/** Process killed by SIGKILL */
|
|
179
|
+
SIGKILL: 137
|
|
154
180
|
};
|
|
155
181
|
|
|
156
182
|
// src/shared/constants/agent.ts
|
|
157
183
|
var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
|
|
158
184
|
var ID_RADIX = AGENT_LIMITS.ID_RADIX;
|
|
159
185
|
var APP_NAME = "Pentest AI";
|
|
160
|
-
var APP_VERSION = "0.
|
|
186
|
+
var APP_VERSION = "0.24.0";
|
|
161
187
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
162
188
|
var LLM_ROLES = {
|
|
163
189
|
SYSTEM: "system",
|
|
@@ -456,13 +482,13 @@ var DEFAULTS = {
|
|
|
456
482
|
};
|
|
457
483
|
|
|
458
484
|
// src/engine/process-manager.ts
|
|
459
|
-
import { spawn as
|
|
460
|
-
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as
|
|
485
|
+
import { spawn as spawn3, execSync as execSync2 } from "child_process";
|
|
486
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
461
487
|
|
|
462
488
|
// src/engine/tools-base.ts
|
|
463
|
-
import { spawn } from "child_process";
|
|
464
|
-
import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
465
|
-
import { join, dirname } from "path";
|
|
489
|
+
import { spawn as spawn2 } from "child_process";
|
|
490
|
+
import { readFileSync, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
491
|
+
import { join as join2, dirname } from "path";
|
|
466
492
|
import { tmpdir } from "os";
|
|
467
493
|
|
|
468
494
|
// src/shared/utils/file-utils.ts
|
|
@@ -571,7 +597,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
571
597
|
"python"
|
|
572
598
|
];
|
|
573
599
|
|
|
574
|
-
// src/shared/utils/command-
|
|
600
|
+
// src/shared/utils/command-security-lists.ts
|
|
575
601
|
var ALLOWED_BINARIES = /* @__PURE__ */ new Set([
|
|
576
602
|
// Network scanning
|
|
577
603
|
"nmap",
|
|
@@ -799,6 +825,126 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
|
799
825
|
"nft",
|
|
800
826
|
"crontab"
|
|
801
827
|
]);
|
|
828
|
+
|
|
829
|
+
// src/shared/utils/debug-logger.ts
|
|
830
|
+
import { appendFileSync, writeFileSync } from "fs";
|
|
831
|
+
import { join } from "path";
|
|
832
|
+
|
|
833
|
+
// src/shared/constants/paths.ts
|
|
834
|
+
import path from "path";
|
|
835
|
+
import { fileURLToPath } from "url";
|
|
836
|
+
import { homedir } from "os";
|
|
837
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
838
|
+
var __dirname = path.dirname(__filename);
|
|
839
|
+
var PROJECT_ROOT = path.resolve(__dirname, "../../../");
|
|
840
|
+
var WORKSPACE_DIR_NAME = ".pentesting";
|
|
841
|
+
function getWorkspaceRoot() {
|
|
842
|
+
return path.join(homedir(), WORKSPACE_DIR_NAME);
|
|
843
|
+
}
|
|
844
|
+
var WORKSPACE = {
|
|
845
|
+
/** Root directory (resolved lazily via getWorkspaceRoot) */
|
|
846
|
+
get ROOT() {
|
|
847
|
+
return getWorkspaceRoot();
|
|
848
|
+
},
|
|
849
|
+
/** Per-session state snapshots */
|
|
850
|
+
get SESSIONS() {
|
|
851
|
+
return path.join(getWorkspaceRoot(), "sessions");
|
|
852
|
+
},
|
|
853
|
+
/** Debug logs */
|
|
854
|
+
get DEBUG() {
|
|
855
|
+
return path.join(getWorkspaceRoot(), "debug");
|
|
856
|
+
},
|
|
857
|
+
/** Generated reports */
|
|
858
|
+
get REPORTS() {
|
|
859
|
+
return path.join(getWorkspaceRoot(), "reports");
|
|
860
|
+
},
|
|
861
|
+
/** Downloaded loot, captured files */
|
|
862
|
+
get LOOT() {
|
|
863
|
+
return path.join(getWorkspaceRoot(), "loot");
|
|
864
|
+
},
|
|
865
|
+
/** Temporary files for active operations */
|
|
866
|
+
get TEMP() {
|
|
867
|
+
return path.join(getWorkspaceRoot(), "temp");
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// src/shared/utils/debug-logger.ts
|
|
872
|
+
var DebugLogger = class _DebugLogger {
|
|
873
|
+
static instance;
|
|
874
|
+
logPath;
|
|
875
|
+
initialized = false;
|
|
876
|
+
constructor(clearOnInit = false) {
|
|
877
|
+
const debugDir = WORKSPACE.DEBUG;
|
|
878
|
+
try {
|
|
879
|
+
ensureDirExists(debugDir);
|
|
880
|
+
this.logPath = join(debugDir, "debug.log");
|
|
881
|
+
if (clearOnInit) {
|
|
882
|
+
this.clear();
|
|
883
|
+
}
|
|
884
|
+
this.initialized = true;
|
|
885
|
+
this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
|
|
886
|
+
this.log("general", `Log file: ${this.logPath}`);
|
|
887
|
+
} catch (e) {
|
|
888
|
+
console.error("[DebugLogger] Failed to initialize:", e);
|
|
889
|
+
this.logPath = "";
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
static getInstance(clearOnInit = false) {
|
|
893
|
+
if (!_DebugLogger.instance) {
|
|
894
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
895
|
+
}
|
|
896
|
+
return _DebugLogger.instance;
|
|
897
|
+
}
|
|
898
|
+
/** Reset the singleton instance (used by initDebugLogger) */
|
|
899
|
+
static resetInstance(clearOnInit = false) {
|
|
900
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
901
|
+
return _DebugLogger.instance;
|
|
902
|
+
}
|
|
903
|
+
log(category, message, data) {
|
|
904
|
+
if (!this.initialized || !this.logPath) return;
|
|
905
|
+
try {
|
|
906
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
907
|
+
let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
|
|
908
|
+
if (data !== void 0) {
|
|
909
|
+
logLine += ` | ${JSON.stringify(data)}`;
|
|
910
|
+
}
|
|
911
|
+
logLine += "\n";
|
|
912
|
+
appendFileSync(this.logPath, logLine);
|
|
913
|
+
} catch (e) {
|
|
914
|
+
console.error("[DebugLogger] Write error:", e);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
logRaw(category, label, raw) {
|
|
918
|
+
if (!this.initialized || !this.logPath) return;
|
|
919
|
+
try {
|
|
920
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
921
|
+
const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
|
|
922
|
+
${raw}
|
|
923
|
+
---
|
|
924
|
+
`;
|
|
925
|
+
appendFileSync(this.logPath, logLine);
|
|
926
|
+
} catch (e) {
|
|
927
|
+
console.error("[DebugLogger] Write error:", e);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
clear() {
|
|
931
|
+
if (!this.logPath) return;
|
|
932
|
+
try {
|
|
933
|
+
writeFileSync(this.logPath, "");
|
|
934
|
+
} catch (e) {
|
|
935
|
+
console.error("[DebugLogger] Clear error:", e);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
var logger = DebugLogger.getInstance(false);
|
|
940
|
+
function initDebugLogger() {
|
|
941
|
+
DebugLogger.resetInstance(true);
|
|
942
|
+
}
|
|
943
|
+
function debugLog(category, message, data) {
|
|
944
|
+
logger.log(category, message, data);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/shared/utils/command-validator.ts
|
|
802
948
|
function validateCommand(command) {
|
|
803
949
|
if (!command || typeof command !== "string") {
|
|
804
950
|
return { safe: false, error: "Empty or invalid command" };
|
|
@@ -893,7 +1039,7 @@ function validateSingleCommand(command) {
|
|
|
893
1039
|
};
|
|
894
1040
|
}
|
|
895
1041
|
if (!ALLOWED_BINARIES.has(binary)) {
|
|
896
|
-
|
|
1042
|
+
debugLog("security", `Unknown binary '${binary}' - use with caution`);
|
|
897
1043
|
}
|
|
898
1044
|
}
|
|
899
1045
|
const redirectResult = validateRedirects(command);
|
|
@@ -992,7 +1138,8 @@ function extractBinary(command) {
|
|
|
992
1138
|
return binary.toLowerCase();
|
|
993
1139
|
}
|
|
994
1140
|
|
|
995
|
-
// src/engine/
|
|
1141
|
+
// src/engine/tool-auto-installer.ts
|
|
1142
|
+
import { spawn } from "child_process";
|
|
996
1143
|
var TOOL_PACKAGE_MAP = {
|
|
997
1144
|
"nmap": { apt: "nmap", brew: "nmap" },
|
|
998
1145
|
"masscan": { apt: "masscan", brew: "masscan" },
|
|
@@ -1046,15 +1193,7 @@ var TOOL_PACKAGE_MAP = {
|
|
|
1046
1193
|
"evil-winrm": { apt: "evil-winrm", brew: "evil-winrm" },
|
|
1047
1194
|
"netexec": { apt: "netexec", brew: "netexec" }
|
|
1048
1195
|
};
|
|
1049
|
-
var globalEventEmitter = null;
|
|
1050
|
-
function setCommandEventEmitter(emitter) {
|
|
1051
|
-
globalEventEmitter = emitter;
|
|
1052
|
-
}
|
|
1053
1196
|
var installedTools = /* @__PURE__ */ new Set();
|
|
1054
|
-
var globalInputHandler = null;
|
|
1055
|
-
function setInputHandler(handler) {
|
|
1056
|
-
globalInputHandler = handler;
|
|
1057
|
-
}
|
|
1058
1197
|
async function detectPackageManager() {
|
|
1059
1198
|
const managers = [
|
|
1060
1199
|
{ name: "apt", check: "which apt-get" },
|
|
@@ -1091,7 +1230,7 @@ function isCommandNotFound(stderr, stdout) {
|
|
|
1091
1230
|
const toolName = toolMatch ? toolMatch[1] : null;
|
|
1092
1231
|
return { missing: true, toolName };
|
|
1093
1232
|
}
|
|
1094
|
-
async function installTool(toolName) {
|
|
1233
|
+
async function installTool(toolName, eventEmitter, inputHandler) {
|
|
1095
1234
|
const pkgInfo = TOOL_PACKAGE_MAP[toolName];
|
|
1096
1235
|
if (!pkgInfo) {
|
|
1097
1236
|
return {
|
|
@@ -1114,7 +1253,7 @@ async function installTool(toolName) {
|
|
|
1114
1253
|
};
|
|
1115
1254
|
}
|
|
1116
1255
|
const packageName = pkgInfo[pkgManager] || pkgInfo.apt;
|
|
1117
|
-
|
|
1256
|
+
eventEmitter?.({
|
|
1118
1257
|
type: COMMAND_EVENT_TYPES.TOOL_INSTALL,
|
|
1119
1258
|
message: `Installing missing tool: ${toolName}`,
|
|
1120
1259
|
detail: `Using ${pkgManager} to install ${packageName}`
|
|
@@ -1134,10 +1273,10 @@ async function installTool(toolName) {
|
|
|
1134
1273
|
let stdout = "";
|
|
1135
1274
|
let stderr = "";
|
|
1136
1275
|
const checkForSudoPrompt = async (data) => {
|
|
1137
|
-
if (!
|
|
1276
|
+
if (!inputHandler) return;
|
|
1138
1277
|
for (const pattern of INPUT_PROMPT_PATTERNS) {
|
|
1139
1278
|
if (pattern.test(data)) {
|
|
1140
|
-
const userInput = await
|
|
1279
|
+
const userInput = await inputHandler(data.trim());
|
|
1141
1280
|
if (userInput !== null && child.stdin.writable) {
|
|
1142
1281
|
child.stdin.write(userInput + "\n");
|
|
1143
1282
|
}
|
|
@@ -1157,14 +1296,14 @@ async function installTool(toolName) {
|
|
|
1157
1296
|
});
|
|
1158
1297
|
child.on("close", (code) => {
|
|
1159
1298
|
if (code === 0) {
|
|
1160
|
-
|
|
1299
|
+
eventEmitter?.({
|
|
1161
1300
|
type: COMMAND_EVENT_TYPES.TOOL_INSTALLED,
|
|
1162
1301
|
message: `Successfully installed: ${toolName}`,
|
|
1163
1302
|
detail: `Package: ${packageName}`
|
|
1164
1303
|
});
|
|
1165
1304
|
resolve({ success: true, output: `Successfully installed ${toolName}` });
|
|
1166
1305
|
} else {
|
|
1167
|
-
|
|
1306
|
+
eventEmitter?.({
|
|
1168
1307
|
type: COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED,
|
|
1169
1308
|
message: `Failed to install: ${toolName}`,
|
|
1170
1309
|
detail: stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)
|
|
@@ -1180,6 +1319,16 @@ async function installTool(toolName) {
|
|
|
1180
1319
|
});
|
|
1181
1320
|
});
|
|
1182
1321
|
}
|
|
1322
|
+
|
|
1323
|
+
// src/engine/tools-base.ts
|
|
1324
|
+
var globalEventEmitter = null;
|
|
1325
|
+
function setCommandEventEmitter(emitter) {
|
|
1326
|
+
globalEventEmitter = emitter;
|
|
1327
|
+
}
|
|
1328
|
+
var globalInputHandler = null;
|
|
1329
|
+
function setInputHandler(handler) {
|
|
1330
|
+
globalInputHandler = handler;
|
|
1331
|
+
}
|
|
1183
1332
|
async function runCommand(command, args = [], options = {}) {
|
|
1184
1333
|
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
1185
1334
|
const validation = validateCommand(fullCommand);
|
|
@@ -1208,7 +1357,7 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1208
1357
|
message: `${STATUS_MARKERS.WARNING} Tool not found: ${toolName}`,
|
|
1209
1358
|
detail: `Attempting to install...`
|
|
1210
1359
|
});
|
|
1211
|
-
const installResult = await installTool(toolName);
|
|
1360
|
+
const installResult = await installTool(toolName, globalEventEmitter, globalInputHandler);
|
|
1212
1361
|
if (!installResult.success) {
|
|
1213
1362
|
return {
|
|
1214
1363
|
success: false,
|
|
@@ -1233,7 +1382,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1233
1382
|
type: COMMAND_EVENT_TYPES.COMMAND_START,
|
|
1234
1383
|
message: `Executing: ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${command.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
|
|
1235
1384
|
});
|
|
1236
|
-
const child =
|
|
1385
|
+
const child = spawn2("sh", ["-c", command], {
|
|
1237
1386
|
timeout,
|
|
1238
1387
|
env: { ...process.env, ...options.env },
|
|
1239
1388
|
cwd: options.cwd
|
|
@@ -1339,7 +1488,7 @@ async function writeFileContent(filePath, content) {
|
|
|
1339
1488
|
try {
|
|
1340
1489
|
const dir = dirname(filePath);
|
|
1341
1490
|
ensureDirExists(dir);
|
|
1342
|
-
|
|
1491
|
+
writeFileSync2(filePath, content, "utf-8");
|
|
1343
1492
|
return {
|
|
1344
1493
|
success: true,
|
|
1345
1494
|
output: `Written to ${filePath}`
|
|
@@ -1354,7 +1503,7 @@ async function writeFileContent(filePath, content) {
|
|
|
1354
1503
|
}
|
|
1355
1504
|
}
|
|
1356
1505
|
function createTempFile(suffix = "") {
|
|
1357
|
-
return
|
|
1506
|
+
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1358
1507
|
}
|
|
1359
1508
|
|
|
1360
1509
|
// src/engine/process-detector.ts
|
|
@@ -1512,12 +1661,12 @@ function startBackgroundProcess(command, options = {}) {
|
|
|
1512
1661
|
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
1513
1662
|
let wrappedCmd;
|
|
1514
1663
|
if (isInteractive) {
|
|
1515
|
-
|
|
1664
|
+
writeFileSync3(stdinFile, "", "utf-8");
|
|
1516
1665
|
wrappedCmd = `tail -f ${stdinFile} | ${command} > ${stdoutFile} 2> ${stderrFile}`;
|
|
1517
1666
|
} else {
|
|
1518
1667
|
wrappedCmd = `${command} > ${stdoutFile} 2> ${stderrFile}`;
|
|
1519
1668
|
}
|
|
1520
|
-
const child =
|
|
1669
|
+
const child = spawn3("sh", ["-c", wrappedCmd], {
|
|
1521
1670
|
detached: true,
|
|
1522
1671
|
stdio: "ignore",
|
|
1523
1672
|
cwd: options.cwd,
|
|
@@ -1571,7 +1720,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
|
|
|
1571
1720
|
} catch {
|
|
1572
1721
|
}
|
|
1573
1722
|
try {
|
|
1574
|
-
|
|
1723
|
+
appendFileSync2(proc.stdinFile, input + "\n", "utf-8");
|
|
1575
1724
|
} catch (error) {
|
|
1576
1725
|
return { success: false, output: `Failed to send input: ${error}`, newOutput: "" };
|
|
1577
1726
|
}
|
|
@@ -2140,6 +2289,9 @@ var SharedState = class {
|
|
|
2140
2289
|
timestamp: Date.now(),
|
|
2141
2290
|
...action
|
|
2142
2291
|
});
|
|
2292
|
+
if (this.data.actionLog.length > AGENT_LIMITS.MAX_ACTION_LOG) {
|
|
2293
|
+
this.data.actionLog.splice(0, this.data.actionLog.length - AGENT_LIMITS.MAX_ACTION_LOG);
|
|
2294
|
+
}
|
|
2143
2295
|
}
|
|
2144
2296
|
getRecentActions(count = DISPLAY_LIMITS.COMPACT_LIST_ITEMS) {
|
|
2145
2297
|
return this.data.actionLog.slice(-count);
|
|
@@ -2564,7 +2716,7 @@ Used ports: ${usedPorts.join(", ")}
|
|
|
2564
2716
|
}
|
|
2565
2717
|
try {
|
|
2566
2718
|
const proc = startBackgroundProcess(command, {
|
|
2567
|
-
description: command.slice(0,
|
|
2719
|
+
description: command.slice(0, DISPLAY_LIMITS.COMMAND_DESCRIPTION_PREVIEW),
|
|
2568
2720
|
purpose: params.purpose
|
|
2569
2721
|
});
|
|
2570
2722
|
const portInfo = proc.listeningPort ? `
|
|
@@ -2937,7 +3089,7 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
2937
3089
|
success: true,
|
|
2938
3090
|
output: `Loot recorded: [${lootType}] from ${p.host}
|
|
2939
3091
|
Detail: ${p.detail}
|
|
2940
|
-
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0,
|
|
3092
|
+
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
2941
3093
|
};
|
|
2942
3094
|
}
|
|
2943
3095
|
},
|
|
@@ -2971,10 +3123,6 @@ Detail: ${p.detail}
|
|
|
2971
3123
|
import { execFileSync } from "child_process";
|
|
2972
3124
|
|
|
2973
3125
|
// src/shared/utils/config.ts
|
|
2974
|
-
import path from "path";
|
|
2975
|
-
import { fileURLToPath } from "url";
|
|
2976
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
2977
|
-
var __dirname = path.dirname(__filename);
|
|
2978
3126
|
var ENV_KEYS = {
|
|
2979
3127
|
API_KEY: "PENTEST_API_KEY",
|
|
2980
3128
|
BASE_URL: "PENTEST_BASE_URL",
|
|
@@ -3022,129 +3170,6 @@ var SEARCH_LIMIT = {
|
|
|
3022
3170
|
TIMEOUT_MS: 1e4
|
|
3023
3171
|
};
|
|
3024
3172
|
|
|
3025
|
-
// src/shared/utils/debug-logger.ts
|
|
3026
|
-
import { appendFileSync as appendFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3027
|
-
import { join as join2 } from "path";
|
|
3028
|
-
|
|
3029
|
-
// src/shared/constants/paths.ts
|
|
3030
|
-
import path2 from "path";
|
|
3031
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3032
|
-
import { homedir } from "os";
|
|
3033
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3034
|
-
var __dirname2 = path2.dirname(__filename2);
|
|
3035
|
-
var PROJECT_ROOT = path2.resolve(__dirname2, "../../../");
|
|
3036
|
-
var WORKSPACE_DIR_NAME = ".pentesting";
|
|
3037
|
-
function getWorkspaceRoot() {
|
|
3038
|
-
return path2.join(homedir(), WORKSPACE_DIR_NAME);
|
|
3039
|
-
}
|
|
3040
|
-
var WORKSPACE = {
|
|
3041
|
-
/** Root directory (resolved lazily via getWorkspaceRoot) */
|
|
3042
|
-
get ROOT() {
|
|
3043
|
-
return getWorkspaceRoot();
|
|
3044
|
-
},
|
|
3045
|
-
/** Per-session state snapshots */
|
|
3046
|
-
get SESSIONS() {
|
|
3047
|
-
return path2.join(getWorkspaceRoot(), "sessions");
|
|
3048
|
-
},
|
|
3049
|
-
/** Debug logs */
|
|
3050
|
-
get DEBUG() {
|
|
3051
|
-
return path2.join(getWorkspaceRoot(), "debug");
|
|
3052
|
-
},
|
|
3053
|
-
/** Generated reports */
|
|
3054
|
-
get REPORTS() {
|
|
3055
|
-
return path2.join(getWorkspaceRoot(), "reports");
|
|
3056
|
-
},
|
|
3057
|
-
/** Downloaded loot, captured files */
|
|
3058
|
-
get LOOT() {
|
|
3059
|
-
return path2.join(getWorkspaceRoot(), "loot");
|
|
3060
|
-
},
|
|
3061
|
-
/** Temporary files for active operations */
|
|
3062
|
-
get TEMP() {
|
|
3063
|
-
return path2.join(getWorkspaceRoot(), "temp");
|
|
3064
|
-
}
|
|
3065
|
-
};
|
|
3066
|
-
var PATHS = {
|
|
3067
|
-
ROOT: PROJECT_ROOT,
|
|
3068
|
-
SRC: path2.join(PROJECT_ROOT, "src"),
|
|
3069
|
-
DIST: path2.join(PROJECT_ROOT, "dist")
|
|
3070
|
-
};
|
|
3071
|
-
|
|
3072
|
-
// src/shared/utils/debug-logger.ts
|
|
3073
|
-
var DebugLogger = class _DebugLogger {
|
|
3074
|
-
static instance;
|
|
3075
|
-
logPath;
|
|
3076
|
-
initialized = false;
|
|
3077
|
-
constructor(clearOnInit = false) {
|
|
3078
|
-
const debugDir = WORKSPACE.DEBUG;
|
|
3079
|
-
try {
|
|
3080
|
-
ensureDirExists(debugDir);
|
|
3081
|
-
this.logPath = join2(debugDir, "debug.log");
|
|
3082
|
-
if (clearOnInit) {
|
|
3083
|
-
this.clear();
|
|
3084
|
-
}
|
|
3085
|
-
this.initialized = true;
|
|
3086
|
-
this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
|
|
3087
|
-
this.log("general", `Log file: ${this.logPath}`);
|
|
3088
|
-
} catch (e) {
|
|
3089
|
-
console.error("[DebugLogger] Failed to initialize:", e);
|
|
3090
|
-
this.logPath = "";
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
static getInstance(clearOnInit = false) {
|
|
3094
|
-
if (!_DebugLogger.instance) {
|
|
3095
|
-
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3096
|
-
}
|
|
3097
|
-
return _DebugLogger.instance;
|
|
3098
|
-
}
|
|
3099
|
-
/** Reset the singleton instance (used by initDebugLogger) */
|
|
3100
|
-
static resetInstance(clearOnInit = false) {
|
|
3101
|
-
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3102
|
-
return _DebugLogger.instance;
|
|
3103
|
-
}
|
|
3104
|
-
log(category, message, data) {
|
|
3105
|
-
if (!this.initialized || !this.logPath) return;
|
|
3106
|
-
try {
|
|
3107
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3108
|
-
let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
|
|
3109
|
-
if (data !== void 0) {
|
|
3110
|
-
logLine += ` | ${JSON.stringify(data)}`;
|
|
3111
|
-
}
|
|
3112
|
-
logLine += "\n";
|
|
3113
|
-
appendFileSync2(this.logPath, logLine);
|
|
3114
|
-
} catch (e) {
|
|
3115
|
-
console.error("[DebugLogger] Write error:", e);
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
logRaw(category, label, raw) {
|
|
3119
|
-
if (!this.initialized || !this.logPath) return;
|
|
3120
|
-
try {
|
|
3121
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3122
|
-
const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
|
|
3123
|
-
${raw}
|
|
3124
|
-
---
|
|
3125
|
-
`;
|
|
3126
|
-
appendFileSync2(this.logPath, logLine);
|
|
3127
|
-
} catch (e) {
|
|
3128
|
-
console.error("[DebugLogger] Write error:", e);
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
clear() {
|
|
3132
|
-
if (!this.logPath) return;
|
|
3133
|
-
try {
|
|
3134
|
-
writeFileSync3(this.logPath, "");
|
|
3135
|
-
} catch (e) {
|
|
3136
|
-
console.error("[DebugLogger] Clear error:", e);
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
};
|
|
3140
|
-
var logger = DebugLogger.getInstance(false);
|
|
3141
|
-
function initDebugLogger() {
|
|
3142
|
-
DebugLogger.resetInstance(true);
|
|
3143
|
-
}
|
|
3144
|
-
function debugLog(category, message, data) {
|
|
3145
|
-
logger.log(category, message, data);
|
|
3146
|
-
}
|
|
3147
|
-
|
|
3148
3173
|
// src/engine/tools/web-browser.ts
|
|
3149
3174
|
import { join as join5 } from "path";
|
|
3150
3175
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -3196,7 +3221,7 @@ var PLAYWRIGHT_SCRIPT = {
|
|
|
3196
3221
|
};
|
|
3197
3222
|
|
|
3198
3223
|
// src/engine/tools/web-browser-setup.ts
|
|
3199
|
-
import { spawn as
|
|
3224
|
+
import { spawn as spawn4 } from "child_process";
|
|
3200
3225
|
import { existsSync as existsSync4 } from "fs";
|
|
3201
3226
|
import { join as join3, dirname as dirname2 } from "path";
|
|
3202
3227
|
function getPlaywrightPath() {
|
|
@@ -3217,12 +3242,12 @@ function getPlaywrightPath() {
|
|
|
3217
3242
|
async function checkPlaywright() {
|
|
3218
3243
|
try {
|
|
3219
3244
|
const result2 = await new Promise((resolve) => {
|
|
3220
|
-
const child =
|
|
3245
|
+
const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
|
|
3221
3246
|
let stdout = "";
|
|
3222
3247
|
child.stdout.on("data", (data) => stdout += data);
|
|
3223
3248
|
child.on("close", (code) => {
|
|
3224
3249
|
if (code === 0) {
|
|
3225
|
-
const browserCheck =
|
|
3250
|
+
const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
|
|
3226
3251
|
browserCheck.on("close", (browserCode) => {
|
|
3227
3252
|
resolve({ installed: true, browserInstalled: browserCode === 0 });
|
|
3228
3253
|
});
|
|
@@ -3240,7 +3265,7 @@ async function checkPlaywright() {
|
|
|
3240
3265
|
}
|
|
3241
3266
|
async function installPlaywright() {
|
|
3242
3267
|
return new Promise((resolve) => {
|
|
3243
|
-
const child =
|
|
3268
|
+
const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.INSTALL, PLAYWRIGHT_BROWSER.CHROMIUM], {
|
|
3244
3269
|
shell: true,
|
|
3245
3270
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3246
3271
|
});
|
|
@@ -3262,7 +3287,7 @@ async function installPlaywright() {
|
|
|
3262
3287
|
}
|
|
3263
3288
|
|
|
3264
3289
|
// src/engine/tools/web-browser-script.ts
|
|
3265
|
-
import { spawn as
|
|
3290
|
+
import { spawn as spawn5 } from "child_process";
|
|
3266
3291
|
import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
3267
3292
|
import { join as join4 } from "path";
|
|
3268
3293
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -3289,7 +3314,7 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
|
3289
3314
|
join4(process.cwd(), "node_modules"),
|
|
3290
3315
|
process.env.NODE_PATH || ""
|
|
3291
3316
|
].filter(Boolean).join(":");
|
|
3292
|
-
const child =
|
|
3317
|
+
const child = spawn5(PLAYWRIGHT_CMD.NODE, [scriptPath], {
|
|
3293
3318
|
timeout: timeout + PLAYWRIGHT_SCRIPT.SPAWN_TIMEOUT_BUFFER_MS,
|
|
3294
3319
|
env: { ...process.env, NODE_PATH: nodePathDirs }
|
|
3295
3320
|
});
|
|
@@ -3392,7 +3417,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
3392
3417
|
result.links = await page.evaluate(() => {
|
|
3393
3418
|
return Array.from(document.querySelectorAll('a[href]')).map(a => ({
|
|
3394
3419
|
href: a.href,
|
|
3395
|
-
text: a.textContent.trim().slice(0,
|
|
3420
|
+
text: a.textContent.trim().slice(0, ${DISPLAY_LIMITS.LINK_TEXT_PREVIEW})
|
|
3396
3421
|
})).slice(0, ${BROWSER_LIMITS.MAX_LINKS_EXTRACTION});
|
|
3397
3422
|
});` : ""}
|
|
3398
3423
|
|
|
@@ -3441,11 +3466,11 @@ function formatBrowserOutput(data, options) {
|
|
|
3441
3466
|
}
|
|
3442
3467
|
if (data.links && options.extractLinks && data.links.length > 0) {
|
|
3443
3468
|
lines.push(`## Links (${data.links.length} found)`);
|
|
3444
|
-
data.links.slice(0,
|
|
3469
|
+
data.links.slice(0, DISPLAY_LIMITS.LINKS_PREVIEW).forEach((link) => {
|
|
3445
3470
|
lines.push(`- [${link.text || "no text"}](${link.href})`);
|
|
3446
3471
|
});
|
|
3447
|
-
if (data.links.length >
|
|
3448
|
-
lines.push(`... and ${data.links.length -
|
|
3472
|
+
if (data.links.length > DISPLAY_LIMITS.LINKS_PREVIEW) {
|
|
3473
|
+
lines.push(`... and ${data.links.length - DISPLAY_LIMITS.LINKS_PREVIEW} more`);
|
|
3449
3474
|
}
|
|
3450
3475
|
lines.push("");
|
|
3451
3476
|
}
|
|
@@ -3600,146 +3625,10 @@ async function webSearchWithBrowser(query, engine = "google") {
|
|
|
3600
3625
|
});
|
|
3601
3626
|
}
|
|
3602
3627
|
|
|
3603
|
-
// src/engine/
|
|
3628
|
+
// src/engine/web-search-providers.ts
|
|
3604
3629
|
function getErrorMessage(error) {
|
|
3605
3630
|
return error instanceof Error ? error.message : String(error);
|
|
3606
3631
|
}
|
|
3607
|
-
async function parseNmap(xmlPath) {
|
|
3608
|
-
try {
|
|
3609
|
-
const fileResult = await readFileContent(xmlPath);
|
|
3610
|
-
if (!fileResult.success) {
|
|
3611
|
-
return fileResult;
|
|
3612
|
-
}
|
|
3613
|
-
const xmlContent = fileResult.output;
|
|
3614
|
-
const results = {
|
|
3615
|
-
targets: [],
|
|
3616
|
-
summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
|
|
3617
|
-
};
|
|
3618
|
-
const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
|
|
3619
|
-
const hosts = xmlContent.match(hostRegex) || [];
|
|
3620
|
-
for (const hostBlock of hosts) {
|
|
3621
|
-
const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
|
|
3622
|
-
const ip = ipMatch ? ipMatch[1] : "";
|
|
3623
|
-
const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
|
|
3624
|
-
const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
|
|
3625
|
-
const ports = [];
|
|
3626
|
-
const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
|
|
3627
|
-
let portMatch;
|
|
3628
|
-
while ((portMatch = portRegex.exec(hostBlock)) !== null) {
|
|
3629
|
-
const protocol = portMatch[1];
|
|
3630
|
-
const port = parseInt(portMatch[2]);
|
|
3631
|
-
const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
|
|
3632
|
-
const state = stateMatch ? stateMatch[1] : "";
|
|
3633
|
-
const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
|
|
3634
|
-
const service = serviceMatch ? serviceMatch[1] : void 0;
|
|
3635
|
-
const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
|
|
3636
|
-
if (state === "open") {
|
|
3637
|
-
ports.push({ port, protocol, state, service, version });
|
|
3638
|
-
results.summary.openPorts++;
|
|
3639
|
-
if (service) results.summary.servicesFound++;
|
|
3640
|
-
}
|
|
3641
|
-
}
|
|
3642
|
-
if (ip) {
|
|
3643
|
-
results.targets.push({ ip, hostname, ports });
|
|
3644
|
-
results.summary.totalTargets++;
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
return {
|
|
3648
|
-
success: true,
|
|
3649
|
-
output: JSON.stringify(results, null, 2)
|
|
3650
|
-
};
|
|
3651
|
-
} catch (error) {
|
|
3652
|
-
return {
|
|
3653
|
-
success: false,
|
|
3654
|
-
output: "",
|
|
3655
|
-
error: getErrorMessage(error)
|
|
3656
|
-
};
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
async function searchCVE(service, version) {
|
|
3660
|
-
try {
|
|
3661
|
-
return searchExploitDB(service, version);
|
|
3662
|
-
} catch (error) {
|
|
3663
|
-
return {
|
|
3664
|
-
success: false,
|
|
3665
|
-
output: "",
|
|
3666
|
-
error: getErrorMessage(error)
|
|
3667
|
-
};
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
async function searchExploitDB(service, version) {
|
|
3671
|
-
try {
|
|
3672
|
-
const query = version ? `${service} ${version}` : service;
|
|
3673
|
-
try {
|
|
3674
|
-
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
3675
|
-
encoding: "utf-8",
|
|
3676
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3677
|
-
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
3678
|
-
});
|
|
3679
|
-
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
3680
|
-
return {
|
|
3681
|
-
success: true,
|
|
3682
|
-
output: lines.join("\n") || `No exploits found for ${query}`
|
|
3683
|
-
};
|
|
3684
|
-
} catch (e) {
|
|
3685
|
-
const execError = e;
|
|
3686
|
-
const stderr = String(execError.stderr || "");
|
|
3687
|
-
const stdout = String(execError.stdout || "");
|
|
3688
|
-
if (stderr.includes("No results")) {
|
|
3689
|
-
return {
|
|
3690
|
-
success: true,
|
|
3691
|
-
output: `No exploits found for ${query}`
|
|
3692
|
-
};
|
|
3693
|
-
}
|
|
3694
|
-
return {
|
|
3695
|
-
success: true,
|
|
3696
|
-
output: stdout || `No exploits found for ${query}`
|
|
3697
|
-
};
|
|
3698
|
-
}
|
|
3699
|
-
} catch (error) {
|
|
3700
|
-
return {
|
|
3701
|
-
success: false,
|
|
3702
|
-
output: "",
|
|
3703
|
-
error: getErrorMessage(error)
|
|
3704
|
-
};
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
async function webSearch(query, _engine) {
|
|
3708
|
-
debugLog("search", "webSearch START", { query });
|
|
3709
|
-
try {
|
|
3710
|
-
const apiKey = getSearchApiKey();
|
|
3711
|
-
const apiUrl = getSearchApiUrl();
|
|
3712
|
-
debugLog("search", "Search API config", {
|
|
3713
|
-
hasApiKey: !!apiKey,
|
|
3714
|
-
apiUrl,
|
|
3715
|
-
apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + "..." : null
|
|
3716
|
-
});
|
|
3717
|
-
if (apiKey) {
|
|
3718
|
-
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
3719
|
-
debugLog("search", "Using GLM search");
|
|
3720
|
-
return await searchWithGLM(query, apiKey, apiUrl);
|
|
3721
|
-
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
3722
|
-
debugLog("search", "Using Brave search");
|
|
3723
|
-
return await searchWithBrave(query, apiKey, apiUrl);
|
|
3724
|
-
} else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
|
|
3725
|
-
debugLog("search", "Using Serper search");
|
|
3726
|
-
return await searchWithSerper(query, apiKey, apiUrl);
|
|
3727
|
-
} else {
|
|
3728
|
-
debugLog("search", "Using generic search API");
|
|
3729
|
-
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
|
|
3733
|
-
return await searchWithPlaywright(query);
|
|
3734
|
-
} catch (error) {
|
|
3735
|
-
debugLog("search", "webSearch ERROR", { error: getErrorMessage(error) });
|
|
3736
|
-
return {
|
|
3737
|
-
success: false,
|
|
3738
|
-
output: "",
|
|
3739
|
-
error: getErrorMessage(error)
|
|
3740
|
-
};
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
3632
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
3744
3633
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
3745
3634
|
const requestBody = {
|
|
@@ -3838,52 +3727,193 @@ async function searchWithSerper(query, apiKey, apiUrl) {
|
|
|
3838
3727
|
if (results.length === 0) {
|
|
3839
3728
|
return { success: true, output: `No results found for: ${query}` };
|
|
3840
3729
|
}
|
|
3841
|
-
const formatted = results.map(
|
|
3842
|
-
(r, i) => `${i + 1}. ${r.title || "No title"}
|
|
3843
|
-
${r.link || ""}
|
|
3844
|
-
${r.snippet || ""}`
|
|
3845
|
-
).join("\n\n");
|
|
3846
|
-
debugLog("search", "Serper search COMPLETE", { resultsLength: formatted.length });
|
|
3847
|
-
return { success: true, output: formatted };
|
|
3730
|
+
const formatted = results.map(
|
|
3731
|
+
(r, i) => `${i + 1}. ${r.title || "No title"}
|
|
3732
|
+
${r.link || ""}
|
|
3733
|
+
${r.snippet || ""}`
|
|
3734
|
+
).join("\n\n");
|
|
3735
|
+
debugLog("search", "Serper search COMPLETE", { resultsLength: formatted.length });
|
|
3736
|
+
return { success: true, output: formatted };
|
|
3737
|
+
}
|
|
3738
|
+
async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
3739
|
+
const response = await fetch(apiUrl, {
|
|
3740
|
+
method: "POST",
|
|
3741
|
+
headers: {
|
|
3742
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
3743
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
|
|
3744
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
3745
|
+
},
|
|
3746
|
+
body: JSON.stringify({ query, q: query })
|
|
3747
|
+
});
|
|
3748
|
+
if (!response.ok) {
|
|
3749
|
+
const errorText = await response.text();
|
|
3750
|
+
throw new Error(`Search API error: ${response.status} - ${errorText}`);
|
|
3751
|
+
}
|
|
3752
|
+
const data = await response.json();
|
|
3753
|
+
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
3754
|
+
}
|
|
3755
|
+
async function searchWithPlaywright(query) {
|
|
3756
|
+
debugLog("search", "Playwright Google search START", { query });
|
|
3757
|
+
try {
|
|
3758
|
+
const result2 = await webSearchWithBrowser(query, "google");
|
|
3759
|
+
if (!result2.success) {
|
|
3760
|
+
return {
|
|
3761
|
+
success: false,
|
|
3762
|
+
output: "",
|
|
3763
|
+
error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
|
|
3767
|
+
return {
|
|
3768
|
+
success: true,
|
|
3769
|
+
output: result2.output || `No results found for: ${query}`
|
|
3770
|
+
};
|
|
3771
|
+
} catch (error) {
|
|
3772
|
+
return {
|
|
3773
|
+
success: false,
|
|
3774
|
+
output: "",
|
|
3775
|
+
error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
// src/engine/tools-mid.ts
|
|
3781
|
+
function getErrorMessage2(error) {
|
|
3782
|
+
return error instanceof Error ? error.message : String(error);
|
|
3783
|
+
}
|
|
3784
|
+
async function parseNmap(xmlPath) {
|
|
3785
|
+
try {
|
|
3786
|
+
const fileResult = await readFileContent(xmlPath);
|
|
3787
|
+
if (!fileResult.success) {
|
|
3788
|
+
return fileResult;
|
|
3789
|
+
}
|
|
3790
|
+
const xmlContent = fileResult.output;
|
|
3791
|
+
const results = {
|
|
3792
|
+
targets: [],
|
|
3793
|
+
summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
|
|
3794
|
+
};
|
|
3795
|
+
const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
|
|
3796
|
+
const hosts = xmlContent.match(hostRegex) || [];
|
|
3797
|
+
for (const hostBlock of hosts) {
|
|
3798
|
+
const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
|
|
3799
|
+
const ip = ipMatch ? ipMatch[1] : "";
|
|
3800
|
+
const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
|
|
3801
|
+
const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
|
|
3802
|
+
const ports = [];
|
|
3803
|
+
const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
|
|
3804
|
+
let portMatch;
|
|
3805
|
+
while ((portMatch = portRegex.exec(hostBlock)) !== null) {
|
|
3806
|
+
const protocol = portMatch[1];
|
|
3807
|
+
const port = parseInt(portMatch[2]);
|
|
3808
|
+
const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
|
|
3809
|
+
const state = stateMatch ? stateMatch[1] : "";
|
|
3810
|
+
const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
|
|
3811
|
+
const service = serviceMatch ? serviceMatch[1] : void 0;
|
|
3812
|
+
const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
|
|
3813
|
+
if (state === "open") {
|
|
3814
|
+
ports.push({ port, protocol, state, service, version });
|
|
3815
|
+
results.summary.openPorts++;
|
|
3816
|
+
if (service) results.summary.servicesFound++;
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
if (ip) {
|
|
3820
|
+
results.targets.push({ ip, hostname, ports });
|
|
3821
|
+
results.summary.totalTargets++;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
return {
|
|
3825
|
+
success: true,
|
|
3826
|
+
output: JSON.stringify(results, null, 2)
|
|
3827
|
+
};
|
|
3828
|
+
} catch (error) {
|
|
3829
|
+
return {
|
|
3830
|
+
success: false,
|
|
3831
|
+
output: "",
|
|
3832
|
+
error: getErrorMessage2(error)
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3848
3835
|
}
|
|
3849
|
-
async function
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
});
|
|
3859
|
-
if (!response.ok) {
|
|
3860
|
-
const errorText = await response.text();
|
|
3861
|
-
throw new Error(`Search API error: ${response.status} - ${errorText}`);
|
|
3836
|
+
async function searchCVE(service, version) {
|
|
3837
|
+
try {
|
|
3838
|
+
return searchExploitDB(service, version);
|
|
3839
|
+
} catch (error) {
|
|
3840
|
+
return {
|
|
3841
|
+
success: false,
|
|
3842
|
+
output: "",
|
|
3843
|
+
error: getErrorMessage2(error)
|
|
3844
|
+
};
|
|
3862
3845
|
}
|
|
3863
|
-
const data = await response.json();
|
|
3864
|
-
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
3865
3846
|
}
|
|
3866
|
-
async function
|
|
3867
|
-
debugLog("search", "Playwright Google search START", { query });
|
|
3847
|
+
async function searchExploitDB(service, version) {
|
|
3868
3848
|
try {
|
|
3869
|
-
const
|
|
3870
|
-
|
|
3849
|
+
const query = version ? `${service} ${version}` : service;
|
|
3850
|
+
try {
|
|
3851
|
+
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
3852
|
+
encoding: "utf-8",
|
|
3853
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3854
|
+
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
3855
|
+
});
|
|
3856
|
+
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
3871
3857
|
return {
|
|
3872
|
-
success:
|
|
3873
|
-
output: ""
|
|
3874
|
-
|
|
3858
|
+
success: true,
|
|
3859
|
+
output: lines.join("\n") || `No exploits found for ${query}`
|
|
3860
|
+
};
|
|
3861
|
+
} catch (e) {
|
|
3862
|
+
const execError = e;
|
|
3863
|
+
const stderr = String(execError.stderr || "");
|
|
3864
|
+
const stdout = String(execError.stdout || "");
|
|
3865
|
+
if (stderr.includes("No results")) {
|
|
3866
|
+
return {
|
|
3867
|
+
success: true,
|
|
3868
|
+
output: `No exploits found for ${query}`
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
return {
|
|
3872
|
+
success: true,
|
|
3873
|
+
output: stdout || `No exploits found for ${query}`
|
|
3875
3874
|
};
|
|
3876
3875
|
}
|
|
3877
|
-
|
|
3876
|
+
} catch (error) {
|
|
3878
3877
|
return {
|
|
3879
|
-
success:
|
|
3880
|
-
output:
|
|
3878
|
+
success: false,
|
|
3879
|
+
output: "",
|
|
3880
|
+
error: getErrorMessage2(error)
|
|
3881
3881
|
};
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
async function webSearch(query, _engine) {
|
|
3885
|
+
debugLog("search", "webSearch START", { query });
|
|
3886
|
+
try {
|
|
3887
|
+
const apiKey = getSearchApiKey();
|
|
3888
|
+
const apiUrl = getSearchApiUrl();
|
|
3889
|
+
debugLog("search", "Search API config", {
|
|
3890
|
+
hasApiKey: !!apiKey,
|
|
3891
|
+
apiUrl,
|
|
3892
|
+
apiKeyPrefix: apiKey ? apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." : null
|
|
3893
|
+
});
|
|
3894
|
+
if (apiKey) {
|
|
3895
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
3896
|
+
debugLog("search", "Using GLM search");
|
|
3897
|
+
return await searchWithGLM(query, apiKey, apiUrl);
|
|
3898
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
3899
|
+
debugLog("search", "Using Brave search");
|
|
3900
|
+
return await searchWithBrave(query, apiKey, apiUrl);
|
|
3901
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
|
|
3902
|
+
debugLog("search", "Using Serper search");
|
|
3903
|
+
return await searchWithSerper(query, apiKey, apiUrl);
|
|
3904
|
+
} else {
|
|
3905
|
+
debugLog("search", "Using generic search API");
|
|
3906
|
+
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
|
|
3910
|
+
return await searchWithPlaywright(query);
|
|
3882
3911
|
} catch (error) {
|
|
3912
|
+
debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
|
|
3883
3913
|
return {
|
|
3884
3914
|
success: false,
|
|
3885
3915
|
output: "",
|
|
3886
|
-
error:
|
|
3916
|
+
error: getErrorMessage2(error)
|
|
3887
3917
|
};
|
|
3888
3918
|
}
|
|
3889
3919
|
}
|
|
@@ -4215,6 +4245,10 @@ Can extract forms and inputs for security testing.`,
|
|
|
4215
4245
|
{
|
|
4216
4246
|
name: TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
4217
4247
|
description: `Get OWASP Top 10 vulnerability knowledge (2017, 2021, 2025 editions).
|
|
4248
|
+
This is BOOTSTRAP knowledge for initial direction. Use it to quickly
|
|
4249
|
+
orient your attack approach, then try attacks based on what you learn.
|
|
4250
|
+
If built-in knowledge isn't enough, use web_search for version-specific details.
|
|
4251
|
+
|
|
4218
4252
|
Returns structured information about:
|
|
4219
4253
|
- OWASP Top 10 category details (detection methods, test payloads, tools)
|
|
4220
4254
|
- Common vulnerability patterns
|
|
@@ -4293,10 +4327,14 @@ Returns a step-by-step guide for:
|
|
|
4293
4327
|
},
|
|
4294
4328
|
{
|
|
4295
4329
|
name: TOOL_NAMES.GET_CVE_INFO,
|
|
4296
|
-
description: `Get information about known CVEs
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4330
|
+
description: `Get information about known CVEs from local bootstrap database.
|
|
4331
|
+
This is a STARTING POINT \u2014 use it first for quick lookups on major CVEs.
|
|
4332
|
+
If the CVE isn't found locally or you need more detail, then use web_search.
|
|
4333
|
+
|
|
4334
|
+
Use this to:
|
|
4335
|
+
- Quick-check if a well-known CVE exists locally
|
|
4336
|
+
- Get initial exploit hints for common vulnerabilities
|
|
4337
|
+
- If not found or insufficient, use web_search for full details and PoCs
|
|
4300
4338
|
|
|
4301
4339
|
Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
|
|
4302
4340
|
If CVE not found locally, use web_search to find it online.`,
|
|
@@ -4664,7 +4702,7 @@ Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt
|
|
|
4664
4702
|
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
4665
4703
|
if (background) {
|
|
4666
4704
|
const proc = startBackgroundProcess(cmd, {
|
|
4667
|
-
description: `Cracking hashes: ${hashes.slice(0,
|
|
4705
|
+
description: `Cracking hashes: ${hashes.slice(0, DISPLAY_LIMITS.HASH_PREVIEW_LENGTH)}...`,
|
|
4668
4706
|
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
4669
4707
|
});
|
|
4670
4708
|
return {
|
|
@@ -4771,72 +4809,61 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4771
4809
|
"/usr/share/dirb/",
|
|
4772
4810
|
"/opt/wordlists/"
|
|
4773
4811
|
];
|
|
4774
|
-
const
|
|
4775
|
-
|
|
4812
|
+
const CATEGORY_KEYWORDS = {
|
|
4813
|
+
passwords: ["password", "rockyou"],
|
|
4814
|
+
usernames: ["user"],
|
|
4815
|
+
web: ["web", "content", "raft"],
|
|
4816
|
+
dns: ["dns", "subdomain"],
|
|
4817
|
+
fuzzing: ["fuzz", "injection"],
|
|
4818
|
+
api: ["api"]
|
|
4819
|
+
};
|
|
4820
|
+
const WORDLIST_EXTENSIONS = /* @__PURE__ */ new Set(["txt", "lst", "dict", "list", "wlist"]);
|
|
4821
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set([".", "doc"]);
|
|
4822
|
+
const matchesCategory = (filePath) => {
|
|
4823
|
+
if (!category) return true;
|
|
4824
|
+
const pathLower = filePath.toLowerCase();
|
|
4825
|
+
const keywords = CATEGORY_KEYWORDS[category.toLowerCase()] || [category.toLowerCase()];
|
|
4826
|
+
return keywords.some((kw) => pathLower.includes(kw));
|
|
4827
|
+
};
|
|
4828
|
+
const matchesSearch = (filePath, fileName) => {
|
|
4829
|
+
if (!search) return true;
|
|
4830
|
+
const q = search.toLowerCase();
|
|
4831
|
+
return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
|
|
4832
|
+
};
|
|
4833
|
+
const results = ["# Available Wordlists on System\\n"];
|
|
4776
4834
|
let totalCount = 0;
|
|
4777
|
-
const
|
|
4778
|
-
|
|
4779
|
-
if (!
|
|
4835
|
+
const processFile = (fullPath, fileName) => {
|
|
4836
|
+
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
4837
|
+
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
4838
|
+
const stats = statSync2(fullPath);
|
|
4839
|
+
if (stats.size < minSize) return;
|
|
4840
|
+
if (!matchesCategory(fullPath)) return;
|
|
4841
|
+
if (!matchesSearch(fullPath, fileName)) return;
|
|
4842
|
+
totalCount++;
|
|
4843
|
+
results.push(`### ${fullPath}`);
|
|
4844
|
+
results.push(`- Size: ${Math.round(stats.size / 1024)} KB (${stats.size.toLocaleString()} bytes)`);
|
|
4845
|
+
results.push("");
|
|
4846
|
+
};
|
|
4847
|
+
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
4848
|
+
if (depth > maxDepth || !existsSync7(dirPath)) return;
|
|
4849
|
+
let entries;
|
|
4780
4850
|
try {
|
|
4781
|
-
|
|
4782
|
-
for (const entry of entries) {
|
|
4783
|
-
const fullPath = join10(dirPath, entry.name);
|
|
4784
|
-
if (entry.isDirectory()) {
|
|
4785
|
-
if (entry.name.startsWith(".")) continue;
|
|
4786
|
-
if (entry.name === "doc") continue;
|
|
4787
|
-
scanDir(fullPath, maxDepth, currentDepth + 1);
|
|
4788
|
-
} else if (entry.isFile()) {
|
|
4789
|
-
const ext = entry.name.split(".").pop()?.toLowerCase();
|
|
4790
|
-
if (!["txt", "lst", "dict", "list", "wlist"].includes(ext || "")) {
|
|
4791
|
-
continue;
|
|
4792
|
-
}
|
|
4793
|
-
try {
|
|
4794
|
-
const stats = statSync2(fullPath);
|
|
4795
|
-
const sizeBytes = stats.size;
|
|
4796
|
-
if (sizeBytes < minSize) continue;
|
|
4797
|
-
const relPath = fullPath;
|
|
4798
|
-
const filename = entry.name;
|
|
4799
|
-
const sizeKB = Math.round(sizeBytes / 1024);
|
|
4800
|
-
if (category) {
|
|
4801
|
-
const pathLower = relPath.toLowerCase();
|
|
4802
|
-
let matchesCategory = false;
|
|
4803
|
-
switch (category.toLowerCase()) {
|
|
4804
|
-
case "passwords":
|
|
4805
|
-
matchesCategory = pathLower.includes("password") || pathLower.includes("rockyou");
|
|
4806
|
-
break;
|
|
4807
|
-
case "usernames":
|
|
4808
|
-
matchesCategory = pathLower.includes("user");
|
|
4809
|
-
break;
|
|
4810
|
-
case "web":
|
|
4811
|
-
matchesCategory = pathLower.includes("web") || pathLower.includes("content") || pathLower.includes("raft");
|
|
4812
|
-
break;
|
|
4813
|
-
case "dns":
|
|
4814
|
-
matchesCategory = pathLower.includes("dns") || pathLower.includes("subdomain");
|
|
4815
|
-
break;
|
|
4816
|
-
case "fuzzing":
|
|
4817
|
-
matchesCategory = pathLower.includes("fuzz") || pathLower.includes("injection");
|
|
4818
|
-
break;
|
|
4819
|
-
case "api":
|
|
4820
|
-
matchesCategory = pathLower.includes("api");
|
|
4821
|
-
break;
|
|
4822
|
-
default:
|
|
4823
|
-
matchesCategory = pathLower.includes(category.toLowerCase());
|
|
4824
|
-
}
|
|
4825
|
-
if (!matchesCategory) continue;
|
|
4826
|
-
}
|
|
4827
|
-
if (search && !filename.toLowerCase().includes(search.toLowerCase()) && !relPath.toLowerCase().includes(search.toLowerCase())) {
|
|
4828
|
-
continue;
|
|
4829
|
-
}
|
|
4830
|
-
totalCount++;
|
|
4831
|
-
results.push(`### ${relPath}`);
|
|
4832
|
-
results.push(`- Size: ${sizeKB} KB (${sizeBytes.toLocaleString()} bytes)`);
|
|
4833
|
-
results.push("");
|
|
4834
|
-
} catch {
|
|
4835
|
-
continue;
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
}
|
|
4851
|
+
entries = readdirSync3(dirPath, { withFileTypes: true });
|
|
4839
4852
|
} catch {
|
|
4853
|
+
return;
|
|
4854
|
+
}
|
|
4855
|
+
for (const entry of entries) {
|
|
4856
|
+
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
4857
|
+
const fullPath = join10(dirPath, entry.name);
|
|
4858
|
+
if (entry.isDirectory()) {
|
|
4859
|
+
scanDir(fullPath, maxDepth, depth + 1);
|
|
4860
|
+
continue;
|
|
4861
|
+
}
|
|
4862
|
+
if (!entry.isFile()) continue;
|
|
4863
|
+
try {
|
|
4864
|
+
processFile(fullPath, entry.name);
|
|
4865
|
+
} catch {
|
|
4866
|
+
}
|
|
4840
4867
|
}
|
|
4841
4868
|
};
|
|
4842
4869
|
for (const basePath of scanPaths) {
|
|
@@ -5848,15 +5875,6 @@ var CLOUD_KEYWORDS = [
|
|
|
5848
5875
|
"heroku",
|
|
5849
5876
|
"vercel"
|
|
5850
5877
|
];
|
|
5851
|
-
var PASSIVE_CATEGORIES = [
|
|
5852
|
-
SERVICE_CATEGORIES.NETWORK
|
|
5853
|
-
];
|
|
5854
|
-
var ACTIVE_CATEGORIES = [
|
|
5855
|
-
SERVICE_CATEGORIES.WEB,
|
|
5856
|
-
SERVICE_CATEGORIES.API,
|
|
5857
|
-
SERVICE_CATEGORIES.EMAIL,
|
|
5858
|
-
SERVICE_CATEGORIES.FILE_SHARING
|
|
5859
|
-
];
|
|
5860
5878
|
var DANGER_LEVEL_MAP = {
|
|
5861
5879
|
[SERVICE_CATEGORIES.NETWORK]: DANGER_LEVELS.PASSIVE,
|
|
5862
5880
|
[SERVICE_CATEGORIES.WEB]: DANGER_LEVELS.ACTIVE,
|
|
@@ -5905,80 +5923,80 @@ var ServiceParser = class {
|
|
|
5905
5923
|
|
|
5906
5924
|
// src/domains/registry.ts
|
|
5907
5925
|
import { join as join6, dirname as dirname3 } from "path";
|
|
5908
|
-
import { fileURLToPath as
|
|
5909
|
-
var
|
|
5926
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5927
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
5910
5928
|
var DOMAINS = {
|
|
5911
5929
|
[SERVICE_CATEGORIES.NETWORK]: {
|
|
5912
5930
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
5913
5931
|
name: "Network Infrastructure",
|
|
5914
5932
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
5915
|
-
promptPath: join6(
|
|
5933
|
+
promptPath: join6(__dirname2, "network/prompt.md")
|
|
5916
5934
|
},
|
|
5917
5935
|
[SERVICE_CATEGORIES.WEB]: {
|
|
5918
5936
|
id: SERVICE_CATEGORIES.WEB,
|
|
5919
5937
|
name: "Web Application",
|
|
5920
5938
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
5921
|
-
promptPath: join6(
|
|
5939
|
+
promptPath: join6(__dirname2, "web/prompt.md")
|
|
5922
5940
|
},
|
|
5923
5941
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
5924
5942
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
5925
5943
|
name: "Database Security",
|
|
5926
5944
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
5927
|
-
promptPath: join6(
|
|
5945
|
+
promptPath: join6(__dirname2, "database/prompt.md")
|
|
5928
5946
|
},
|
|
5929
5947
|
[SERVICE_CATEGORIES.AD]: {
|
|
5930
5948
|
id: SERVICE_CATEGORIES.AD,
|
|
5931
5949
|
name: "Active Directory",
|
|
5932
5950
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
5933
|
-
promptPath: join6(
|
|
5951
|
+
promptPath: join6(__dirname2, "ad/prompt.md")
|
|
5934
5952
|
},
|
|
5935
5953
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
5936
5954
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
5937
5955
|
name: "Email Services",
|
|
5938
5956
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
5939
|
-
promptPath: join6(
|
|
5957
|
+
promptPath: join6(__dirname2, "email/prompt.md")
|
|
5940
5958
|
},
|
|
5941
5959
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
5942
5960
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
5943
5961
|
name: "Remote Access",
|
|
5944
5962
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
5945
|
-
promptPath: join6(
|
|
5963
|
+
promptPath: join6(__dirname2, "remote-access/prompt.md")
|
|
5946
5964
|
},
|
|
5947
5965
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
5948
5966
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
5949
5967
|
name: "File Sharing",
|
|
5950
5968
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
5951
|
-
promptPath: join6(
|
|
5969
|
+
promptPath: join6(__dirname2, "file-sharing/prompt.md")
|
|
5952
5970
|
},
|
|
5953
5971
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
5954
5972
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
5955
5973
|
name: "Cloud Infrastructure",
|
|
5956
5974
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
5957
|
-
promptPath: join6(
|
|
5975
|
+
promptPath: join6(__dirname2, "cloud/prompt.md")
|
|
5958
5976
|
},
|
|
5959
5977
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
5960
5978
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
5961
5979
|
name: "Container Systems",
|
|
5962
5980
|
description: "Docker and Kubernetes security testing.",
|
|
5963
|
-
promptPath: join6(
|
|
5981
|
+
promptPath: join6(__dirname2, "container/prompt.md")
|
|
5964
5982
|
},
|
|
5965
5983
|
[SERVICE_CATEGORIES.API]: {
|
|
5966
5984
|
id: SERVICE_CATEGORIES.API,
|
|
5967
5985
|
name: "API Security",
|
|
5968
5986
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
5969
|
-
promptPath: join6(
|
|
5987
|
+
promptPath: join6(__dirname2, "api/prompt.md")
|
|
5970
5988
|
},
|
|
5971
5989
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
5972
5990
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
5973
5991
|
name: "Wireless Networks",
|
|
5974
5992
|
description: "WiFi and Bluetooth security testing.",
|
|
5975
|
-
promptPath: join6(
|
|
5993
|
+
promptPath: join6(__dirname2, "wireless/prompt.md")
|
|
5976
5994
|
},
|
|
5977
5995
|
[SERVICE_CATEGORIES.ICS]: {
|
|
5978
5996
|
id: SERVICE_CATEGORIES.ICS,
|
|
5979
5997
|
name: "Industrial Systems",
|
|
5980
5998
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
5981
|
-
promptPath: join6(
|
|
5999
|
+
promptPath: join6(__dirname2, "ics/prompt.md")
|
|
5982
6000
|
}
|
|
5983
6001
|
};
|
|
5984
6002
|
|
|
@@ -6092,237 +6110,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
6092
6110
|
}
|
|
6093
6111
|
};
|
|
6094
6112
|
|
|
6095
|
-
// src/shared/constants/service-ports.ts
|
|
6096
|
-
var SERVICE_PORTS = {
|
|
6097
|
-
SSH: 22,
|
|
6098
|
-
FTP: 21,
|
|
6099
|
-
TELNET: 23,
|
|
6100
|
-
SMTP: 25,
|
|
6101
|
-
DNS: 53,
|
|
6102
|
-
HTTP: 80,
|
|
6103
|
-
POP3: 110,
|
|
6104
|
-
IMAP: 143,
|
|
6105
|
-
SMB_NETBIOS: 139,
|
|
6106
|
-
SMB: 445,
|
|
6107
|
-
HTTPS: 443,
|
|
6108
|
-
MSSQL: 1433,
|
|
6109
|
-
MYSQL: 3306,
|
|
6110
|
-
RDP: 3389,
|
|
6111
|
-
POSTGRESQL: 5432,
|
|
6112
|
-
REDIS: 6379,
|
|
6113
|
-
HTTP_ALT: 8080,
|
|
6114
|
-
HTTPS_ALT: 8443,
|
|
6115
|
-
MONGODB: 27017,
|
|
6116
|
-
ELASTICSEARCH: 9200,
|
|
6117
|
-
MEMCACHED: 11211,
|
|
6118
|
-
NODE_DEFAULT: 3e3,
|
|
6119
|
-
FLASK_DEFAULT: 5e3,
|
|
6120
|
-
DJANGO_DEFAULT: 8e3
|
|
6121
|
-
};
|
|
6122
|
-
var CRITICAL_SERVICE_PORTS = [
|
|
6123
|
-
SERVICE_PORTS.SSH,
|
|
6124
|
-
SERVICE_PORTS.RDP,
|
|
6125
|
-
SERVICE_PORTS.MYSQL,
|
|
6126
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
6127
|
-
SERVICE_PORTS.REDIS,
|
|
6128
|
-
SERVICE_PORTS.MONGODB
|
|
6129
|
-
];
|
|
6130
|
-
var NO_AUTH_CRITICAL_PORTS = [
|
|
6131
|
-
SERVICE_PORTS.REDIS,
|
|
6132
|
-
SERVICE_PORTS.MONGODB,
|
|
6133
|
-
SERVICE_PORTS.ELASTICSEARCH,
|
|
6134
|
-
SERVICE_PORTS.MEMCACHED
|
|
6135
|
-
];
|
|
6136
|
-
var WEB_SERVICE_PORTS = [
|
|
6137
|
-
SERVICE_PORTS.HTTP,
|
|
6138
|
-
SERVICE_PORTS.HTTPS,
|
|
6139
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
6140
|
-
SERVICE_PORTS.HTTPS_ALT,
|
|
6141
|
-
SERVICE_PORTS.NODE_DEFAULT,
|
|
6142
|
-
SERVICE_PORTS.FLASK_DEFAULT,
|
|
6143
|
-
SERVICE_PORTS.DJANGO_DEFAULT
|
|
6144
|
-
];
|
|
6145
|
-
var PLAINTEXT_HTTP_PORTS = [
|
|
6146
|
-
SERVICE_PORTS.HTTP,
|
|
6147
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
6148
|
-
SERVICE_PORTS.NODE_DEFAULT
|
|
6149
|
-
];
|
|
6150
|
-
var DATABASE_PORTS = [
|
|
6151
|
-
SERVICE_PORTS.MYSQL,
|
|
6152
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
6153
|
-
SERVICE_PORTS.MSSQL,
|
|
6154
|
-
SERVICE_PORTS.MONGODB,
|
|
6155
|
-
SERVICE_PORTS.REDIS
|
|
6156
|
-
];
|
|
6157
|
-
var SMB_PORTS = [
|
|
6158
|
-
SERVICE_PORTS.SMB,
|
|
6159
|
-
SERVICE_PORTS.SMB_NETBIOS
|
|
6160
|
-
];
|
|
6161
|
-
|
|
6162
|
-
// src/shared/utils/logger.ts
|
|
6163
|
-
var COLORS = {
|
|
6164
|
-
reset: "\x1B[0m",
|
|
6165
|
-
dim: "\x1B[2m",
|
|
6166
|
-
red: "\x1B[31m",
|
|
6167
|
-
yellow: "\x1B[33m",
|
|
6168
|
-
green: "\x1B[32m",
|
|
6169
|
-
blue: "\x1B[34m",
|
|
6170
|
-
magenta: "\x1B[35m",
|
|
6171
|
-
cyan: "\x1B[36m"
|
|
6172
|
-
};
|
|
6173
|
-
var levelColors = {
|
|
6174
|
-
[0 /* DEBUG */]: COLORS.dim,
|
|
6175
|
-
[1 /* INFO */]: COLORS.green,
|
|
6176
|
-
[2 /* WARN */]: COLORS.yellow,
|
|
6177
|
-
[3 /* ERROR */]: COLORS.red,
|
|
6178
|
-
[999 /* SILENT */]: COLORS.reset
|
|
6179
|
-
};
|
|
6180
|
-
var levelNames = {
|
|
6181
|
-
[0 /* DEBUG */]: "DEBUG",
|
|
6182
|
-
[1 /* INFO */]: "INFO",
|
|
6183
|
-
[2 /* WARN */]: "WARN",
|
|
6184
|
-
[3 /* ERROR */]: "ERROR",
|
|
6185
|
-
[999 /* SILENT */]: "SILENT"
|
|
6186
|
-
};
|
|
6187
|
-
var Logger = class {
|
|
6188
|
-
constructor(component, config = {}) {
|
|
6189
|
-
this.component = component;
|
|
6190
|
-
this.config = {
|
|
6191
|
-
minLevel: config.minLevel ?? 1 /* INFO */,
|
|
6192
|
-
includeTimestamp: config.includeTimestamp ?? true,
|
|
6193
|
-
includeComponent: config.includeComponent ?? true,
|
|
6194
|
-
colorOutput: config.colorOutput ?? true,
|
|
6195
|
-
outputToFile: config.outputToFile ?? false,
|
|
6196
|
-
logFilePath: config.logFilePath
|
|
6197
|
-
};
|
|
6198
|
-
}
|
|
6199
|
-
config;
|
|
6200
|
-
logBuffer = [];
|
|
6201
|
-
maxBufferSize = 1e3;
|
|
6202
|
-
/**
|
|
6203
|
-
* Set minimum log level
|
|
6204
|
-
*/
|
|
6205
|
-
setMinLevel(level) {
|
|
6206
|
-
this.config.minLevel = level;
|
|
6207
|
-
}
|
|
6208
|
-
/**
|
|
6209
|
-
* Log at a specific level
|
|
6210
|
-
*/
|
|
6211
|
-
log(level, message, data) {
|
|
6212
|
-
if (level < this.config.minLevel) {
|
|
6213
|
-
return;
|
|
6214
|
-
}
|
|
6215
|
-
const entry = {
|
|
6216
|
-
timestamp: Date.now(),
|
|
6217
|
-
level,
|
|
6218
|
-
component: this.component,
|
|
6219
|
-
message,
|
|
6220
|
-
data
|
|
6221
|
-
};
|
|
6222
|
-
this.logBuffer.push(entry);
|
|
6223
|
-
if (this.logBuffer.length > this.maxBufferSize) {
|
|
6224
|
-
this.logBuffer.shift();
|
|
6225
|
-
}
|
|
6226
|
-
const formatted = this.formatEntry(entry);
|
|
6227
|
-
console.log(formatted);
|
|
6228
|
-
}
|
|
6229
|
-
/**
|
|
6230
|
-
* Format a log entry for output
|
|
6231
|
-
*/
|
|
6232
|
-
formatEntry(entry) {
|
|
6233
|
-
const parts = [];
|
|
6234
|
-
if (this.config.includeTimestamp) {
|
|
6235
|
-
const date = new Date(entry.timestamp);
|
|
6236
|
-
const ts = date.toISOString().split("T")[1].slice(0, -1);
|
|
6237
|
-
parts.push(this.config.colorOutput ? COLORS.dim + ts + COLORS.reset : ts);
|
|
6238
|
-
}
|
|
6239
|
-
const levelName = levelNames[entry.level];
|
|
6240
|
-
const levelColor = this.config.colorOutput ? levelColors[entry.level] : "";
|
|
6241
|
-
parts.push(levelColor + `[${levelName}]` + (this.config.colorOutput ? COLORS.reset : ""));
|
|
6242
|
-
if (this.config.includeComponent) {
|
|
6243
|
-
const comp = this.config.colorOutput ? COLORS.cyan + entry.component + COLORS.reset : entry.component;
|
|
6244
|
-
parts.push(`[${comp}]`);
|
|
6245
|
-
}
|
|
6246
|
-
parts.push(entry.message);
|
|
6247
|
-
if (entry.data) {
|
|
6248
|
-
const dataStr = Object.entries(entry.data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
6249
|
-
parts.push(this.config.colorOutput ? COLORS.dim + dataStr + COLORS.reset : dataStr);
|
|
6250
|
-
}
|
|
6251
|
-
return parts.join(" ");
|
|
6252
|
-
}
|
|
6253
|
-
/**
|
|
6254
|
-
* Debug level logging
|
|
6255
|
-
*/
|
|
6256
|
-
debug(message, data) {
|
|
6257
|
-
this.log(0 /* DEBUG */, message, data);
|
|
6258
|
-
}
|
|
6259
|
-
/**
|
|
6260
|
-
* Info level logging
|
|
6261
|
-
*/
|
|
6262
|
-
info(message, data) {
|
|
6263
|
-
this.log(1 /* INFO */, message, data);
|
|
6264
|
-
}
|
|
6265
|
-
/**
|
|
6266
|
-
* Warning level logging
|
|
6267
|
-
*/
|
|
6268
|
-
warn(message, data) {
|
|
6269
|
-
this.log(2 /* WARN */, message, data);
|
|
6270
|
-
}
|
|
6271
|
-
/**
|
|
6272
|
-
* Error level logging
|
|
6273
|
-
*/
|
|
6274
|
-
error(message, data) {
|
|
6275
|
-
this.log(3 /* ERROR */, message, data);
|
|
6276
|
-
}
|
|
6277
|
-
/**
|
|
6278
|
-
* Get all log entries
|
|
6279
|
-
*/
|
|
6280
|
-
getLogs() {
|
|
6281
|
-
return [...this.logBuffer];
|
|
6282
|
-
}
|
|
6283
|
-
/**
|
|
6284
|
-
* Get logs by level
|
|
6285
|
-
*/
|
|
6286
|
-
getLogsByLevel(minLevel) {
|
|
6287
|
-
return this.logBuffer.filter((entry) => entry.level >= minLevel);
|
|
6288
|
-
}
|
|
6289
|
-
/**
|
|
6290
|
-
* Clear log buffer
|
|
6291
|
-
*/
|
|
6292
|
-
clearLogs() {
|
|
6293
|
-
this.logBuffer = [];
|
|
6294
|
-
}
|
|
6295
|
-
/**
|
|
6296
|
-
* Export logs to string
|
|
6297
|
-
*/
|
|
6298
|
-
exportLogs() {
|
|
6299
|
-
return this.logBuffer.map((entry) => this.formatEntry(entry)).join("\n");
|
|
6300
|
-
}
|
|
6301
|
-
};
|
|
6302
|
-
var agentLogger = new Logger("Agent", {
|
|
6303
|
-
minLevel: 1 /* INFO */,
|
|
6304
|
-
colorOutput: true
|
|
6305
|
-
});
|
|
6306
|
-
|
|
6307
|
-
// src/shared/constants/_shared/http.const.ts
|
|
6308
|
-
var HTTP_STATUS = {
|
|
6309
|
-
// 2xx Success
|
|
6310
|
-
OK: 200,
|
|
6311
|
-
CREATED: 201,
|
|
6312
|
-
NO_CONTENT: 204,
|
|
6313
|
-
// 4xx Client Errors
|
|
6314
|
-
BAD_REQUEST: 400,
|
|
6315
|
-
UNAUTHORIZED: 401,
|
|
6316
|
-
FORBIDDEN: 403,
|
|
6317
|
-
NOT_FOUND: 404,
|
|
6318
|
-
RATE_LIMIT: 429,
|
|
6319
|
-
// 5xx Server Errors
|
|
6320
|
-
INTERNAL_ERROR: 500,
|
|
6321
|
-
BAD_GATEWAY: 502,
|
|
6322
|
-
SERVICE_UNAVAILABLE: 503,
|
|
6323
|
-
GATEWAY_TIMEOUT: 504
|
|
6324
|
-
};
|
|
6325
|
-
|
|
6326
6113
|
// src/shared/constants/llm/api.ts
|
|
6327
6114
|
var LLM_API = {
|
|
6328
6115
|
/** Default Anthropic API base URL */
|
|
@@ -6380,8 +6167,8 @@ var RETRY_CONFIG = {
|
|
|
6380
6167
|
// Initial delay for rate limit retry (exponential backoff)
|
|
6381
6168
|
};
|
|
6382
6169
|
var LLM_LIMITS = {
|
|
6383
|
-
nonStreamMaxTokens:
|
|
6384
|
-
streamMaxTokens:
|
|
6170
|
+
nonStreamMaxTokens: 16384,
|
|
6171
|
+
streamMaxTokens: 32768,
|
|
6385
6172
|
/** WHY: ~3.5 chars/token is a reasonable average for mixed English/CJK content */
|
|
6386
6173
|
charsPerTokenEstimate: 3.5
|
|
6387
6174
|
};
|
|
@@ -6394,7 +6181,8 @@ var LLM_ERROR_TYPES = {
|
|
|
6394
6181
|
UNKNOWN: "unknown"
|
|
6395
6182
|
};
|
|
6396
6183
|
|
|
6397
|
-
// src/engine/llm.ts
|
|
6184
|
+
// src/engine/llm-types.ts
|
|
6185
|
+
var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
|
|
6398
6186
|
var LLMError = class extends Error {
|
|
6399
6187
|
/** Structured error information */
|
|
6400
6188
|
errorInfo;
|
|
@@ -6425,6 +6213,8 @@ function classifyError(error) {
|
|
|
6425
6213
|
}
|
|
6426
6214
|
return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
|
|
6427
6215
|
}
|
|
6216
|
+
|
|
6217
|
+
// src/engine/llm.ts
|
|
6428
6218
|
var LLMClient = class {
|
|
6429
6219
|
apiKey;
|
|
6430
6220
|
baseUrl;
|
|
@@ -6434,7 +6224,7 @@ var LLMClient = class {
|
|
|
6434
6224
|
this.apiKey = getApiKey();
|
|
6435
6225
|
this.baseUrl = getBaseUrl() || LLM_API.DEFAULT_BASE_URL;
|
|
6436
6226
|
this.model = getModel();
|
|
6437
|
-
debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0,
|
|
6227
|
+
debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." });
|
|
6438
6228
|
}
|
|
6439
6229
|
/**
|
|
6440
6230
|
* Generate a non-streaming response
|
|
@@ -6614,7 +6404,7 @@ var LLMClient = class {
|
|
|
6614
6404
|
toolCall.input = parseResult.data;
|
|
6615
6405
|
debugLog("llm", `[${requestId}] Tool parsed OK`, { id, name: toolCall.name, input: toolCall.input });
|
|
6616
6406
|
} else {
|
|
6617
|
-
toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0,
|
|
6407
|
+
toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, DISPLAY_LIMITS.RAW_JSON_ERROR_PREVIEW) };
|
|
6618
6408
|
debugLog("llm", `[${requestId}] Tool parse FAILED`, { id, name: toolCall.name, error: parseResult.error, raw: toolCall._pendingJson });
|
|
6619
6409
|
}
|
|
6620
6410
|
delete toolCall._pendingJson;
|
|
@@ -6758,14 +6548,14 @@ function logLLM(message, data) {
|
|
|
6758
6548
|
}
|
|
6759
6549
|
|
|
6760
6550
|
// src/engine/orchestrator/orchestrator.ts
|
|
6761
|
-
import { fileURLToPath as
|
|
6551
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6762
6552
|
import { dirname as dirname4, join as join7 } from "path";
|
|
6763
|
-
var
|
|
6764
|
-
var
|
|
6553
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
6554
|
+
var __dirname3 = dirname4(__filename2);
|
|
6765
6555
|
|
|
6766
6556
|
// src/engine/state-persistence.ts
|
|
6767
|
-
import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
6768
|
-
import { join as join8
|
|
6557
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
|
|
6558
|
+
import { join as join8 } from "path";
|
|
6769
6559
|
function saveState(state) {
|
|
6770
6560
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
6771
6561
|
ensureDirExists(sessionsDir);
|
|
@@ -6777,7 +6567,7 @@ function saveState(state) {
|
|
|
6777
6567
|
findings: state.getFindings(),
|
|
6778
6568
|
loot: state.getLoot(),
|
|
6779
6569
|
todo: state.getTodo(),
|
|
6780
|
-
actionLog: state.getRecentActions(
|
|
6570
|
+
actionLog: state.getRecentActions(AGENT_LIMITS.MAX_ACTION_LOG),
|
|
6781
6571
|
currentPhase: state.getPhase(),
|
|
6782
6572
|
missionSummary: state.getMissionSummary(),
|
|
6783
6573
|
missionChecklist: state.getMissionChecklist()
|
|
@@ -6787,8 +6577,23 @@ function saveState(state) {
|
|
|
6787
6577
|
writeFileSync5(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
6788
6578
|
const latestFile = join8(sessionsDir, "latest.json");
|
|
6789
6579
|
writeFileSync5(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
6580
|
+
pruneOldSessions(sessionsDir);
|
|
6790
6581
|
return sessionFile;
|
|
6791
6582
|
}
|
|
6583
|
+
function pruneOldSessions(sessionsDir) {
|
|
6584
|
+
try {
|
|
6585
|
+
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".json") && f !== "latest.json").map((f) => ({
|
|
6586
|
+
name: f,
|
|
6587
|
+
path: join8(sessionsDir, f),
|
|
6588
|
+
mtime: statSync(join8(sessionsDir, f)).mtimeMs
|
|
6589
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
6590
|
+
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
6591
|
+
for (const file of toDelete) {
|
|
6592
|
+
unlinkSync3(file.path);
|
|
6593
|
+
}
|
|
6594
|
+
} catch {
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6792
6597
|
function loadState(state) {
|
|
6793
6598
|
const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
|
|
6794
6599
|
if (!existsSync5(latestFile)) {
|
|
@@ -6798,7 +6603,7 @@ function loadState(state) {
|
|
|
6798
6603
|
const raw = readFileSync3(latestFile, "utf-8");
|
|
6799
6604
|
const snapshot = JSON.parse(raw);
|
|
6800
6605
|
if (snapshot.version !== 1) {
|
|
6801
|
-
|
|
6606
|
+
debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
|
|
6802
6607
|
return false;
|
|
6803
6608
|
}
|
|
6804
6609
|
if (snapshot.engagement) {
|
|
@@ -6816,7 +6621,9 @@ function loadState(state) {
|
|
|
6816
6621
|
for (const item of snapshot.todo) {
|
|
6817
6622
|
state.addTodo(item.content, item.priority);
|
|
6818
6623
|
}
|
|
6819
|
-
|
|
6624
|
+
const validPhases = new Set(Object.values(PHASES));
|
|
6625
|
+
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
6626
|
+
state.setPhase(restoredPhase);
|
|
6820
6627
|
if (snapshot.missionSummary) {
|
|
6821
6628
|
state.setMissionSummary(snapshot.missionSummary);
|
|
6822
6629
|
}
|
|
@@ -6825,7 +6632,7 @@ function loadState(state) {
|
|
|
6825
6632
|
}
|
|
6826
6633
|
return true;
|
|
6827
6634
|
} catch (err) {
|
|
6828
|
-
|
|
6635
|
+
debugLog("general", `Failed to load state: ${err}`);
|
|
6829
6636
|
return false;
|
|
6830
6637
|
}
|
|
6831
6638
|
}
|
|
@@ -6835,11 +6642,24 @@ var FLAG_PATTERNS = {
|
|
|
6835
6642
|
// Generic CTF flag formats
|
|
6836
6643
|
generic: /flag\{[^}]+\}/gi,
|
|
6837
6644
|
curly_upper: /FLAG\{[^}]+\}/g,
|
|
6838
|
-
// Platform-specific
|
|
6645
|
+
// Platform-specific (major competitions)
|
|
6839
6646
|
htb: /HTB\{[^}]+\}/g,
|
|
6840
6647
|
thm: /THM\{[^}]+\}/g,
|
|
6841
6648
|
picoCTF: /picoCTF\{[^}]+\}/g,
|
|
6649
|
+
defcon_ooo: /OOO\{[^}]+\}/g,
|
|
6650
|
+
// DEF CON CTF (Order of the Overflow)
|
|
6651
|
+
csaw: /CSAW\{[^}]+\}/g,
|
|
6652
|
+
// CSAW CTF
|
|
6653
|
+
google: /CTF\{[^}]+\}/g,
|
|
6654
|
+
// Google CTF
|
|
6655
|
+
dragon: /DrgnS\{[^}]+\}/g,
|
|
6656
|
+
// Dragon Sector
|
|
6657
|
+
hxp: /hxp\{[^}]+\}/g,
|
|
6658
|
+
// hxp CTF
|
|
6659
|
+
zer0pts: /zer0pts\{[^}]+\}/g,
|
|
6660
|
+
// zer0pts CTF
|
|
6842
6661
|
ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
|
|
6662
|
+
// Generic CTFd format
|
|
6843
6663
|
// Hash-style flags (HTB user.txt / root.txt — 32-char hex)
|
|
6844
6664
|
hash_flag: /\b[a-f0-9]{32}\b/g,
|
|
6845
6665
|
// Base64-encoded flag detection (common in steganography/forensics)
|
|
@@ -6859,6 +6679,83 @@ function detectFlags(output) {
|
|
|
6859
6679
|
return Array.from(found);
|
|
6860
6680
|
}
|
|
6861
6681
|
|
|
6682
|
+
// src/agents/tool-error-enrichment.ts
|
|
6683
|
+
function enrichToolErrorContext(ctx) {
|
|
6684
|
+
const { toolName, input, error, originalOutput, progress } = ctx;
|
|
6685
|
+
const lines = [];
|
|
6686
|
+
if (originalOutput) lines.push(originalOutput);
|
|
6687
|
+
lines.push("");
|
|
6688
|
+
lines.push(`[TOOL ERROR ANALYSIS]`);
|
|
6689
|
+
lines.push(`Tool: ${toolName}`);
|
|
6690
|
+
lines.push(`Error: ${error}`);
|
|
6691
|
+
const errorLower = error.toLowerCase();
|
|
6692
|
+
if (isBlockedCommandError(errorLower)) {
|
|
6693
|
+
trackBlockedPattern(progress, toolName, errorLower);
|
|
6694
|
+
appendBlockedCommandHints(lines, errorLower);
|
|
6695
|
+
} else if (isMissingParamError(errorLower)) {
|
|
6696
|
+
const providedParams = Object.keys(input).join(", ") || "none";
|
|
6697
|
+
lines.push(`Provided parameters: ${providedParams}`);
|
|
6698
|
+
lines.push(`Fix: Check the tool's required parameters and provide ALL required values.`);
|
|
6699
|
+
lines.push(`Action: Retry this tool call with the missing parameter(s) added.`);
|
|
6700
|
+
} else if (isNotFoundError(errorLower)) {
|
|
6701
|
+
lines.push(`Fix: The tool or command may not be installed.`);
|
|
6702
|
+
lines.push(`Actions: (1) Try an alternative tool, (2) Use web_search to find installation instructions, or (3) Use a different approach entirely.`);
|
|
6703
|
+
} else if (isPermissionError(errorLower)) {
|
|
6704
|
+
lines.push(`Fix: Insufficient permissions for this operation.`);
|
|
6705
|
+
lines.push(`Actions: (1) Try with sudo if appropriate, (2) Use a different approach, or (3) Check if the target path/resource is correct.`);
|
|
6706
|
+
} else if (isConnectionError(errorLower)) {
|
|
6707
|
+
lines.push(`Fix: Cannot reach the target service.`);
|
|
6708
|
+
lines.push(`Actions: (1) Verify the target host/port is correct, (2) Check if the service is running, (3) Try a different port or protocol.`);
|
|
6709
|
+
} else if (isInvalidInputError(errorLower)) {
|
|
6710
|
+
lines.push(`Fix: The input format is incorrect.`);
|
|
6711
|
+
lines.push(`Actions: (1) Check the input format and fix it, (2) Use web_search to find the correct syntax, or (3) Try a simpler input.`);
|
|
6712
|
+
} else {
|
|
6713
|
+
lines.push(`Actions: (1) Analyze the error and modify your approach, (2) Use web_search("${toolName} ${errorLower.slice(0, AGENT_LIMITS.ERROR_SEARCH_PREVIEW_SLICE)}") to research the error, (3) Try an alternative tool or method.`);
|
|
6714
|
+
}
|
|
6715
|
+
return lines.join("\n");
|
|
6716
|
+
}
|
|
6717
|
+
function isBlockedCommandError(e) {
|
|
6718
|
+
return e.includes("command blocked") || e.includes("injection pattern") || e.includes("not in the allowed") || e.includes("not in allowed paths");
|
|
6719
|
+
}
|
|
6720
|
+
function isMissingParamError(e) {
|
|
6721
|
+
return e.includes("missing") && e.includes("parameter") || e.includes("required") && e.includes("missing") || e.includes("is required");
|
|
6722
|
+
}
|
|
6723
|
+
function isNotFoundError(e) {
|
|
6724
|
+
return e.includes("not found") || e.includes("command not found") || e.includes("no such file");
|
|
6725
|
+
}
|
|
6726
|
+
function isPermissionError(e) {
|
|
6727
|
+
return e.includes("permission denied") || e.includes("access denied") || e.includes("eacces");
|
|
6728
|
+
}
|
|
6729
|
+
function isConnectionError(e) {
|
|
6730
|
+
return e.includes("connection refused") || e.includes("econnrefused") || e.includes("connection reset") || e.includes("timeout");
|
|
6731
|
+
}
|
|
6732
|
+
function isInvalidInputError(e) {
|
|
6733
|
+
return e.includes("invalid") || e.includes("malformed") || e.includes("syntax error");
|
|
6734
|
+
}
|
|
6735
|
+
function trackBlockedPattern(progress, toolName, errorLower) {
|
|
6736
|
+
if (!progress) return;
|
|
6737
|
+
const patternKey = `${toolName}:${errorLower.slice(0, AGENT_LIMITS.BLOCKED_PATTERN_KEY_SLICE)}`;
|
|
6738
|
+
const count = (progress.blockedCommandPatterns.get(patternKey) || 0) + 1;
|
|
6739
|
+
progress.blockedCommandPatterns.set(patternKey, count);
|
|
6740
|
+
progress.totalBlockedCommands++;
|
|
6741
|
+
}
|
|
6742
|
+
function appendBlockedCommandHints(lines, errorLower) {
|
|
6743
|
+
if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
|
|
6744
|
+
lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
|
|
6745
|
+
lines.push(`Alternative approaches:`);
|
|
6746
|
+
lines.push(` 1. Save output to file: command > /tmp/output.txt, then use read_file("/tmp/output.txt")`);
|
|
6747
|
+
lines.push(` 2. Use tool-specific flags: nmap -oN /tmp/scan.txt, curl -o /tmp/page.html`);
|
|
6748
|
+
lines.push(` 3. Use browse_url for web content instead of curl | grep`);
|
|
6749
|
+
lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
|
|
6750
|
+
} else if (errorLower.includes("redirect")) {
|
|
6751
|
+
lines.push(`Fix: Redirect to /tmp/ or /root/ paths only.`);
|
|
6752
|
+
lines.push(`Example: command > /tmp/output.txt or command 2>&1 > /tmp/errors.txt`);
|
|
6753
|
+
} else {
|
|
6754
|
+
lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
|
|
6755
|
+
lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
|
|
6862
6759
|
// src/agents/core-agent.ts
|
|
6863
6760
|
var CoreAgent = class _CoreAgent {
|
|
6864
6761
|
llm;
|
|
@@ -6929,44 +6826,28 @@ var CoreAgent = class _CoreAgent {
|
|
|
6929
6826
|
const phase = this.state.getPhase();
|
|
6930
6827
|
const targets = this.state.getTargets().size;
|
|
6931
6828
|
const findings = this.state.getFindings().length;
|
|
6932
|
-
const
|
|
6933
|
-
[PHASES.RECON]: `RECON
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
[PHASES.
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
[PHASES.
|
|
6940
|
-
\u2192 Pick the highest-severity finding and write an exploit script.
|
|
6941
|
-
\u2192 web_search("{CVE} PoC github") \u2192 browse_url \u2192 write_file \u2192 run_cmd`,
|
|
6942
|
-
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges and move laterally.
|
|
6943
|
-
\u2192 bg_process interact: "cat /etc/passwd && cat /etc/shadow && sudo -l"
|
|
6944
|
-
\u2192 web_search("{OS} privilege escalation hacktricks")`,
|
|
6945
|
-
[PHASES.PRIV_ESC]: `PRIVESC: Escalate NOW.
|
|
6946
|
-
\u2192 run_cmd({ command: "sudo -l && find / -perm -4000 -type f 2>/dev/null" })
|
|
6947
|
-
\u2192 web_search("{kernel_version} privilege escalation exploit")`,
|
|
6948
|
-
[PHASES.LATERAL]: `LATERAL MOVEMENT: Spread to other hosts.
|
|
6949
|
-
\u2192 Reuse discovered credentials on all known hosts
|
|
6950
|
-
\u2192 run_cmd({ command: "for ip in $(cat /tmp/hosts); do sshpass -p '<cred>' ssh user@$ip id; done" })`,
|
|
6951
|
-
[PHASES.WEB]: `WEB PHASE: Test injection points.
|
|
6952
|
-
\u2192 get_web_attack_surface with target URL
|
|
6953
|
-
\u2192 Test every parameter for SQLi, SSTI, XSS, command injection`
|
|
6829
|
+
const phaseDirection = {
|
|
6830
|
+
[PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
|
|
6831
|
+
[PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
|
|
6832
|
+
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
|
|
6833
|
+
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
|
|
6834
|
+
[PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
|
|
6835
|
+
[PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
|
|
6836
|
+
[PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
|
|
6954
6837
|
};
|
|
6955
|
-
const
|
|
6838
|
+
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
6956
6839
|
messages.push({
|
|
6957
6840
|
role: LLM_ROLES.USER,
|
|
6958
|
-
content:
|
|
6841
|
+
content: `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
6959
6842
|
Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
|
|
6960
6843
|
|
|
6961
|
-
${
|
|
6844
|
+
${direction}
|
|
6962
6845
|
|
|
6963
|
-
|
|
6964
|
-
-
|
|
6965
|
-
-
|
|
6966
|
-
-
|
|
6967
|
-
-
|
|
6968
|
-
- \u26A1 1 fail \u2260 stop: Try DIFFERENT approach immediately
|
|
6969
|
-
- \u26A1 NEVER output text without a tool call`
|
|
6846
|
+
RULES:
|
|
6847
|
+
- Every turn MUST have tool calls
|
|
6848
|
+
- If stuck: search for techniques (web_search)
|
|
6849
|
+
- If failed: try a DIFFERENT approach
|
|
6850
|
+
- ACT NOW \u2014 do not plan or explain`
|
|
6970
6851
|
});
|
|
6971
6852
|
}
|
|
6972
6853
|
} catch (error) {
|
|
@@ -7162,7 +7043,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7162
7043
|
toolName,
|
|
7163
7044
|
success,
|
|
7164
7045
|
output,
|
|
7165
|
-
outputSummary: output.slice(0,
|
|
7046
|
+
outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
7166
7047
|
error,
|
|
7167
7048
|
duration
|
|
7168
7049
|
}
|
|
@@ -7273,7 +7154,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7273
7154
|
\u{1F4A1} TIP: If you need to see the full output, use a tool to read the file directly or run the command with | head, | tail, or | grep.`;
|
|
7274
7155
|
}
|
|
7275
7156
|
if (result2.error) {
|
|
7276
|
-
outputText = this.
|
|
7157
|
+
outputText = this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
|
|
7277
7158
|
if (progress) progress.toolErrors++;
|
|
7278
7159
|
} else {
|
|
7279
7160
|
if (progress) {
|
|
@@ -7286,75 +7167,17 @@ Please decide how to handle this error and continue.`;
|
|
|
7286
7167
|
return { toolCallId: call.id, output: outputText, error: result2.error };
|
|
7287
7168
|
} catch (error) {
|
|
7288
7169
|
const errorMsg = String(error);
|
|
7289
|
-
const enrichedError = this.
|
|
7170
|
+
const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
7290
7171
|
if (progress) progress.toolErrors++;
|
|
7291
7172
|
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
7292
7173
|
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
7293
7174
|
}
|
|
7294
7175
|
}
|
|
7295
7176
|
/**
|
|
7296
|
-
* Enrich tool error
|
|
7297
|
-
* Instead of just showing the raw error, we add hints about what went wrong
|
|
7298
|
-
* and what the agent can do to fix it.
|
|
7177
|
+
* Enrich tool error — delegates to extracted module (§3-1)
|
|
7299
7178
|
*/
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
const lines = [];
|
|
7303
|
-
if (originalOutput) {
|
|
7304
|
-
lines.push(originalOutput);
|
|
7305
|
-
}
|
|
7306
|
-
lines.push("");
|
|
7307
|
-
lines.push(`[TOOL ERROR ANALYSIS]`);
|
|
7308
|
-
lines.push(`Tool: ${toolName}`);
|
|
7309
|
-
lines.push(`Error: ${error}`);
|
|
7310
|
-
const errorLower = error.toLowerCase();
|
|
7311
|
-
if (errorLower.includes("command blocked") || errorLower.includes("injection pattern") || errorLower.includes("not in the allowed") || errorLower.includes("not in allowed paths")) {
|
|
7312
|
-
if (progress) {
|
|
7313
|
-
const patternKey = `${toolName}:${errorLower.slice(0, AGENT_LIMITS.BLOCKED_PATTERN_KEY_SLICE)}`;
|
|
7314
|
-
const count = (progress.blockedCommandPatterns.get(patternKey) || 0) + 1;
|
|
7315
|
-
progress.blockedCommandPatterns.set(patternKey, count);
|
|
7316
|
-
progress.totalBlockedCommands++;
|
|
7317
|
-
if (count >= AGENT_LIMITS.MAX_BLOCKED_BEFORE_WARN) {
|
|
7318
|
-
lines.push(`
|
|
7319
|
-
\u26A0\uFE0F REPEAT BLOCK DETECTED (${count}x same pattern). STOP retrying this approach.`);
|
|
7320
|
-
lines.push(`You have been blocked ${progress.totalBlockedCommands} times total this session.`);
|
|
7321
|
-
}
|
|
7322
|
-
}
|
|
7323
|
-
if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
|
|
7324
|
-
lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
|
|
7325
|
-
lines.push(`Alternative approaches:`);
|
|
7326
|
-
lines.push(` 1. Save output to file: command > /tmp/output.txt, then use read_file("/tmp/output.txt")`);
|
|
7327
|
-
lines.push(` 2. Use tool-specific flags: nmap -oN /tmp/scan.txt, curl -o /tmp/page.html`);
|
|
7328
|
-
lines.push(` 3. Use browse_url for web content instead of curl | grep`);
|
|
7329
|
-
lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
|
|
7330
|
-
} else if (errorLower.includes("redirect")) {
|
|
7331
|
-
lines.push(`Fix: Redirect to /tmp/ or /root/ paths only.`);
|
|
7332
|
-
lines.push(`Example: command > /tmp/output.txt or command 2>&1 > /tmp/errors.txt`);
|
|
7333
|
-
} else {
|
|
7334
|
-
lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
|
|
7335
|
-
lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
|
|
7336
|
-
}
|
|
7337
|
-
} else if (errorLower.includes("missing") && errorLower.includes("parameter") || errorLower.includes("required") && errorLower.includes("missing") || errorLower.includes("is required")) {
|
|
7338
|
-
const providedParams = Object.keys(input).join(", ") || "none";
|
|
7339
|
-
lines.push(`Provided parameters: ${providedParams}`);
|
|
7340
|
-
lines.push(`Fix: Check the tool's required parameters and provide ALL required values.`);
|
|
7341
|
-
lines.push(`Action: Retry this tool call with the missing parameter(s) added.`);
|
|
7342
|
-
} else if (errorLower.includes("not found") || errorLower.includes("command not found") || errorLower.includes("no such file")) {
|
|
7343
|
-
lines.push(`Fix: The tool or command may not be installed.`);
|
|
7344
|
-
lines.push(`Actions: (1) Try an alternative tool, (2) Use web_search to find installation instructions, or (3) Use a different approach entirely.`);
|
|
7345
|
-
} else if (errorLower.includes("permission denied") || errorLower.includes("access denied") || errorLower.includes("eacces")) {
|
|
7346
|
-
lines.push(`Fix: Insufficient permissions for this operation.`);
|
|
7347
|
-
lines.push(`Actions: (1) Try with sudo if appropriate, (2) Use a different approach, or (3) Check if the target path/resource is correct.`);
|
|
7348
|
-
} else if (errorLower.includes("connection refused") || errorLower.includes("econnrefused") || errorLower.includes("connection reset") || errorLower.includes("timeout")) {
|
|
7349
|
-
lines.push(`Fix: Cannot reach the target service.`);
|
|
7350
|
-
lines.push(`Actions: (1) Verify the target host/port is correct, (2) Check if the service is running, (3) Try a different port or protocol.`);
|
|
7351
|
-
} else if (errorLower.includes("invalid") || errorLower.includes("malformed") || errorLower.includes("syntax error")) {
|
|
7352
|
-
lines.push(`Fix: The input format is incorrect.`);
|
|
7353
|
-
lines.push(`Actions: (1) Check the input format and fix it, (2) Use web_search to find the correct syntax, or (3) Try a simpler input.`);
|
|
7354
|
-
} else {
|
|
7355
|
-
lines.push(`Actions: (1) Analyze the error and modify your approach, (2) Use web_search("${toolName} ${errorLower.slice(0, AGENT_LIMITS.ERROR_SEARCH_PREVIEW_SLICE)}") to research the error, (3) Try an alternative tool or method.`);
|
|
7356
|
-
}
|
|
7357
|
-
return lines.join("\n");
|
|
7179
|
+
enrichToolError(ctx) {
|
|
7180
|
+
return enrichToolErrorContext(ctx);
|
|
7358
7181
|
}
|
|
7359
7182
|
getToolSchemas() {
|
|
7360
7183
|
if (!this.toolRegistry) {
|
|
@@ -7416,9 +7239,9 @@ Please decide how to handle this error and continue.`;
|
|
|
7416
7239
|
};
|
|
7417
7240
|
|
|
7418
7241
|
// src/agents/prompt-builder.ts
|
|
7419
|
-
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
7242
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
|
|
7420
7243
|
import { join as join9, dirname as dirname5 } from "path";
|
|
7421
|
-
import { fileURLToPath as
|
|
7244
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7422
7245
|
|
|
7423
7246
|
// src/shared/constants/prompts.ts
|
|
7424
7247
|
var PROMPT_PATHS = {
|
|
@@ -7472,8 +7295,8 @@ var INITIAL_TASKS = {
|
|
|
7472
7295
|
};
|
|
7473
7296
|
|
|
7474
7297
|
// src/agents/prompt-builder.ts
|
|
7475
|
-
var
|
|
7476
|
-
var PROMPTS_DIR = join9(
|
|
7298
|
+
var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
|
|
7299
|
+
var PROMPTS_DIR = join9(__dirname4, "prompts");
|
|
7477
7300
|
var TECHNIQUES_DIR = join9(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
7478
7301
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
7479
7302
|
var PHASE_PROMPT_MAP = {
|
|
@@ -7504,13 +7327,13 @@ var CORE_KNOWLEDGE_FILES = [
|
|
|
7504
7327
|
];
|
|
7505
7328
|
var PHASE_TECHNIQUE_MAP = {
|
|
7506
7329
|
[PHASES.RECON]: ["network-svc", "shells", "crypto"],
|
|
7507
|
-
[PHASES.VULN_ANALYSIS]: ["injection", "network-svc", "file-attacks", "crypto"],
|
|
7508
|
-
[PHASES.EXPLOIT]: ["injection", "shells", "file-attacks", "network-svc", "pwn", "container-escape"],
|
|
7509
|
-
[PHASES.POST_EXPLOIT]: ["privesc", "lateral", "auth-access", "shells", "container-escape"],
|
|
7330
|
+
[PHASES.VULN_ANALYSIS]: ["injection", "network-svc", "file-attacks", "crypto", "reversing"],
|
|
7331
|
+
[PHASES.EXPLOIT]: ["injection", "shells", "file-attacks", "network-svc", "pwn", "container-escape", "reversing"],
|
|
7332
|
+
[PHASES.POST_EXPLOIT]: ["privesc", "lateral", "auth-access", "shells", "container-escape", "forensics"],
|
|
7510
7333
|
[PHASES.PRIV_ESC]: ["privesc", "auth-access", "shells", "pwn", "container-escape"],
|
|
7511
7334
|
[PHASES.LATERAL]: ["lateral", "ad-attack", "auth-access", "container-escape"],
|
|
7512
7335
|
[PHASES.PERSISTENCE]: ["shells", "privesc"],
|
|
7513
|
-
[PHASES.EXFIL]: ["lateral", "network-svc"],
|
|
7336
|
+
[PHASES.EXFIL]: ["lateral", "network-svc", "forensics"],
|
|
7514
7337
|
[PHASES.WEB]: ["injection", "file-attacks", "auth-access", "crypto"],
|
|
7515
7338
|
[PHASES.REPORT]: []
|
|
7516
7339
|
// Report phase needs no attack techniques
|
|
@@ -7562,8 +7385,8 @@ ${content}
|
|
|
7562
7385
|
* Load a prompt file from src/agents/prompts/
|
|
7563
7386
|
*/
|
|
7564
7387
|
loadPromptFile(filename) {
|
|
7565
|
-
const
|
|
7566
|
-
return existsSync6(
|
|
7388
|
+
const path2 = join9(PROMPTS_DIR, filename);
|
|
7389
|
+
return existsSync6(path2) ? readFileSync4(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
7567
7390
|
}
|
|
7568
7391
|
/**
|
|
7569
7392
|
* Load phase-specific prompt.
|
|
@@ -7598,19 +7421,22 @@ ${content}
|
|
|
7598
7421
|
return fragments.join("\n\n");
|
|
7599
7422
|
}
|
|
7600
7423
|
/**
|
|
7601
|
-
* Load
|
|
7602
|
-
*
|
|
7424
|
+
* Load technique files relevant to the current phase.
|
|
7425
|
+
*
|
|
7426
|
+
* Loading strategy (Philosophy §11 — zero-code extension):
|
|
7427
|
+
* 1. PHASE_TECHNIQUE_MAP defines priority techniques per phase (loaded first)
|
|
7428
|
+
* 2. Any .md file in techniques/ NOT in the map is auto-discovered and loaded
|
|
7429
|
+
* as general reference — NO code change needed to add new techniques.
|
|
7603
7430
|
*
|
|
7604
|
-
*
|
|
7605
|
-
*
|
|
7606
|
-
* use web_search to look up specific attack techniques on demand.
|
|
7431
|
+
* The map is an optimization (priority ordering), not a gate.
|
|
7432
|
+
* "마크다운 파일 하나를 폴더에 넣으면, PromptBuilder가 자동으로 발견하고 로드한다."
|
|
7607
7433
|
*/
|
|
7608
7434
|
loadPhaseRelevantTechniques(phase) {
|
|
7609
7435
|
if (!existsSync6(TECHNIQUES_DIR)) return "";
|
|
7610
|
-
const
|
|
7611
|
-
|
|
7436
|
+
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
7437
|
+
const loadedSet = /* @__PURE__ */ new Set();
|
|
7612
7438
|
const fragments = [];
|
|
7613
|
-
for (const technique of
|
|
7439
|
+
for (const technique of priorityTechniques) {
|
|
7614
7440
|
const filePath = join9(TECHNIQUES_DIR, `${technique}.md`);
|
|
7615
7441
|
try {
|
|
7616
7442
|
if (!existsSync6(filePath)) continue;
|
|
@@ -7619,10 +7445,25 @@ ${content}
|
|
|
7619
7445
|
fragments.push(`<technique-reference category="${technique}">
|
|
7620
7446
|
${content}
|
|
7621
7447
|
</technique-reference>`);
|
|
7448
|
+
loadedSet.add(`${technique}.md`);
|
|
7622
7449
|
}
|
|
7623
7450
|
} catch {
|
|
7624
7451
|
}
|
|
7625
7452
|
}
|
|
7453
|
+
try {
|
|
7454
|
+
const allFiles = readdirSync2(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
7455
|
+
for (const file of allFiles) {
|
|
7456
|
+
const filePath = join9(TECHNIQUES_DIR, file);
|
|
7457
|
+
const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
|
|
7458
|
+
if (content) {
|
|
7459
|
+
const category = file.replace(".md", "");
|
|
7460
|
+
fragments.push(`<technique-reference category="${category}">
|
|
7461
|
+
${content}
|
|
7462
|
+
</technique-reference>`);
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
} catch {
|
|
7466
|
+
}
|
|
7626
7467
|
return fragments.join("\n\n");
|
|
7627
7468
|
}
|
|
7628
7469
|
getScopeFragment() {
|
|
@@ -7851,30 +7692,6 @@ var formatInlineStatus = () => {
|
|
|
7851
7692
|
// src/platform/tui/hooks/useAgentState.ts
|
|
7852
7693
|
import { useState, useRef, useCallback } from "react";
|
|
7853
7694
|
|
|
7854
|
-
// src/shared/constants/thought.ts
|
|
7855
|
-
var THOUGHT_TYPE = {
|
|
7856
|
-
THINKING: "thinking",
|
|
7857
|
-
// LLM text streaming
|
|
7858
|
-
REASONING: "reasoning",
|
|
7859
|
-
// LLM extended thinking
|
|
7860
|
-
PLANNING: "planning",
|
|
7861
|
-
// Strategic planning
|
|
7862
|
-
OBSERVATION: "observation",
|
|
7863
|
-
// Observing results
|
|
7864
|
-
HYPOTHESIS: "hypothesis",
|
|
7865
|
-
// Forming hypothesis
|
|
7866
|
-
REFLECTION: "reflection",
|
|
7867
|
-
// Self-reflection
|
|
7868
|
-
ACTION: "action",
|
|
7869
|
-
// Taking action
|
|
7870
|
-
RESULT: "result",
|
|
7871
|
-
// Action result
|
|
7872
|
-
STUCK: "stuck",
|
|
7873
|
-
// Detected stuck state
|
|
7874
|
-
BREAKTHROUGH: "breakthrough"
|
|
7875
|
-
// Found breakthrough
|
|
7876
|
-
};
|
|
7877
|
-
|
|
7878
7695
|
// src/shared/constants/theme.ts
|
|
7879
7696
|
var THEME = {
|
|
7880
7697
|
// Backgrounds (deep dark with blue tint)
|
|
@@ -8022,18 +7839,6 @@ var ICONS = {
|
|
|
8022
7839
|
pwned: "\u25C8"
|
|
8023
7840
|
// Compromised
|
|
8024
7841
|
};
|
|
8025
|
-
var THOUGHT_LABELS = {
|
|
8026
|
-
[THOUGHT_TYPE.THINKING]: "[think]",
|
|
8027
|
-
[THOUGHT_TYPE.REASONING]: "[reason]",
|
|
8028
|
-
[THOUGHT_TYPE.PLANNING]: "[plan]",
|
|
8029
|
-
[THOUGHT_TYPE.OBSERVATION]: "[observe]",
|
|
8030
|
-
[THOUGHT_TYPE.HYPOTHESIS]: "[hypothesis]",
|
|
8031
|
-
[THOUGHT_TYPE.REFLECTION]: "[reflect]",
|
|
8032
|
-
[THOUGHT_TYPE.ACTION]: "[action]",
|
|
8033
|
-
[THOUGHT_TYPE.RESULT]: "[result]",
|
|
8034
|
-
[THOUGHT_TYPE.STUCK]: "[stuck]",
|
|
8035
|
-
[THOUGHT_TYPE.BREAKTHROUGH]: "[!]"
|
|
8036
|
-
};
|
|
8037
7842
|
|
|
8038
7843
|
// src/platform/tui/constants/display.ts
|
|
8039
7844
|
var TUI_DISPLAY_LIMITS = {
|
|
@@ -8062,7 +7867,19 @@ var TUI_DISPLAY_LIMITS = {
|
|
|
8062
7867
|
/** Timer update interval in ms */
|
|
8063
7868
|
timerInterval: 1e3,
|
|
8064
7869
|
/** Exit delay in ms */
|
|
8065
|
-
exitDelay: 100
|
|
7870
|
+
exitDelay: 100,
|
|
7871
|
+
/** Purpose text max length before truncation */
|
|
7872
|
+
purposeMaxLength: 30,
|
|
7873
|
+
/** Purpose text truncated length */
|
|
7874
|
+
purposeTruncated: 27,
|
|
7875
|
+
/** Tool detail preview length in flow display */
|
|
7876
|
+
toolDetailPreview: 100,
|
|
7877
|
+
/** Observe detail preview length in flow display */
|
|
7878
|
+
observeDetailPreview: 80,
|
|
7879
|
+
/** Max flow nodes to display */
|
|
7880
|
+
maxFlowNodes: 50,
|
|
7881
|
+
/** Max stopped processes to show */
|
|
7882
|
+
maxStoppedProcesses: 3
|
|
8066
7883
|
};
|
|
8067
7884
|
var MESSAGE_STYLES = {
|
|
8068
7885
|
colors: {
|
|
@@ -8532,7 +8349,7 @@ function ProcessRow({ proc, compact }) {
|
|
|
8532
8349
|
const duration = formatDuration2(proc.durationMs);
|
|
8533
8350
|
const port = proc.listeningPort ? `:${proc.listeningPort}` : "";
|
|
8534
8351
|
const purpose = proc.purpose || proc.description || "";
|
|
8535
|
-
const truncatedPurpose = compact && purpose.length >
|
|
8352
|
+
const truncatedPurpose = compact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
|
|
8536
8353
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
8537
8354
|
/* @__PURE__ */ jsx(StatusIndicator, { running: proc.running, exitCode: proc.exitCode }),
|
|
8538
8355
|
/* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
|
|
@@ -8589,10 +8406,10 @@ var InlineStatus = ({
|
|
|
8589
8406
|
stopped.length,
|
|
8590
8407
|
")"
|
|
8591
8408
|
] }),
|
|
8592
|
-
stopped.slice(0,
|
|
8593
|
-
stopped.length >
|
|
8409
|
+
stopped.slice(0, TUI_DISPLAY_LIMITS.maxStoppedProcesses).map((proc) => /* @__PURE__ */ jsx(ProcessRow, { proc, compact }, proc.id)),
|
|
8410
|
+
stopped.length > TUI_DISPLAY_LIMITS.maxStoppedProcesses && /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
|
|
8594
8411
|
" ... and ",
|
|
8595
|
-
stopped.length -
|
|
8412
|
+
stopped.length - TUI_DISPLAY_LIMITS.maxStoppedProcesses,
|
|
8596
8413
|
" more"
|
|
8597
8414
|
] })
|
|
8598
8415
|
] }),
|
|
@@ -9029,19 +8846,6 @@ ${procData.stdout || "(no output)"}
|
|
|
9029
8846
|
};
|
|
9030
8847
|
var app_default = App;
|
|
9031
8848
|
|
|
9032
|
-
// src/shared/constants/_shared/signal.const.ts
|
|
9033
|
-
var EXIT_CODE = {
|
|
9034
|
-
SUCCESS: 0,
|
|
9035
|
-
ERROR: 1,
|
|
9036
|
-
// Unix convention: 128 + signal number
|
|
9037
|
-
SIGINT: 130,
|
|
9038
|
-
// 128 + 2
|
|
9039
|
-
SIGTERM: 143,
|
|
9040
|
-
// 128 + 15
|
|
9041
|
-
SIGKILL: 137
|
|
9042
|
-
// 128 + 9
|
|
9043
|
-
};
|
|
9044
|
-
|
|
9045
8849
|
// src/shared/constants/cli-defaults.const.ts
|
|
9046
8850
|
var CLI_DEFAULT = {
|
|
9047
8851
|
/** Default maximum steps for run command */
|
|
@@ -9112,8 +8916,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
9112
8916
|
const shutdown = async (exitCode = 0) => {
|
|
9113
8917
|
process.exit(exitCode);
|
|
9114
8918
|
};
|
|
9115
|
-
process.on("SIGINT", () => shutdown(
|
|
9116
|
-
process.on("SIGTERM", () => shutdown(
|
|
8919
|
+
process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
|
|
8920
|
+
process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
|
|
9117
8921
|
try {
|
|
9118
8922
|
const result2 = await agent.execute(objective);
|
|
9119
8923
|
console.log(chalk.hex(THEME.status.success)("\n[+] Assessment complete!\n"));
|
|
@@ -9145,8 +8949,8 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
9145
8949
|
const shutdown = async (exitCode = 0) => {
|
|
9146
8950
|
process.exit(exitCode);
|
|
9147
8951
|
};
|
|
9148
|
-
process.on("SIGINT", () => shutdown(
|
|
9149
|
-
process.on("SIGTERM", () => shutdown(
|
|
8952
|
+
process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
|
|
8953
|
+
process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
|
|
9150
8954
|
try {
|
|
9151
8955
|
await agent.execute(`Perform a ${options.scanType} scan on ${target}${options.ports ? ` focusing on ports ${options.ports}` : ""}. Analyze the results and identify potential vulnerabilities.`);
|
|
9152
8956
|
console.log(chalk.hex(THEME.status.success)("[+] Scan complete!"));
|