pentesting 0.47.2 → 0.47.3
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 +62 -5
- package/dist/cloud/prompt.md +1 -1
- package/dist/file-sharing/prompt.md +2 -2
- package/dist/main.js +396 -225
- package/dist/network/prompt.md +1 -1
- package/dist/orchestrator/orchestrator.md +44 -43
- package/dist/prompts/base.md +39 -9
- package/dist/prompts/infra.md +1 -1
- package/dist/prompts/vuln.md +3 -3
- package/dist/web/prompt.md +2 -2
- package/dist/wireless/prompt.md +8 -8
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -105,7 +105,27 @@ var DISPLAY_LIMITS = {
|
|
|
105
105
|
/** Prefix length for sensitive value redaction */
|
|
106
106
|
REDACT_PREFIX: 4,
|
|
107
107
|
/** Max characters for raw JSON error preview */
|
|
108
|
-
RAW_JSON_ERROR_PREVIEW: 500
|
|
108
|
+
RAW_JSON_ERROR_PREVIEW: 500,
|
|
109
|
+
// ─── Memory/History Slice Sizes (§4-3 Extract Magic Values) ─────
|
|
110
|
+
/** Recent credentials to include in context */
|
|
111
|
+
RECENT_CREDENTIALS: 5,
|
|
112
|
+
/** Recent failures to analyze for patterns */
|
|
113
|
+
RECENT_FAILURES: 5,
|
|
114
|
+
/** Recent successes to include in summary */
|
|
115
|
+
RECENT_SUCCESSES: 3,
|
|
116
|
+
/** Recent insights to display */
|
|
117
|
+
RECENT_INSIGHTS: 3,
|
|
118
|
+
/** Recent memory events to include */
|
|
119
|
+
RECENT_MEMORY_EVENTS: 10,
|
|
120
|
+
/** Summary window - small (for quick summaries) */
|
|
121
|
+
SUMMARY_WINDOW_SMALL: 100,
|
|
122
|
+
/** Summary window - large (for detailed summaries) */
|
|
123
|
+
SUMMARY_WINDOW_LARGE: 500,
|
|
124
|
+
// ─── Evidence Display ────────────────────────────────────────
|
|
125
|
+
/** Max evidence items to show in finding preview */
|
|
126
|
+
EVIDENCE_ITEMS_PREVIEW: 3,
|
|
127
|
+
/** Max characters per evidence item in preview */
|
|
128
|
+
EVIDENCE_PREVIEW_LENGTH: 120
|
|
109
129
|
};
|
|
110
130
|
var AGENT_LIMITS = {
|
|
111
131
|
/** Maximum agent loop iterations — generous to allow complex tasks.
|
|
@@ -311,7 +331,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
311
331
|
|
|
312
332
|
// src/shared/constants/agent.ts
|
|
313
333
|
var APP_NAME = "Pentest AI";
|
|
314
|
-
var APP_VERSION = "0.47.
|
|
334
|
+
var APP_VERSION = "0.47.3";
|
|
315
335
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
316
336
|
var LLM_ROLES = {
|
|
317
337
|
SYSTEM: "system",
|
|
@@ -653,12 +673,44 @@ var TODO_STATUSES = {
|
|
|
653
673
|
DONE: "done",
|
|
654
674
|
SKIPPED: "skipped"
|
|
655
675
|
};
|
|
676
|
+
var LOOT_TYPES = {
|
|
677
|
+
CREDENTIAL: "credential",
|
|
678
|
+
SESSION: "session",
|
|
679
|
+
HASH: "hash",
|
|
680
|
+
TOKEN: "token",
|
|
681
|
+
FILE: "file",
|
|
682
|
+
TICKET: "ticket",
|
|
683
|
+
SSH_KEY: "ssh_key",
|
|
684
|
+
CERTIFICATE: "certificate",
|
|
685
|
+
API_KEY: "api_key"
|
|
686
|
+
};
|
|
687
|
+
var ATTACK_TACTICS = {
|
|
688
|
+
INITIAL_ACCESS: "initial_access",
|
|
689
|
+
EXECUTION: "execution",
|
|
690
|
+
PERSISTENCE: "persistence",
|
|
691
|
+
PRIV_ESC: "privilege_escalation",
|
|
692
|
+
DEFENSE_EVASION: "defense_evasion",
|
|
693
|
+
CREDENTIAL_ACCESS: "credential_access",
|
|
694
|
+
DISCOVERY: "discovery",
|
|
695
|
+
LATERAL_MOVEMENT: "lateral_movement",
|
|
696
|
+
COLLECTION: "collection",
|
|
697
|
+
EXFILTRATION: "exfiltration",
|
|
698
|
+
C2: "command_and_control",
|
|
699
|
+
IMPACT: "impact"
|
|
700
|
+
};
|
|
656
701
|
var APPROVAL_STATUSES = {
|
|
657
702
|
AUTO: "auto",
|
|
658
703
|
USER_CONFIRMED: "user_confirmed",
|
|
659
704
|
USER_REVIEWED: "user_reviewed",
|
|
660
705
|
DENIED: "denied"
|
|
661
706
|
};
|
|
707
|
+
var SEVERITIES = {
|
|
708
|
+
CRITICAL: "critical",
|
|
709
|
+
HIGH: "high",
|
|
710
|
+
MEDIUM: "medium",
|
|
711
|
+
LOW: "low",
|
|
712
|
+
INFO: "info"
|
|
713
|
+
};
|
|
662
714
|
var PRIORITIES = {
|
|
663
715
|
HIGH: "high",
|
|
664
716
|
MEDIUM: "medium",
|
|
@@ -757,6 +809,68 @@ function ensureDirExists(dirPath) {
|
|
|
757
809
|
}
|
|
758
810
|
}
|
|
759
811
|
|
|
812
|
+
// src/shared/constants/time.ts
|
|
813
|
+
var MS_PER_MINUTE = 6e4;
|
|
814
|
+
var SECONDS_PER_MINUTE = 60;
|
|
815
|
+
var SECONDS_PER_HOUR = 3600;
|
|
816
|
+
|
|
817
|
+
// src/shared/constants/paths.ts
|
|
818
|
+
import path from "path";
|
|
819
|
+
import { fileURLToPath } from "url";
|
|
820
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
821
|
+
var __dirname = path.dirname(__filename);
|
|
822
|
+
var PROJECT_ROOT = path.resolve(__dirname, "../../../");
|
|
823
|
+
var PENTESTING_ROOT = ".pentesting";
|
|
824
|
+
var WORK_DIR = `${PENTESTING_ROOT}/tmp`;
|
|
825
|
+
var MEMORY_DIR = `${PENTESTING_ROOT}/memory`;
|
|
826
|
+
var REPORTS_DIR = `${PENTESTING_ROOT}/reports`;
|
|
827
|
+
var SESSIONS_DIR = `${PENTESTING_ROOT}/sessions`;
|
|
828
|
+
var LOOT_DIR = `${PENTESTING_ROOT}/loot`;
|
|
829
|
+
var OUTPUTS_DIR = `${PENTESTING_ROOT}/outputs`;
|
|
830
|
+
var DEBUG_DIR = `${PENTESTING_ROOT}/debug`;
|
|
831
|
+
var WORKSPACE = {
|
|
832
|
+
/** Root directory */
|
|
833
|
+
get ROOT() {
|
|
834
|
+
return path.resolve(PENTESTING_ROOT);
|
|
835
|
+
},
|
|
836
|
+
/** Temporary files */
|
|
837
|
+
get TMP() {
|
|
838
|
+
return path.resolve(WORK_DIR);
|
|
839
|
+
},
|
|
840
|
+
/** Persistent memory */
|
|
841
|
+
get MEMORY() {
|
|
842
|
+
return path.resolve(MEMORY_DIR);
|
|
843
|
+
},
|
|
844
|
+
/** Generated reports */
|
|
845
|
+
get REPORTS() {
|
|
846
|
+
return path.resolve(REPORTS_DIR);
|
|
847
|
+
},
|
|
848
|
+
/** Session snapshots */
|
|
849
|
+
get SESSIONS() {
|
|
850
|
+
return path.resolve(SESSIONS_DIR);
|
|
851
|
+
},
|
|
852
|
+
/** Captured loot */
|
|
853
|
+
get LOOT() {
|
|
854
|
+
return path.resolve(LOOT_DIR);
|
|
855
|
+
},
|
|
856
|
+
/** Full tool outputs */
|
|
857
|
+
get OUTPUTS() {
|
|
858
|
+
return path.resolve(OUTPUTS_DIR);
|
|
859
|
+
},
|
|
860
|
+
/** Debug logs */
|
|
861
|
+
get DEBUG() {
|
|
862
|
+
return path.resolve(DEBUG_DIR);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// src/shared/constants/orchestrator.ts
|
|
867
|
+
var GRACEFUL_SHUTDOWN_WAIT_MS = 200;
|
|
868
|
+
var PROCESS_OUTPUT_TRUNCATION_LIMIT = 1e4;
|
|
869
|
+
var LONG_RUNNING_THRESHOLD_MS = 5 * MS_PER_MINUTE;
|
|
870
|
+
var VERY_LONG_RUNNING_THRESHOLD_MS = 15 * MS_PER_MINUTE;
|
|
871
|
+
var MAX_RECOMMENDATIONS_FOR_HEALTHY = 2;
|
|
872
|
+
var DEFAULT_DIRECTIVE_FOCUS = PHASES.RECON;
|
|
873
|
+
|
|
760
874
|
// src/shared/utils/command-security-lists.ts
|
|
761
875
|
var ALLOWED_BINARIES = /* @__PURE__ */ new Set([
|
|
762
876
|
// Network scanning
|
|
@@ -964,7 +1078,7 @@ var SAFE_PIPE_TARGETS = /* @__PURE__ */ new Set([
|
|
|
964
1078
|
"python3",
|
|
965
1079
|
"python"
|
|
966
1080
|
]);
|
|
967
|
-
var SAFE_REDIRECT_PATHS = [
|
|
1081
|
+
var SAFE_REDIRECT_PATHS = [`${WORK_DIR}/`];
|
|
968
1082
|
var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
969
1083
|
"rm",
|
|
970
1084
|
"shred",
|
|
@@ -989,50 +1103,6 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
|
989
1103
|
// src/shared/utils/debug-logger.ts
|
|
990
1104
|
import { appendFileSync, writeFileSync } from "fs";
|
|
991
1105
|
import { join } from "path";
|
|
992
|
-
|
|
993
|
-
// src/shared/constants/paths.ts
|
|
994
|
-
import path from "path";
|
|
995
|
-
import { fileURLToPath } from "url";
|
|
996
|
-
import { homedir } from "os";
|
|
997
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
998
|
-
var __dirname = path.dirname(__filename);
|
|
999
|
-
var PROJECT_ROOT = path.resolve(__dirname, "../../../");
|
|
1000
|
-
var WORKSPACE_DIR_NAME = ".pentesting";
|
|
1001
|
-
function getWorkspaceRoot() {
|
|
1002
|
-
return path.join(homedir(), WORKSPACE_DIR_NAME);
|
|
1003
|
-
}
|
|
1004
|
-
var WORKSPACE = {
|
|
1005
|
-
/** Root directory (resolved lazily via getWorkspaceRoot) */
|
|
1006
|
-
get ROOT() {
|
|
1007
|
-
return getWorkspaceRoot();
|
|
1008
|
-
},
|
|
1009
|
-
/** Per-session state snapshots */
|
|
1010
|
-
get SESSIONS() {
|
|
1011
|
-
return path.join(getWorkspaceRoot(), "sessions");
|
|
1012
|
-
},
|
|
1013
|
-
/** Debug logs */
|
|
1014
|
-
get DEBUG() {
|
|
1015
|
-
return path.join(getWorkspaceRoot(), "debug");
|
|
1016
|
-
},
|
|
1017
|
-
/** Generated reports */
|
|
1018
|
-
get REPORTS() {
|
|
1019
|
-
return path.join(getWorkspaceRoot(), "reports");
|
|
1020
|
-
},
|
|
1021
|
-
/** Downloaded loot, captured files */
|
|
1022
|
-
get LOOT() {
|
|
1023
|
-
return path.join(getWorkspaceRoot(), "loot");
|
|
1024
|
-
},
|
|
1025
|
-
/** Temporary files for active operations */
|
|
1026
|
-
get TEMP() {
|
|
1027
|
-
return path.join(getWorkspaceRoot(), "temp");
|
|
1028
|
-
},
|
|
1029
|
-
/** Full tool output files saved by Context Digest */
|
|
1030
|
-
get OUTPUTS() {
|
|
1031
|
-
return path.join(getWorkspaceRoot(), "outputs");
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
// src/shared/utils/debug-logger.ts
|
|
1036
1106
|
var DebugLogger = class _DebugLogger {
|
|
1037
1107
|
static instance;
|
|
1038
1108
|
logPath;
|
|
@@ -1123,16 +1193,16 @@ var SHELL_CHARS = {
|
|
|
1123
1193
|
};
|
|
1124
1194
|
function validateCommand(command) {
|
|
1125
1195
|
if (!command || typeof command !== "string") {
|
|
1126
|
-
return {
|
|
1196
|
+
return { isSafe: false, error: "Empty or invalid command" };
|
|
1127
1197
|
}
|
|
1128
1198
|
const normalizedCommand = command.trim();
|
|
1129
1199
|
if (normalizedCommand.length === 0) {
|
|
1130
|
-
return {
|
|
1200
|
+
return { isSafe: false, error: "Empty command" };
|
|
1131
1201
|
}
|
|
1132
1202
|
for (const pattern of INJECTION_PATTERNS) {
|
|
1133
1203
|
if (pattern.test(normalizedCommand)) {
|
|
1134
1204
|
return {
|
|
1135
|
-
|
|
1205
|
+
isSafe: false,
|
|
1136
1206
|
error: `Injection pattern detected: ${pattern.source}`,
|
|
1137
1207
|
suggestion: "Avoid command substitution ($(), ``), variable expansion (${}) and control characters."
|
|
1138
1208
|
};
|
|
@@ -1141,13 +1211,13 @@ function validateCommand(command) {
|
|
|
1141
1211
|
const subCommands = splitChainedCommands(normalizedCommand);
|
|
1142
1212
|
for (const subCmd of subCommands) {
|
|
1143
1213
|
const result2 = validateSingleCommand(subCmd.trim());
|
|
1144
|
-
if (!result2.
|
|
1214
|
+
if (!result2.isSafe) {
|
|
1145
1215
|
return result2;
|
|
1146
1216
|
}
|
|
1147
1217
|
}
|
|
1148
1218
|
const primaryBinary = extractBinary(subCommands[0].trim());
|
|
1149
1219
|
return {
|
|
1150
|
-
|
|
1220
|
+
isSafe: true,
|
|
1151
1221
|
binary: primaryBinary || void 0,
|
|
1152
1222
|
args: normalizedCommand.split(/\s+/).slice(1)
|
|
1153
1223
|
};
|
|
@@ -1202,14 +1272,14 @@ function validateSingleCommand(command) {
|
|
|
1202
1272
|
if (!binary) continue;
|
|
1203
1273
|
if (BLOCKED_BINARIES.has(binary)) {
|
|
1204
1274
|
return {
|
|
1205
|
-
|
|
1275
|
+
isSafe: false,
|
|
1206
1276
|
error: `Binary '${binary}' is blocked for security reasons`,
|
|
1207
1277
|
suggestion: `'${binary}' is not allowed. Use a different approach.`
|
|
1208
1278
|
};
|
|
1209
1279
|
}
|
|
1210
1280
|
if (idx > 0 && !SAFE_PIPE_TARGETS.has(binary) && !ALLOWED_BINARIES.has(binary)) {
|
|
1211
1281
|
return {
|
|
1212
|
-
|
|
1282
|
+
isSafe: false,
|
|
1213
1283
|
error: `Pipe target '${binary}' is not in the allowed list`,
|
|
1214
1284
|
suggestion: `Piping to '${binary}' is not allowed. Safe pipe targets: head, tail, grep, awk, sed, cut, sort, uniq, wc, jq, tee.`
|
|
1215
1285
|
};
|
|
@@ -1219,10 +1289,10 @@ function validateSingleCommand(command) {
|
|
|
1219
1289
|
}
|
|
1220
1290
|
}
|
|
1221
1291
|
const redirectResult = validateRedirects(command);
|
|
1222
|
-
if (!redirectResult.
|
|
1292
|
+
if (!redirectResult.isSafe) {
|
|
1223
1293
|
return redirectResult;
|
|
1224
1294
|
}
|
|
1225
|
-
return {
|
|
1295
|
+
return { isSafe: true };
|
|
1226
1296
|
}
|
|
1227
1297
|
function splitByPipe(command) {
|
|
1228
1298
|
const parts = [];
|
|
@@ -1265,33 +1335,91 @@ function stripRedirects(segment) {
|
|
|
1265
1335
|
return segment.replace(/\d*>\s*&\d+/g, "").replace(/\d*>>\s*\S+/g, "").replace(/\d*>\s*\S+/g, "").replace(/<\s*\S+/g, "").trim();
|
|
1266
1336
|
}
|
|
1267
1337
|
function validateRedirects(command) {
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1338
|
+
const redirects = extractRedirectTargets(command);
|
|
1339
|
+
for (const { type, fd, target } of redirects) {
|
|
1340
|
+
if (type === ">" || type === ">>") {
|
|
1341
|
+
if (target.startsWith("&") || target === "/dev/null") continue;
|
|
1342
|
+
const isAllowed = SAFE_REDIRECT_PATHS.some((p) => target.startsWith(p));
|
|
1343
|
+
if (!isAllowed) {
|
|
1344
|
+
return {
|
|
1345
|
+
isSafe: false,
|
|
1346
|
+
error: `Redirect to '${target}' is not in allowed paths`,
|
|
1347
|
+
suggestion: `Redirect output to ${WORK_DIR}/ paths. Example: > ${WORK_DIR}/output.txt`
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
} else if (type === "<") {
|
|
1351
|
+
const isAllowed = SAFE_REDIRECT_PATHS.some((p) => target.startsWith(p));
|
|
1352
|
+
if (!isAllowed) {
|
|
1353
|
+
return {
|
|
1354
|
+
isSafe: false,
|
|
1355
|
+
error: `Input redirect from '${target}' is not in allowed paths`,
|
|
1356
|
+
suggestion: `Input redirect only from ${WORK_DIR}/ paths.`
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1280
1359
|
}
|
|
1281
1360
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1361
|
+
return { isSafe: true };
|
|
1362
|
+
}
|
|
1363
|
+
function extractRedirectTargets(command) {
|
|
1364
|
+
const redirects = [];
|
|
1365
|
+
let inSingle = false;
|
|
1366
|
+
let inDouble = false;
|
|
1367
|
+
let i = 0;
|
|
1368
|
+
while (i < command.length) {
|
|
1369
|
+
const ch = command[i];
|
|
1370
|
+
if (ch === "'" && !inDouble) {
|
|
1371
|
+
inSingle = !inSingle;
|
|
1372
|
+
i++;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (ch === '"' && !inSingle) {
|
|
1376
|
+
inDouble = !inDouble;
|
|
1377
|
+
i++;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
if (!inSingle && !inDouble) {
|
|
1381
|
+
if (ch === ">" || ch === "<") {
|
|
1382
|
+
const isAppend = ch === ">" && command[i + 1] === ">";
|
|
1383
|
+
const type = isAppend ? ">>" : ch;
|
|
1384
|
+
const jump = isAppend ? 2 : 1;
|
|
1385
|
+
let fd = "";
|
|
1386
|
+
if (ch === ">") {
|
|
1387
|
+
let b = i - 1;
|
|
1388
|
+
while (b >= 0 && /\d/.test(command[b])) {
|
|
1389
|
+
fd = command[b] + fd;
|
|
1390
|
+
b--;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
i += jump;
|
|
1394
|
+
while (i < command.length && /\s/.test(command[i])) i++;
|
|
1395
|
+
let target = "";
|
|
1396
|
+
let targetInSingle = false;
|
|
1397
|
+
let targetInDouble = false;
|
|
1398
|
+
while (i < command.length) {
|
|
1399
|
+
const tc = command[i];
|
|
1400
|
+
if (tc === "'" && !targetInDouble) {
|
|
1401
|
+
targetInSingle = !targetInSingle;
|
|
1402
|
+
i++;
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
if (tc === '"' && !targetInSingle) {
|
|
1406
|
+
targetInDouble = !targetInDouble;
|
|
1407
|
+
i++;
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (!targetInSingle && !targetInDouble && /[\s|;&<>]/.test(tc)) break;
|
|
1411
|
+
target += tc;
|
|
1412
|
+
i++;
|
|
1413
|
+
}
|
|
1414
|
+
if (target) {
|
|
1415
|
+
redirects.push({ type, fd, target });
|
|
1416
|
+
}
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1292
1419
|
}
|
|
1420
|
+
i++;
|
|
1293
1421
|
}
|
|
1294
|
-
return
|
|
1422
|
+
return redirects;
|
|
1295
1423
|
}
|
|
1296
1424
|
function extractBinary(command) {
|
|
1297
1425
|
const parts = command.trim().split(/\s+/);
|
|
@@ -1514,7 +1642,7 @@ function clearCommandEventEmitter() {
|
|
|
1514
1642
|
async function runCommand(command, args = [], options = {}) {
|
|
1515
1643
|
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
1516
1644
|
const validation = validateCommand(fullCommand);
|
|
1517
|
-
if (!validation.
|
|
1645
|
+
if (!validation.isSafe) {
|
|
1518
1646
|
return {
|
|
1519
1647
|
success: false,
|
|
1520
1648
|
output: "",
|
|
@@ -1688,6 +1816,34 @@ function createTempFile(suffix = "") {
|
|
|
1688
1816
|
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1689
1817
|
}
|
|
1690
1818
|
|
|
1819
|
+
// src/shared/constants/files.ts
|
|
1820
|
+
var FILE_EXTENSIONS = {
|
|
1821
|
+
// Data formats
|
|
1822
|
+
JSON: ".json",
|
|
1823
|
+
MARKDOWN: ".md",
|
|
1824
|
+
TXT: ".txt",
|
|
1825
|
+
XML: ".xml",
|
|
1826
|
+
// Network capture
|
|
1827
|
+
PCAP: ".pcap",
|
|
1828
|
+
HOSTS: ".hosts",
|
|
1829
|
+
MITM: ".mitm",
|
|
1830
|
+
// Process I/O
|
|
1831
|
+
STDOUT: ".stdout",
|
|
1832
|
+
STDERR: ".stderr",
|
|
1833
|
+
STDIN: ".stdin",
|
|
1834
|
+
// Scripts
|
|
1835
|
+
SH: ".sh",
|
|
1836
|
+
PY: ".py",
|
|
1837
|
+
CJS: ".cjs",
|
|
1838
|
+
// Config
|
|
1839
|
+
ENV: ".env",
|
|
1840
|
+
BAK: ".bak"
|
|
1841
|
+
};
|
|
1842
|
+
var SPECIAL_FILES = {
|
|
1843
|
+
LATEST_STATE: "latest.json",
|
|
1844
|
+
README: "README.md"
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1691
1847
|
// src/engine/process-cleanup.ts
|
|
1692
1848
|
import { unlinkSync } from "fs";
|
|
1693
1849
|
|
|
@@ -1877,9 +2033,9 @@ function logEvent(processId, event, detail) {
|
|
|
1877
2033
|
}
|
|
1878
2034
|
function startBackgroundProcess(command, options = {}) {
|
|
1879
2035
|
const processId = generatePrefixedId("bg");
|
|
1880
|
-
const stdoutFile = createTempFile(
|
|
1881
|
-
const stderrFile = createTempFile(
|
|
1882
|
-
const stdinFile = createTempFile(
|
|
2036
|
+
const stdoutFile = createTempFile(FILE_EXTENSIONS.STDOUT);
|
|
2037
|
+
const stderrFile = createTempFile(FILE_EXTENSIONS.STDERR);
|
|
2038
|
+
const stdinFile = createTempFile(FILE_EXTENSIONS.STDIN);
|
|
1883
2039
|
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
1884
2040
|
let wrappedCmd;
|
|
1885
2041
|
if (isInteractive) {
|
|
@@ -2979,7 +3135,7 @@ var WorkingMemory = class {
|
|
|
2979
3135
|
const lines = ["<working-memory>"];
|
|
2980
3136
|
if (failures.length > 0) {
|
|
2981
3137
|
lines.push(`\u26A0\uFE0F FAILED ATTEMPTS (${failures.length} \u2014 DO NOT REPEAT):`);
|
|
2982
|
-
for (const f of failures.slice(-
|
|
3138
|
+
for (const f of failures.slice(-DISPLAY_LIMITS.RECENT_FAILURES)) {
|
|
2983
3139
|
lines.push(` \u2717 ${f.content}`);
|
|
2984
3140
|
}
|
|
2985
3141
|
}
|
|
@@ -2989,13 +3145,13 @@ var WorkingMemory = class {
|
|
|
2989
3145
|
}
|
|
2990
3146
|
if (successes.length > 0) {
|
|
2991
3147
|
lines.push(`\u2705 RECENT SUCCESSES (${successes.length}):`);
|
|
2992
|
-
for (const s of successes.slice(-
|
|
3148
|
+
for (const s of successes.slice(-DISPLAY_LIMITS.RECENT_SUCCESSES)) {
|
|
2993
3149
|
lines.push(` \u2713 ${s.content}`);
|
|
2994
3150
|
}
|
|
2995
3151
|
}
|
|
2996
3152
|
if (insights.length > 0) {
|
|
2997
3153
|
lines.push(`\u{1F4A1} INSIGHTS:`);
|
|
2998
|
-
for (const i of insights.slice(-
|
|
3154
|
+
for (const i of insights.slice(-DISPLAY_LIMITS.RECENT_INSIGHTS)) {
|
|
2999
3155
|
lines.push(` \u2192 ${i.content}`);
|
|
3000
3156
|
}
|
|
3001
3157
|
}
|
|
@@ -3048,9 +3204,9 @@ var EpisodicMemory = class {
|
|
|
3048
3204
|
toPrompt() {
|
|
3049
3205
|
if (this.events.length === 0) return "";
|
|
3050
3206
|
const lines = ["<session-timeline>"];
|
|
3051
|
-
const recent = this.events.slice(-
|
|
3207
|
+
const recent = this.events.slice(-DISPLAY_LIMITS.RECENT_MEMORY_EVENTS);
|
|
3052
3208
|
for (const e of recent) {
|
|
3053
|
-
const mins = Math.floor((Date.now() - e.timestamp) /
|
|
3209
|
+
const mins = Math.floor((Date.now() - e.timestamp) / MS_PER_MINUTE);
|
|
3054
3210
|
const icon = {
|
|
3055
3211
|
tool_success: "\u2705",
|
|
3056
3212
|
tool_failure: "\u274C",
|
|
@@ -3069,7 +3225,6 @@ var EpisodicMemory = class {
|
|
|
3069
3225
|
this.events = [];
|
|
3070
3226
|
}
|
|
3071
3227
|
};
|
|
3072
|
-
var MEMORY_DIR = "/tmp/pentesting-memory";
|
|
3073
3228
|
var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
|
|
3074
3229
|
var PersistentMemory = class {
|
|
3075
3230
|
knowledge;
|
|
@@ -3186,7 +3341,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3186
3341
|
});
|
|
3187
3342
|
if (this.techniques.length > this.maxTechniques) {
|
|
3188
3343
|
this.techniques.sort((a, b) => {
|
|
3189
|
-
if (a.
|
|
3344
|
+
if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
|
|
3190
3345
|
return b.learnedAt - a.learnedAt;
|
|
3191
3346
|
});
|
|
3192
3347
|
this.techniques = this.techniques.slice(0, this.maxTechniques);
|
|
@@ -3222,7 +3377,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3222
3377
|
source: `Web search: "${query}"`,
|
|
3223
3378
|
technique: tech,
|
|
3224
3379
|
applicableTo,
|
|
3225
|
-
|
|
3380
|
+
isVerified: false,
|
|
3226
3381
|
fromQuery: query
|
|
3227
3382
|
});
|
|
3228
3383
|
}
|
|
@@ -3233,7 +3388,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3233
3388
|
verify(techniqueSubstring) {
|
|
3234
3389
|
for (const t of this.techniques) {
|
|
3235
3390
|
if (t.technique.toLowerCase().includes(techniqueSubstring.toLowerCase())) {
|
|
3236
|
-
t.
|
|
3391
|
+
t.isVerified = true;
|
|
3237
3392
|
}
|
|
3238
3393
|
}
|
|
3239
3394
|
}
|
|
@@ -3261,8 +3416,8 @@ var DynamicTechniqueLibrary = class {
|
|
|
3261
3416
|
*/
|
|
3262
3417
|
toPrompt() {
|
|
3263
3418
|
if (this.techniques.length === 0) return "";
|
|
3264
|
-
const verified = this.techniques.filter((t) => t.
|
|
3265
|
-
const unverified = this.techniques.filter((t) => !t.
|
|
3419
|
+
const verified = this.techniques.filter((t) => t.isVerified);
|
|
3420
|
+
const unverified = this.techniques.filter((t) => !t.isVerified);
|
|
3266
3421
|
const lines = ["<learned-techniques>"];
|
|
3267
3422
|
if (verified.length > 0) {
|
|
3268
3423
|
lines.push("VERIFIED (worked in this session):");
|
|
@@ -3539,8 +3694,8 @@ var SharedState = class {
|
|
|
3539
3694
|
return this.data.currentPhase;
|
|
3540
3695
|
}
|
|
3541
3696
|
// --- CTF Mode ---
|
|
3542
|
-
setCtfMode(
|
|
3543
|
-
this.data.ctfMode =
|
|
3697
|
+
setCtfMode(shouldEnable) {
|
|
3698
|
+
this.data.ctfMode = shouldEnable;
|
|
3544
3699
|
}
|
|
3545
3700
|
isCtfMode() {
|
|
3546
3701
|
return this.data.ctfMode;
|
|
@@ -3847,8 +4002,8 @@ var ApprovalGate = class {
|
|
|
3847
4002
|
/**
|
|
3848
4003
|
* Set auto-approve mode
|
|
3849
4004
|
*/
|
|
3850
|
-
setAutoApprove(
|
|
3851
|
-
this.shouldAutoApprove =
|
|
4005
|
+
setAutoApprove(shouldEnable) {
|
|
4006
|
+
this.shouldAutoApprove = shouldEnable;
|
|
3852
4007
|
}
|
|
3853
4008
|
/**
|
|
3854
4009
|
* Get current auto-approve mode
|
|
@@ -4989,6 +5144,36 @@ function formatValidation(result2) {
|
|
|
4989
5144
|
}
|
|
4990
5145
|
|
|
4991
5146
|
// src/engine/tools/pentest-target-tools.ts
|
|
5147
|
+
function isPortArray(value) {
|
|
5148
|
+
if (!Array.isArray(value)) return false;
|
|
5149
|
+
return value.every(
|
|
5150
|
+
(item) => typeof item === "object" && item !== null && typeof item.port === "number"
|
|
5151
|
+
);
|
|
5152
|
+
}
|
|
5153
|
+
function parsePorts(value) {
|
|
5154
|
+
return isPortArray(value) ? value : [];
|
|
5155
|
+
}
|
|
5156
|
+
function isValidSeverity(value) {
|
|
5157
|
+
return typeof value === "string" && Object.values(SEVERITIES).includes(value);
|
|
5158
|
+
}
|
|
5159
|
+
function parseSeverity(value) {
|
|
5160
|
+
return isValidSeverity(value) ? value : "medium";
|
|
5161
|
+
}
|
|
5162
|
+
function isValidLootType(value) {
|
|
5163
|
+
return typeof value === "string" && Object.values(LOOT_TYPES).includes(value);
|
|
5164
|
+
}
|
|
5165
|
+
function parseLootType(value) {
|
|
5166
|
+
return isValidLootType(value) ? value : "file";
|
|
5167
|
+
}
|
|
5168
|
+
function isValidAttackTactic(value) {
|
|
5169
|
+
return typeof value === "string" && Object.values(ATTACK_TACTICS).includes(value);
|
|
5170
|
+
}
|
|
5171
|
+
function parseStringArray(value) {
|
|
5172
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? value : [];
|
|
5173
|
+
}
|
|
5174
|
+
function parseString(value, defaultValue = "") {
|
|
5175
|
+
return typeof value === "string" ? value : defaultValue;
|
|
5176
|
+
}
|
|
4992
5177
|
var createTargetTools = (state) => [
|
|
4993
5178
|
{
|
|
4994
5179
|
name: TOOL_NAMES.ADD_TARGET,
|
|
@@ -5020,10 +5205,10 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5020
5205
|
},
|
|
5021
5206
|
required: ["ip"],
|
|
5022
5207
|
execute: async (p) => {
|
|
5023
|
-
const ip = p.ip;
|
|
5208
|
+
const ip = parseString(p.ip);
|
|
5024
5209
|
const existing = state.getTarget(ip);
|
|
5025
5210
|
if (existing) {
|
|
5026
|
-
const newPorts = p.ports
|
|
5211
|
+
const newPorts = parsePorts(p.ports);
|
|
5027
5212
|
for (const np of newPorts) {
|
|
5028
5213
|
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
5029
5214
|
if (!exists) {
|
|
@@ -5037,11 +5222,11 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5037
5222
|
state.attackGraph.addService(ip, np.port, np.service || "unknown", np.version);
|
|
5038
5223
|
}
|
|
5039
5224
|
}
|
|
5040
|
-
if (p.hostname) existing.hostname = p.hostname;
|
|
5041
|
-
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
5225
|
+
if (p.hostname) existing.hostname = parseString(p.hostname);
|
|
5226
|
+
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...parseStringArray(p.tags)])];
|
|
5042
5227
|
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
5043
5228
|
}
|
|
5044
|
-
const ports = (p.ports
|
|
5229
|
+
const ports = parsePorts(p.ports).map((port) => ({
|
|
5045
5230
|
port: port.port,
|
|
5046
5231
|
service: port.service || "unknown",
|
|
5047
5232
|
version: port.version,
|
|
@@ -5050,9 +5235,9 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5050
5235
|
}));
|
|
5051
5236
|
state.addTarget({
|
|
5052
5237
|
ip,
|
|
5053
|
-
hostname: p.hostname,
|
|
5238
|
+
hostname: parseString(p.hostname) || void 0,
|
|
5054
5239
|
ports,
|
|
5055
|
-
tags: p.tags
|
|
5240
|
+
tags: parseStringArray(p.tags),
|
|
5056
5241
|
firstSeen: Date.now()
|
|
5057
5242
|
});
|
|
5058
5243
|
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
@@ -5071,30 +5256,30 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
5071
5256
|
},
|
|
5072
5257
|
required: ["type", "host", "detail"],
|
|
5073
5258
|
execute: async (p) => {
|
|
5074
|
-
const
|
|
5259
|
+
const lootTypeStr = parseString(p.type);
|
|
5075
5260
|
const crackableTypes = ["hash"];
|
|
5076
|
-
const detail = p.detail;
|
|
5077
|
-
const host = p.host;
|
|
5261
|
+
const detail = parseString(p.detail);
|
|
5262
|
+
const host = parseString(p.host);
|
|
5078
5263
|
state.addLoot({
|
|
5079
|
-
type:
|
|
5264
|
+
type: parseLootType(lootTypeStr),
|
|
5080
5265
|
host,
|
|
5081
5266
|
detail,
|
|
5082
5267
|
obtainedAt: Date.now(),
|
|
5083
|
-
isCrackable: crackableTypes.includes(
|
|
5268
|
+
isCrackable: crackableTypes.includes(lootTypeStr),
|
|
5084
5269
|
isCracked: false
|
|
5085
5270
|
});
|
|
5086
|
-
if (["credential", "token", "ssh_key", "api_key"].includes(
|
|
5271
|
+
if (["credential", "token", "ssh_key", "api_key"].includes(lootTypeStr)) {
|
|
5087
5272
|
const parts = detail.split(":");
|
|
5088
5273
|
if (parts.length >= 2) {
|
|
5089
5274
|
state.attackGraph.addCredential(parts[0], parts.slice(1).join(":"), host);
|
|
5090
5275
|
}
|
|
5091
5276
|
}
|
|
5092
|
-
state.episodicMemory.record("access_gained", `Loot [${
|
|
5277
|
+
state.episodicMemory.record("access_gained", `Loot [${lootTypeStr}] from ${host}: ${detail.slice(0, DISPLAY_LIMITS.MEMORY_EVENT_PREVIEW)}`);
|
|
5093
5278
|
return {
|
|
5094
5279
|
success: true,
|
|
5095
|
-
output: `Loot recorded: [${
|
|
5280
|
+
output: `Loot recorded: [${lootTypeStr}] from ${host}
|
|
5096
5281
|
Detail: ${detail}
|
|
5097
|
-
` + (crackableTypes.includes(
|
|
5282
|
+
` + (crackableTypes.includes(lootTypeStr) ? `This is crackable. Consider: hash_crack({ hashes: "${detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
5098
5283
|
};
|
|
5099
5284
|
}
|
|
5100
5285
|
},
|
|
@@ -5113,12 +5298,13 @@ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
|
5113
5298
|
},
|
|
5114
5299
|
required: ["title", "severity", "description", "evidence"],
|
|
5115
5300
|
execute: async (p) => {
|
|
5116
|
-
const evidence = p.evidence
|
|
5117
|
-
const title = p.title;
|
|
5118
|
-
const severity = p.severity;
|
|
5119
|
-
const affected = p.affected
|
|
5120
|
-
const description = p.description
|
|
5301
|
+
const evidence = parseStringArray(p.evidence);
|
|
5302
|
+
const title = parseString(p.title);
|
|
5303
|
+
const severity = parseSeverity(p.severity);
|
|
5304
|
+
const affected = parseStringArray(p.affected);
|
|
5305
|
+
const description = parseString(p.description);
|
|
5121
5306
|
const validation = validateFinding(evidence, severity);
|
|
5307
|
+
const attackPattern = parseString(p.attackPattern);
|
|
5122
5308
|
state.addFinding({
|
|
5123
5309
|
id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
|
|
5124
5310
|
title,
|
|
@@ -5129,7 +5315,7 @@ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
|
5129
5315
|
isVerified: validation.isVerified,
|
|
5130
5316
|
remediation: "",
|
|
5131
5317
|
foundAt: Date.now(),
|
|
5132
|
-
...
|
|
5318
|
+
...attackPattern && isValidAttackTactic(attackPattern) ? { attackPattern } : {}
|
|
5133
5319
|
});
|
|
5134
5320
|
const hasExploit = validation.isVerified;
|
|
5135
5321
|
const target = affected[0] || "unknown";
|
|
@@ -5147,7 +5333,7 @@ ${formatValidation(validation)}`
|
|
|
5147
5333
|
}
|
|
5148
5334
|
];
|
|
5149
5335
|
|
|
5150
|
-
// src/engine/tools-
|
|
5336
|
+
// src/engine/tools/intel-utils.ts
|
|
5151
5337
|
import { execFileSync } from "child_process";
|
|
5152
5338
|
|
|
5153
5339
|
// src/shared/utils/config.ts
|
|
@@ -5160,7 +5346,7 @@ var ENV_KEYS = {
|
|
|
5160
5346
|
THINKING: "PENTEST_THINKING",
|
|
5161
5347
|
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
5162
5348
|
};
|
|
5163
|
-
var DEFAULT_SEARCH_API_URL = "https://
|
|
5349
|
+
var DEFAULT_SEARCH_API_URL = "https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro";
|
|
5164
5350
|
function getApiKey() {
|
|
5165
5351
|
return process.env[ENV_KEYS.API_KEY] || "";
|
|
5166
5352
|
}
|
|
@@ -5171,7 +5357,10 @@ function getModel() {
|
|
|
5171
5357
|
return process.env[ENV_KEYS.MODEL] || "";
|
|
5172
5358
|
}
|
|
5173
5359
|
function getSearchApiKey() {
|
|
5174
|
-
|
|
5360
|
+
if (process.env[ENV_KEYS.SEARCH_API_KEY]) {
|
|
5361
|
+
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
5362
|
+
}
|
|
5363
|
+
return process.env[ENV_KEYS.API_KEY];
|
|
5175
5364
|
}
|
|
5176
5365
|
function getSearchApiUrl() {
|
|
5177
5366
|
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
@@ -5663,14 +5852,11 @@ async function webSearchWithBrowser(query, engine = "google") {
|
|
|
5663
5852
|
}
|
|
5664
5853
|
|
|
5665
5854
|
// src/engine/web-search-providers.ts
|
|
5666
|
-
function getErrorMessage(error) {
|
|
5667
|
-
return error instanceof Error ? error.message : String(error);
|
|
5668
|
-
}
|
|
5669
5855
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
5670
5856
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5671
5857
|
const requestBody = {
|
|
5672
5858
|
model: "web-search-pro",
|
|
5673
|
-
messages: [{ role:
|
|
5859
|
+
messages: [{ role: LLM_ROLES.USER, content: query }],
|
|
5674
5860
|
stream: false
|
|
5675
5861
|
};
|
|
5676
5862
|
debugLog("search", "GLM request body", requestBody);
|
|
@@ -5789,38 +5975,14 @@ async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
|
5789
5975
|
const data = await response.json();
|
|
5790
5976
|
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
5791
5977
|
}
|
|
5792
|
-
async function searchWithPlaywright(query) {
|
|
5793
|
-
debugLog("search", "Playwright Google search START", { query });
|
|
5794
|
-
try {
|
|
5795
|
-
const result2 = await webSearchWithBrowser(query, "google");
|
|
5796
|
-
if (!result2.success) {
|
|
5797
|
-
return {
|
|
5798
|
-
success: false,
|
|
5799
|
-
output: "",
|
|
5800
|
-
error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
|
|
5801
|
-
};
|
|
5802
|
-
}
|
|
5803
|
-
debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
|
|
5804
|
-
return {
|
|
5805
|
-
success: true,
|
|
5806
|
-
output: result2.output || `No results found for: ${query}`
|
|
5807
|
-
};
|
|
5808
|
-
} catch (error) {
|
|
5809
|
-
return {
|
|
5810
|
-
success: false,
|
|
5811
|
-
output: "",
|
|
5812
|
-
error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
|
|
5813
|
-
};
|
|
5814
|
-
}
|
|
5815
|
-
}
|
|
5816
5978
|
|
|
5817
|
-
// src/engine/tools-
|
|
5979
|
+
// src/engine/tools/intel-utils.ts
|
|
5818
5980
|
var PORT_STATE2 = {
|
|
5819
5981
|
OPEN: "open",
|
|
5820
5982
|
CLOSED: "closed",
|
|
5821
5983
|
FILTERED: "filtered"
|
|
5822
5984
|
};
|
|
5823
|
-
function
|
|
5985
|
+
function getErrorMessage(error) {
|
|
5824
5986
|
return error instanceof Error ? error.message : String(error);
|
|
5825
5987
|
}
|
|
5826
5988
|
async function parseNmap(xmlPath) {
|
|
@@ -5871,7 +6033,7 @@ async function parseNmap(xmlPath) {
|
|
|
5871
6033
|
return {
|
|
5872
6034
|
success: false,
|
|
5873
6035
|
output: "",
|
|
5874
|
-
error:
|
|
6036
|
+
error: getErrorMessage(error)
|
|
5875
6037
|
};
|
|
5876
6038
|
}
|
|
5877
6039
|
}
|
|
@@ -5882,7 +6044,7 @@ async function searchCVE(service, version) {
|
|
|
5882
6044
|
return {
|
|
5883
6045
|
success: false,
|
|
5884
6046
|
output: "",
|
|
5885
|
-
error:
|
|
6047
|
+
error: getErrorMessage(error)
|
|
5886
6048
|
};
|
|
5887
6049
|
}
|
|
5888
6050
|
}
|
|
@@ -5919,43 +6081,58 @@ async function searchExploitDB(service, version) {
|
|
|
5919
6081
|
return {
|
|
5920
6082
|
success: false,
|
|
5921
6083
|
output: "",
|
|
5922
|
-
error:
|
|
6084
|
+
error: getErrorMessage(error)
|
|
5923
6085
|
};
|
|
5924
6086
|
}
|
|
5925
6087
|
}
|
|
5926
6088
|
async function webSearch(query, _engine) {
|
|
5927
6089
|
debugLog("search", "webSearch START", { query });
|
|
6090
|
+
const apiKey = getSearchApiKey();
|
|
6091
|
+
const apiUrl = getSearchApiUrl();
|
|
6092
|
+
debugLog("search", "Search API config", {
|
|
6093
|
+
hasApiKey: !!apiKey,
|
|
6094
|
+
apiUrl,
|
|
6095
|
+
apiKeyPrefix: apiKey ? apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." : null
|
|
6096
|
+
});
|
|
6097
|
+
if (!apiKey) {
|
|
6098
|
+
return {
|
|
6099
|
+
success: false,
|
|
6100
|
+
output: "",
|
|
6101
|
+
error: `SEARCH_API_KEY is required for web search. Set it in your environment:
|
|
6102
|
+
|
|
6103
|
+
# Brave Search (recommended - free tier available)
|
|
6104
|
+
export SEARCH_API_KEY=your_brave_api_key
|
|
6105
|
+
# Get key at: https://brave.com/search/api/
|
|
6106
|
+
|
|
6107
|
+
# Or GLM Web Search (\u667A\u8C31 AI)
|
|
6108
|
+
export SEARCH_API_KEY=your_glm_api_key
|
|
6109
|
+
export SEARCH_API_URL=https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro
|
|
6110
|
+
|
|
6111
|
+
# Or Serper (Google)
|
|
6112
|
+
export SEARCH_API_KEY=your_serper_key
|
|
6113
|
+
export SEARCH_API_URL=https://google.serper.dev/search`
|
|
6114
|
+
};
|
|
6115
|
+
}
|
|
5928
6116
|
try {
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
})
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
debugLog("search", "Using Brave search");
|
|
5942
|
-
return await searchWithBrave(query, apiKey, apiUrl);
|
|
5943
|
-
} else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
|
|
5944
|
-
debugLog("search", "Using Serper search");
|
|
5945
|
-
return await searchWithSerper(query, apiKey, apiUrl);
|
|
5946
|
-
} else {
|
|
5947
|
-
debugLog("search", "Using generic search API");
|
|
5948
|
-
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
5949
|
-
}
|
|
6117
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
6118
|
+
debugLog("search", "Using GLM search");
|
|
6119
|
+
return await searchWithGLM(query, apiKey, apiUrl);
|
|
6120
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
6121
|
+
debugLog("search", "Using Brave search");
|
|
6122
|
+
return await searchWithBrave(query, apiKey, apiUrl);
|
|
6123
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
|
|
6124
|
+
debugLog("search", "Using Serper search");
|
|
6125
|
+
return await searchWithSerper(query, apiKey, apiUrl);
|
|
6126
|
+
} else {
|
|
6127
|
+
debugLog("search", "Using generic search API");
|
|
6128
|
+
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
5950
6129
|
}
|
|
5951
|
-
debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
|
|
5952
|
-
return await searchWithPlaywright(query);
|
|
5953
6130
|
} catch (error) {
|
|
5954
|
-
debugLog("search", "webSearch ERROR", { error:
|
|
6131
|
+
debugLog("search", "webSearch ERROR", { error: getErrorMessage(error) });
|
|
5955
6132
|
return {
|
|
5956
6133
|
success: false,
|
|
5957
6134
|
output: "",
|
|
5958
|
-
error:
|
|
6135
|
+
error: getErrorMessage(error)
|
|
5959
6136
|
};
|
|
5960
6137
|
}
|
|
5961
6138
|
}
|
|
@@ -7235,7 +7412,7 @@ Requires root/sudo privileges.`,
|
|
|
7235
7412
|
const iface = p.interface || "any";
|
|
7236
7413
|
const filter = p.filter || "";
|
|
7237
7414
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SNIFF_DURATION;
|
|
7238
|
-
const outputFile = p.output_file || createTempFile(
|
|
7415
|
+
const outputFile = p.output_file || createTempFile(FILE_EXTENSIONS.PCAP);
|
|
7239
7416
|
const count = p.count || NETWORK_CONFIG.DEFAULT_PACKET_COUNT;
|
|
7240
7417
|
const extractCreds = p.extract_creds !== false;
|
|
7241
7418
|
const filterFlag = filter ? `"${filter}"` : "";
|
|
@@ -7320,7 +7497,7 @@ Requires root/sudo privileges.`,
|
|
|
7320
7497
|
const spoofIp = p.spoof_ip;
|
|
7321
7498
|
const iface = p.interface || "";
|
|
7322
7499
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7323
|
-
const hostsFile = createTempFile(
|
|
7500
|
+
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7324
7501
|
const { writeFileSync: writeFileSync8 } = await import("fs");
|
|
7325
7502
|
writeFileSync8(hostsFile, `${spoofIp} ${domain}
|
|
7326
7503
|
${spoofIp} *.${domain}
|
|
@@ -7372,7 +7549,7 @@ Combine with arp_spoof for transparent proxying.`,
|
|
|
7372
7549
|
const port = p.port || NETWORK_CONFIG.DEFAULT_PROXY_PORT;
|
|
7373
7550
|
const targetHost = p.target_host;
|
|
7374
7551
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7375
|
-
const outputFile = p.output_file || createTempFile(
|
|
7552
|
+
const outputFile = p.output_file || createTempFile(FILE_EXTENSIONS.MITM);
|
|
7376
7553
|
const sslInsecure = p.ssl_insecure !== false;
|
|
7377
7554
|
let cmd;
|
|
7378
7555
|
const sslFlag = sslInsecure ? "--ssl-insecure" : "";
|
|
@@ -7436,7 +7613,7 @@ This is a high-level tool that combines tcpdump capture with protocol analysis.`
|
|
|
7436
7613
|
const iface = p.interface || "any";
|
|
7437
7614
|
const protocols = p.protocols || "all";
|
|
7438
7615
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SNIFF_DURATION;
|
|
7439
|
-
const outputFile = createTempFile(
|
|
7616
|
+
const outputFile = createTempFile(FILE_EXTENSIONS.PCAP);
|
|
7440
7617
|
let bpfFilter = `host ${target}`;
|
|
7441
7618
|
if (protocols !== "all") {
|
|
7442
7619
|
const protoFilters = [];
|
|
@@ -7587,15 +7764,6 @@ var ZombieHunter = class {
|
|
|
7587
7764
|
}
|
|
7588
7765
|
};
|
|
7589
7766
|
|
|
7590
|
-
// src/shared/constants/orchestrator.ts
|
|
7591
|
-
var GRACEFUL_SHUTDOWN_WAIT_MS = 200;
|
|
7592
|
-
var PROCESS_OUTPUT_TRUNCATION_LIMIT = 1e4;
|
|
7593
|
-
var MS_PER_MINUTE = 6e4;
|
|
7594
|
-
var LONG_RUNNING_THRESHOLD_MS = 5 * MS_PER_MINUTE;
|
|
7595
|
-
var VERY_LONG_RUNNING_THRESHOLD_MS = 15 * MS_PER_MINUTE;
|
|
7596
|
-
var MAX_RECOMMENDATIONS_FOR_HEALTHY = 2;
|
|
7597
|
-
var DEFAULT_DIRECTIVE_FOCUS = PHASES.RECON;
|
|
7598
|
-
|
|
7599
7767
|
// src/engine/resource/health-monitor.ts
|
|
7600
7768
|
var HEALTH_STATUS = {
|
|
7601
7769
|
HEALTHY: "healthy",
|
|
@@ -8696,6 +8864,10 @@ var DOMAINS = {
|
|
|
8696
8864
|
};
|
|
8697
8865
|
|
|
8698
8866
|
// src/engine/tools-registry.ts
|
|
8867
|
+
var VALID_CATEGORIES = Object.values(SERVICE_CATEGORIES);
|
|
8868
|
+
function isValidCategory(id) {
|
|
8869
|
+
return VALID_CATEGORIES.includes(id);
|
|
8870
|
+
}
|
|
8699
8871
|
var CategorizedToolRegistry = class extends ToolRegistry {
|
|
8700
8872
|
categories = /* @__PURE__ */ new Map();
|
|
8701
8873
|
constructor(state, scopeGuard, approvalGate, events) {
|
|
@@ -8727,13 +8899,13 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8727
8899
|
];
|
|
8728
8900
|
const coreTools = coreToolNames.map((name) => this.getTool(name)).filter((t) => !!t);
|
|
8729
8901
|
Object.keys(DOMAINS).forEach((id) => {
|
|
8730
|
-
|
|
8731
|
-
this.categories.set(
|
|
8732
|
-
name:
|
|
8733
|
-
description: DOMAINS[
|
|
8902
|
+
if (!isValidCategory(id)) return;
|
|
8903
|
+
this.categories.set(id, {
|
|
8904
|
+
name: id,
|
|
8905
|
+
description: DOMAINS[id]?.description || "",
|
|
8734
8906
|
tools: [...coreTools],
|
|
8735
|
-
dangerLevel: this.calculateDanger(
|
|
8736
|
-
defaultApproval: CATEGORY_APPROVAL2[
|
|
8907
|
+
dangerLevel: this.calculateDanger(id),
|
|
8908
|
+
defaultApproval: CATEGORY_APPROVAL2[id] || "confirm"
|
|
8737
8909
|
});
|
|
8738
8910
|
});
|
|
8739
8911
|
}
|
|
@@ -8750,19 +8922,17 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8750
8922
|
const cat = ServiceParser.detectCategory(p.port, p.service);
|
|
8751
8923
|
if (cat && !seen.has(cat)) {
|
|
8752
8924
|
seen.add(cat);
|
|
8753
|
-
|
|
8925
|
+
results.push({
|
|
8754
8926
|
category: cat,
|
|
8755
8927
|
tools: this.getByCategory(cat).map((t) => t.name)
|
|
8756
|
-
};
|
|
8757
|
-
results.push(suggestion);
|
|
8928
|
+
});
|
|
8758
8929
|
}
|
|
8759
8930
|
});
|
|
8760
8931
|
if (target.hostname && ServiceParser.isCloudHostname(target.hostname) && !seen.has(SERVICE_CATEGORIES.CLOUD)) {
|
|
8761
|
-
|
|
8932
|
+
results.push({
|
|
8762
8933
|
category: SERVICE_CATEGORIES.CLOUD,
|
|
8763
8934
|
tools: this.getByCategory(SERVICE_CATEGORIES.CLOUD).map((t) => t.name)
|
|
8764
|
-
};
|
|
8765
|
-
results.push(cloudSuggestion);
|
|
8935
|
+
});
|
|
8766
8936
|
}
|
|
8767
8937
|
return results;
|
|
8768
8938
|
}
|
|
@@ -9340,7 +9510,7 @@ function saveState(state) {
|
|
|
9340
9510
|
}
|
|
9341
9511
|
function pruneOldSessions(sessionsDir) {
|
|
9342
9512
|
try {
|
|
9343
|
-
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(
|
|
9513
|
+
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => ({
|
|
9344
9514
|
name: f,
|
|
9345
9515
|
path: join9(sessionsDir, f),
|
|
9346
9516
|
mtime: statSync(join9(sessionsDir, f)).mtimeMs
|
|
@@ -9481,13 +9651,13 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9481
9651
|
if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
|
|
9482
9652
|
lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
|
|
9483
9653
|
lines.push(`Alternative approaches:`);
|
|
9484
|
-
lines.push(` 1. Save output to file: command > /
|
|
9485
|
-
lines.push(` 2. Use tool-specific flags: nmap -oN /
|
|
9654
|
+
lines.push(` 1. Save output to file: command > ${WORK_DIR}/output.txt, then use read_file("${WORK_DIR}/output.txt")`);
|
|
9655
|
+
lines.push(` 2. Use tool-specific flags: nmap -oN ${WORK_DIR}/scan.txt, curl -o ${WORK_DIR}/page.html`);
|
|
9486
9656
|
lines.push(` 3. Use browse_url for web content instead of curl | grep`);
|
|
9487
9657
|
lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
|
|
9488
9658
|
} else if (errorLower.includes("redirect")) {
|
|
9489
|
-
lines.push(`Fix: Redirect to /
|
|
9490
|
-
lines.push(`Example: command > /
|
|
9659
|
+
lines.push(`Fix: Redirect to ${WORK_DIR}/ path.`);
|
|
9660
|
+
lines.push(`Example: command > ${WORK_DIR}/output.txt or command 2>&1 > ${WORK_DIR}/errors.txt`);
|
|
9491
9661
|
} else {
|
|
9492
9662
|
lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
|
|
9493
9663
|
lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
|
|
@@ -10006,7 +10176,7 @@ function createLLMDigestFn(llmClient) {
|
|
|
10006
10176
|
return async (text, context) => {
|
|
10007
10177
|
const truncatedText = text.length > LAYER3_MAX_INPUT_CHARS ? text.slice(0, LAYER3_MAX_INPUT_CHARS) + `
|
|
10008
10178
|
... [truncated for summarization, ${text.length - LAYER3_MAX_INPUT_CHARS} chars omitted]` : text;
|
|
10009
|
-
const messages = [{ role:
|
|
10179
|
+
const messages = [{ role: LLM_ROLES.USER, content: `Analyze this pentesting tool output and extract actionable intelligence.
|
|
10010
10180
|
|
|
10011
10181
|
Context: ${context}
|
|
10012
10182
|
|
|
@@ -11287,7 +11457,7 @@ var Strategist = class {
|
|
|
11287
11457
|
// ─── LLM Call ───────────────────────────────────────────────
|
|
11288
11458
|
async callLLM(input) {
|
|
11289
11459
|
const messages = [{
|
|
11290
|
-
role:
|
|
11460
|
+
role: LLM_ROLES.USER,
|
|
11291
11461
|
content: `Analyze this penetration test situation and write a tactical directive for the attack agent.
|
|
11292
11462
|
|
|
11293
11463
|
${input}`
|
|
@@ -11316,7 +11486,7 @@ ${input}`
|
|
|
11316
11486
|
*/
|
|
11317
11487
|
formatForPrompt(directive, isStale = false) {
|
|
11318
11488
|
if (!directive.content) return "";
|
|
11319
|
-
const age = Math.floor((Date.now() - directive.generatedAt) /
|
|
11489
|
+
const age = Math.floor((Date.now() - directive.generatedAt) / MS_PER_MINUTE);
|
|
11320
11490
|
const staleWarning = isStale ? `
|
|
11321
11491
|
NOTE: This directive is from ${age}min ago (Strategist call failed this turn). Verify assumptions are still valid.` : "";
|
|
11322
11492
|
return [
|
|
@@ -11465,8 +11635,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
11465
11635
|
});
|
|
11466
11636
|
}
|
|
11467
11637
|
// ─── Public API Surface ─────────────────────────────────────
|
|
11468
|
-
setAutoApprove(
|
|
11469
|
-
this.approvalGate.setAutoApprove(
|
|
11638
|
+
setAutoApprove(shouldEnable) {
|
|
11639
|
+
this.approvalGate.setAutoApprove(shouldEnable);
|
|
11470
11640
|
}
|
|
11471
11641
|
getState() {
|
|
11472
11642
|
return this.state;
|
|
@@ -11978,9 +12148,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11978
12148
|
const onReasoningDelta = (e) => {
|
|
11979
12149
|
reasoningBufferRef.current += e.data.content;
|
|
11980
12150
|
const chars = reasoningBufferRef.current.length;
|
|
11981
|
-
const estTokens = Math.round(chars /
|
|
12151
|
+
const estTokens = Math.round(chars / LLM_LIMITS.charsPerTokenEstimate);
|
|
12152
|
+
const firstLine = reasoningBufferRef.current.split("\n")[0]?.slice(0, TUI_DISPLAY_LIMITS.reasoningPreviewChars) || "";
|
|
11982
12153
|
setCurrentStatus(`Reasoning (~${estTokens} tokens)
|
|
11983
|
-
${
|
|
12154
|
+
${firstLine}`);
|
|
11984
12155
|
};
|
|
11985
12156
|
const onReasoningEnd = () => {
|
|
11986
12157
|
const text = reasoningBufferRef.current.trim();
|
|
@@ -12439,7 +12610,7 @@ var MessageList = memo(({ messages }) => {
|
|
|
12439
12610
|
if (msg.type === "thinking") {
|
|
12440
12611
|
const lines = msg.content.split("\n");
|
|
12441
12612
|
const charCount = msg.content.length;
|
|
12442
|
-
const estTokens = Math.round(charCount /
|
|
12613
|
+
const estTokens = Math.round(charCount / LLM_LIMITS.charsPerTokenEstimate);
|
|
12443
12614
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 0, marginBottom: 1, children: [
|
|
12444
12615
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
12445
12616
|
/* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, bold: true, children: "Reasoning" }),
|
|
@@ -12701,9 +12872,9 @@ import { memo as memo5 } from "react";
|
|
|
12701
12872
|
import { Box as Box5, Text as Text6 } from "ink";
|
|
12702
12873
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
12703
12874
|
var formatElapsed = (totalSeconds) => {
|
|
12704
|
-
const hours = Math.floor(totalSeconds /
|
|
12705
|
-
const minutes = Math.floor(totalSeconds %
|
|
12706
|
-
const seconds = Math.floor(totalSeconds %
|
|
12875
|
+
const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
|
|
12876
|
+
const minutes = Math.floor(totalSeconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
|
|
12877
|
+
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE);
|
|
12707
12878
|
const pad = (n) => String(n).padStart(2, "0");
|
|
12708
12879
|
if (hours > 0) {
|
|
12709
12880
|
return `${hours}:${pad(minutes)}:${pad(seconds)}`;
|
|
@@ -12857,11 +13028,11 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12857
13028
|
}
|
|
12858
13029
|
if (f.evidence.length > 0) {
|
|
12859
13030
|
findingLines.push(` Evidence:`);
|
|
12860
|
-
f.evidence.slice(0,
|
|
12861
|
-
const preview = e.length >
|
|
13031
|
+
f.evidence.slice(0, DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW).forEach((e) => {
|
|
13032
|
+
const preview = e.length > DISPLAY_LIMITS.EVIDENCE_PREVIEW_LENGTH ? e.slice(0, DISPLAY_LIMITS.EVIDENCE_PREVIEW_LENGTH) + "..." : e;
|
|
12862
13033
|
findingLines.push(` \u25B8 ${preview}`);
|
|
12863
13034
|
});
|
|
12864
|
-
if (f.evidence.length >
|
|
13035
|
+
if (f.evidence.length > DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW) findingLines.push(` ... +${f.evidence.length - DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW} more`);
|
|
12865
13036
|
}
|
|
12866
13037
|
findingLines.push("");
|
|
12867
13038
|
});
|