pentesting 0.47.2 → 0.47.4
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 +58 -5
- package/dist/cloud/prompt.md +1 -1
- package/dist/file-sharing/prompt.md +2 -2
- package/dist/main.js +1294 -889
- package/dist/network/prompt.md +1 -1
- package/dist/orchestrator/orchestrator.md +44 -43
- package/dist/prompts/base.md +40 -10
- 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.4";
|
|
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,73 @@ 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 JOURNAL_DIR = `${PENTESTING_ROOT}/journal`;
|
|
832
|
+
var WORKSPACE = {
|
|
833
|
+
/** Root directory */
|
|
834
|
+
get ROOT() {
|
|
835
|
+
return path.resolve(PENTESTING_ROOT);
|
|
836
|
+
},
|
|
837
|
+
/** Temporary files */
|
|
838
|
+
get TMP() {
|
|
839
|
+
return path.resolve(WORK_DIR);
|
|
840
|
+
},
|
|
841
|
+
/** Persistent memory */
|
|
842
|
+
get MEMORY() {
|
|
843
|
+
return path.resolve(MEMORY_DIR);
|
|
844
|
+
},
|
|
845
|
+
/** Generated reports */
|
|
846
|
+
get REPORTS() {
|
|
847
|
+
return path.resolve(REPORTS_DIR);
|
|
848
|
+
},
|
|
849
|
+
/** Session snapshots */
|
|
850
|
+
get SESSIONS() {
|
|
851
|
+
return path.resolve(SESSIONS_DIR);
|
|
852
|
+
},
|
|
853
|
+
/** Captured loot */
|
|
854
|
+
get LOOT() {
|
|
855
|
+
return path.resolve(LOOT_DIR);
|
|
856
|
+
},
|
|
857
|
+
/** Full tool outputs */
|
|
858
|
+
get OUTPUTS() {
|
|
859
|
+
return path.resolve(OUTPUTS_DIR);
|
|
860
|
+
},
|
|
861
|
+
/** Debug logs */
|
|
862
|
+
get DEBUG() {
|
|
863
|
+
return path.resolve(DEBUG_DIR);
|
|
864
|
+
},
|
|
865
|
+
/** Persistent per-turn journal (§13 memo system) */
|
|
866
|
+
get JOURNAL() {
|
|
867
|
+
return path.resolve(JOURNAL_DIR);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// src/shared/constants/orchestrator.ts
|
|
872
|
+
var GRACEFUL_SHUTDOWN_WAIT_MS = 200;
|
|
873
|
+
var PROCESS_OUTPUT_TRUNCATION_LIMIT = 1e4;
|
|
874
|
+
var LONG_RUNNING_THRESHOLD_MS = 5 * MS_PER_MINUTE;
|
|
875
|
+
var VERY_LONG_RUNNING_THRESHOLD_MS = 15 * MS_PER_MINUTE;
|
|
876
|
+
var MAX_RECOMMENDATIONS_FOR_HEALTHY = 2;
|
|
877
|
+
var DEFAULT_DIRECTIVE_FOCUS = PHASES.RECON;
|
|
878
|
+
|
|
760
879
|
// src/shared/utils/command-security-lists.ts
|
|
761
880
|
var ALLOWED_BINARIES = /* @__PURE__ */ new Set([
|
|
762
881
|
// Network scanning
|
|
@@ -964,7 +1083,7 @@ var SAFE_PIPE_TARGETS = /* @__PURE__ */ new Set([
|
|
|
964
1083
|
"python3",
|
|
965
1084
|
"python"
|
|
966
1085
|
]);
|
|
967
|
-
var SAFE_REDIRECT_PATHS = [
|
|
1086
|
+
var SAFE_REDIRECT_PATHS = [`${WORK_DIR}/`];
|
|
968
1087
|
var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
969
1088
|
"rm",
|
|
970
1089
|
"shred",
|
|
@@ -989,50 +1108,6 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
|
989
1108
|
// src/shared/utils/debug-logger.ts
|
|
990
1109
|
import { appendFileSync, writeFileSync } from "fs";
|
|
991
1110
|
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
1111
|
var DebugLogger = class _DebugLogger {
|
|
1037
1112
|
static instance;
|
|
1038
1113
|
logPath;
|
|
@@ -1123,16 +1198,16 @@ var SHELL_CHARS = {
|
|
|
1123
1198
|
};
|
|
1124
1199
|
function validateCommand(command) {
|
|
1125
1200
|
if (!command || typeof command !== "string") {
|
|
1126
|
-
return {
|
|
1201
|
+
return { isSafe: false, error: "Empty or invalid command" };
|
|
1127
1202
|
}
|
|
1128
1203
|
const normalizedCommand = command.trim();
|
|
1129
1204
|
if (normalizedCommand.length === 0) {
|
|
1130
|
-
return {
|
|
1205
|
+
return { isSafe: false, error: "Empty command" };
|
|
1131
1206
|
}
|
|
1132
1207
|
for (const pattern of INJECTION_PATTERNS) {
|
|
1133
1208
|
if (pattern.test(normalizedCommand)) {
|
|
1134
1209
|
return {
|
|
1135
|
-
|
|
1210
|
+
isSafe: false,
|
|
1136
1211
|
error: `Injection pattern detected: ${pattern.source}`,
|
|
1137
1212
|
suggestion: "Avoid command substitution ($(), ``), variable expansion (${}) and control characters."
|
|
1138
1213
|
};
|
|
@@ -1141,13 +1216,13 @@ function validateCommand(command) {
|
|
|
1141
1216
|
const subCommands = splitChainedCommands(normalizedCommand);
|
|
1142
1217
|
for (const subCmd of subCommands) {
|
|
1143
1218
|
const result2 = validateSingleCommand(subCmd.trim());
|
|
1144
|
-
if (!result2.
|
|
1219
|
+
if (!result2.isSafe) {
|
|
1145
1220
|
return result2;
|
|
1146
1221
|
}
|
|
1147
1222
|
}
|
|
1148
1223
|
const primaryBinary = extractBinary(subCommands[0].trim());
|
|
1149
1224
|
return {
|
|
1150
|
-
|
|
1225
|
+
isSafe: true,
|
|
1151
1226
|
binary: primaryBinary || void 0,
|
|
1152
1227
|
args: normalizedCommand.split(/\s+/).slice(1)
|
|
1153
1228
|
};
|
|
@@ -1202,14 +1277,14 @@ function validateSingleCommand(command) {
|
|
|
1202
1277
|
if (!binary) continue;
|
|
1203
1278
|
if (BLOCKED_BINARIES.has(binary)) {
|
|
1204
1279
|
return {
|
|
1205
|
-
|
|
1280
|
+
isSafe: false,
|
|
1206
1281
|
error: `Binary '${binary}' is blocked for security reasons`,
|
|
1207
1282
|
suggestion: `'${binary}' is not allowed. Use a different approach.`
|
|
1208
1283
|
};
|
|
1209
1284
|
}
|
|
1210
1285
|
if (idx > 0 && !SAFE_PIPE_TARGETS.has(binary) && !ALLOWED_BINARIES.has(binary)) {
|
|
1211
1286
|
return {
|
|
1212
|
-
|
|
1287
|
+
isSafe: false,
|
|
1213
1288
|
error: `Pipe target '${binary}' is not in the allowed list`,
|
|
1214
1289
|
suggestion: `Piping to '${binary}' is not allowed. Safe pipe targets: head, tail, grep, awk, sed, cut, sort, uniq, wc, jq, tee.`
|
|
1215
1290
|
};
|
|
@@ -1219,10 +1294,10 @@ function validateSingleCommand(command) {
|
|
|
1219
1294
|
}
|
|
1220
1295
|
}
|
|
1221
1296
|
const redirectResult = validateRedirects(command);
|
|
1222
|
-
if (!redirectResult.
|
|
1297
|
+
if (!redirectResult.isSafe) {
|
|
1223
1298
|
return redirectResult;
|
|
1224
1299
|
}
|
|
1225
|
-
return {
|
|
1300
|
+
return { isSafe: true };
|
|
1226
1301
|
}
|
|
1227
1302
|
function splitByPipe(command) {
|
|
1228
1303
|
const parts = [];
|
|
@@ -1265,33 +1340,91 @@ function stripRedirects(segment) {
|
|
|
1265
1340
|
return segment.replace(/\d*>\s*&\d+/g, "").replace(/\d*>>\s*\S+/g, "").replace(/\d*>\s*\S+/g, "").replace(/<\s*\S+/g, "").trim();
|
|
1266
1341
|
}
|
|
1267
1342
|
function validateRedirects(command) {
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1343
|
+
const redirects = extractRedirectTargets(command);
|
|
1344
|
+
for (const { type, fd, target } of redirects) {
|
|
1345
|
+
if (type === ">" || type === ">>") {
|
|
1346
|
+
if (target.startsWith("&") || target === "/dev/null") continue;
|
|
1347
|
+
const isAllowed = SAFE_REDIRECT_PATHS.some((p) => target.startsWith(p));
|
|
1348
|
+
if (!isAllowed) {
|
|
1349
|
+
return {
|
|
1350
|
+
isSafe: false,
|
|
1351
|
+
error: `Redirect to '${target}' is not in allowed paths`,
|
|
1352
|
+
suggestion: `Redirect output to ${WORK_DIR}/ paths. Example: > ${WORK_DIR}/output.txt`
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
} else if (type === "<") {
|
|
1356
|
+
const isAllowed = SAFE_REDIRECT_PATHS.some((p) => target.startsWith(p));
|
|
1357
|
+
if (!isAllowed) {
|
|
1358
|
+
return {
|
|
1359
|
+
isSafe: false,
|
|
1360
|
+
error: `Input redirect from '${target}' is not in allowed paths`,
|
|
1361
|
+
suggestion: `Input redirect only from ${WORK_DIR}/ paths.`
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1280
1364
|
}
|
|
1281
1365
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1366
|
+
return { isSafe: true };
|
|
1367
|
+
}
|
|
1368
|
+
function extractRedirectTargets(command) {
|
|
1369
|
+
const redirects = [];
|
|
1370
|
+
let inSingle = false;
|
|
1371
|
+
let inDouble = false;
|
|
1372
|
+
let i = 0;
|
|
1373
|
+
while (i < command.length) {
|
|
1374
|
+
const ch = command[i];
|
|
1375
|
+
if (ch === "'" && !inDouble) {
|
|
1376
|
+
inSingle = !inSingle;
|
|
1377
|
+
i++;
|
|
1378
|
+
continue;
|
|
1292
1379
|
}
|
|
1380
|
+
if (ch === '"' && !inSingle) {
|
|
1381
|
+
inDouble = !inDouble;
|
|
1382
|
+
i++;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (!inSingle && !inDouble) {
|
|
1386
|
+
if (ch === ">" || ch === "<") {
|
|
1387
|
+
const isAppend = ch === ">" && command[i + 1] === ">";
|
|
1388
|
+
const type = isAppend ? ">>" : ch;
|
|
1389
|
+
const jump = isAppend ? 2 : 1;
|
|
1390
|
+
let fd = "";
|
|
1391
|
+
if (ch === ">") {
|
|
1392
|
+
let b = i - 1;
|
|
1393
|
+
while (b >= 0 && /\d/.test(command[b])) {
|
|
1394
|
+
fd = command[b] + fd;
|
|
1395
|
+
b--;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
i += jump;
|
|
1399
|
+
while (i < command.length && /\s/.test(command[i])) i++;
|
|
1400
|
+
let target = "";
|
|
1401
|
+
let targetInSingle = false;
|
|
1402
|
+
let targetInDouble = false;
|
|
1403
|
+
while (i < command.length) {
|
|
1404
|
+
const tc = command[i];
|
|
1405
|
+
if (tc === "'" && !targetInDouble) {
|
|
1406
|
+
targetInSingle = !targetInSingle;
|
|
1407
|
+
i++;
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (tc === '"' && !targetInSingle) {
|
|
1411
|
+
targetInDouble = !targetInDouble;
|
|
1412
|
+
i++;
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
if (!targetInSingle && !targetInDouble && /[\s|;&<>]/.test(tc)) break;
|
|
1416
|
+
target += tc;
|
|
1417
|
+
i++;
|
|
1418
|
+
}
|
|
1419
|
+
if (target) {
|
|
1420
|
+
redirects.push({ type, fd, target });
|
|
1421
|
+
}
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
i++;
|
|
1293
1426
|
}
|
|
1294
|
-
return
|
|
1427
|
+
return redirects;
|
|
1295
1428
|
}
|
|
1296
1429
|
function extractBinary(command) {
|
|
1297
1430
|
const parts = command.trim().split(/\s+/);
|
|
@@ -1514,7 +1647,7 @@ function clearCommandEventEmitter() {
|
|
|
1514
1647
|
async function runCommand(command, args = [], options = {}) {
|
|
1515
1648
|
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
1516
1649
|
const validation = validateCommand(fullCommand);
|
|
1517
|
-
if (!validation.
|
|
1650
|
+
if (!validation.isSafe) {
|
|
1518
1651
|
return {
|
|
1519
1652
|
success: false,
|
|
1520
1653
|
output: "",
|
|
@@ -1688,6 +1821,34 @@ function createTempFile(suffix = "") {
|
|
|
1688
1821
|
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1689
1822
|
}
|
|
1690
1823
|
|
|
1824
|
+
// src/shared/constants/files.ts
|
|
1825
|
+
var FILE_EXTENSIONS = {
|
|
1826
|
+
// Data formats
|
|
1827
|
+
JSON: ".json",
|
|
1828
|
+
MARKDOWN: ".md",
|
|
1829
|
+
TXT: ".txt",
|
|
1830
|
+
XML: ".xml",
|
|
1831
|
+
// Network capture
|
|
1832
|
+
PCAP: ".pcap",
|
|
1833
|
+
HOSTS: ".hosts",
|
|
1834
|
+
MITM: ".mitm",
|
|
1835
|
+
// Process I/O
|
|
1836
|
+
STDOUT: ".stdout",
|
|
1837
|
+
STDERR: ".stderr",
|
|
1838
|
+
STDIN: ".stdin",
|
|
1839
|
+
// Scripts
|
|
1840
|
+
SH: ".sh",
|
|
1841
|
+
PY: ".py",
|
|
1842
|
+
CJS: ".cjs",
|
|
1843
|
+
// Config
|
|
1844
|
+
ENV: ".env",
|
|
1845
|
+
BAK: ".bak"
|
|
1846
|
+
};
|
|
1847
|
+
var SPECIAL_FILES = {
|
|
1848
|
+
LATEST_STATE: "latest.json",
|
|
1849
|
+
README: "README.md"
|
|
1850
|
+
};
|
|
1851
|
+
|
|
1691
1852
|
// src/engine/process-cleanup.ts
|
|
1692
1853
|
import { unlinkSync } from "fs";
|
|
1693
1854
|
|
|
@@ -1877,9 +2038,9 @@ function logEvent(processId, event, detail) {
|
|
|
1877
2038
|
}
|
|
1878
2039
|
function startBackgroundProcess(command, options = {}) {
|
|
1879
2040
|
const processId = generatePrefixedId("bg");
|
|
1880
|
-
const stdoutFile = createTempFile(
|
|
1881
|
-
const stderrFile = createTempFile(
|
|
1882
|
-
const stdinFile = createTempFile(
|
|
2041
|
+
const stdoutFile = createTempFile(FILE_EXTENSIONS.STDOUT);
|
|
2042
|
+
const stderrFile = createTempFile(FILE_EXTENSIONS.STDERR);
|
|
2043
|
+
const stdinFile = createTempFile(FILE_EXTENSIONS.STDIN);
|
|
1883
2044
|
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
1884
2045
|
let wrappedCmd;
|
|
1885
2046
|
if (isInteractive) {
|
|
@@ -2531,13 +2692,13 @@ var AttackGraph = class {
|
|
|
2531
2692
|
* Record a credential discovery and create spray edges.
|
|
2532
2693
|
*/
|
|
2533
2694
|
addCredential(username, password, source) {
|
|
2534
|
-
const credId = this.addNode(
|
|
2695
|
+
const credId = this.addNode(NODE_TYPE.CREDENTIAL, `${username}:***`, {
|
|
2535
2696
|
username,
|
|
2536
2697
|
password,
|
|
2537
2698
|
source
|
|
2538
2699
|
});
|
|
2539
2700
|
for (const [id, node] of this.nodes) {
|
|
2540
|
-
if (node.type ===
|
|
2701
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
2541
2702
|
const svc = String(node.data.service || "");
|
|
2542
2703
|
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
|
|
2543
2704
|
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
@@ -2550,7 +2711,7 @@ var AttackGraph = class {
|
|
|
2550
2711
|
* Record a vulnerability finding.
|
|
2551
2712
|
*/
|
|
2552
2713
|
addVulnerability(title, target, severity, hasExploit = false) {
|
|
2553
|
-
const vulnId = this.addNode(
|
|
2714
|
+
const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, title, {
|
|
2554
2715
|
target,
|
|
2555
2716
|
severity,
|
|
2556
2717
|
hasExploit
|
|
@@ -2561,7 +2722,7 @@ var AttackGraph = class {
|
|
|
2561
2722
|
}
|
|
2562
2723
|
}
|
|
2563
2724
|
if (hasExploit) {
|
|
2564
|
-
const accessId = this.addNode(
|
|
2725
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `shell via ${title}`, {
|
|
2565
2726
|
via: title,
|
|
2566
2727
|
status: GRAPH_STATUS.POTENTIAL
|
|
2567
2728
|
});
|
|
@@ -2573,14 +2734,14 @@ var AttackGraph = class {
|
|
|
2573
2734
|
* Record gained access.
|
|
2574
2735
|
*/
|
|
2575
2736
|
addAccess(host, level, via) {
|
|
2576
|
-
const accessId = this.addNode(
|
|
2737
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `${level}@${host}`, {
|
|
2577
2738
|
host,
|
|
2578
2739
|
level,
|
|
2579
2740
|
via
|
|
2580
2741
|
});
|
|
2581
2742
|
this.markSucceeded(accessId);
|
|
2582
2743
|
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
2583
|
-
const lootId = this.addNode(
|
|
2744
|
+
const lootId = this.addNode(NODE_TYPE.LOOT, `flags on ${host}`, {
|
|
2584
2745
|
host,
|
|
2585
2746
|
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
2586
2747
|
});
|
|
@@ -2592,7 +2753,7 @@ var AttackGraph = class {
|
|
|
2592
2753
|
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
2593
2754
|
*/
|
|
2594
2755
|
addOSINT(category, detail, data = {}) {
|
|
2595
|
-
const osintId = this.addNode(
|
|
2756
|
+
const osintId = this.addNode(NODE_TYPE.OSINT, `${category}: ${detail}`, {
|
|
2596
2757
|
category,
|
|
2597
2758
|
detail,
|
|
2598
2759
|
...data
|
|
@@ -2979,7 +3140,7 @@ var WorkingMemory = class {
|
|
|
2979
3140
|
const lines = ["<working-memory>"];
|
|
2980
3141
|
if (failures.length > 0) {
|
|
2981
3142
|
lines.push(`\u26A0\uFE0F FAILED ATTEMPTS (${failures.length} \u2014 DO NOT REPEAT):`);
|
|
2982
|
-
for (const f of failures.slice(-
|
|
3143
|
+
for (const f of failures.slice(-DISPLAY_LIMITS.RECENT_FAILURES)) {
|
|
2983
3144
|
lines.push(` \u2717 ${f.content}`);
|
|
2984
3145
|
}
|
|
2985
3146
|
}
|
|
@@ -2989,13 +3150,13 @@ var WorkingMemory = class {
|
|
|
2989
3150
|
}
|
|
2990
3151
|
if (successes.length > 0) {
|
|
2991
3152
|
lines.push(`\u2705 RECENT SUCCESSES (${successes.length}):`);
|
|
2992
|
-
for (const s of successes.slice(-
|
|
3153
|
+
for (const s of successes.slice(-DISPLAY_LIMITS.RECENT_SUCCESSES)) {
|
|
2993
3154
|
lines.push(` \u2713 ${s.content}`);
|
|
2994
3155
|
}
|
|
2995
3156
|
}
|
|
2996
3157
|
if (insights.length > 0) {
|
|
2997
3158
|
lines.push(`\u{1F4A1} INSIGHTS:`);
|
|
2998
|
-
for (const i of insights.slice(-
|
|
3159
|
+
for (const i of insights.slice(-DISPLAY_LIMITS.RECENT_INSIGHTS)) {
|
|
2999
3160
|
lines.push(` \u2192 ${i.content}`);
|
|
3000
3161
|
}
|
|
3001
3162
|
}
|
|
@@ -3048,9 +3209,9 @@ var EpisodicMemory = class {
|
|
|
3048
3209
|
toPrompt() {
|
|
3049
3210
|
if (this.events.length === 0) return "";
|
|
3050
3211
|
const lines = ["<session-timeline>"];
|
|
3051
|
-
const recent = this.events.slice(-
|
|
3212
|
+
const recent = this.events.slice(-DISPLAY_LIMITS.RECENT_MEMORY_EVENTS);
|
|
3052
3213
|
for (const e of recent) {
|
|
3053
|
-
const mins = Math.floor((Date.now() - e.timestamp) /
|
|
3214
|
+
const mins = Math.floor((Date.now() - e.timestamp) / MS_PER_MINUTE);
|
|
3054
3215
|
const icon = {
|
|
3055
3216
|
tool_success: "\u2705",
|
|
3056
3217
|
tool_failure: "\u274C",
|
|
@@ -3069,7 +3230,6 @@ var EpisodicMemory = class {
|
|
|
3069
3230
|
this.events = [];
|
|
3070
3231
|
}
|
|
3071
3232
|
};
|
|
3072
|
-
var MEMORY_DIR = "/tmp/pentesting-memory";
|
|
3073
3233
|
var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
|
|
3074
3234
|
var PersistentMemory = class {
|
|
3075
3235
|
knowledge;
|
|
@@ -3186,7 +3346,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3186
3346
|
});
|
|
3187
3347
|
if (this.techniques.length > this.maxTechniques) {
|
|
3188
3348
|
this.techniques.sort((a, b) => {
|
|
3189
|
-
if (a.
|
|
3349
|
+
if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
|
|
3190
3350
|
return b.learnedAt - a.learnedAt;
|
|
3191
3351
|
});
|
|
3192
3352
|
this.techniques = this.techniques.slice(0, this.maxTechniques);
|
|
@@ -3222,7 +3382,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3222
3382
|
source: `Web search: "${query}"`,
|
|
3223
3383
|
technique: tech,
|
|
3224
3384
|
applicableTo,
|
|
3225
|
-
|
|
3385
|
+
isVerified: false,
|
|
3226
3386
|
fromQuery: query
|
|
3227
3387
|
});
|
|
3228
3388
|
}
|
|
@@ -3233,7 +3393,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3233
3393
|
verify(techniqueSubstring) {
|
|
3234
3394
|
for (const t of this.techniques) {
|
|
3235
3395
|
if (t.technique.toLowerCase().includes(techniqueSubstring.toLowerCase())) {
|
|
3236
|
-
t.
|
|
3396
|
+
t.isVerified = true;
|
|
3237
3397
|
}
|
|
3238
3398
|
}
|
|
3239
3399
|
}
|
|
@@ -3261,8 +3421,8 @@ var DynamicTechniqueLibrary = class {
|
|
|
3261
3421
|
*/
|
|
3262
3422
|
toPrompt() {
|
|
3263
3423
|
if (this.techniques.length === 0) return "";
|
|
3264
|
-
const verified = this.techniques.filter((t) => t.
|
|
3265
|
-
const unverified = this.techniques.filter((t) => !t.
|
|
3424
|
+
const verified = this.techniques.filter((t) => t.isVerified);
|
|
3425
|
+
const unverified = this.techniques.filter((t) => !t.isVerified);
|
|
3266
3426
|
const lines = ["<learned-techniques>"];
|
|
3267
3427
|
if (verified.length > 0) {
|
|
3268
3428
|
lines.push("VERIFIED (worked in this session):");
|
|
@@ -3539,8 +3699,8 @@ var SharedState = class {
|
|
|
3539
3699
|
return this.data.currentPhase;
|
|
3540
3700
|
}
|
|
3541
3701
|
// --- CTF Mode ---
|
|
3542
|
-
setCtfMode(
|
|
3543
|
-
this.data.ctfMode =
|
|
3702
|
+
setCtfMode(shouldEnable) {
|
|
3703
|
+
this.data.ctfMode = shouldEnable;
|
|
3544
3704
|
}
|
|
3545
3705
|
isCtfMode() {
|
|
3546
3706
|
return this.data.ctfMode;
|
|
@@ -3826,20 +3986,6 @@ var ScopeGuard = class {
|
|
|
3826
3986
|
};
|
|
3827
3987
|
|
|
3828
3988
|
// src/engine/approval.ts
|
|
3829
|
-
var CATEGORY_APPROVAL = {
|
|
3830
|
-
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
3831
|
-
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
3832
|
-
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
3833
|
-
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
3834
|
-
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
3835
|
-
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
3836
|
-
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
3837
|
-
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
3838
|
-
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
3839
|
-
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
3840
|
-
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
3841
|
-
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
3842
|
-
};
|
|
3843
3989
|
var ApprovalGate = class {
|
|
3844
3990
|
constructor(shouldAutoApprove = false) {
|
|
3845
3991
|
this.shouldAutoApprove = shouldAutoApprove;
|
|
@@ -3847,8 +3993,8 @@ var ApprovalGate = class {
|
|
|
3847
3993
|
/**
|
|
3848
3994
|
* Set auto-approve mode
|
|
3849
3995
|
*/
|
|
3850
|
-
setAutoApprove(
|
|
3851
|
-
this.shouldAutoApprove =
|
|
3996
|
+
setAutoApprove(shouldEnable) {
|
|
3997
|
+
this.shouldAutoApprove = shouldEnable;
|
|
3852
3998
|
}
|
|
3853
3999
|
/**
|
|
3854
4000
|
* Get current auto-approve mode
|
|
@@ -4143,7 +4289,7 @@ function autoExtractStructured(toolName, output) {
|
|
|
4143
4289
|
data.vulnerabilities = vulns;
|
|
4144
4290
|
hasData = true;
|
|
4145
4291
|
}
|
|
4146
|
-
if (toolName ===
|
|
4292
|
+
if (toolName === TOOL_NAMES.PARSE_NMAP || /nmap scan report/i.test(output)) {
|
|
4147
4293
|
const nmap = extractNmapStructured(output);
|
|
4148
4294
|
if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
|
|
4149
4295
|
data.openPorts = nmap.structured.openPorts;
|
|
@@ -4548,7 +4694,8 @@ Used ports: ${usedPorts.join(", ")}
|
|
|
4548
4694
|
[!] STRATEGY ADAPTATION REQUIRED:
|
|
4549
4695
|
1. Try the next available port (e.g., ${nextPort} or 4445, 9001)
|
|
4550
4696
|
2. If this is for a listener, update your Mission/Checklist with the NEW port so other agents know.
|
|
4551
|
-
3. Check bg_process({ action: "list" }) to see if you can stop the conflicting process
|
|
4697
|
+
3. Check bg_process({ action: "list" }) to see if you can stop the conflicting process.`,
|
|
4698
|
+
error: `Port ${requestedPort} already in use`
|
|
4552
4699
|
};
|
|
4553
4700
|
}
|
|
4554
4701
|
}
|
|
@@ -4695,7 +4842,7 @@ ${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connecti
|
|
|
4695
4842
|
if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
|
|
4696
4843
|
const waitMs = Math.min(params.wait_ms || SYSTEM_LIMITS.DEFAULT_WAIT_MS_INTERACT, SYSTEM_LIMITS.MAX_WAIT_MS_INTERACT);
|
|
4697
4844
|
const result2 = await sendToProcess(processId, cmd, waitMs);
|
|
4698
|
-
if (!result2.success) return { success: false, output: result2.output };
|
|
4845
|
+
if (!result2.success) return { success: false, output: result2.output, error: result2.output };
|
|
4699
4846
|
return {
|
|
4700
4847
|
success: true,
|
|
4701
4848
|
output: `Command sent: ${cmd}
|
|
@@ -4711,7 +4858,7 @@ ${result2.output}`
|
|
|
4711
4858
|
if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
|
|
4712
4859
|
const desc = params.description;
|
|
4713
4860
|
const success = promoteToShell(processId, desc);
|
|
4714
|
-
if (!success) return { success: false, output: `Process ${processId} not found` };
|
|
4861
|
+
if (!success) return { success: false, output: `Process ${processId} not found`, error: `Process ${processId} not found` };
|
|
4715
4862
|
return {
|
|
4716
4863
|
success: true,
|
|
4717
4864
|
output: `[OK] Process ${processId} promoted to ACTIVE SHELL.
|
|
@@ -4861,7 +5008,8 @@ Examples:
|
|
|
4861
5008
|
if (!validPhases.includes(newPhase)) {
|
|
4862
5009
|
return {
|
|
4863
5010
|
success: false,
|
|
4864
|
-
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}
|
|
5011
|
+
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}`,
|
|
5012
|
+
error: `Invalid phase: ${newPhase}`
|
|
4865
5013
|
};
|
|
4866
5014
|
}
|
|
4867
5015
|
state.setPhase(newPhase);
|
|
@@ -4989,6 +5137,36 @@ function formatValidation(result2) {
|
|
|
4989
5137
|
}
|
|
4990
5138
|
|
|
4991
5139
|
// src/engine/tools/pentest-target-tools.ts
|
|
5140
|
+
function isPortArray(value) {
|
|
5141
|
+
if (!Array.isArray(value)) return false;
|
|
5142
|
+
return value.every(
|
|
5143
|
+
(item) => typeof item === "object" && item !== null && typeof item.port === "number"
|
|
5144
|
+
);
|
|
5145
|
+
}
|
|
5146
|
+
function parsePorts(value) {
|
|
5147
|
+
return isPortArray(value) ? value : [];
|
|
5148
|
+
}
|
|
5149
|
+
function isValidSeverity(value) {
|
|
5150
|
+
return typeof value === "string" && Object.values(SEVERITIES).includes(value);
|
|
5151
|
+
}
|
|
5152
|
+
function parseSeverity(value) {
|
|
5153
|
+
return isValidSeverity(value) ? value : "medium";
|
|
5154
|
+
}
|
|
5155
|
+
function isValidLootType(value) {
|
|
5156
|
+
return typeof value === "string" && Object.values(LOOT_TYPES).includes(value);
|
|
5157
|
+
}
|
|
5158
|
+
function parseLootType(value) {
|
|
5159
|
+
return isValidLootType(value) ? value : "file";
|
|
5160
|
+
}
|
|
5161
|
+
function isValidAttackTactic(value) {
|
|
5162
|
+
return typeof value === "string" && Object.values(ATTACK_TACTICS).includes(value);
|
|
5163
|
+
}
|
|
5164
|
+
function parseStringArray(value) {
|
|
5165
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? value : [];
|
|
5166
|
+
}
|
|
5167
|
+
function parseString(value, defaultValue = "") {
|
|
5168
|
+
return typeof value === "string" ? value : defaultValue;
|
|
5169
|
+
}
|
|
4992
5170
|
var createTargetTools = (state) => [
|
|
4993
5171
|
{
|
|
4994
5172
|
name: TOOL_NAMES.ADD_TARGET,
|
|
@@ -5020,10 +5198,10 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5020
5198
|
},
|
|
5021
5199
|
required: ["ip"],
|
|
5022
5200
|
execute: async (p) => {
|
|
5023
|
-
const ip = p.ip;
|
|
5201
|
+
const ip = parseString(p.ip);
|
|
5024
5202
|
const existing = state.getTarget(ip);
|
|
5025
5203
|
if (existing) {
|
|
5026
|
-
const newPorts = p.ports
|
|
5204
|
+
const newPorts = parsePorts(p.ports);
|
|
5027
5205
|
for (const np of newPorts) {
|
|
5028
5206
|
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
5029
5207
|
if (!exists) {
|
|
@@ -5037,11 +5215,11 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5037
5215
|
state.attackGraph.addService(ip, np.port, np.service || "unknown", np.version);
|
|
5038
5216
|
}
|
|
5039
5217
|
}
|
|
5040
|
-
if (p.hostname) existing.hostname = p.hostname;
|
|
5041
|
-
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
5218
|
+
if (p.hostname) existing.hostname = parseString(p.hostname);
|
|
5219
|
+
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...parseStringArray(p.tags)])];
|
|
5042
5220
|
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
5043
5221
|
}
|
|
5044
|
-
const ports = (p.ports
|
|
5222
|
+
const ports = parsePorts(p.ports).map((port) => ({
|
|
5045
5223
|
port: port.port,
|
|
5046
5224
|
service: port.service || "unknown",
|
|
5047
5225
|
version: port.version,
|
|
@@ -5050,9 +5228,9 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5050
5228
|
}));
|
|
5051
5229
|
state.addTarget({
|
|
5052
5230
|
ip,
|
|
5053
|
-
hostname: p.hostname,
|
|
5231
|
+
hostname: parseString(p.hostname) || void 0,
|
|
5054
5232
|
ports,
|
|
5055
|
-
tags: p.tags
|
|
5233
|
+
tags: parseStringArray(p.tags),
|
|
5056
5234
|
firstSeen: Date.now()
|
|
5057
5235
|
});
|
|
5058
5236
|
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
@@ -5071,30 +5249,30 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
5071
5249
|
},
|
|
5072
5250
|
required: ["type", "host", "detail"],
|
|
5073
5251
|
execute: async (p) => {
|
|
5074
|
-
const
|
|
5252
|
+
const lootTypeStr = parseString(p.type);
|
|
5075
5253
|
const crackableTypes = ["hash"];
|
|
5076
|
-
const detail = p.detail;
|
|
5077
|
-
const host = p.host;
|
|
5254
|
+
const detail = parseString(p.detail);
|
|
5255
|
+
const host = parseString(p.host);
|
|
5078
5256
|
state.addLoot({
|
|
5079
|
-
type:
|
|
5257
|
+
type: parseLootType(lootTypeStr),
|
|
5080
5258
|
host,
|
|
5081
5259
|
detail,
|
|
5082
5260
|
obtainedAt: Date.now(),
|
|
5083
|
-
isCrackable: crackableTypes.includes(
|
|
5261
|
+
isCrackable: crackableTypes.includes(lootTypeStr),
|
|
5084
5262
|
isCracked: false
|
|
5085
5263
|
});
|
|
5086
|
-
if (["credential", "token", "ssh_key", "api_key"].includes(
|
|
5264
|
+
if (["credential", "token", "ssh_key", "api_key"].includes(lootTypeStr)) {
|
|
5087
5265
|
const parts = detail.split(":");
|
|
5088
5266
|
if (parts.length >= 2) {
|
|
5089
5267
|
state.attackGraph.addCredential(parts[0], parts.slice(1).join(":"), host);
|
|
5090
5268
|
}
|
|
5091
5269
|
}
|
|
5092
|
-
state.episodicMemory.record("access_gained", `Loot [${
|
|
5270
|
+
state.episodicMemory.record("access_gained", `Loot [${lootTypeStr}] from ${host}: ${detail.slice(0, DISPLAY_LIMITS.MEMORY_EVENT_PREVIEW)}`);
|
|
5093
5271
|
return {
|
|
5094
5272
|
success: true,
|
|
5095
|
-
output: `Loot recorded: [${
|
|
5273
|
+
output: `Loot recorded: [${lootTypeStr}] from ${host}
|
|
5096
5274
|
Detail: ${detail}
|
|
5097
|
-
` + (crackableTypes.includes(
|
|
5275
|
+
` + (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
5276
|
};
|
|
5099
5277
|
}
|
|
5100
5278
|
},
|
|
@@ -5113,12 +5291,13 @@ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
|
5113
5291
|
},
|
|
5114
5292
|
required: ["title", "severity", "description", "evidence"],
|
|
5115
5293
|
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
|
|
5294
|
+
const evidence = parseStringArray(p.evidence);
|
|
5295
|
+
const title = parseString(p.title);
|
|
5296
|
+
const severity = parseSeverity(p.severity);
|
|
5297
|
+
const affected = parseStringArray(p.affected);
|
|
5298
|
+
const description = parseString(p.description);
|
|
5121
5299
|
const validation = validateFinding(evidence, severity);
|
|
5300
|
+
const attackPattern = parseString(p.attackPattern);
|
|
5122
5301
|
state.addFinding({
|
|
5123
5302
|
id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
|
|
5124
5303
|
title,
|
|
@@ -5129,7 +5308,7 @@ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
|
5129
5308
|
isVerified: validation.isVerified,
|
|
5130
5309
|
remediation: "",
|
|
5131
5310
|
foundAt: Date.now(),
|
|
5132
|
-
...
|
|
5311
|
+
...attackPattern && isValidAttackTactic(attackPattern) ? { attackPattern } : {}
|
|
5133
5312
|
});
|
|
5134
5313
|
const hasExploit = validation.isVerified;
|
|
5135
5314
|
const target = affected[0] || "unknown";
|
|
@@ -5147,7 +5326,7 @@ ${formatValidation(validation)}`
|
|
|
5147
5326
|
}
|
|
5148
5327
|
];
|
|
5149
5328
|
|
|
5150
|
-
// src/engine/tools-
|
|
5329
|
+
// src/engine/tools/intel-utils.ts
|
|
5151
5330
|
import { execFileSync } from "child_process";
|
|
5152
5331
|
|
|
5153
5332
|
// src/shared/utils/config.ts
|
|
@@ -5160,7 +5339,7 @@ var ENV_KEYS = {
|
|
|
5160
5339
|
THINKING: "PENTEST_THINKING",
|
|
5161
5340
|
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
5162
5341
|
};
|
|
5163
|
-
var DEFAULT_SEARCH_API_URL = "https://
|
|
5342
|
+
var DEFAULT_SEARCH_API_URL = "https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro";
|
|
5164
5343
|
function getApiKey() {
|
|
5165
5344
|
return process.env[ENV_KEYS.API_KEY] || "";
|
|
5166
5345
|
}
|
|
@@ -5171,7 +5350,10 @@ function getModel() {
|
|
|
5171
5350
|
return process.env[ENV_KEYS.MODEL] || "";
|
|
5172
5351
|
}
|
|
5173
5352
|
function getSearchApiKey() {
|
|
5174
|
-
|
|
5353
|
+
if (process.env[ENV_KEYS.SEARCH_API_KEY]) {
|
|
5354
|
+
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
5355
|
+
}
|
|
5356
|
+
return process.env[ENV_KEYS.API_KEY];
|
|
5175
5357
|
}
|
|
5176
5358
|
function getSearchApiUrl() {
|
|
5177
5359
|
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
@@ -5543,50 +5725,60 @@ var DEFAULT_BROWSER_OPTIONS = {
|
|
|
5543
5725
|
|
|
5544
5726
|
// src/engine/tools/web-browser.ts
|
|
5545
5727
|
async function browseUrl(url, options = {}) {
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5728
|
+
try {
|
|
5729
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5730
|
+
const { installed, browserInstalled } = await checkPlaywright();
|
|
5731
|
+
if (!installed || !browserInstalled) {
|
|
5732
|
+
const installResult = await installPlaywright();
|
|
5733
|
+
if (!installResult.success) {
|
|
5734
|
+
return {
|
|
5735
|
+
success: false,
|
|
5736
|
+
output: "",
|
|
5737
|
+
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
5738
|
+
};
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
5742
|
+
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5743
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5744
|
+
if (!result2.success) {
|
|
5551
5745
|
return {
|
|
5552
5746
|
success: false,
|
|
5553
|
-
output:
|
|
5554
|
-
error:
|
|
5747
|
+
output: result2.output,
|
|
5748
|
+
error: result2.error
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
if (result2.parsedData) {
|
|
5752
|
+
return {
|
|
5753
|
+
success: true,
|
|
5754
|
+
output: formatBrowserOutput(result2.parsedData, browserOptions),
|
|
5755
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
5756
|
+
extractedData: result2.parsedData
|
|
5555
5757
|
};
|
|
5556
5758
|
}
|
|
5557
|
-
}
|
|
5558
|
-
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
5559
|
-
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5560
|
-
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5561
|
-
if (!result2.success) {
|
|
5562
5759
|
return {
|
|
5563
|
-
success:
|
|
5564
|
-
output: result2.output,
|
|
5565
|
-
|
|
5760
|
+
success: true,
|
|
5761
|
+
output: result2.output || "Navigation completed",
|
|
5762
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5566
5763
|
};
|
|
5567
|
-
}
|
|
5568
|
-
|
|
5764
|
+
} catch (error) {
|
|
5765
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5569
5766
|
return {
|
|
5570
|
-
success:
|
|
5571
|
-
output:
|
|
5572
|
-
|
|
5573
|
-
extractedData: result2.parsedData
|
|
5767
|
+
success: false,
|
|
5768
|
+
output: "",
|
|
5769
|
+
error: `Browser error: ${msg}`
|
|
5574
5770
|
};
|
|
5575
5771
|
}
|
|
5576
|
-
return {
|
|
5577
|
-
success: true,
|
|
5578
|
-
output: result2.output || "Navigation completed",
|
|
5579
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5580
|
-
};
|
|
5581
5772
|
}
|
|
5582
5773
|
async function fillAndSubmitForm(url, formData, options = {}) {
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5774
|
+
try {
|
|
5775
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5776
|
+
const safeUrl = safeJsString(url);
|
|
5777
|
+
const safeFormData = JSON.stringify(formData);
|
|
5778
|
+
const playwrightPath = getPlaywrightPath();
|
|
5779
|
+
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5780
|
+
const headlessMode = process.env.HEADLESS !== "false";
|
|
5781
|
+
const script = `
|
|
5590
5782
|
const { chromium } = require(${safePlaywrightPath});
|
|
5591
5783
|
|
|
5592
5784
|
(async () => {
|
|
@@ -5626,27 +5818,35 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
5626
5818
|
}
|
|
5627
5819
|
})();
|
|
5628
5820
|
`;
|
|
5629
|
-
|
|
5630
|
-
|
|
5821
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "form");
|
|
5822
|
+
if (!result2.success) {
|
|
5823
|
+
return {
|
|
5824
|
+
success: false,
|
|
5825
|
+
output: result2.output,
|
|
5826
|
+
error: result2.error
|
|
5827
|
+
};
|
|
5828
|
+
}
|
|
5829
|
+
if (result2.parsedData) {
|
|
5830
|
+
const data = result2.parsedData;
|
|
5831
|
+
return {
|
|
5832
|
+
success: true,
|
|
5833
|
+
output: `Form submitted. Current URL: ${data.url}
|
|
5834
|
+
Title: ${data.title}`,
|
|
5835
|
+
extractedData: result2.parsedData
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5631
5838
|
return {
|
|
5632
|
-
success:
|
|
5633
|
-
output: result2.output
|
|
5634
|
-
error: result2.error
|
|
5839
|
+
success: true,
|
|
5840
|
+
output: result2.output || "Form submitted"
|
|
5635
5841
|
};
|
|
5636
|
-
}
|
|
5637
|
-
|
|
5638
|
-
const data = result2.parsedData;
|
|
5842
|
+
} catch (error) {
|
|
5843
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5639
5844
|
return {
|
|
5640
|
-
success:
|
|
5641
|
-
output:
|
|
5642
|
-
|
|
5643
|
-
extractedData: result2.parsedData
|
|
5845
|
+
success: false,
|
|
5846
|
+
output: "",
|
|
5847
|
+
error: `Form submission error: ${msg}`
|
|
5644
5848
|
};
|
|
5645
5849
|
}
|
|
5646
|
-
return {
|
|
5647
|
-
success: true,
|
|
5648
|
-
output: result2.output || "Form submitted"
|
|
5649
|
-
};
|
|
5650
5850
|
}
|
|
5651
5851
|
async function webSearchWithBrowser(query, engine = "google") {
|
|
5652
5852
|
const searchUrls = {
|
|
@@ -5663,6 +5863,7 @@ async function webSearchWithBrowser(query, engine = "google") {
|
|
|
5663
5863
|
}
|
|
5664
5864
|
|
|
5665
5865
|
// src/engine/web-search-providers.ts
|
|
5866
|
+
var SEARCH_TIMEOUT_MS = 15e3;
|
|
5666
5867
|
function getErrorMessage(error) {
|
|
5667
5868
|
return error instanceof Error ? error.message : String(error);
|
|
5668
5869
|
}
|
|
@@ -5670,25 +5871,43 @@ async function searchWithGLM(query, apiKey, apiUrl) {
|
|
|
5670
5871
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5671
5872
|
const requestBody = {
|
|
5672
5873
|
model: "web-search-pro",
|
|
5673
|
-
messages: [{ role:
|
|
5874
|
+
messages: [{ role: LLM_ROLES.USER, content: query }],
|
|
5674
5875
|
stream: false
|
|
5675
5876
|
};
|
|
5676
5877
|
debugLog("search", "GLM request body", requestBody);
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5878
|
+
let response;
|
|
5879
|
+
try {
|
|
5880
|
+
response = await fetch(apiUrl, {
|
|
5881
|
+
method: "POST",
|
|
5882
|
+
headers: {
|
|
5883
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5884
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`
|
|
5885
|
+
},
|
|
5886
|
+
body: JSON.stringify(requestBody),
|
|
5887
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5888
|
+
});
|
|
5889
|
+
} catch (err) {
|
|
5890
|
+
const msg = getErrorMessage(err);
|
|
5891
|
+
debugLog("search", "GLM fetch FAILED (network)", { error: msg });
|
|
5892
|
+
return { success: false, output: "", error: `GLM search network error: ${msg}. Check internet connection or API endpoint.` };
|
|
5893
|
+
}
|
|
5685
5894
|
debugLog("search", "GLM response status", { status: response.status, ok: response.ok });
|
|
5686
5895
|
if (!response.ok) {
|
|
5687
|
-
|
|
5896
|
+
let errorText = "";
|
|
5897
|
+
try {
|
|
5898
|
+
errorText = await response.text();
|
|
5899
|
+
} catch {
|
|
5900
|
+
}
|
|
5688
5901
|
debugLog("search", "GLM response ERROR", { status: response.status, error: errorText });
|
|
5689
|
-
|
|
5902
|
+
return { success: false, output: "", error: `GLM Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5903
|
+
}
|
|
5904
|
+
let data;
|
|
5905
|
+
try {
|
|
5906
|
+
data = await response.json();
|
|
5907
|
+
} catch (err) {
|
|
5908
|
+
debugLog("search", "GLM JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5909
|
+
return { success: false, output: "", error: `GLM search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5690
5910
|
}
|
|
5691
|
-
const data = await response.json();
|
|
5692
5911
|
debugLog("search", "GLM response data", { hasChoices: !!data.choices, choicesCount: data.choices?.length });
|
|
5693
5912
|
let results = "";
|
|
5694
5913
|
if (data.choices?.[0]?.message?.content) {
|
|
@@ -5716,19 +5935,37 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5716
5935
|
debugLog("search", "Brave request START", { apiUrl, query });
|
|
5717
5936
|
const url = `${apiUrl}?q=${encodeURIComponent(query)}&count=${SEARCH_LIMIT.DEFAULT_RESULT_COUNT}`;
|
|
5718
5937
|
debugLog("search", "Brave request URL", { url });
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5938
|
+
let response;
|
|
5939
|
+
try {
|
|
5940
|
+
response = await fetch(url, {
|
|
5941
|
+
headers: {
|
|
5942
|
+
[SEARCH_HEADER.ACCEPT]: "application/json",
|
|
5943
|
+
[SEARCH_HEADER.X_SUBSCRIPTION_TOKEN]: apiKey
|
|
5944
|
+
},
|
|
5945
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5946
|
+
});
|
|
5947
|
+
} catch (err) {
|
|
5948
|
+
const msg = getErrorMessage(err);
|
|
5949
|
+
debugLog("search", "Brave fetch FAILED (network)", { error: msg });
|
|
5950
|
+
return { success: false, output: "", error: `Brave search network error: ${msg}. Check internet connection.` };
|
|
5951
|
+
}
|
|
5725
5952
|
debugLog("search", "Brave response status", { status: response.status, ok: response.ok });
|
|
5726
5953
|
if (!response.ok) {
|
|
5727
|
-
|
|
5954
|
+
let errorText = "";
|
|
5955
|
+
try {
|
|
5956
|
+
errorText = await response.text();
|
|
5957
|
+
} catch {
|
|
5958
|
+
}
|
|
5728
5959
|
debugLog("search", "Brave response ERROR", { status: response.status, error: errorText });
|
|
5729
|
-
|
|
5960
|
+
return { success: false, output: "", error: `Brave API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5961
|
+
}
|
|
5962
|
+
let data;
|
|
5963
|
+
try {
|
|
5964
|
+
data = await response.json();
|
|
5965
|
+
} catch (err) {
|
|
5966
|
+
debugLog("search", "Brave JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5967
|
+
return { success: false, output: "", error: `Brave search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5730
5968
|
}
|
|
5731
|
-
const data = await response.json();
|
|
5732
5969
|
const results = data.web?.results || [];
|
|
5733
5970
|
debugLog("search", "Brave results count", { count: results.length });
|
|
5734
5971
|
if (results.length === 0) {
|
|
@@ -5744,21 +5981,39 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5744
5981
|
}
|
|
5745
5982
|
async function searchWithSerper(query, apiKey, apiUrl) {
|
|
5746
5983
|
debugLog("search", "Serper request START", { apiUrl, query });
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5984
|
+
let response;
|
|
5985
|
+
try {
|
|
5986
|
+
response = await fetch(apiUrl, {
|
|
5987
|
+
method: "POST",
|
|
5988
|
+
headers: {
|
|
5989
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5990
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
5991
|
+
},
|
|
5992
|
+
body: JSON.stringify({ q: query }),
|
|
5993
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5994
|
+
});
|
|
5995
|
+
} catch (err) {
|
|
5996
|
+
const msg = getErrorMessage(err);
|
|
5997
|
+
debugLog("search", "Serper fetch FAILED (network)", { error: msg });
|
|
5998
|
+
return { success: false, output: "", error: `Serper search network error: ${msg}. Check internet connection.` };
|
|
5999
|
+
}
|
|
5755
6000
|
debugLog("search", "Serper response status", { status: response.status, ok: response.ok });
|
|
5756
6001
|
if (!response.ok) {
|
|
5757
|
-
|
|
6002
|
+
let errorText = "";
|
|
6003
|
+
try {
|
|
6004
|
+
errorText = await response.text();
|
|
6005
|
+
} catch {
|
|
6006
|
+
}
|
|
5758
6007
|
debugLog("search", "Serper response ERROR", { status: response.status, error: errorText });
|
|
5759
|
-
|
|
6008
|
+
return { success: false, output: "", error: `Serper API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6009
|
+
}
|
|
6010
|
+
let data;
|
|
6011
|
+
try {
|
|
6012
|
+
data = await response.json();
|
|
6013
|
+
} catch (err) {
|
|
6014
|
+
debugLog("search", "Serper JSON parse FAILED", { error: getErrorMessage(err) });
|
|
6015
|
+
return { success: false, output: "", error: `Serper search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5760
6016
|
}
|
|
5761
|
-
const data = await response.json();
|
|
5762
6017
|
const results = data.organic || [];
|
|
5763
6018
|
debugLog("search", "Serper results count", { count: results.length });
|
|
5764
6019
|
if (results.length === 0) {
|
|
@@ -5773,48 +6028,40 @@ async function searchWithSerper(query, apiKey, apiUrl) {
|
|
|
5773
6028
|
return { success: true, output: formatted };
|
|
5774
6029
|
}
|
|
5775
6030
|
async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
6031
|
+
let response;
|
|
6032
|
+
try {
|
|
6033
|
+
response = await fetch(apiUrl, {
|
|
6034
|
+
method: "POST",
|
|
6035
|
+
headers: {
|
|
6036
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
6037
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
|
|
6038
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
6039
|
+
},
|
|
6040
|
+
body: JSON.stringify({ query, q: query }),
|
|
6041
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
6042
|
+
});
|
|
6043
|
+
} catch (err) {
|
|
6044
|
+
const msg = getErrorMessage(err);
|
|
6045
|
+
return { success: false, output: "", error: `Search API network error: ${msg}. Check internet connection or API endpoint.` };
|
|
6046
|
+
}
|
|
5785
6047
|
if (!response.ok) {
|
|
5786
|
-
|
|
5787
|
-
|
|
6048
|
+
let errorText = "";
|
|
6049
|
+
try {
|
|
6050
|
+
errorText = await response.text();
|
|
6051
|
+
} catch {
|
|
6052
|
+
}
|
|
6053
|
+
return { success: false, output: "", error: `Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6054
|
+
}
|
|
6055
|
+
let data;
|
|
6056
|
+
try {
|
|
6057
|
+
data = await response.json();
|
|
6058
|
+
} catch (err) {
|
|
6059
|
+
return { success: false, output: "", error: `Search API returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5788
6060
|
}
|
|
5789
|
-
const data = await response.json();
|
|
5790
6061
|
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
5791
6062
|
}
|
|
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
6063
|
|
|
5817
|
-
// src/engine/tools-
|
|
6064
|
+
// src/engine/tools/intel-utils.ts
|
|
5818
6065
|
var PORT_STATE2 = {
|
|
5819
6066
|
OPEN: "open",
|
|
5820
6067
|
CLOSED: "closed",
|
|
@@ -5925,31 +6172,46 @@ async function searchExploitDB(service, version) {
|
|
|
5925
6172
|
}
|
|
5926
6173
|
async function webSearch(query, _engine) {
|
|
5927
6174
|
debugLog("search", "webSearch START", { query });
|
|
6175
|
+
const apiKey = getSearchApiKey();
|
|
6176
|
+
const apiUrl = getSearchApiUrl();
|
|
6177
|
+
debugLog("search", "Search API config", {
|
|
6178
|
+
hasApiKey: !!apiKey,
|
|
6179
|
+
apiUrl,
|
|
6180
|
+
apiKeyPrefix: apiKey ? apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." : null
|
|
6181
|
+
});
|
|
6182
|
+
if (!apiKey) {
|
|
6183
|
+
return {
|
|
6184
|
+
success: false,
|
|
6185
|
+
output: "",
|
|
6186
|
+
error: `SEARCH_API_KEY is required for web search. Set it in your environment:
|
|
6187
|
+
|
|
6188
|
+
# Brave Search (recommended - free tier available)
|
|
6189
|
+
export SEARCH_API_KEY=your_brave_api_key
|
|
6190
|
+
# Get key at: https://brave.com/search/api/
|
|
6191
|
+
|
|
6192
|
+
# Or GLM Web Search (\u667A\u8C31 AI)
|
|
6193
|
+
export SEARCH_API_KEY=your_glm_api_key
|
|
6194
|
+
export SEARCH_API_URL=https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro
|
|
6195
|
+
|
|
6196
|
+
# Or Serper (Google)
|
|
6197
|
+
export SEARCH_API_KEY=your_serper_key
|
|
6198
|
+
export SEARCH_API_URL=https://google.serper.dev/search`
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
5928
6201
|
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
|
-
}
|
|
6202
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
6203
|
+
debugLog("search", "Using GLM search");
|
|
6204
|
+
return await searchWithGLM(query, apiKey, apiUrl);
|
|
6205
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
6206
|
+
debugLog("search", "Using Brave search");
|
|
6207
|
+
return await searchWithBrave(query, apiKey, apiUrl);
|
|
6208
|
+
} else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
|
|
6209
|
+
debugLog("search", "Using Serper search");
|
|
6210
|
+
return await searchWithSerper(query, apiKey, apiUrl);
|
|
6211
|
+
} else {
|
|
6212
|
+
debugLog("search", "Using generic search API");
|
|
6213
|
+
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
5950
6214
|
}
|
|
5951
|
-
debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
|
|
5952
|
-
return await searchWithPlaywright(query);
|
|
5953
6215
|
} catch (error) {
|
|
5954
6216
|
debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
|
|
5955
6217
|
return {
|
|
@@ -6332,7 +6594,8 @@ ${results.join("\n\n")}`
|
|
|
6332
6594
|
}
|
|
6333
6595
|
return {
|
|
6334
6596
|
success: false,
|
|
6335
|
-
output: `Category ${category} not found in any edition
|
|
6597
|
+
output: `Category ${category} not found in any edition.`,
|
|
6598
|
+
error: `Category ${category} not found`
|
|
6336
6599
|
};
|
|
6337
6600
|
}
|
|
6338
6601
|
if (edition === "all") {
|
|
@@ -6343,7 +6606,7 @@ ${results.join("\n\n")}`
|
|
|
6343
6606
|
}
|
|
6344
6607
|
const data = OWASP_FULL_HISTORY[edition];
|
|
6345
6608
|
if (!data) {
|
|
6346
|
-
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025
|
|
6609
|
+
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025.`, error: `Edition ${edition} not found` };
|
|
6347
6610
|
}
|
|
6348
6611
|
return {
|
|
6349
6612
|
success: true,
|
|
@@ -6888,8 +7151,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6888
7151
|
}
|
|
6889
7152
|
},
|
|
6890
7153
|
execute: async (p) => {
|
|
6891
|
-
const { existsSync:
|
|
6892
|
-
const { join:
|
|
7154
|
+
const { existsSync: existsSync11, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7155
|
+
const { join: join13 } = await import("path");
|
|
6893
7156
|
const category = p.category || "";
|
|
6894
7157
|
const search = p.search || "";
|
|
6895
7158
|
const minSize = p.min_size || 0;
|
|
@@ -6925,7 +7188,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6925
7188
|
const processFile = (fullPath, fileName) => {
|
|
6926
7189
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
6927
7190
|
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
6928
|
-
const stats =
|
|
7191
|
+
const stats = statSync3(fullPath);
|
|
6929
7192
|
if (stats.size < minSize) return;
|
|
6930
7193
|
if (!matchesCategory(fullPath)) return;
|
|
6931
7194
|
if (!matchesSearch(fullPath, fileName)) return;
|
|
@@ -6935,16 +7198,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6935
7198
|
results.push("");
|
|
6936
7199
|
};
|
|
6937
7200
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
6938
|
-
if (depth > maxDepth || !
|
|
7201
|
+
if (depth > maxDepth || !existsSync11(dirPath)) return;
|
|
6939
7202
|
let entries;
|
|
6940
7203
|
try {
|
|
6941
|
-
entries =
|
|
7204
|
+
entries = readdirSync4(dirPath, { withFileTypes: true });
|
|
6942
7205
|
} catch {
|
|
6943
7206
|
return;
|
|
6944
7207
|
}
|
|
6945
7208
|
for (const entry of entries) {
|
|
6946
7209
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
6947
|
-
const fullPath =
|
|
7210
|
+
const fullPath = join13(dirPath, entry.name);
|
|
6948
7211
|
if (entry.isDirectory()) {
|
|
6949
7212
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
6950
7213
|
continue;
|
|
@@ -7235,7 +7498,7 @@ Requires root/sudo privileges.`,
|
|
|
7235
7498
|
const iface = p.interface || "any";
|
|
7236
7499
|
const filter = p.filter || "";
|
|
7237
7500
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SNIFF_DURATION;
|
|
7238
|
-
const outputFile = p.output_file || createTempFile(
|
|
7501
|
+
const outputFile = p.output_file || createTempFile(FILE_EXTENSIONS.PCAP);
|
|
7239
7502
|
const count = p.count || NETWORK_CONFIG.DEFAULT_PACKET_COUNT;
|
|
7240
7503
|
const extractCreds = p.extract_creds !== false;
|
|
7241
7504
|
const filterFlag = filter ? `"${filter}"` : "";
|
|
@@ -7320,9 +7583,9 @@ Requires root/sudo privileges.`,
|
|
|
7320
7583
|
const spoofIp = p.spoof_ip;
|
|
7321
7584
|
const iface = p.interface || "";
|
|
7322
7585
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7323
|
-
const hostsFile = createTempFile(
|
|
7324
|
-
const { writeFileSync:
|
|
7325
|
-
|
|
7586
|
+
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7587
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
7588
|
+
writeFileSync9(hostsFile, `${spoofIp} ${domain}
|
|
7326
7589
|
${spoofIp} *.${domain}
|
|
7327
7590
|
`);
|
|
7328
7591
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -7372,7 +7635,7 @@ Combine with arp_spoof for transparent proxying.`,
|
|
|
7372
7635
|
const port = p.port || NETWORK_CONFIG.DEFAULT_PROXY_PORT;
|
|
7373
7636
|
const targetHost = p.target_host;
|
|
7374
7637
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7375
|
-
const outputFile = p.output_file || createTempFile(
|
|
7638
|
+
const outputFile = p.output_file || createTempFile(FILE_EXTENSIONS.MITM);
|
|
7376
7639
|
const sslInsecure = p.ssl_insecure !== false;
|
|
7377
7640
|
let cmd;
|
|
7378
7641
|
const sslFlag = sslInsecure ? "--ssl-insecure" : "";
|
|
@@ -7436,7 +7699,7 @@ This is a high-level tool that combines tcpdump capture with protocol analysis.`
|
|
|
7436
7699
|
const iface = p.interface || "any";
|
|
7437
7700
|
const protocols = p.protocols || "all";
|
|
7438
7701
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SNIFF_DURATION;
|
|
7439
|
-
const outputFile = createTempFile(
|
|
7702
|
+
const outputFile = createTempFile(FILE_EXTENSIONS.PCAP);
|
|
7440
7703
|
let bpfFilter = `host ${target}`;
|
|
7441
7704
|
if (protocols !== "all") {
|
|
7442
7705
|
const protoFilters = [];
|
|
@@ -7587,15 +7850,6 @@ var ZombieHunter = class {
|
|
|
7587
7850
|
}
|
|
7588
7851
|
};
|
|
7589
7852
|
|
|
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
7853
|
// src/engine/resource/health-monitor.ts
|
|
7600
7854
|
var HEALTH_STATUS = {
|
|
7601
7855
|
HEALTHY: "healthy",
|
|
@@ -7838,6 +8092,86 @@ Returns recommendations on process status, port conflicts, long-running tasks, e
|
|
|
7838
8092
|
}
|
|
7839
8093
|
];
|
|
7840
8094
|
|
|
8095
|
+
// src/shared/constants/service-ports.ts
|
|
8096
|
+
var SERVICE_PORTS = {
|
|
8097
|
+
SSH: 22,
|
|
8098
|
+
FTP: 21,
|
|
8099
|
+
TELNET: 23,
|
|
8100
|
+
SMTP: 25,
|
|
8101
|
+
DNS: 53,
|
|
8102
|
+
HTTP: 80,
|
|
8103
|
+
POP3: 110,
|
|
8104
|
+
IMAP: 143,
|
|
8105
|
+
SMB_NETBIOS: 139,
|
|
8106
|
+
KERBEROS: 88,
|
|
8107
|
+
LDAP: 389,
|
|
8108
|
+
SMB: 445,
|
|
8109
|
+
HTTPS: 443,
|
|
8110
|
+
SMTPS: 465,
|
|
8111
|
+
SMTP_TLS: 587,
|
|
8112
|
+
MODBUS: 502,
|
|
8113
|
+
IMAPS: 993,
|
|
8114
|
+
POP3S: 995,
|
|
8115
|
+
MSSQL: 1433,
|
|
8116
|
+
MYSQL: 3306,
|
|
8117
|
+
RDP: 3389,
|
|
8118
|
+
POSTGRESQL: 5432,
|
|
8119
|
+
VNC: 5900,
|
|
8120
|
+
REDIS: 6379,
|
|
8121
|
+
DOCKER_HTTP: 2375,
|
|
8122
|
+
DOCKER_HTTPS: 2376,
|
|
8123
|
+
KUBERNETES_API: 6443,
|
|
8124
|
+
HTTP_ALT: 8080,
|
|
8125
|
+
HTTPS_ALT: 8443,
|
|
8126
|
+
NFS: 2049,
|
|
8127
|
+
DNP3: 2e4,
|
|
8128
|
+
MONGODB: 27017,
|
|
8129
|
+
ELASTICSEARCH: 9200,
|
|
8130
|
+
MEMCACHED: 11211,
|
|
8131
|
+
NODE_DEFAULT: 3e3,
|
|
8132
|
+
FLASK_DEFAULT: 5e3,
|
|
8133
|
+
DJANGO_DEFAULT: 8e3
|
|
8134
|
+
};
|
|
8135
|
+
var CRITICAL_SERVICE_PORTS = [
|
|
8136
|
+
SERVICE_PORTS.SSH,
|
|
8137
|
+
SERVICE_PORTS.RDP,
|
|
8138
|
+
SERVICE_PORTS.MYSQL,
|
|
8139
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8140
|
+
SERVICE_PORTS.REDIS,
|
|
8141
|
+
SERVICE_PORTS.MONGODB
|
|
8142
|
+
];
|
|
8143
|
+
var NO_AUTH_CRITICAL_PORTS = [
|
|
8144
|
+
SERVICE_PORTS.REDIS,
|
|
8145
|
+
SERVICE_PORTS.MONGODB,
|
|
8146
|
+
SERVICE_PORTS.ELASTICSEARCH,
|
|
8147
|
+
SERVICE_PORTS.MEMCACHED
|
|
8148
|
+
];
|
|
8149
|
+
var WEB_SERVICE_PORTS = [
|
|
8150
|
+
SERVICE_PORTS.HTTP,
|
|
8151
|
+
SERVICE_PORTS.HTTPS,
|
|
8152
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8153
|
+
SERVICE_PORTS.HTTPS_ALT,
|
|
8154
|
+
SERVICE_PORTS.NODE_DEFAULT,
|
|
8155
|
+
SERVICE_PORTS.FLASK_DEFAULT,
|
|
8156
|
+
SERVICE_PORTS.DJANGO_DEFAULT
|
|
8157
|
+
];
|
|
8158
|
+
var PLAINTEXT_HTTP_PORTS = [
|
|
8159
|
+
SERVICE_PORTS.HTTP,
|
|
8160
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8161
|
+
SERVICE_PORTS.NODE_DEFAULT
|
|
8162
|
+
];
|
|
8163
|
+
var DATABASE_PORTS = [
|
|
8164
|
+
SERVICE_PORTS.MYSQL,
|
|
8165
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8166
|
+
SERVICE_PORTS.MSSQL,
|
|
8167
|
+
SERVICE_PORTS.MONGODB,
|
|
8168
|
+
SERVICE_PORTS.REDIS
|
|
8169
|
+
];
|
|
8170
|
+
var SMB_PORTS = [
|
|
8171
|
+
SERVICE_PORTS.SMB,
|
|
8172
|
+
SERVICE_PORTS.SMB_NETBIOS
|
|
8173
|
+
];
|
|
8174
|
+
|
|
7841
8175
|
// src/domains/network/tools.ts
|
|
7842
8176
|
var NETWORK_TOOLS = [
|
|
7843
8177
|
{
|
|
@@ -7883,7 +8217,7 @@ var NETWORK_CONFIG2 = {
|
|
|
7883
8217
|
tools: NETWORK_TOOLS,
|
|
7884
8218
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
7885
8219
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
7886
|
-
commonPorts: [
|
|
8220
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
|
|
7887
8221
|
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
|
|
7888
8222
|
};
|
|
7889
8223
|
|
|
@@ -7946,7 +8280,7 @@ var WEB_CONFIG = {
|
|
|
7946
8280
|
tools: WEB_TOOLS,
|
|
7947
8281
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
7948
8282
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
7949
|
-
commonPorts: [
|
|
8283
|
+
commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
|
|
7950
8284
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
7951
8285
|
};
|
|
7952
8286
|
|
|
@@ -7981,12 +8315,12 @@ var DATABASE_TOOLS = [
|
|
|
7981
8315
|
description: "MySQL enumeration - version, users, databases",
|
|
7982
8316
|
parameters: {
|
|
7983
8317
|
target: { type: "string", description: "Target IP/hostname" },
|
|
7984
|
-
port: { type: "string", description:
|
|
8318
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
7985
8319
|
},
|
|
7986
8320
|
required: ["target"],
|
|
7987
8321
|
execute: async (params) => {
|
|
7988
8322
|
const target = params.target;
|
|
7989
|
-
const port = params.port ||
|
|
8323
|
+
const port = params.port || String(SERVICE_PORTS.MYSQL);
|
|
7990
8324
|
return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
|
|
7991
8325
|
}
|
|
7992
8326
|
},
|
|
@@ -8007,12 +8341,12 @@ var DATABASE_TOOLS = [
|
|
|
8007
8341
|
description: "Redis enumeration",
|
|
8008
8342
|
parameters: {
|
|
8009
8343
|
target: { type: "string", description: "Target IP" },
|
|
8010
|
-
port: { type: "string", description:
|
|
8344
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8011
8345
|
},
|
|
8012
8346
|
required: ["target"],
|
|
8013
8347
|
execute: async (params) => {
|
|
8014
8348
|
const target = params.target;
|
|
8015
|
-
const port = params.port ||
|
|
8349
|
+
const port = params.port || String(SERVICE_PORTS.REDIS);
|
|
8016
8350
|
return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
|
|
8017
8351
|
}
|
|
8018
8352
|
},
|
|
@@ -8044,7 +8378,7 @@ var DATABASE_CONFIG = {
|
|
|
8044
8378
|
tools: DATABASE_TOOLS,
|
|
8045
8379
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8046
8380
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8047
|
-
commonPorts: [
|
|
8381
|
+
commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
|
|
8048
8382
|
commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
|
|
8049
8383
|
};
|
|
8050
8384
|
|
|
@@ -8097,7 +8431,7 @@ var AD_CONFIG = {
|
|
|
8097
8431
|
tools: AD_TOOLS,
|
|
8098
8432
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8099
8433
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8100
|
-
commonPorts: [
|
|
8434
|
+
commonPorts: [SERVICE_PORTS.KERBEROS, SERVICE_PORTS.LDAP, SERVICE_PORTS.SMB],
|
|
8101
8435
|
commonServices: [SERVICES.AD, SERVICES.SMB]
|
|
8102
8436
|
};
|
|
8103
8437
|
|
|
@@ -8121,7 +8455,7 @@ var EMAIL_CONFIG = {
|
|
|
8121
8455
|
tools: EMAIL_TOOLS,
|
|
8122
8456
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8123
8457
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8124
|
-
commonPorts: [
|
|
8458
|
+
commonPorts: [SERVICE_PORTS.SMTP, SERVICE_PORTS.POP3, SERVICE_PORTS.IMAP, SERVICE_PORTS.SMTPS, SERVICE_PORTS.SMTP_TLS, SERVICE_PORTS.IMAPS, SERVICE_PORTS.POP3S],
|
|
8125
8459
|
commonServices: [SERVICES.SMTP, SERVICES.POP3, SERVICES.IMAP]
|
|
8126
8460
|
};
|
|
8127
8461
|
|
|
@@ -8146,7 +8480,7 @@ var REMOTE_ACCESS_TOOLS = [
|
|
|
8146
8480
|
},
|
|
8147
8481
|
required: ["target"],
|
|
8148
8482
|
execute: async (params) => {
|
|
8149
|
-
return await runCommand("nmap", ["-p",
|
|
8483
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
|
|
8150
8484
|
}
|
|
8151
8485
|
}
|
|
8152
8486
|
];
|
|
@@ -8156,7 +8490,7 @@ var REMOTE_ACCESS_CONFIG = {
|
|
|
8156
8490
|
tools: REMOTE_ACCESS_TOOLS,
|
|
8157
8491
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8158
8492
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8159
|
-
commonPorts: [
|
|
8493
|
+
commonPorts: [SERVICE_PORTS.SSH, SERVICE_PORTS.RDP, SERVICE_PORTS.VNC],
|
|
8160
8494
|
commonServices: [SERVICES.SSH, SERVICES.RDP, SERVICES.VNC]
|
|
8161
8495
|
};
|
|
8162
8496
|
|
|
@@ -8170,7 +8504,7 @@ var FILE_SHARING_TOOLS = [
|
|
|
8170
8504
|
},
|
|
8171
8505
|
required: ["target"],
|
|
8172
8506
|
execute: async (params) => {
|
|
8173
|
-
return await runCommand("nmap", ["-p",
|
|
8507
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", params.target]);
|
|
8174
8508
|
}
|
|
8175
8509
|
},
|
|
8176
8510
|
{
|
|
@@ -8191,7 +8525,7 @@ var FILE_SHARING_CONFIG = {
|
|
|
8191
8525
|
tools: FILE_SHARING_TOOLS,
|
|
8192
8526
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8193
8527
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8194
|
-
commonPorts: [
|
|
8528
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SMB_NETBIOS, SERVICE_PORTS.SMB, SERVICE_PORTS.NFS],
|
|
8195
8529
|
commonServices: [SERVICES.FTP, SERVICES.SMB, SERVICES.NFS]
|
|
8196
8530
|
};
|
|
8197
8531
|
|
|
@@ -8235,7 +8569,7 @@ var CLOUD_CONFIG = {
|
|
|
8235
8569
|
tools: CLOUD_TOOLS,
|
|
8236
8570
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8237
8571
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8238
|
-
commonPorts: [
|
|
8572
|
+
commonPorts: [SERVICE_PORTS.HTTPS],
|
|
8239
8573
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8240
8574
|
};
|
|
8241
8575
|
|
|
@@ -8270,7 +8604,7 @@ var CONTAINER_CONFIG = {
|
|
|
8270
8604
|
tools: CONTAINER_TOOLS,
|
|
8271
8605
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8272
8606
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8273
|
-
commonPorts: [
|
|
8607
|
+
commonPorts: [SERVICE_PORTS.DOCKER_HTTP, SERVICE_PORTS.DOCKER_HTTPS, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.KUBERNETES_API],
|
|
8274
8608
|
commonServices: [SERVICES.DOCKER, SERVICES.KUBERNETES]
|
|
8275
8609
|
};
|
|
8276
8610
|
|
|
@@ -8333,7 +8667,7 @@ var API_CONFIG = {
|
|
|
8333
8667
|
tools: API_TOOLS,
|
|
8334
8668
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8335
8669
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8336
|
-
commonPorts: [
|
|
8670
|
+
commonPorts: [SERVICE_PORTS.NODE_DEFAULT, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.DJANGO_DEFAULT, SERVICE_PORTS.HTTP_ALT],
|
|
8337
8671
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8338
8672
|
};
|
|
8339
8673
|
|
|
@@ -8371,7 +8705,7 @@ var ICS_TOOLS = [
|
|
|
8371
8705
|
},
|
|
8372
8706
|
required: ["target"],
|
|
8373
8707
|
execute: async (params) => {
|
|
8374
|
-
return await runCommand("nmap", ["-p",
|
|
8708
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
|
|
8375
8709
|
}
|
|
8376
8710
|
}
|
|
8377
8711
|
];
|
|
@@ -8381,7 +8715,7 @@ var ICS_CONFIG = {
|
|
|
8381
8715
|
tools: ICS_TOOLS,
|
|
8382
8716
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8383
8717
|
defaultApproval: APPROVAL_LEVELS.BLOCK,
|
|
8384
|
-
commonPorts: [
|
|
8718
|
+
commonPorts: [SERVICE_PORTS.MODBUS, SERVICE_PORTS.DNP3],
|
|
8385
8719
|
commonServices: [SERVICES.MODBUS, SERVICES.DNP3]
|
|
8386
8720
|
};
|
|
8387
8721
|
|
|
@@ -8443,8 +8777,18 @@ var ToolRegistry = class {
|
|
|
8443
8777
|
this.logDeniedAction(toolCall, approval.reason || "Execution denied");
|
|
8444
8778
|
return { success: false, output: "", error: approval.reason || "Denied by policy" };
|
|
8445
8779
|
}
|
|
8446
|
-
|
|
8447
|
-
|
|
8780
|
+
let result2;
|
|
8781
|
+
try {
|
|
8782
|
+
result2 = await tool.execute(toolCall.input);
|
|
8783
|
+
} catch (execError) {
|
|
8784
|
+
const errMsg = execError instanceof Error ? execError.message : String(execError);
|
|
8785
|
+
result2 = {
|
|
8786
|
+
success: false,
|
|
8787
|
+
output: "",
|
|
8788
|
+
error: `Tool execution error: ${errMsg}`
|
|
8789
|
+
};
|
|
8790
|
+
}
|
|
8791
|
+
const command = String(toolCall.input.command || toolCall.input.url || toolCall.input.query || JSON.stringify(toolCall.input));
|
|
8448
8792
|
if (result2.success) {
|
|
8449
8793
|
this.state.workingMemory.recordSuccess(toolCall.name, command, result2.output || "");
|
|
8450
8794
|
} else {
|
|
@@ -8547,7 +8891,7 @@ var SERVICE_CATEGORY_MAP = {
|
|
|
8547
8891
|
"docker": SERVICE_CATEGORIES.CONTAINER,
|
|
8548
8892
|
"modbus": SERVICE_CATEGORIES.ICS
|
|
8549
8893
|
};
|
|
8550
|
-
var
|
|
8894
|
+
var CATEGORY_APPROVAL = {
|
|
8551
8895
|
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
8552
8896
|
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
8553
8897
|
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
@@ -8696,6 +9040,10 @@ var DOMAINS = {
|
|
|
8696
9040
|
};
|
|
8697
9041
|
|
|
8698
9042
|
// src/engine/tools-registry.ts
|
|
9043
|
+
var VALID_CATEGORIES = Object.values(SERVICE_CATEGORIES);
|
|
9044
|
+
function isValidCategory(id) {
|
|
9045
|
+
return VALID_CATEGORIES.includes(id);
|
|
9046
|
+
}
|
|
8699
9047
|
var CategorizedToolRegistry = class extends ToolRegistry {
|
|
8700
9048
|
categories = /* @__PURE__ */ new Map();
|
|
8701
9049
|
constructor(state, scopeGuard, approvalGate, events) {
|
|
@@ -8727,13 +9075,13 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8727
9075
|
];
|
|
8728
9076
|
const coreTools = coreToolNames.map((name) => this.getTool(name)).filter((t) => !!t);
|
|
8729
9077
|
Object.keys(DOMAINS).forEach((id) => {
|
|
8730
|
-
|
|
8731
|
-
this.categories.set(
|
|
8732
|
-
name:
|
|
8733
|
-
description: DOMAINS[
|
|
9078
|
+
if (!isValidCategory(id)) return;
|
|
9079
|
+
this.categories.set(id, {
|
|
9080
|
+
name: id,
|
|
9081
|
+
description: DOMAINS[id]?.description || "",
|
|
8734
9082
|
tools: [...coreTools],
|
|
8735
|
-
dangerLevel: this.calculateDanger(
|
|
8736
|
-
defaultApproval:
|
|
9083
|
+
dangerLevel: this.calculateDanger(id),
|
|
9084
|
+
defaultApproval: CATEGORY_APPROVAL[id] || "confirm"
|
|
8737
9085
|
});
|
|
8738
9086
|
});
|
|
8739
9087
|
}
|
|
@@ -8750,19 +9098,17 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8750
9098
|
const cat = ServiceParser.detectCategory(p.port, p.service);
|
|
8751
9099
|
if (cat && !seen.has(cat)) {
|
|
8752
9100
|
seen.add(cat);
|
|
8753
|
-
|
|
9101
|
+
results.push({
|
|
8754
9102
|
category: cat,
|
|
8755
9103
|
tools: this.getByCategory(cat).map((t) => t.name)
|
|
8756
|
-
};
|
|
8757
|
-
results.push(suggestion);
|
|
9104
|
+
});
|
|
8758
9105
|
}
|
|
8759
9106
|
});
|
|
8760
9107
|
if (target.hostname && ServiceParser.isCloudHostname(target.hostname) && !seen.has(SERVICE_CATEGORIES.CLOUD)) {
|
|
8761
|
-
|
|
9108
|
+
results.push({
|
|
8762
9109
|
category: SERVICE_CATEGORIES.CLOUD,
|
|
8763
9110
|
tools: this.getByCategory(SERVICE_CATEGORIES.CLOUD).map((t) => t.name)
|
|
8764
|
-
};
|
|
8765
|
-
results.push(cloudSuggestion);
|
|
9111
|
+
});
|
|
8766
9112
|
}
|
|
8767
9113
|
return results;
|
|
8768
9114
|
}
|
|
@@ -8801,7 +9147,7 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8801
9147
|
return Array.from(this.categories.values());
|
|
8802
9148
|
}
|
|
8803
9149
|
getApprovalForCategory(cat) {
|
|
8804
|
-
return
|
|
9150
|
+
return CATEGORY_APPROVAL[cat] || "confirm";
|
|
8805
9151
|
}
|
|
8806
9152
|
};
|
|
8807
9153
|
|
|
@@ -8886,14 +9232,28 @@ var LLM_ERROR_TYPES = {
|
|
|
8886
9232
|
var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
|
|
8887
9233
|
var NETWORK_ERROR_CODES = {
|
|
8888
9234
|
ECONNRESET: "ECONNRESET",
|
|
9235
|
+
ECONNREFUSED: "ECONNREFUSED",
|
|
8889
9236
|
ETIMEDOUT: "ETIMEDOUT",
|
|
8890
9237
|
ENOTFOUND: "ENOTFOUND",
|
|
8891
|
-
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
|
|
9238
|
+
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT",
|
|
9239
|
+
SOCKET_TIMEOUT: "UND_ERR_SOCKET"
|
|
8892
9240
|
};
|
|
8893
9241
|
var TRANSIENT_NETWORK_ERRORS = [
|
|
8894
9242
|
NETWORK_ERROR_CODES.ECONNRESET,
|
|
9243
|
+
NETWORK_ERROR_CODES.ECONNREFUSED,
|
|
8895
9244
|
NETWORK_ERROR_CODES.ETIMEDOUT,
|
|
8896
|
-
NETWORK_ERROR_CODES.ENOTFOUND
|
|
9245
|
+
NETWORK_ERROR_CODES.ENOTFOUND,
|
|
9246
|
+
NETWORK_ERROR_CODES.SOCKET_TIMEOUT
|
|
9247
|
+
];
|
|
9248
|
+
var NETWORK_ERROR_PATTERNS = [
|
|
9249
|
+
"fetch failed",
|
|
9250
|
+
"network error",
|
|
9251
|
+
"econnrefused",
|
|
9252
|
+
"econnreset",
|
|
9253
|
+
"enotfound",
|
|
9254
|
+
"etimedout",
|
|
9255
|
+
"socket hang up",
|
|
9256
|
+
"dns lookup failed"
|
|
8897
9257
|
];
|
|
8898
9258
|
var LLMError = class extends Error {
|
|
8899
9259
|
/** Structured error information */
|
|
@@ -8920,12 +9280,17 @@ function classifyError(error) {
|
|
|
8920
9280
|
if (statusCode === HTTP_STATUS.BAD_REQUEST) {
|
|
8921
9281
|
return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
|
|
8922
9282
|
}
|
|
8923
|
-
|
|
9283
|
+
const errorCode = e.code || e.cause?.code;
|
|
9284
|
+
if (errorCode && TRANSIENT_NETWORK_ERRORS.includes(errorCode)) {
|
|
8924
9285
|
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
8925
9286
|
}
|
|
8926
|
-
if (errorMessage.toLowerCase().includes("timeout") ||
|
|
9287
|
+
if (errorMessage.toLowerCase().includes("timeout") || errorCode === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
|
|
8927
9288
|
return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
|
|
8928
9289
|
}
|
|
9290
|
+
const lowerMsg = errorMessage.toLowerCase();
|
|
9291
|
+
if (NETWORK_ERROR_PATTERNS.some((pattern) => lowerMsg.includes(pattern))) {
|
|
9292
|
+
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
9293
|
+
}
|
|
8929
9294
|
return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
|
|
8930
9295
|
}
|
|
8931
9296
|
|
|
@@ -9004,7 +9369,11 @@ var LLMClient = class {
|
|
|
9004
9369
|
signal
|
|
9005
9370
|
});
|
|
9006
9371
|
if (!response.ok) {
|
|
9007
|
-
|
|
9372
|
+
let errorBody = `HTTP ${response.status}`;
|
|
9373
|
+
try {
|
|
9374
|
+
errorBody = await response.text();
|
|
9375
|
+
} catch {
|
|
9376
|
+
}
|
|
9008
9377
|
const error = new Error(errorBody);
|
|
9009
9378
|
error.status = response.status;
|
|
9010
9379
|
throw error;
|
|
@@ -9340,7 +9709,7 @@ function saveState(state) {
|
|
|
9340
9709
|
}
|
|
9341
9710
|
function pruneOldSessions(sessionsDir) {
|
|
9342
9711
|
try {
|
|
9343
|
-
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(
|
|
9712
|
+
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => ({
|
|
9344
9713
|
name: f,
|
|
9345
9714
|
path: join9(sessionsDir, f),
|
|
9346
9715
|
mtime: statSync(join9(sessionsDir, f)).mtimeMs
|
|
@@ -9377,7 +9746,10 @@ function loadState(state) {
|
|
|
9377
9746
|
state.addLoot(loot);
|
|
9378
9747
|
}
|
|
9379
9748
|
for (const item of snapshot.todo) {
|
|
9380
|
-
state.addTodo(item.content, item.priority);
|
|
9749
|
+
const id = state.addTodo(item.content, item.priority);
|
|
9750
|
+
if (item.status && item.status !== "pending") {
|
|
9751
|
+
state.updateTodo(id, { status: item.status });
|
|
9752
|
+
}
|
|
9381
9753
|
}
|
|
9382
9754
|
const validPhases = new Set(Object.values(PHASES));
|
|
9383
9755
|
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
@@ -9387,6 +9759,20 @@ function loadState(state) {
|
|
|
9387
9759
|
}
|
|
9388
9760
|
if (snapshot.missionChecklist?.length > 0) {
|
|
9389
9761
|
state.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
|
|
9762
|
+
const restoredChecklist = state.getMissionChecklist();
|
|
9763
|
+
const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
|
|
9764
|
+
const completedUpdates = [];
|
|
9765
|
+
for (let i = 0; i < snapshot.missionChecklist.length; i++) {
|
|
9766
|
+
if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
|
|
9767
|
+
completedUpdates.push({
|
|
9768
|
+
id: restoredChecklist[baseIndex + i].id,
|
|
9769
|
+
isCompleted: true
|
|
9770
|
+
});
|
|
9771
|
+
}
|
|
9772
|
+
}
|
|
9773
|
+
if (completedUpdates.length > 0) {
|
|
9774
|
+
state.updateMissionChecklist(completedUpdates);
|
|
9775
|
+
}
|
|
9390
9776
|
}
|
|
9391
9777
|
return true;
|
|
9392
9778
|
} catch (err) {
|
|
@@ -9400,8 +9786,9 @@ function clearWorkspace() {
|
|
|
9400
9786
|
const dirsToClean = [
|
|
9401
9787
|
{ path: WORKSPACE.SESSIONS, label: "sessions" },
|
|
9402
9788
|
{ path: WORKSPACE.DEBUG, label: "debug logs" },
|
|
9403
|
-
{ path: WORKSPACE.
|
|
9404
|
-
{ path: WORKSPACE.OUTPUTS, label: "outputs" }
|
|
9789
|
+
{ path: WORKSPACE.TMP, label: "temp files" },
|
|
9790
|
+
{ path: WORKSPACE.OUTPUTS, label: "outputs" },
|
|
9791
|
+
{ path: WORKSPACE.JOURNAL, label: "journal" }
|
|
9405
9792
|
];
|
|
9406
9793
|
for (const dir of dirsToClean) {
|
|
9407
9794
|
try {
|
|
@@ -9481,387 +9868,97 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9481
9868
|
if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
|
|
9482
9869
|
lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
|
|
9483
9870
|
lines.push(`Alternative approaches:`);
|
|
9484
|
-
lines.push(` 1. Save output to file: command > /
|
|
9485
|
-
lines.push(` 2. Use tool-specific flags: nmap -oN /
|
|
9871
|
+
lines.push(` 1. Save output to file: command > ${WORK_DIR}/output.txt, then use read_file("${WORK_DIR}/output.txt")`);
|
|
9872
|
+
lines.push(` 2. Use tool-specific flags: nmap -oN ${WORK_DIR}/scan.txt, curl -o ${WORK_DIR}/page.html`);
|
|
9486
9873
|
lines.push(` 3. Use browse_url for web content instead of curl | grep`);
|
|
9487
9874
|
lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
|
|
9488
9875
|
} else if (errorLower.includes("redirect")) {
|
|
9489
|
-
lines.push(`Fix: Redirect to /
|
|
9490
|
-
lines.push(`Example: command > /
|
|
9876
|
+
lines.push(`Fix: Redirect to ${WORK_DIR}/ path.`);
|
|
9877
|
+
lines.push(`Example: command > ${WORK_DIR}/output.txt or command 2>&1 > ${WORK_DIR}/errors.txt`);
|
|
9491
9878
|
} else {
|
|
9492
9879
|
lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
|
|
9493
9880
|
lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
|
|
9494
9881
|
}
|
|
9495
9882
|
}
|
|
9496
9883
|
|
|
9497
|
-
// src/shared/utils/output-compressor.ts
|
|
9498
|
-
var MIN_COMPRESS_LENGTH = 3e3;
|
|
9499
|
-
var SUMMARY_HEADER = "\u2550\u2550\u2550 INTELLIGENCE SUMMARY (auto-extracted) \u2550\u2550\u2550";
|
|
9500
|
-
var SUMMARY_FOOTER = "\u2550\u2550\u2550 END SUMMARY \u2014 Full output follows \u2550\u2550\u2550";
|
|
9501
|
-
var EXTRACT_LIMITS = {
|
|
9502
|
-
NMAP_PORTS: 30,
|
|
9503
|
-
NMAP_VULNS: 10,
|
|
9504
|
-
LINPEAS_SUDO: 500,
|
|
9505
|
-
LINPEAS_WRITABLE: 300,
|
|
9506
|
-
LINPEAS_CRON: 5,
|
|
9507
|
-
LINPEAS_PASSWORDS: 5,
|
|
9508
|
-
ENUM4LINUX_SHARES: 10,
|
|
9509
|
-
DIRBUST_PATHS: 20,
|
|
9510
|
-
SQLMAP_INJECTIONS: 5,
|
|
9511
|
-
HASH_NTLM: 5,
|
|
9512
|
-
HASH_PREVIEW_LEN: 100,
|
|
9513
|
-
GENERIC_CREDS: 5,
|
|
9514
|
-
GENERIC_PATHS: 10
|
|
9515
|
-
};
|
|
9516
|
-
var TOOL_SIGNATURES = [
|
|
9517
|
-
{
|
|
9518
|
-
name: "nmap",
|
|
9519
|
-
signatures: [/Nmap scan report/i, /PORT\s+STATE\s+SERVICE/i, /Nmap done:/i],
|
|
9520
|
-
extract: extractNmapIntel
|
|
9521
|
-
},
|
|
9522
|
-
{
|
|
9523
|
-
name: "linpeas",
|
|
9524
|
-
signatures: [/linpeas/i, /╔══.*╗/, /Linux Privilege Escalation/i],
|
|
9525
|
-
extract: extractLinpeasIntel
|
|
9526
|
-
},
|
|
9527
|
-
{
|
|
9528
|
-
name: "enum4linux",
|
|
9529
|
-
signatures: [/enum4linux/i, /Starting enum4linux/i, /\|\s+Target\s+Information/i],
|
|
9530
|
-
extract: extractEnum4linuxIntel
|
|
9531
|
-
},
|
|
9532
|
-
{
|
|
9533
|
-
name: "gobuster/ffuf/feroxbuster",
|
|
9534
|
-
signatures: [/Gobuster/i, /FFUF/i, /feroxbuster/i, /Status:\s*\d{3}/],
|
|
9535
|
-
extract: extractDirBustIntel
|
|
9536
|
-
},
|
|
9537
|
-
{
|
|
9538
|
-
name: "sqlmap",
|
|
9539
|
-
signatures: [/sqlmap/i, /\[INFO\]\s+testing/i, /Parameter:\s+/i],
|
|
9540
|
-
extract: extractSqlmapIntel
|
|
9541
|
-
},
|
|
9542
|
-
{
|
|
9543
|
-
name: "hash_dump",
|
|
9544
|
-
signatures: [/\$[0-9]\$/, /\$2[aby]\$/, /:[0-9]+:[a-f0-9]{32}:/i],
|
|
9545
|
-
extract: extractHashIntel
|
|
9546
|
-
}
|
|
9547
|
-
];
|
|
9548
|
-
function compressToolOutput(output, toolName) {
|
|
9549
|
-
if (!output || output.length < MIN_COMPRESS_LENGTH) {
|
|
9550
|
-
return output;
|
|
9551
|
-
}
|
|
9552
|
-
let intel = [];
|
|
9553
|
-
let detectedTool = "";
|
|
9554
|
-
for (const sig of TOOL_SIGNATURES) {
|
|
9555
|
-
const matched = sig.signatures.some((s) => s.test(output));
|
|
9556
|
-
if (matched) {
|
|
9557
|
-
detectedTool = sig.name;
|
|
9558
|
-
intel = sig.extract(output);
|
|
9559
|
-
break;
|
|
9560
|
-
}
|
|
9561
|
-
}
|
|
9562
|
-
if (intel.length === 0) {
|
|
9563
|
-
intel = extractGenericIntel(output);
|
|
9564
|
-
detectedTool = toolName || "unknown";
|
|
9565
|
-
}
|
|
9566
|
-
if (intel.length === 0) {
|
|
9567
|
-
return output;
|
|
9568
|
-
}
|
|
9569
|
-
const summary = [
|
|
9570
|
-
SUMMARY_HEADER,
|
|
9571
|
-
`Tool: ${detectedTool} | Output length: ${output.length} chars`,
|
|
9572
|
-
"",
|
|
9573
|
-
...intel,
|
|
9574
|
-
"",
|
|
9575
|
-
SUMMARY_FOOTER,
|
|
9576
|
-
""
|
|
9577
|
-
].join("\n");
|
|
9578
|
-
return summary + output;
|
|
9579
|
-
}
|
|
9580
|
-
function extractNmapIntel(output) {
|
|
9581
|
-
const intel = [];
|
|
9582
|
-
const lines = output.split("\n");
|
|
9583
|
-
const hostMatches = output.match(/Nmap scan report for\s+(\S+)/gi);
|
|
9584
|
-
const openPorts = output.match(/^\d+\/\w+\s+open\s+/gm);
|
|
9585
|
-
if (hostMatches) intel.push(`Hosts scanned: ${hostMatches.length}`);
|
|
9586
|
-
if (openPorts) intel.push(`Open ports found: ${openPorts.length}`);
|
|
9587
|
-
const portLines = lines.filter((l) => /^\d+\/\w+\s+open\s+/.test(l.trim()));
|
|
9588
|
-
if (portLines.length > 0) {
|
|
9589
|
-
intel.push("Open ports:");
|
|
9590
|
-
for (const pl of portLines.slice(0, EXTRACT_LIMITS.NMAP_PORTS)) {
|
|
9591
|
-
intel.push(` ${pl.trim()}`);
|
|
9592
|
-
}
|
|
9593
|
-
}
|
|
9594
|
-
const vulnLines = lines.filter(
|
|
9595
|
-
(l) => /VULNERABLE|CVE-\d{4}-\d+|exploit|CRITICAL/i.test(l)
|
|
9596
|
-
);
|
|
9597
|
-
if (vulnLines.length > 0) {
|
|
9598
|
-
intel.push("\u26A0\uFE0F Vulnerability indicators:");
|
|
9599
|
-
for (const vl of vulnLines.slice(0, EXTRACT_LIMITS.NMAP_VULNS)) {
|
|
9600
|
-
intel.push(` ${vl.trim()}`);
|
|
9601
|
-
}
|
|
9602
|
-
}
|
|
9603
|
-
const osMatch = output.match(/OS details:\s*(.+)/i) || output.match(/Running:\s*(.+)/i);
|
|
9604
|
-
if (osMatch) intel.push(`OS: ${osMatch[1].trim()}`);
|
|
9605
|
-
return intel;
|
|
9606
|
-
}
|
|
9607
|
-
function extractLinpeasIntel(output) {
|
|
9608
|
-
const intel = [];
|
|
9609
|
-
const suidSection = output.match(/SUID[\s\S]*?(?=\n[═╔╗━]+|\n\n\n)/i);
|
|
9610
|
-
if (suidSection) {
|
|
9611
|
-
const suidBins = suidSection[0].match(/\/\S+/g);
|
|
9612
|
-
if (suidBins) {
|
|
9613
|
-
const interestingSuid = suidBins.filter(
|
|
9614
|
-
(b) => /python|perl|ruby|node|bash|vim|less|more|nano|find|nmap|awk|env|php|gcc|gdb|docker|strace|ltrace/i.test(b)
|
|
9615
|
-
);
|
|
9616
|
-
if (interestingSuid.length > 0) {
|
|
9617
|
-
intel.push(`\u{1F534} Exploitable SUID binaries: ${interestingSuid.join(", ")}`);
|
|
9618
|
-
}
|
|
9619
|
-
}
|
|
9620
|
-
}
|
|
9621
|
-
const sudoMatch = output.match(/User \S+ may run[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9622
|
-
if (sudoMatch) {
|
|
9623
|
-
intel.push(`\u{1F534} sudo -l: ${sudoMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_SUDO)}`);
|
|
9624
|
-
}
|
|
9625
|
-
const writableMatch = output.match(/Interesting writable[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9626
|
-
if (writableMatch) {
|
|
9627
|
-
intel.push(`\u{1F4DD} Writable: ${writableMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_WRITABLE)}`);
|
|
9628
|
-
}
|
|
9629
|
-
const cronMatch = output.match(/Cron[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9630
|
-
if (cronMatch) {
|
|
9631
|
-
const cronLines = cronMatch[0].split("\n").filter((l) => l.includes("*") || /\/(root|cron)/i.test(l));
|
|
9632
|
-
if (cronLines.length > 0) {
|
|
9633
|
-
intel.push("\u23F0 Cron entries:");
|
|
9634
|
-
cronLines.slice(0, EXTRACT_LIMITS.LINPEAS_CRON).forEach((c) => intel.push(` ${c.trim()}`));
|
|
9635
|
-
}
|
|
9636
|
-
}
|
|
9637
|
-
const kernelMatch = output.match(/Linux version\s+(\S+)/i) || output.match(/Kernel:\s*(\S+)/i);
|
|
9638
|
-
if (kernelMatch) intel.push(`\u{1F427} Kernel: ${kernelMatch[1]}`);
|
|
9639
|
-
const passLines = output.split("\n").filter(
|
|
9640
|
-
(l) => /password\s*[=:]\s*\S+/i.test(l) && !/\*\*\*|example|sample/i.test(l)
|
|
9641
|
-
);
|
|
9642
|
-
if (passLines.length > 0) {
|
|
9643
|
-
intel.push("\u{1F511} Potential credentials found:");
|
|
9644
|
-
passLines.slice(0, EXTRACT_LIMITS.LINPEAS_PASSWORDS).forEach((p) => intel.push(` ${p.trim()}`));
|
|
9645
|
-
}
|
|
9646
|
-
const cveMatches = output.match(/CVE-\d{4}-\d+/gi);
|
|
9647
|
-
if (cveMatches) {
|
|
9648
|
-
const uniqueCves = [...new Set(cveMatches)];
|
|
9649
|
-
intel.push(`\u26A0\uFE0F CVEs mentioned: ${uniqueCves.join(", ")}`);
|
|
9650
|
-
}
|
|
9651
|
-
return intel;
|
|
9652
|
-
}
|
|
9653
|
-
function extractEnum4linuxIntel(output) {
|
|
9654
|
-
const intel = [];
|
|
9655
|
-
const userMatches = output.match(/user:\[(\S+?)\]/gi);
|
|
9656
|
-
if (userMatches) {
|
|
9657
|
-
const users = userMatches.map((u) => u.replace(/user:\[|\]/gi, ""));
|
|
9658
|
-
intel.push(`\u{1F464} Users found: ${users.join(", ")}`);
|
|
9659
|
-
}
|
|
9660
|
-
const shareMatches = output.match(/Mapping: (\S+),\s*Listing: (\S+)/gi) || output.match(/\\\\\S+\\\\\S+/g);
|
|
9661
|
-
if (shareMatches) {
|
|
9662
|
-
intel.push(`\u{1F4C2} Shares: ${shareMatches.slice(0, EXTRACT_LIMITS.ENUM4LINUX_SHARES).join(", ")}`);
|
|
9663
|
-
}
|
|
9664
|
-
const domainMatch = output.match(/Domain:\s*\[(\S+?)\]/i) || output.match(/Workgroup:\s*\[(\S+?)\]/i);
|
|
9665
|
-
if (domainMatch) intel.push(`\u{1F3E2} Domain: ${domainMatch[1]}`);
|
|
9666
|
-
const policyMatch = output.match(/Password Complexity|Account Lockout/i);
|
|
9667
|
-
if (policyMatch) intel.push("\u{1F510} Password policy information found");
|
|
9668
|
-
return intel;
|
|
9669
|
-
}
|
|
9670
|
-
function extractDirBustIntel(output) {
|
|
9671
|
-
const intel = [];
|
|
9672
|
-
const lines = output.split("\n");
|
|
9673
|
-
const interestingPaths = [];
|
|
9674
|
-
for (const line of lines) {
|
|
9675
|
-
const match = line.match(/(?:Status:\s*(\d{3})|(\d{3})\s+\d+\s+\d+).*?(\/\S+)/);
|
|
9676
|
-
if (match) {
|
|
9677
|
-
const status = match[1] || match[2];
|
|
9678
|
-
const path2 = match[3];
|
|
9679
|
-
if (["200", "301", "302", "403"].includes(status)) {
|
|
9680
|
-
interestingPaths.push(`[${status}] ${path2}`);
|
|
9681
|
-
}
|
|
9682
|
-
}
|
|
9683
|
-
const ffufMatch = line.match(/(\S+)\s+\[Status:\s*(\d+),?\s*Size:\s*(\d+)/);
|
|
9684
|
-
if (ffufMatch && ["200", "301", "302", "403"].includes(ffufMatch[2])) {
|
|
9685
|
-
interestingPaths.push(`[${ffufMatch[2]}] ${ffufMatch[1]} (${ffufMatch[3]}b)`);
|
|
9686
|
-
}
|
|
9687
|
-
}
|
|
9688
|
-
if (interestingPaths.length > 0) {
|
|
9689
|
-
intel.push(`\u{1F4C1} Discovered paths (${interestingPaths.length}):`);
|
|
9690
|
-
interestingPaths.slice(0, EXTRACT_LIMITS.DIRBUST_PATHS).forEach((p) => intel.push(` ${p}`));
|
|
9691
|
-
if (interestingPaths.length > EXTRACT_LIMITS.DIRBUST_PATHS) {
|
|
9692
|
-
intel.push(` ... and ${interestingPaths.length - EXTRACT_LIMITS.DIRBUST_PATHS} more`);
|
|
9693
|
-
}
|
|
9694
|
-
}
|
|
9695
|
-
return intel;
|
|
9696
|
-
}
|
|
9697
|
-
function extractSqlmapIntel(output) {
|
|
9698
|
-
const intel = [];
|
|
9699
|
-
const injectionTypes = output.match(/Type:\s*(\S.*?)(?:\n|$)/gi);
|
|
9700
|
-
if (injectionTypes) {
|
|
9701
|
-
intel.push("\u{1F489} SQL injection found:");
|
|
9702
|
-
injectionTypes.slice(0, EXTRACT_LIMITS.SQLMAP_INJECTIONS).forEach((t) => intel.push(` ${t.trim()}`));
|
|
9703
|
-
}
|
|
9704
|
-
const dbMatch = output.match(/back-end DBMS:\s*(.+)/i);
|
|
9705
|
-
if (dbMatch) intel.push(`\u{1F5C4}\uFE0F DBMS: ${dbMatch[1].trim()}`);
|
|
9706
|
-
const dbListMatch = output.match(/available databases.*?:\s*([\s\S]*?)(?=\n\[|\n$)/i);
|
|
9707
|
-
if (dbListMatch) {
|
|
9708
|
-
intel.push(`\u{1F4CA} Databases: ${dbListMatch[1].trim().replace(/\n/g, ", ")}`);
|
|
9709
|
-
}
|
|
9710
|
-
const tableMatches = output.match(/Database:\s*\S+\s+Table:\s*\S+/gi);
|
|
9711
|
-
if (tableMatches) {
|
|
9712
|
-
intel.push(`\u{1F4CB} Tables dumped: ${tableMatches.length}`);
|
|
9713
|
-
}
|
|
9714
|
-
return intel;
|
|
9715
|
-
}
|
|
9716
|
-
function extractHashIntel(output) {
|
|
9717
|
-
const intel = [];
|
|
9718
|
-
const lines = output.split("\n");
|
|
9719
|
-
const md5Hashes = lines.filter((l) => /\b[a-f0-9]{32}\b/i.test(l) && !l.includes("{"));
|
|
9720
|
-
const sha256Hashes = lines.filter((l) => /\b[a-f0-9]{64}\b/i.test(l));
|
|
9721
|
-
const unixHashes = lines.filter((l) => /\$[0-9]\$|\$2[aby]\$|\$6\$|\$5\$/i.test(l));
|
|
9722
|
-
const ntlmHashes = lines.filter((l) => /:[0-9]+:[a-f0-9]{32}:[a-f0-9]{32}:::/i.test(l));
|
|
9723
|
-
if (md5Hashes.length > 0) intel.push(`#\uFE0F\u20E3 MD5 hashes: ${md5Hashes.length}`);
|
|
9724
|
-
if (sha256Hashes.length > 0) intel.push(`#\uFE0F\u20E3 SHA256 hashes: ${sha256Hashes.length}`);
|
|
9725
|
-
if (unixHashes.length > 0) intel.push(`#\uFE0F\u20E3 Unix crypt hashes: ${unixHashes.length}`);
|
|
9726
|
-
if (ntlmHashes.length > 0) {
|
|
9727
|
-
intel.push(`#\uFE0F\u20E3 NTLM hashes: ${ntlmHashes.length}`);
|
|
9728
|
-
ntlmHashes.slice(0, EXTRACT_LIMITS.HASH_NTLM).forEach((h) => intel.push(` ${h.trim().slice(0, EXTRACT_LIMITS.HASH_PREVIEW_LEN)}`));
|
|
9729
|
-
}
|
|
9730
|
-
return intel;
|
|
9731
|
-
}
|
|
9732
|
-
function extractGenericIntel(output) {
|
|
9733
|
-
const intel = [];
|
|
9734
|
-
const credPatterns = output.match(/(?:password|passwd|pwd|credentials?)\s*[=:]\s*\S+/gi);
|
|
9735
|
-
if (credPatterns) {
|
|
9736
|
-
intel.push("\u{1F511} Potential credentials detected:");
|
|
9737
|
-
credPatterns.slice(0, EXTRACT_LIMITS.GENERIC_CREDS).forEach((c) => intel.push(` ${c.trim()}`));
|
|
9738
|
-
}
|
|
9739
|
-
const cves = output.match(/CVE-\d{4}-\d+/gi);
|
|
9740
|
-
if (cves) {
|
|
9741
|
-
const unique = [...new Set(cves)];
|
|
9742
|
-
intel.push(`\u26A0\uFE0F CVEs mentioned: ${unique.join(", ")}`);
|
|
9743
|
-
}
|
|
9744
|
-
const ips = output.match(/\b(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\b/g);
|
|
9745
|
-
if (ips) {
|
|
9746
|
-
const uniqueIps = [...new Set(ips)].filter(
|
|
9747
|
-
(ip) => !ip.startsWith("0.") && !ip.startsWith("255.") && ip !== "127.0.0.1"
|
|
9748
|
-
);
|
|
9749
|
-
if (uniqueIps.length > 0 && uniqueIps.length <= 20) {
|
|
9750
|
-
intel.push(`\u{1F310} IP addresses found: ${uniqueIps.join(", ")}`);
|
|
9751
|
-
}
|
|
9752
|
-
}
|
|
9753
|
-
const paths = output.match(/\/(?:etc\/(?:shadow|passwd|sudoers)|root\/|home\/\S+|var\/www\/\S+|opt\/\S+)\S*/g);
|
|
9754
|
-
if (paths) {
|
|
9755
|
-
const uniquePaths = [...new Set(paths)].slice(0, EXTRACT_LIMITS.GENERIC_PATHS);
|
|
9756
|
-
intel.push(`\u{1F4C2} Interesting paths: ${uniquePaths.join(", ")}`);
|
|
9757
|
-
}
|
|
9758
|
-
const flagPatterns = output.match(/(?:flag|secret|key|token)\{[^}]+\}/gi);
|
|
9759
|
-
if (flagPatterns) {
|
|
9760
|
-
intel.push("\u{1F3F4} FLAG/SECRET patterns detected:");
|
|
9761
|
-
flagPatterns.forEach((f) => intel.push(` ${f}`));
|
|
9762
|
-
}
|
|
9763
|
-
return intel;
|
|
9764
|
-
}
|
|
9765
|
-
|
|
9766
9884
|
// src/shared/utils/context-digest.ts
|
|
9767
9885
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
9768
|
-
var PASSTHROUGH_THRESHOLD =
|
|
9769
|
-
var
|
|
9770
|
-
var
|
|
9771
|
-
var MAX_REDUCED_LINES = 500;
|
|
9886
|
+
var PASSTHROUGH_THRESHOLD = 500;
|
|
9887
|
+
var PREPROCESS_THRESHOLD = 3e3;
|
|
9888
|
+
var MAX_PREPROCESSED_LINES = 800;
|
|
9772
9889
|
var getOutputDir = () => WORKSPACE.OUTPUTS;
|
|
9773
9890
|
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9774
|
-
var
|
|
9891
|
+
var ANALYST_MAX_INPUT_CHARS = 8e4;
|
|
9775
9892
|
var FALLBACK_MAX_CHARS = 3e4;
|
|
9776
|
-
|
|
9777
|
-
/error|fail|denied|refused|timeout|exception/i,
|
|
9778
|
-
/warning|warn|deprecated|insecure/i,
|
|
9779
|
-
/success|found|detected|discovered|vulnerable|VULNERABLE/i,
|
|
9780
|
-
/password|passwd|credential|secret|key|token|hash/i,
|
|
9781
|
-
/CVE-\d{4}-\d+/i,
|
|
9782
|
-
/\d+\/\w+\s+open\s+/,
|
|
9783
|
-
// nmap open port
|
|
9784
|
-
/flag\{|ctf\{|HTB\{|THM\{/i,
|
|
9785
|
-
// CTF flags
|
|
9786
|
-
/root:|admin:|www-data:|nobody:/,
|
|
9787
|
-
// /etc/passwd entries
|
|
9788
|
-
/\$[0-9]\$|\$2[aby]\$/,
|
|
9789
|
-
// password hashes
|
|
9790
|
-
/\b(?:192\.168|10\.|172\.(?:1[6-9]|2\d|3[01]))\.\d+\.\d+\b/,
|
|
9791
|
-
// internal IPs
|
|
9792
|
-
/BEGIN\s+(?:RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY/i,
|
|
9793
|
-
// private keys
|
|
9794
|
-
/Authorization:|Bearer\s+|Basic\s+/i
|
|
9795
|
-
// auth tokens
|
|
9796
|
-
];
|
|
9797
|
-
var NOISE_PATTERNS = [
|
|
9798
|
-
/^\s*$/,
|
|
9799
|
-
// blank lines
|
|
9800
|
-
/^\[[\d:]+\]\s*$/,
|
|
9801
|
-
// timestamp-only lines
|
|
9802
|
-
/^#+\s*$/,
|
|
9803
|
-
// separator lines
|
|
9804
|
-
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d+/i,
|
|
9805
|
-
// progress bars
|
|
9806
|
-
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
9807
|
-
// request counters
|
|
9808
|
-
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
9809
|
-
// decoration lines
|
|
9810
|
-
];
|
|
9811
|
-
async function digestToolOutput(output, toolName, toolInput, llmDigestFn) {
|
|
9893
|
+
async function digestToolOutput(output, toolName, toolInput, analystFn) {
|
|
9812
9894
|
const originalLength = output.length;
|
|
9813
9895
|
if (originalLength < PASSTHROUGH_THRESHOLD) {
|
|
9814
9896
|
return {
|
|
9815
9897
|
digestedOutput: output,
|
|
9816
9898
|
fullOutputPath: null,
|
|
9817
|
-
|
|
9899
|
+
analystUsed: false,
|
|
9900
|
+
memo: null,
|
|
9818
9901
|
originalLength,
|
|
9819
9902
|
digestedLength: originalLength,
|
|
9820
9903
|
compressionRatio: 1
|
|
9821
9904
|
};
|
|
9822
9905
|
}
|
|
9823
|
-
const
|
|
9824
|
-
let
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
if (
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9906
|
+
const savedOutputPath = saveFullOutput(output, toolName);
|
|
9907
|
+
let preprocessed = output;
|
|
9908
|
+
if (originalLength > PREPROCESS_THRESHOLD) {
|
|
9909
|
+
preprocessed = structuralPreprocess(output);
|
|
9910
|
+
}
|
|
9911
|
+
if (analystFn) {
|
|
9912
|
+
try {
|
|
9913
|
+
const context = `Tool: ${toolName}${toolInput ? ` | Input: ${toolInput}` : ""}`;
|
|
9914
|
+
const rawAnalystResponse = await analystFn(preprocessed, context);
|
|
9915
|
+
const memo6 = parseAnalystMemo(rawAnalystResponse);
|
|
9916
|
+
const formatted = formatAnalystDigest(rawAnalystResponse, savedOutputPath, originalLength);
|
|
9917
|
+
return {
|
|
9918
|
+
digestedOutput: formatted,
|
|
9919
|
+
fullOutputPath: savedOutputPath,
|
|
9920
|
+
analystUsed: true,
|
|
9921
|
+
memo: memo6,
|
|
9922
|
+
originalLength,
|
|
9923
|
+
digestedLength: formatted.length,
|
|
9924
|
+
compressionRatio: formatted.length / originalLength
|
|
9925
|
+
};
|
|
9926
|
+
} catch (err) {
|
|
9927
|
+
debugLog("general", "Analyst LLM failed, falling back to truncation", {
|
|
9928
|
+
toolName,
|
|
9929
|
+
error: String(err)
|
|
9930
|
+
});
|
|
9847
9931
|
}
|
|
9848
|
-
} else if (layersApplied.includes(2)) {
|
|
9849
|
-
savedOutputPath = saveFullOutput(output, toolName);
|
|
9850
9932
|
}
|
|
9933
|
+
const fallback = formatFallbackDigest(preprocessed, savedOutputPath, originalLength);
|
|
9851
9934
|
return {
|
|
9852
|
-
digestedOutput:
|
|
9935
|
+
digestedOutput: fallback,
|
|
9853
9936
|
fullOutputPath: savedOutputPath,
|
|
9854
|
-
|
|
9937
|
+
analystUsed: false,
|
|
9938
|
+
memo: null,
|
|
9855
9939
|
originalLength,
|
|
9856
|
-
digestedLength:
|
|
9857
|
-
compressionRatio:
|
|
9940
|
+
digestedLength: fallback.length,
|
|
9941
|
+
compressionRatio: fallback.length / originalLength
|
|
9858
9942
|
};
|
|
9859
9943
|
}
|
|
9860
|
-
|
|
9944
|
+
var NOISE_PATTERNS = [
|
|
9945
|
+
/^\s*$/,
|
|
9946
|
+
// blank lines
|
|
9947
|
+
/^\[[\d:]+\]\s*$/,
|
|
9948
|
+
// timestamp-only lines
|
|
9949
|
+
/^#+\s*$/,
|
|
9950
|
+
// separator lines
|
|
9951
|
+
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d/i,
|
|
9952
|
+
// progress bars
|
|
9953
|
+
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
9954
|
+
// request counters
|
|
9955
|
+
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
9956
|
+
// decoration lines
|
|
9957
|
+
];
|
|
9958
|
+
function structuralPreprocess(output) {
|
|
9861
9959
|
let cleaned = stripAnsi(output);
|
|
9862
9960
|
const lines = cleaned.split("\n");
|
|
9863
9961
|
const result2 = [];
|
|
9864
|
-
const duplicateCounts = /* @__PURE__ */ new Map();
|
|
9865
9962
|
let lastLine = "";
|
|
9866
9963
|
let consecutiveDupes = 0;
|
|
9867
9964
|
for (const line of lines) {
|
|
@@ -9869,11 +9966,9 @@ function structuralReduce(output) {
|
|
|
9869
9966
|
if (NOISE_PATTERNS.some((p) => p.test(trimmed))) {
|
|
9870
9967
|
continue;
|
|
9871
9968
|
}
|
|
9872
|
-
const isSignal = SIGNAL_PATTERNS.some((p) => p.test(trimmed));
|
|
9873
9969
|
const normalized = normalizeLine(trimmed);
|
|
9874
|
-
if (normalized === normalizeLine(lastLine)
|
|
9970
|
+
if (normalized === normalizeLine(lastLine)) {
|
|
9875
9971
|
consecutiveDupes++;
|
|
9876
|
-
duplicateCounts.set(normalized, (duplicateCounts.get(normalized) || 1) + 1);
|
|
9877
9972
|
continue;
|
|
9878
9973
|
}
|
|
9879
9974
|
if (consecutiveDupes > 0) {
|
|
@@ -9896,57 +9991,124 @@ function structuralReduce(output) {
|
|
|
9896
9991
|
result2.push(lastLine);
|
|
9897
9992
|
}
|
|
9898
9993
|
}
|
|
9899
|
-
if (result2.length >
|
|
9900
|
-
const headSize = Math.floor(
|
|
9901
|
-
const tailSize = Math.floor(
|
|
9902
|
-
const signalBudget = MAX_REDUCED_LINES - headSize - tailSize;
|
|
9994
|
+
if (result2.length > MAX_PREPROCESSED_LINES) {
|
|
9995
|
+
const headSize = Math.floor(MAX_PREPROCESSED_LINES * 0.5);
|
|
9996
|
+
const tailSize = Math.floor(MAX_PREPROCESSED_LINES * 0.3);
|
|
9903
9997
|
const head = result2.slice(0, headSize);
|
|
9904
9998
|
const tail = result2.slice(-tailSize);
|
|
9905
|
-
const
|
|
9906
|
-
const middleSignals = middle.filter((line) => SIGNAL_PATTERNS.some((p) => p.test(line))).slice(0, signalBudget);
|
|
9907
|
-
const skipped = middle.length - middleSignals.length;
|
|
9999
|
+
const skipped = result2.length - headSize - tailSize;
|
|
9908
10000
|
cleaned = [
|
|
9909
10001
|
...head,
|
|
9910
10002
|
"",
|
|
9911
|
-
`... [${skipped}
|
|
9912
|
-
"",
|
|
9913
|
-
...middleSignals,
|
|
9914
|
-
"",
|
|
9915
|
-
`... [resuming last ${tailSize} lines] ...`,
|
|
10003
|
+
`... [${skipped} lines skipped for Analyst LLM context \u2014 full output saved to file] ...`,
|
|
9916
10004
|
"",
|
|
9917
10005
|
...tail
|
|
9918
10006
|
].join("\n");
|
|
9919
10007
|
} else {
|
|
9920
10008
|
cleaned = result2.join("\n");
|
|
9921
10009
|
}
|
|
10010
|
+
if (cleaned.length > ANALYST_MAX_INPUT_CHARS) {
|
|
10011
|
+
cleaned = cleaned.slice(0, ANALYST_MAX_INPUT_CHARS) + `
|
|
10012
|
+
... [truncated at ${ANALYST_MAX_INPUT_CHARS} chars for Analyst LLM \u2014 full output saved to file]`;
|
|
10013
|
+
}
|
|
9922
10014
|
return cleaned;
|
|
9923
10015
|
}
|
|
9924
|
-
var
|
|
10016
|
+
var ANALYST_SYSTEM_PROMPT = `You are an independent pentesting output analyst. You receive raw tool output and must extract ONLY actionable intelligence for the main attack agent.
|
|
9925
10017
|
|
|
9926
10018
|
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
10019
|
+
|
|
9927
10020
|
## Key Findings
|
|
9928
|
-
- [finding 1]
|
|
10021
|
+
- [finding 1 with exact values: ports, versions, paths]
|
|
9929
10022
|
- [finding 2]
|
|
9930
10023
|
|
|
9931
10024
|
## Credentials/Secrets
|
|
9932
|
-
- [any discovered credentials, hashes, tokens, keys]
|
|
10025
|
+
- [any discovered credentials, hashes, tokens, keys, certificates]
|
|
10026
|
+
- (write "None found" if none)
|
|
9933
10027
|
|
|
9934
10028
|
## Attack Vectors
|
|
9935
|
-
- [exploitable services, vulnerabilities, misconfigurations]
|
|
10029
|
+
- [exploitable services, vulnerabilities, misconfigurations, CVEs]
|
|
10030
|
+
- (write "None identified" if none)
|
|
10031
|
+
|
|
10032
|
+
## Failures/Errors
|
|
10033
|
+
- [what was attempted and FAILED \u2014 include the FULL command, wordlist, target, and the reason WHY it failed]
|
|
10034
|
+
- [e.g.: "SSH brute force: hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://10.0.0.1 \u2014 connection refused (port filtered)"]
|
|
10035
|
+
- [e.g.: "SQLi on /login with sqlmap --tamper=space2comment \u2014 input sanitized, WAF detected (ModSecurity)"]
|
|
10036
|
+
- (write "No failures" if everything succeeded)
|
|
10037
|
+
|
|
10038
|
+
## Suspicious Signals
|
|
10039
|
+
- [anomalies that are NOT confirmed vulnerabilities but suggest exploitable surface]
|
|
10040
|
+
- [e.g.: "Response time 3x slower on /admin path \u2014 possible auth check or backend processing"]
|
|
10041
|
+
- [e.g.: "X-Debug-Token header present \u2014 debug mode may be enabled"]
|
|
10042
|
+
- [e.g.: "Verbose error message reveals stack trace / internal path / DB schema"]
|
|
10043
|
+
- [e.g.: "Unexpected 302 redirect with session param leaked in URL"]
|
|
10044
|
+
- (write "No suspicious signals" if nothing anomalous)
|
|
10045
|
+
|
|
10046
|
+
## Attack Value
|
|
10047
|
+
- [ONE word: HIGH / MED / LOW / NONE]
|
|
10048
|
+
- Reasoning: [1 sentence why \u2014 what makes this worth pursuing or abandoning]
|
|
9936
10049
|
|
|
9937
10050
|
## Next Steps
|
|
9938
10051
|
- [recommended immediate actions based on findings]
|
|
9939
10052
|
|
|
9940
10053
|
RULES:
|
|
9941
|
-
-
|
|
9942
|
-
-
|
|
10054
|
+
- Include EXACT values: port numbers, versions, usernames, file paths, IPs, full commands used
|
|
10055
|
+
- For failures: include the COMPLETE command with all flags, wordlists, and targets \u2014 "brute force failed" alone is USELESS
|
|
10056
|
+
- Look for the UNEXPECTED \u2014 non-standard ports, unusual banners, timing anomalies, error leaks
|
|
10057
|
+
- Credentials include: passwords, hashes, API keys, tokens, private keys, cookies, session IDs
|
|
10058
|
+
- Flag any information disclosure: server versions, internal paths, stack traces, debug output
|
|
9943
10059
|
- If nothing interesting found, say "No actionable findings in this output"
|
|
9944
|
-
-
|
|
9945
|
-
-
|
|
9946
|
-
|
|
10060
|
+
- Never include decorative output, banners, or progress information
|
|
10061
|
+
- Do NOT miss subtle signals: unusual HTTP headers, non-standard responses, timing differences
|
|
10062
|
+
- Write as much detail as needed \u2014 do NOT artificially shorten. Every detail matters for strategy.
|
|
10063
|
+
|
|
10064
|
+
## Reflection
|
|
10065
|
+
- What this output tells us: [1-line assessment]
|
|
10066
|
+
- Recommended next action: [1-2 specific follow-up actions]`;
|
|
10067
|
+
function parseAnalystMemo(response) {
|
|
10068
|
+
const sections = {};
|
|
10069
|
+
let currentSection = "";
|
|
10070
|
+
let reflectionLines = [];
|
|
10071
|
+
let attackValueLine = "NONE";
|
|
10072
|
+
let attackValueReasoning = "";
|
|
10073
|
+
for (const line of response.split("\n")) {
|
|
10074
|
+
if (line.startsWith("## ")) {
|
|
10075
|
+
currentSection = line.replace("## ", "").trim().toLowerCase();
|
|
10076
|
+
sections[currentSection] = [];
|
|
10077
|
+
} else if (currentSection === "reflection") {
|
|
10078
|
+
if (line.trim()) reflectionLines.push(line.trim());
|
|
10079
|
+
} else if (currentSection === "attack value") {
|
|
10080
|
+
const match = line.match(/\b(HIGH|MED|LOW|NONE)\b/);
|
|
10081
|
+
if (match) attackValueLine = match[1];
|
|
10082
|
+
const reasonMatch = line.match(/[Rr]easoning:\s*(.+)/);
|
|
10083
|
+
if (reasonMatch) attackValueReasoning = reasonMatch[1].trim();
|
|
10084
|
+
} else if (currentSection) {
|
|
10085
|
+
const trimmed = line.trim();
|
|
10086
|
+
if (!trimmed) continue;
|
|
10087
|
+
const content = trimmed.replace(/^(?:-|\*|\d+[.)]\s*)\s*/, "").trim();
|
|
10088
|
+
if (content) sections[currentSection].push(content);
|
|
10089
|
+
}
|
|
10090
|
+
}
|
|
10091
|
+
const filterNone = (items) => items.filter((i) => !/(^none|^no )/i.test(i.trim()));
|
|
10092
|
+
const rawValue = attackValueLine.toUpperCase();
|
|
10093
|
+
const attackValue = ["HIGH", "MED", "LOW", "NONE"].includes(rawValue) ? rawValue : "LOW";
|
|
10094
|
+
return {
|
|
10095
|
+
keyFindings: filterNone(sections["key findings"] || []),
|
|
10096
|
+
credentials: filterNone(sections["credentials/secrets"] || []),
|
|
10097
|
+
attackVectors: filterNone(sections["attack vectors"] || []),
|
|
10098
|
+
failures: filterNone(sections["failures/errors"] || []),
|
|
10099
|
+
suspicions: filterNone(sections["suspicious signals"] || []),
|
|
10100
|
+
attackValue,
|
|
10101
|
+
nextSteps: filterNone(sections["next steps"] || []),
|
|
10102
|
+
reflection: [
|
|
10103
|
+
attackValueReasoning ? `[${attackValue}] ${attackValueReasoning}` : "",
|
|
10104
|
+
...reflectionLines
|
|
10105
|
+
].filter(Boolean).join(" | ")
|
|
10106
|
+
};
|
|
10107
|
+
}
|
|
10108
|
+
function formatAnalystDigest(digest, filePath, originalChars) {
|
|
9947
10109
|
return [
|
|
9948
10110
|
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
9949
|
-
"\u2551
|
|
10111
|
+
"\u2551 ANALYST DIGEST (Independent LLM analysis) \u2551",
|
|
9950
10112
|
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
9951
10113
|
"",
|
|
9952
10114
|
digest,
|
|
@@ -9956,29 +10118,26 @@ function formatLLMDigest(digest, filePath, originalChars) {
|
|
|
9956
10118
|
].join("\n");
|
|
9957
10119
|
}
|
|
9958
10120
|
function formatFallbackDigest(processed, filePath, originalChars) {
|
|
9959
|
-
const lines = processed.split("\n");
|
|
9960
|
-
const summaryEndIdx = lines.findIndex((l) => l.includes("END SUMMARY"));
|
|
9961
|
-
const summaryBlock = summaryEndIdx > 0 ? lines.slice(0, summaryEndIdx + 1).join("\n") : "";
|
|
9962
|
-
const remaining = summaryEndIdx > 0 ? lines.slice(summaryEndIdx + 1).join("\n") : processed;
|
|
9963
10121
|
const maxChars = FALLBACK_MAX_CHARS;
|
|
9964
|
-
let
|
|
9965
|
-
if (
|
|
10122
|
+
let truncated = processed;
|
|
10123
|
+
if (processed.length > maxChars) {
|
|
9966
10124
|
const headChars = Math.floor(maxChars * 0.6);
|
|
9967
10125
|
const tailChars = Math.floor(maxChars * 0.4);
|
|
9968
|
-
const skipped =
|
|
9969
|
-
|
|
10126
|
+
const skipped = processed.length - headChars - tailChars;
|
|
10127
|
+
truncated = processed.slice(0, headChars) + `
|
|
9970
10128
|
|
|
9971
|
-
... [${skipped} chars omitted \u2014 read full output from file] ...
|
|
10129
|
+
... [${skipped} chars omitted \u2014 Analyst LLM unavailable, read full output from file] ...
|
|
9972
10130
|
|
|
9973
|
-
` +
|
|
10131
|
+
` + processed.slice(-tailChars);
|
|
9974
10132
|
}
|
|
9975
10133
|
return [
|
|
9976
|
-
|
|
9977
|
-
|
|
10134
|
+
"\u26A0\uFE0F ANALYST UNAVAILABLE \u2014 showing truncated raw output:",
|
|
10135
|
+
"",
|
|
10136
|
+
truncated,
|
|
9978
10137
|
"",
|
|
9979
10138
|
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
9980
10139
|
`\u{1F4A1} Use read_file("${filePath}") to see the complete raw output.`
|
|
9981
|
-
].
|
|
10140
|
+
].join("\n");
|
|
9982
10141
|
}
|
|
9983
10142
|
function stripAnsi(text) {
|
|
9984
10143
|
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\[[\d;]*m/g, "");
|
|
@@ -10004,20 +10163,21 @@ function saveFullOutput(output, toolName) {
|
|
|
10004
10163
|
}
|
|
10005
10164
|
function createLLMDigestFn(llmClient) {
|
|
10006
10165
|
return async (text, context) => {
|
|
10007
|
-
const
|
|
10008
|
-
|
|
10009
|
-
|
|
10166
|
+
const messages = [{
|
|
10167
|
+
role: LLM_ROLES.USER,
|
|
10168
|
+
content: `Analyze this pentesting tool output and extract actionable intelligence.
|
|
10010
10169
|
|
|
10011
10170
|
Context: ${context}
|
|
10012
10171
|
|
|
10013
10172
|
--- OUTPUT START ---
|
|
10014
|
-
${
|
|
10015
|
-
--- OUTPUT END ---`
|
|
10173
|
+
${text}
|
|
10174
|
+
--- OUTPUT END ---`
|
|
10175
|
+
}];
|
|
10016
10176
|
const response = await llmClient.generateResponse(
|
|
10017
10177
|
messages,
|
|
10018
10178
|
void 0,
|
|
10019
|
-
// no tools —
|
|
10020
|
-
|
|
10179
|
+
// no tools — analysis only
|
|
10180
|
+
ANALYST_SYSTEM_PROMPT
|
|
10021
10181
|
);
|
|
10022
10182
|
return response.content || "No actionable findings extracted.";
|
|
10023
10183
|
};
|
|
@@ -10032,6 +10192,16 @@ var CoreAgent = class _CoreAgent {
|
|
|
10032
10192
|
agentType;
|
|
10033
10193
|
maxIterations;
|
|
10034
10194
|
abortController = null;
|
|
10195
|
+
/**
|
|
10196
|
+
* Collected tool execution records for the current turn.
|
|
10197
|
+
* MainAgent reads this after each step to write journal entries.
|
|
10198
|
+
* Cleared at the start of each step.
|
|
10199
|
+
*/
|
|
10200
|
+
turnToolJournal = [];
|
|
10201
|
+
/** Aggregated memo from all tools in the current turn */
|
|
10202
|
+
turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10203
|
+
/** Analyst reflections collected during this turn (1-line assessments) */
|
|
10204
|
+
turnReflections = [];
|
|
10035
10205
|
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
10036
10206
|
this.agentType = agentType;
|
|
10037
10207
|
this.state = state;
|
|
@@ -10159,7 +10329,22 @@ RULES:
|
|
|
10159
10329
|
});
|
|
10160
10330
|
continue;
|
|
10161
10331
|
}
|
|
10162
|
-
|
|
10332
|
+
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10333
|
+
this.events.emit({
|
|
10334
|
+
type: EVENT_TYPES.ERROR,
|
|
10335
|
+
timestamp: Date.now(),
|
|
10336
|
+
data: {
|
|
10337
|
+
message: `Unexpected error: ${unexpectedMsg}`,
|
|
10338
|
+
phase: this.state.getPhase(),
|
|
10339
|
+
isRecoverable: true
|
|
10340
|
+
}
|
|
10341
|
+
});
|
|
10342
|
+
messages.push({
|
|
10343
|
+
role: LLM_ROLES.USER,
|
|
10344
|
+
content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10345
|
+
This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
|
|
10346
|
+
});
|
|
10347
|
+
continue;
|
|
10163
10348
|
}
|
|
10164
10349
|
}
|
|
10165
10350
|
const summary = `Max iterations (${this.maxIterations}) reached. Progress: ${progress.totalToolsExecuted} tools executed (${progress.toolSuccesses} succeeded, ${progress.toolErrors} failed). Current phase: ${this.state.getPhase()}. Findings: ${this.state.getFindings?.()?.length ?? "unknown"}.`;
|
|
@@ -10477,27 +10662,62 @@ ${firstLine}`, phase }
|
|
|
10477
10662
|
progress.blockedCommandPatterns.clear();
|
|
10478
10663
|
}
|
|
10479
10664
|
}
|
|
10665
|
+
const rawOutputForTUI = outputText;
|
|
10666
|
+
let digestedOutputForLLM = outputText;
|
|
10667
|
+
let digestResult = null;
|
|
10480
10668
|
try {
|
|
10481
10669
|
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10482
|
-
|
|
10670
|
+
digestResult = await digestToolOutput(
|
|
10483
10671
|
outputText,
|
|
10484
10672
|
call.name,
|
|
10485
10673
|
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10486
10674
|
llmDigestFn
|
|
10487
10675
|
);
|
|
10488
|
-
|
|
10676
|
+
digestedOutputForLLM = digestResult.digestedOutput;
|
|
10489
10677
|
} catch {
|
|
10490
|
-
if (
|
|
10491
|
-
const truncated =
|
|
10492
|
-
const remaining =
|
|
10493
|
-
|
|
10678
|
+
if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10679
|
+
const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10680
|
+
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10681
|
+
digestedOutputForLLM = `${truncated}
|
|
10494
10682
|
|
|
10495
10683
|
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
10496
10684
|
\u{1F4A1} TIP: If you need to see the full output, use a tool to read the file directly or run the command with | head, | tail, or | grep.`;
|
|
10497
10685
|
}
|
|
10498
10686
|
}
|
|
10499
|
-
this.emitToolResult(call.name, result2.success,
|
|
10500
|
-
|
|
10687
|
+
this.emitToolResult(call.name, result2.success, rawOutputForTUI, result2.error, Date.now() - toolStartTime);
|
|
10688
|
+
const inputSummary = JSON.stringify(call.input);
|
|
10689
|
+
this.turnToolJournal.push({
|
|
10690
|
+
name: call.name,
|
|
10691
|
+
inputSummary,
|
|
10692
|
+
success: result2.success,
|
|
10693
|
+
analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
|
|
10694
|
+
outputFile: digestResult?.fullOutputPath ?? null
|
|
10695
|
+
});
|
|
10696
|
+
if (digestResult?.memo) {
|
|
10697
|
+
const m = digestResult.memo;
|
|
10698
|
+
this.turnMemo.keyFindings.push(...m.keyFindings);
|
|
10699
|
+
this.turnMemo.credentials.push(...m.credentials);
|
|
10700
|
+
this.turnMemo.attackVectors.push(...m.attackVectors);
|
|
10701
|
+
this.turnMemo.failures.push(...m.failures);
|
|
10702
|
+
this.turnMemo.suspicions.push(...m.suspicions);
|
|
10703
|
+
const VALUE_RANK = { HIGH: 3, MED: 2, LOW: 1, NONE: 0 };
|
|
10704
|
+
if ((VALUE_RANK[m.attackValue] ?? 0) > (VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
|
|
10705
|
+
this.turnMemo.attackValue = m.attackValue;
|
|
10706
|
+
}
|
|
10707
|
+
this.turnMemo.nextSteps.push(...m.nextSteps);
|
|
10708
|
+
if (m.reflection) this.turnReflections.push(m.reflection);
|
|
10709
|
+
}
|
|
10710
|
+
if (digestResult?.memo?.credentials.length) {
|
|
10711
|
+
for (const cred of digestResult.memo.credentials) {
|
|
10712
|
+
this.state.addLoot({
|
|
10713
|
+
type: "credential",
|
|
10714
|
+
host: "auto-extracted",
|
|
10715
|
+
detail: cred,
|
|
10716
|
+
obtainedAt: Date.now()
|
|
10717
|
+
});
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10501
10721
|
} catch (error) {
|
|
10502
10722
|
const errorMsg = String(error);
|
|
10503
10723
|
const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
@@ -10572,8 +10792,8 @@ ${firstLine}`, phase }
|
|
|
10572
10792
|
};
|
|
10573
10793
|
|
|
10574
10794
|
// src/agents/prompt-builder.ts
|
|
10575
|
-
import { readFileSync as
|
|
10576
|
-
import { join as
|
|
10795
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
10796
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
10577
10797
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10578
10798
|
|
|
10579
10799
|
// src/shared/constants/prompts.ts
|
|
@@ -10634,73 +10854,6 @@ var INITIAL_TASKS = {
|
|
|
10634
10854
|
RECON: "Initial reconnaissance and target discovery"
|
|
10635
10855
|
};
|
|
10636
10856
|
|
|
10637
|
-
// src/shared/constants/service-ports.ts
|
|
10638
|
-
var SERVICE_PORTS = {
|
|
10639
|
-
SSH: 22,
|
|
10640
|
-
FTP: 21,
|
|
10641
|
-
TELNET: 23,
|
|
10642
|
-
SMTP: 25,
|
|
10643
|
-
DNS: 53,
|
|
10644
|
-
HTTP: 80,
|
|
10645
|
-
POP3: 110,
|
|
10646
|
-
IMAP: 143,
|
|
10647
|
-
SMB_NETBIOS: 139,
|
|
10648
|
-
SMB: 445,
|
|
10649
|
-
HTTPS: 443,
|
|
10650
|
-
MSSQL: 1433,
|
|
10651
|
-
MYSQL: 3306,
|
|
10652
|
-
RDP: 3389,
|
|
10653
|
-
POSTGRESQL: 5432,
|
|
10654
|
-
REDIS: 6379,
|
|
10655
|
-
HTTP_ALT: 8080,
|
|
10656
|
-
HTTPS_ALT: 8443,
|
|
10657
|
-
MONGODB: 27017,
|
|
10658
|
-
ELASTICSEARCH: 9200,
|
|
10659
|
-
MEMCACHED: 11211,
|
|
10660
|
-
NODE_DEFAULT: 3e3,
|
|
10661
|
-
FLASK_DEFAULT: 5e3,
|
|
10662
|
-
DJANGO_DEFAULT: 8e3
|
|
10663
|
-
};
|
|
10664
|
-
var CRITICAL_SERVICE_PORTS = [
|
|
10665
|
-
SERVICE_PORTS.SSH,
|
|
10666
|
-
SERVICE_PORTS.RDP,
|
|
10667
|
-
SERVICE_PORTS.MYSQL,
|
|
10668
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
10669
|
-
SERVICE_PORTS.REDIS,
|
|
10670
|
-
SERVICE_PORTS.MONGODB
|
|
10671
|
-
];
|
|
10672
|
-
var NO_AUTH_CRITICAL_PORTS = [
|
|
10673
|
-
SERVICE_PORTS.REDIS,
|
|
10674
|
-
SERVICE_PORTS.MONGODB,
|
|
10675
|
-
SERVICE_PORTS.ELASTICSEARCH,
|
|
10676
|
-
SERVICE_PORTS.MEMCACHED
|
|
10677
|
-
];
|
|
10678
|
-
var WEB_SERVICE_PORTS = [
|
|
10679
|
-
SERVICE_PORTS.HTTP,
|
|
10680
|
-
SERVICE_PORTS.HTTPS,
|
|
10681
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
10682
|
-
SERVICE_PORTS.HTTPS_ALT,
|
|
10683
|
-
SERVICE_PORTS.NODE_DEFAULT,
|
|
10684
|
-
SERVICE_PORTS.FLASK_DEFAULT,
|
|
10685
|
-
SERVICE_PORTS.DJANGO_DEFAULT
|
|
10686
|
-
];
|
|
10687
|
-
var PLAINTEXT_HTTP_PORTS = [
|
|
10688
|
-
SERVICE_PORTS.HTTP,
|
|
10689
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
10690
|
-
SERVICE_PORTS.NODE_DEFAULT
|
|
10691
|
-
];
|
|
10692
|
-
var DATABASE_PORTS = [
|
|
10693
|
-
SERVICE_PORTS.MYSQL,
|
|
10694
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
10695
|
-
SERVICE_PORTS.MSSQL,
|
|
10696
|
-
SERVICE_PORTS.MONGODB,
|
|
10697
|
-
SERVICE_PORTS.REDIS
|
|
10698
|
-
];
|
|
10699
|
-
var SMB_PORTS = [
|
|
10700
|
-
SERVICE_PORTS.SMB,
|
|
10701
|
-
SERVICE_PORTS.SMB_NETBIOS
|
|
10702
|
-
];
|
|
10703
|
-
|
|
10704
10857
|
// src/shared/constants/scoring.ts
|
|
10705
10858
|
var ATTACK_SCORING = {
|
|
10706
10859
|
/** Base score for all attack prioritization */
|
|
@@ -10861,10 +11014,229 @@ function getAttacksForService(service, port) {
|
|
|
10861
11014
|
return attacks;
|
|
10862
11015
|
}
|
|
10863
11016
|
|
|
11017
|
+
// src/shared/utils/journal.ts
|
|
11018
|
+
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
|
|
11019
|
+
import { join as join10 } from "path";
|
|
11020
|
+
var MAX_JOURNAL_ENTRIES = 50;
|
|
11021
|
+
var SUMMARY_REGEN_INTERVAL = 10;
|
|
11022
|
+
var MAX_OUTPUT_FILES = 30;
|
|
11023
|
+
var TURN_PREFIX = "turn-";
|
|
11024
|
+
var SUMMARY_FILE = "summary.md";
|
|
11025
|
+
function writeJournalEntry(entry) {
|
|
11026
|
+
try {
|
|
11027
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11028
|
+
ensureDirExists(journalDir);
|
|
11029
|
+
const padded = String(entry.turn).padStart(4, "0");
|
|
11030
|
+
const filePath = join10(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11031
|
+
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11032
|
+
return filePath;
|
|
11033
|
+
} catch (err) {
|
|
11034
|
+
debugLog("general", "Failed to write journal entry", { turn: entry.turn, error: String(err) });
|
|
11035
|
+
return null;
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
function readJournalSummary() {
|
|
11039
|
+
try {
|
|
11040
|
+
const summaryPath = join10(WORKSPACE.JOURNAL, SUMMARY_FILE);
|
|
11041
|
+
if (!existsSync8(summaryPath)) return "";
|
|
11042
|
+
return readFileSync5(summaryPath, "utf-8");
|
|
11043
|
+
} catch {
|
|
11044
|
+
return "";
|
|
11045
|
+
}
|
|
11046
|
+
}
|
|
11047
|
+
function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
11048
|
+
try {
|
|
11049
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11050
|
+
if (!existsSync8(journalDir)) return [];
|
|
11051
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort().slice(-count);
|
|
11052
|
+
const entries = [];
|
|
11053
|
+
for (const file of files) {
|
|
11054
|
+
try {
|
|
11055
|
+
const raw = readFileSync5(join10(journalDir, file), "utf-8");
|
|
11056
|
+
entries.push(JSON.parse(raw));
|
|
11057
|
+
} catch {
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
11060
|
+
return entries;
|
|
11061
|
+
} catch {
|
|
11062
|
+
return [];
|
|
11063
|
+
}
|
|
11064
|
+
}
|
|
11065
|
+
function getNextTurnNumber() {
|
|
11066
|
+
try {
|
|
11067
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11068
|
+
if (!existsSync8(journalDir)) return 1;
|
|
11069
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11070
|
+
if (files.length === 0) return 1;
|
|
11071
|
+
const lastFile = files[files.length - 1];
|
|
11072
|
+
const match = lastFile.match(/turn-(\d+)\.json/);
|
|
11073
|
+
return match ? parseInt(match[1], 10) + 1 : 1;
|
|
11074
|
+
} catch {
|
|
11075
|
+
return 1;
|
|
11076
|
+
}
|
|
11077
|
+
}
|
|
11078
|
+
function shouldRegenerateSummary(currentTurn) {
|
|
11079
|
+
return currentTurn > 0 && currentTurn % SUMMARY_REGEN_INTERVAL === 0;
|
|
11080
|
+
}
|
|
11081
|
+
function regenerateJournalSummary() {
|
|
11082
|
+
try {
|
|
11083
|
+
const entries = getRecentEntries();
|
|
11084
|
+
if (entries.length === 0) return;
|
|
11085
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11086
|
+
ensureDirExists(journalDir);
|
|
11087
|
+
const summary = buildSummaryFromEntries(entries);
|
|
11088
|
+
const summaryPath = join10(journalDir, SUMMARY_FILE);
|
|
11089
|
+
writeFileSync8(summaryPath, summary, "utf-8");
|
|
11090
|
+
debugLog("general", "Journal summary regenerated", {
|
|
11091
|
+
entries: entries.length,
|
|
11092
|
+
summaryLength: summary.length
|
|
11093
|
+
});
|
|
11094
|
+
} catch (err) {
|
|
11095
|
+
debugLog("general", "Failed to regenerate journal summary", { error: String(err) });
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
function buildSummaryFromEntries(entries) {
|
|
11099
|
+
const attempts = [];
|
|
11100
|
+
const findings = [];
|
|
11101
|
+
const credentials = [];
|
|
11102
|
+
const successes = [];
|
|
11103
|
+
const failures = [];
|
|
11104
|
+
const suspicions = [];
|
|
11105
|
+
const nextSteps = [];
|
|
11106
|
+
const reflections = [];
|
|
11107
|
+
const VALUE_ORDER = { HIGH: 0, MED: 1, LOW: 2, NONE: 3 };
|
|
11108
|
+
const reversed = [...entries].reverse();
|
|
11109
|
+
for (const entry of reversed) {
|
|
11110
|
+
const value = entry.memo.attackValue || "LOW";
|
|
11111
|
+
for (const tool of entry.tools) {
|
|
11112
|
+
attempts.push({
|
|
11113
|
+
turn: entry.turn,
|
|
11114
|
+
phase: entry.phase,
|
|
11115
|
+
ok: tool.success,
|
|
11116
|
+
name: tool.name,
|
|
11117
|
+
input: tool.inputSummary,
|
|
11118
|
+
value
|
|
11119
|
+
});
|
|
11120
|
+
}
|
|
11121
|
+
for (const finding of entry.memo.keyFindings) {
|
|
11122
|
+
const line = `- [T${entry.turn}|\u26A1${value}] ${finding}`;
|
|
11123
|
+
if (!findings.includes(line)) findings.push(line);
|
|
11124
|
+
}
|
|
11125
|
+
for (const cred of entry.memo.credentials) {
|
|
11126
|
+
const line = `- [T${entry.turn}] ${cred}`;
|
|
11127
|
+
if (!credentials.includes(line)) credentials.push(line);
|
|
11128
|
+
}
|
|
11129
|
+
for (const vector of entry.memo.attackVectors) {
|
|
11130
|
+
const line = `- [T${entry.turn}] ${vector}`;
|
|
11131
|
+
if (!successes.includes(line)) successes.push(line);
|
|
11132
|
+
}
|
|
11133
|
+
for (const fail of entry.memo.failures) {
|
|
11134
|
+
const line = `- [T${entry.turn}] ${fail}`;
|
|
11135
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11136
|
+
}
|
|
11137
|
+
for (const tool of entry.tools) {
|
|
11138
|
+
if (!tool.success) {
|
|
11139
|
+
const detail = `${tool.name}(${tool.inputSummary}): ${tool.analystSummary}`;
|
|
11140
|
+
const line = `- [T${entry.turn}] ${detail}`;
|
|
11141
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11142
|
+
}
|
|
11143
|
+
}
|
|
11144
|
+
for (const s of entry.memo.suspicions || []) {
|
|
11145
|
+
const line = `- [T${entry.turn}] ${s}`;
|
|
11146
|
+
if (!suspicions.includes(line)) suspicions.push(line);
|
|
11147
|
+
}
|
|
11148
|
+
if (nextSteps.length < 5) {
|
|
11149
|
+
for (const step of entry.memo.nextSteps) {
|
|
11150
|
+
if (!nextSteps.includes(`- ${step}`)) nextSteps.push(`- ${step}`);
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
11153
|
+
if (entry.reflection) {
|
|
11154
|
+
reflections.push(`- [T${entry.turn}|\u26A1${value}] ${entry.reflection}`);
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
attempts.sort((a, b) => {
|
|
11158
|
+
const vd = (VALUE_ORDER[a.value] ?? 3) - (VALUE_ORDER[b.value] ?? 3);
|
|
11159
|
+
return vd !== 0 ? vd : b.turn - a.turn;
|
|
11160
|
+
});
|
|
11161
|
+
const attemptLines = attempts.map(
|
|
11162
|
+
(a) => `- [T${a.turn}|${a.phase}|\u26A1${a.value}] ${a.ok ? "\u2705" : "\u274C"} ${a.name}: ${a.input}`
|
|
11163
|
+
);
|
|
11164
|
+
const lastTurn = entries[entries.length - 1]?.turn || 0;
|
|
11165
|
+
const sections = [
|
|
11166
|
+
`# Session Journal Summary`,
|
|
11167
|
+
`> Turn ${lastTurn} / ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}`,
|
|
11168
|
+
""
|
|
11169
|
+
];
|
|
11170
|
+
const addSection = (title, items) => {
|
|
11171
|
+
if (items.length === 0) return;
|
|
11172
|
+
sections.push(`## ${title}`);
|
|
11173
|
+
sections.push(...items);
|
|
11174
|
+
sections.push("");
|
|
11175
|
+
};
|
|
11176
|
+
if (attemptLines.length > 0) {
|
|
11177
|
+
sections.push("## Techniques Tried (by attack value)");
|
|
11178
|
+
sections.push("> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon");
|
|
11179
|
+
sections.push(...attemptLines);
|
|
11180
|
+
sections.push("");
|
|
11181
|
+
}
|
|
11182
|
+
addSection("\u{1F9E0} Analyst Analysis (attack value rationale)", reflections);
|
|
11183
|
+
addSection("\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)", suspicions);
|
|
11184
|
+
addSection("\u{1F4CB} Key Findings", findings);
|
|
11185
|
+
addSection("\u{1F511} Credentials Obtained", credentials);
|
|
11186
|
+
addSection("\u2705 Successful Attack Vectors", successes);
|
|
11187
|
+
addSection("\u274C Failure Causes (do not repeat)", failures);
|
|
11188
|
+
addSection("\u27A1\uFE0F Next Recommendations", nextSteps);
|
|
11189
|
+
return sections.join("\n");
|
|
11190
|
+
}
|
|
11191
|
+
function rotateJournalEntries() {
|
|
11192
|
+
try {
|
|
11193
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11194
|
+
if (!existsSync8(journalDir)) return;
|
|
11195
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11196
|
+
if (files.length <= MAX_JOURNAL_ENTRIES) return;
|
|
11197
|
+
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11198
|
+
for (const file of toDelete) {
|
|
11199
|
+
try {
|
|
11200
|
+
unlinkSync5(join10(journalDir, file));
|
|
11201
|
+
} catch {
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
11204
|
+
debugLog("general", "Journal entries rotated", {
|
|
11205
|
+
deleted: toDelete.length,
|
|
11206
|
+
remaining: MAX_JOURNAL_ENTRIES
|
|
11207
|
+
});
|
|
11208
|
+
} catch {
|
|
11209
|
+
}
|
|
11210
|
+
}
|
|
11211
|
+
function rotateOutputFiles() {
|
|
11212
|
+
try {
|
|
11213
|
+
const outputDir = WORKSPACE.OUTPUTS;
|
|
11214
|
+
if (!existsSync8(outputDir)) return;
|
|
11215
|
+
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11216
|
+
name: f,
|
|
11217
|
+
path: join10(outputDir, f),
|
|
11218
|
+
mtime: statSync2(join10(outputDir, f)).mtimeMs
|
|
11219
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
11220
|
+
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11221
|
+
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
11222
|
+
for (const file of toDelete) {
|
|
11223
|
+
try {
|
|
11224
|
+
unlinkSync5(file.path);
|
|
11225
|
+
} catch {
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11228
|
+
debugLog("general", "Output files rotated", {
|
|
11229
|
+
deleted: toDelete.length,
|
|
11230
|
+
remaining: MAX_OUTPUT_FILES
|
|
11231
|
+
});
|
|
11232
|
+
} catch {
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
|
|
10864
11236
|
// src/agents/prompt-builder.ts
|
|
10865
11237
|
var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
|
|
10866
|
-
var PROMPTS_DIR =
|
|
10867
|
-
var TECHNIQUES_DIR =
|
|
11238
|
+
var PROMPTS_DIR = join11(__dirname4, "prompts");
|
|
11239
|
+
var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
10868
11240
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
10869
11241
|
var PHASE_PROMPT_MAP = {
|
|
10870
11242
|
// Direct mappings — phase has its own prompt file
|
|
@@ -10938,6 +11310,7 @@ var PromptBuilder = class {
|
|
|
10938
11310
|
* 13. Learned techniques (#7: dynamic technique library)
|
|
10939
11311
|
* 14. Persistent memory (#12: cross-session knowledge)
|
|
10940
11312
|
* ★ 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
|
|
11313
|
+
* ★ 15b. SESSION JOURNAL — compressed history of past turns (§13 memo system)
|
|
10941
11314
|
* 16. User context
|
|
10942
11315
|
*/
|
|
10943
11316
|
async build(userInput, phase) {
|
|
@@ -10963,8 +11336,10 @@ var PromptBuilder = class {
|
|
|
10963
11336
|
// #12
|
|
10964
11337
|
this.getDynamicTechniquesFragment(),
|
|
10965
11338
|
// #7
|
|
10966
|
-
this.getPersistentMemoryFragment()
|
|
11339
|
+
this.getPersistentMemoryFragment(),
|
|
10967
11340
|
// #12
|
|
11341
|
+
this.getJournalFragment()
|
|
11342
|
+
// §13 session journal
|
|
10968
11343
|
];
|
|
10969
11344
|
const strategistDirective = await this.getStrategistFragment();
|
|
10970
11345
|
if (strategistDirective) {
|
|
@@ -10988,8 +11363,8 @@ ${content}
|
|
|
10988
11363
|
* Load a prompt file from src/agents/prompts/
|
|
10989
11364
|
*/
|
|
10990
11365
|
loadPromptFile(filename) {
|
|
10991
|
-
const path2 =
|
|
10992
|
-
return
|
|
11366
|
+
const path2 = join11(PROMPTS_DIR, filename);
|
|
11367
|
+
return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
10993
11368
|
}
|
|
10994
11369
|
/**
|
|
10995
11370
|
* Load phase-specific prompt.
|
|
@@ -11032,18 +11407,18 @@ ${content}
|
|
|
11032
11407
|
* as general reference — NO code change needed to add new techniques.
|
|
11033
11408
|
*
|
|
11034
11409
|
* The map is an optimization (priority ordering), not a gate.
|
|
11035
|
-
* "
|
|
11410
|
+
* "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
|
|
11036
11411
|
*/
|
|
11037
11412
|
loadPhaseRelevantTechniques(phase) {
|
|
11038
|
-
if (!
|
|
11413
|
+
if (!existsSync9(TECHNIQUES_DIR)) return "";
|
|
11039
11414
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
11040
11415
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11041
11416
|
const fragments = [];
|
|
11042
11417
|
for (const technique of priorityTechniques) {
|
|
11043
|
-
const filePath =
|
|
11418
|
+
const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
|
|
11044
11419
|
try {
|
|
11045
|
-
if (!
|
|
11046
|
-
const content =
|
|
11420
|
+
if (!existsSync9(filePath)) continue;
|
|
11421
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11047
11422
|
if (content) {
|
|
11048
11423
|
fragments.push(`<technique-reference category="${technique}">
|
|
11049
11424
|
${content}
|
|
@@ -11054,10 +11429,10 @@ ${content}
|
|
|
11054
11429
|
}
|
|
11055
11430
|
}
|
|
11056
11431
|
try {
|
|
11057
|
-
const allFiles =
|
|
11432
|
+
const allFiles = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11058
11433
|
for (const file of allFiles) {
|
|
11059
|
-
const filePath =
|
|
11060
|
-
const content =
|
|
11434
|
+
const filePath = join11(TECHNIQUES_DIR, file);
|
|
11435
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11061
11436
|
if (content) {
|
|
11062
11437
|
const category = file.replace(".md", "");
|
|
11063
11438
|
fragments.push(`<technique-reference category="${category}">
|
|
@@ -11160,6 +11535,23 @@ ${lines.join("\n")}
|
|
|
11160
11535
|
}
|
|
11161
11536
|
return this.state.persistentMemory.toPrompt(services);
|
|
11162
11537
|
}
|
|
11538
|
+
// --- §13: Session Journal Summary ---
|
|
11539
|
+
/**
|
|
11540
|
+
* Load journal summary from .pentesting/journal/summary.md
|
|
11541
|
+
* Provides compressed history of past turns — what worked, what failed,
|
|
11542
|
+
* what was discovered. Main LLM uses this for continuity across many turns.
|
|
11543
|
+
*/
|
|
11544
|
+
getJournalFragment() {
|
|
11545
|
+
try {
|
|
11546
|
+
const summary = readJournalSummary();
|
|
11547
|
+
if (!summary) return "";
|
|
11548
|
+
return `<session-journal>
|
|
11549
|
+
${summary}
|
|
11550
|
+
</session-journal>`;
|
|
11551
|
+
} catch {
|
|
11552
|
+
return "";
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11163
11555
|
// --- D-CIPHER: Strategist Meta-Prompting ---
|
|
11164
11556
|
/**
|
|
11165
11557
|
* Generate strategic directive via Strategist LLM.
|
|
@@ -11172,29 +11564,11 @@ ${lines.join("\n")}
|
|
|
11172
11564
|
};
|
|
11173
11565
|
|
|
11174
11566
|
// src/agents/strategist.ts
|
|
11175
|
-
import { readFileSync as
|
|
11176
|
-
import { join as
|
|
11567
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
11568
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
11177
11569
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11178
|
-
|
|
11179
|
-
// src/shared/constants/strategist.ts
|
|
11180
|
-
var STRATEGIST_LIMITS = {
|
|
11181
|
-
/** Maximum characters of state context sent to Strategist LLM.
|
|
11182
|
-
* WHY: Keeps Strategist input focused and affordable (~3-5K tokens).
|
|
11183
|
-
* Full state can be 20K+; Strategist needs summary, not everything. */
|
|
11184
|
-
MAX_INPUT_CHARS: 15e3,
|
|
11185
|
-
/** Maximum characters for the Strategist's response.
|
|
11186
|
-
* WHY: Directives should be terse and actionable (~800-1500 tokens).
|
|
11187
|
-
* Enhanced format includes SITUATION, priorities, EXHAUSTED, and SEARCH ORDERS. */
|
|
11188
|
-
MAX_OUTPUT_CHARS: 5e3,
|
|
11189
|
-
/** Maximum lines in the directive output.
|
|
11190
|
-
* WHY: Forces concise, prioritized directives while allowing
|
|
11191
|
-
* structured format (priorities + exhausted + search orders). */
|
|
11192
|
-
MAX_DIRECTIVE_LINES: 60
|
|
11193
|
-
};
|
|
11194
|
-
|
|
11195
|
-
// src/agents/strategist.ts
|
|
11196
11570
|
var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
|
|
11197
|
-
var STRATEGIST_PROMPT_PATH =
|
|
11571
|
+
var STRATEGIST_PROMPT_PATH = join12(__dirname5, "prompts", "strategist-system.md");
|
|
11198
11572
|
var Strategist = class {
|
|
11199
11573
|
llm;
|
|
11200
11574
|
state;
|
|
@@ -11245,24 +11619,33 @@ var Strategist = class {
|
|
|
11245
11619
|
const sections = [];
|
|
11246
11620
|
sections.push("## Engagement State");
|
|
11247
11621
|
sections.push(this.state.toPrompt());
|
|
11248
|
-
const timeline = this.state.episodicMemory.toPrompt();
|
|
11249
|
-
if (timeline) {
|
|
11250
|
-
sections.push("");
|
|
11251
|
-
sections.push("## Recent Actions");
|
|
11252
|
-
sections.push(timeline);
|
|
11253
|
-
}
|
|
11254
11622
|
const failures = this.state.workingMemory.toPrompt();
|
|
11255
11623
|
if (failures) {
|
|
11256
11624
|
sections.push("");
|
|
11257
11625
|
sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
|
|
11258
11626
|
sections.push(failures);
|
|
11259
11627
|
}
|
|
11628
|
+
try {
|
|
11629
|
+
const journalSummary = readJournalSummary();
|
|
11630
|
+
if (journalSummary) {
|
|
11631
|
+
sections.push("");
|
|
11632
|
+
sections.push("## Session Journal (past turns summary)");
|
|
11633
|
+
sections.push(journalSummary);
|
|
11634
|
+
}
|
|
11635
|
+
} catch {
|
|
11636
|
+
}
|
|
11260
11637
|
const graph = this.state.attackGraph.toPrompt();
|
|
11261
11638
|
if (graph) {
|
|
11262
11639
|
sections.push("");
|
|
11263
11640
|
sections.push("## Attack Graph");
|
|
11264
11641
|
sections.push(graph);
|
|
11265
11642
|
}
|
|
11643
|
+
const timeline = this.state.episodicMemory.toPrompt();
|
|
11644
|
+
if (timeline) {
|
|
11645
|
+
sections.push("");
|
|
11646
|
+
sections.push("## Recent Actions");
|
|
11647
|
+
sections.push(timeline);
|
|
11648
|
+
}
|
|
11266
11649
|
const techniques = this.state.dynamicTechniques.toPrompt();
|
|
11267
11650
|
if (techniques) {
|
|
11268
11651
|
sections.push("");
|
|
@@ -11278,16 +11661,12 @@ var Strategist = class {
|
|
|
11278
11661
|
sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
|
|
11279
11662
|
sections.push(analysis.strategySuggestion);
|
|
11280
11663
|
}
|
|
11281
|
-
|
|
11282
|
-
if (input.length > STRATEGIST_LIMITS.MAX_INPUT_CHARS) {
|
|
11283
|
-
input = input.slice(0, STRATEGIST_LIMITS.MAX_INPUT_CHARS) + "\n\n... [state truncated for Strategist context]";
|
|
11284
|
-
}
|
|
11285
|
-
return input;
|
|
11664
|
+
return sections.join("\n");
|
|
11286
11665
|
}
|
|
11287
11666
|
// ─── LLM Call ───────────────────────────────────────────────
|
|
11288
11667
|
async callLLM(input) {
|
|
11289
11668
|
const messages = [{
|
|
11290
|
-
role:
|
|
11669
|
+
role: LLM_ROLES.USER,
|
|
11291
11670
|
content: `Analyze this penetration test situation and write a tactical directive for the attack agent.
|
|
11292
11671
|
|
|
11293
11672
|
${input}`
|
|
@@ -11299,9 +11678,6 @@ ${input}`
|
|
|
11299
11678
|
this.systemPrompt
|
|
11300
11679
|
);
|
|
11301
11680
|
let content = response.content || "";
|
|
11302
|
-
if (content.length > STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) {
|
|
11303
|
-
content = content.slice(0, STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) + "\n... [directive truncated]";
|
|
11304
|
-
}
|
|
11305
11681
|
const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
|
|
11306
11682
|
this.totalTokenCost += cost;
|
|
11307
11683
|
return {
|
|
@@ -11316,7 +11692,7 @@ ${input}`
|
|
|
11316
11692
|
*/
|
|
11317
11693
|
formatForPrompt(directive, isStale = false) {
|
|
11318
11694
|
if (!directive.content) return "";
|
|
11319
|
-
const age = Math.floor((Date.now() - directive.generatedAt) /
|
|
11695
|
+
const age = Math.floor((Date.now() - directive.generatedAt) / MS_PER_MINUTE);
|
|
11320
11696
|
const staleWarning = isStale ? `
|
|
11321
11697
|
NOTE: This directive is from ${age}min ago (Strategist call failed this turn). Verify assumptions are still valid.` : "";
|
|
11322
11698
|
return [
|
|
@@ -11331,8 +11707,8 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
|
|
|
11331
11707
|
// ─── System Prompt Loading ──────────────────────────────────
|
|
11332
11708
|
loadSystemPrompt() {
|
|
11333
11709
|
try {
|
|
11334
|
-
if (
|
|
11335
|
-
return
|
|
11710
|
+
if (existsSync10(STRATEGIST_PROMPT_PATH)) {
|
|
11711
|
+
return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11336
11712
|
}
|
|
11337
11713
|
} catch {
|
|
11338
11714
|
}
|
|
@@ -11374,6 +11750,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
11374
11750
|
approvalGate;
|
|
11375
11751
|
scopeGuard;
|
|
11376
11752
|
userInput = "";
|
|
11753
|
+
/** Monotonic turn counter for journal entries */
|
|
11754
|
+
turnCounter = 0;
|
|
11377
11755
|
constructor(state, events, toolRegistry, approvalGate, scopeGuard) {
|
|
11378
11756
|
super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
|
|
11379
11757
|
this.approvalGate = approvalGate;
|
|
@@ -11411,8 +11789,34 @@ var MainAgent = class extends CoreAgent {
|
|
|
11411
11789
|
* The Strategist LLM generates a fresh tactical directive every turn.
|
|
11412
11790
|
*/
|
|
11413
11791
|
async step(iteration, messages, _unusedPrompt, progress) {
|
|
11792
|
+
if (this.turnCounter === 0) {
|
|
11793
|
+
this.turnCounter = getNextTurnNumber();
|
|
11794
|
+
}
|
|
11795
|
+
this.turnToolJournal = [];
|
|
11796
|
+
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
11797
|
+
this.turnReflections = [];
|
|
11414
11798
|
const dynamicPrompt = await this.getCurrentPrompt();
|
|
11415
11799
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
11800
|
+
if (this.turnToolJournal.length > 0) {
|
|
11801
|
+
try {
|
|
11802
|
+
const entry = {
|
|
11803
|
+
turn: this.turnCounter,
|
|
11804
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11805
|
+
phase: this.state.getPhase(),
|
|
11806
|
+
tools: this.turnToolJournal,
|
|
11807
|
+
memo: this.turnMemo,
|
|
11808
|
+
reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
|
|
11809
|
+
};
|
|
11810
|
+
writeJournalEntry(entry);
|
|
11811
|
+
if (shouldRegenerateSummary(this.turnCounter)) {
|
|
11812
|
+
regenerateJournalSummary();
|
|
11813
|
+
}
|
|
11814
|
+
rotateJournalEntries();
|
|
11815
|
+
rotateOutputFiles();
|
|
11816
|
+
} catch {
|
|
11817
|
+
}
|
|
11818
|
+
this.turnCounter++;
|
|
11819
|
+
}
|
|
11416
11820
|
this.emitStateChange();
|
|
11417
11821
|
return result2;
|
|
11418
11822
|
}
|
|
@@ -11465,8 +11869,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
11465
11869
|
});
|
|
11466
11870
|
}
|
|
11467
11871
|
// ─── Public API Surface ─────────────────────────────────────
|
|
11468
|
-
setAutoApprove(
|
|
11469
|
-
this.approvalGate.setAutoApprove(
|
|
11872
|
+
setAutoApprove(shouldEnable) {
|
|
11873
|
+
this.approvalGate.setAutoApprove(shouldEnable);
|
|
11470
11874
|
}
|
|
11471
11875
|
getState() {
|
|
11472
11876
|
return this.state;
|
|
@@ -11978,9 +12382,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11978
12382
|
const onReasoningDelta = (e) => {
|
|
11979
12383
|
reasoningBufferRef.current += e.data.content;
|
|
11980
12384
|
const chars = reasoningBufferRef.current.length;
|
|
11981
|
-
const estTokens = Math.round(chars /
|
|
12385
|
+
const estTokens = Math.round(chars / LLM_LIMITS.charsPerTokenEstimate);
|
|
12386
|
+
const firstLine = reasoningBufferRef.current.split("\n")[0]?.slice(0, TUI_DISPLAY_LIMITS.reasoningPreviewChars) || "";
|
|
11982
12387
|
setCurrentStatus(`Reasoning (~${estTokens} tokens)
|
|
11983
|
-
${
|
|
12388
|
+
${firstLine}`);
|
|
11984
12389
|
};
|
|
11985
12390
|
const onReasoningEnd = () => {
|
|
11986
12391
|
const text = reasoningBufferRef.current.trim();
|
|
@@ -12439,7 +12844,7 @@ var MessageList = memo(({ messages }) => {
|
|
|
12439
12844
|
if (msg.type === "thinking") {
|
|
12440
12845
|
const lines = msg.content.split("\n");
|
|
12441
12846
|
const charCount = msg.content.length;
|
|
12442
|
-
const estTokens = Math.round(charCount /
|
|
12847
|
+
const estTokens = Math.round(charCount / LLM_LIMITS.charsPerTokenEstimate);
|
|
12443
12848
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 0, marginBottom: 1, children: [
|
|
12444
12849
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
12445
12850
|
/* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, bold: true, children: "Reasoning" }),
|
|
@@ -12701,9 +13106,9 @@ import { memo as memo5 } from "react";
|
|
|
12701
13106
|
import { Box as Box5, Text as Text6 } from "ink";
|
|
12702
13107
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
12703
13108
|
var formatElapsed = (totalSeconds) => {
|
|
12704
|
-
const hours = Math.floor(totalSeconds /
|
|
12705
|
-
const minutes = Math.floor(totalSeconds %
|
|
12706
|
-
const seconds = Math.floor(totalSeconds %
|
|
13109
|
+
const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
|
|
13110
|
+
const minutes = Math.floor(totalSeconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
|
|
13111
|
+
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE);
|
|
12707
13112
|
const pad = (n) => String(n).padStart(2, "0");
|
|
12708
13113
|
if (hours > 0) {
|
|
12709
13114
|
return `${hours}:${pad(minutes)}:${pad(seconds)}`;
|
|
@@ -12857,11 +13262,11 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12857
13262
|
}
|
|
12858
13263
|
if (f.evidence.length > 0) {
|
|
12859
13264
|
findingLines.push(` Evidence:`);
|
|
12860
|
-
f.evidence.slice(0,
|
|
12861
|
-
const preview = e.length >
|
|
13265
|
+
f.evidence.slice(0, DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW).forEach((e) => {
|
|
13266
|
+
const preview = e.length > DISPLAY_LIMITS.EVIDENCE_PREVIEW_LENGTH ? e.slice(0, DISPLAY_LIMITS.EVIDENCE_PREVIEW_LENGTH) + "..." : e;
|
|
12862
13267
|
findingLines.push(` \u25B8 ${preview}`);
|
|
12863
13268
|
});
|
|
12864
|
-
if (f.evidence.length >
|
|
13269
|
+
if (f.evidence.length > DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW) findingLines.push(` ... +${f.evidence.length - DISPLAY_LIMITS.EVIDENCE_ITEMS_PREVIEW} more`);
|
|
12865
13270
|
}
|
|
12866
13271
|
findingLines.push("");
|
|
12867
13272
|
});
|