pentesting 0.40.6 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +861 -243
- package/dist/prompts/recon.md +130 -0
- package/dist/prompts/strategy.md +15 -1
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -13,8 +13,8 @@ import chalk from "chalk";
|
|
|
13
13
|
import gradient from "gradient-string";
|
|
14
14
|
|
|
15
15
|
// src/platform/tui/app.tsx
|
|
16
|
-
import { useState as useState4, useCallback as
|
|
17
|
-
import { Box as Box6, useInput, useApp } from "ink";
|
|
16
|
+
import { useState as useState4, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
17
|
+
import { Box as Box6, useInput as useInput2, useApp } from "ink";
|
|
18
18
|
|
|
19
19
|
// src/platform/tui/hooks/useAgent.ts
|
|
20
20
|
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
@@ -199,6 +199,14 @@ var EXIT_CODES = {
|
|
|
199
199
|
/** Process killed by SIGKILL */
|
|
200
200
|
SIGKILL: 137
|
|
201
201
|
};
|
|
202
|
+
var PROCESS_ACTIONS = {
|
|
203
|
+
LIST: "list",
|
|
204
|
+
STATUS: "status",
|
|
205
|
+
INTERACT: "interact",
|
|
206
|
+
PROMOTE: "promote",
|
|
207
|
+
STOP: "stop",
|
|
208
|
+
STOP_ALL: "stop_all"
|
|
209
|
+
};
|
|
202
210
|
var PROCESS_ROLES = {
|
|
203
211
|
LISTENER: "listener",
|
|
204
212
|
ACTIVE_SHELL: "active_shell",
|
|
@@ -306,7 +314,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
306
314
|
|
|
307
315
|
// src/shared/constants/agent.ts
|
|
308
316
|
var APP_NAME = "Pentest AI";
|
|
309
|
-
var APP_VERSION = "0.
|
|
317
|
+
var APP_VERSION = "0.41.0";
|
|
310
318
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
311
319
|
var LLM_ROLES = {
|
|
312
320
|
SYSTEM: "system",
|
|
@@ -565,6 +573,11 @@ var UI_COMMANDS = {
|
|
|
565
573
|
LOGS: "logs",
|
|
566
574
|
LOGS_SHORT: "l",
|
|
567
575
|
CTF: "ctf",
|
|
576
|
+
AUTO: "auto",
|
|
577
|
+
GRAPH: "graph",
|
|
578
|
+
GRAPH_SHORT: "g",
|
|
579
|
+
PATHS: "paths",
|
|
580
|
+
PATHS_SHORT: "p",
|
|
568
581
|
EXIT: "exit",
|
|
569
582
|
QUIT: "quit",
|
|
570
583
|
EXIT_SHORT: "q"
|
|
@@ -958,6 +971,18 @@ function debugLog(category, message, data) {
|
|
|
958
971
|
}
|
|
959
972
|
|
|
960
973
|
// src/shared/utils/command-validator.ts
|
|
974
|
+
var SHELL_OPERATORS = {
|
|
975
|
+
AND: "&&",
|
|
976
|
+
OR: "||",
|
|
977
|
+
SEQUENCE: ";",
|
|
978
|
+
PIPE: "|",
|
|
979
|
+
BACKGROUND: "&"
|
|
980
|
+
};
|
|
981
|
+
var SHELL_CHARS = {
|
|
982
|
+
SINGLE_QUOTE: "'",
|
|
983
|
+
DOUBLE_QUOTE: '"',
|
|
984
|
+
BACKTICK: "`"
|
|
985
|
+
};
|
|
961
986
|
function validateCommand(command) {
|
|
962
987
|
if (!command || typeof command !== "string") {
|
|
963
988
|
return { safe: false, error: "Empty or invalid command" };
|
|
@@ -997,13 +1022,13 @@ function splitChainedCommands(command) {
|
|
|
997
1022
|
let i = 0;
|
|
998
1023
|
while (i < command.length) {
|
|
999
1024
|
const ch = command[i];
|
|
1000
|
-
if (ch ===
|
|
1025
|
+
if (ch === SHELL_CHARS.SINGLE_QUOTE && !inDouble) {
|
|
1001
1026
|
inSingle = !inSingle;
|
|
1002
1027
|
current += ch;
|
|
1003
1028
|
i++;
|
|
1004
1029
|
continue;
|
|
1005
1030
|
}
|
|
1006
|
-
if (ch ===
|
|
1031
|
+
if (ch === SHELL_CHARS.DOUBLE_QUOTE && !inSingle) {
|
|
1007
1032
|
inDouble = !inDouble;
|
|
1008
1033
|
current += ch;
|
|
1009
1034
|
i++;
|
|
@@ -1016,7 +1041,7 @@ function splitChainedCommands(command) {
|
|
|
1016
1041
|
i += 2;
|
|
1017
1042
|
continue;
|
|
1018
1043
|
}
|
|
1019
|
-
if (ch ===
|
|
1044
|
+
if (ch === SHELL_OPERATORS.SEQUENCE) {
|
|
1020
1045
|
if (current.trim()) parts.push(current.trim());
|
|
1021
1046
|
current = "";
|
|
1022
1047
|
i++;
|
|
@@ -1342,6 +1367,12 @@ var globalInputHandler = null;
|
|
|
1342
1367
|
function setInputHandler(handler) {
|
|
1343
1368
|
globalInputHandler = handler;
|
|
1344
1369
|
}
|
|
1370
|
+
function clearInputHandler() {
|
|
1371
|
+
globalInputHandler = null;
|
|
1372
|
+
}
|
|
1373
|
+
function clearCommandEventEmitter() {
|
|
1374
|
+
globalEventEmitter = null;
|
|
1375
|
+
}
|
|
1345
1376
|
async function runCommand(command, args = [], options = {}) {
|
|
1346
1377
|
const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
1347
1378
|
const validation = validateCommand(fullCommand);
|
|
@@ -1935,7 +1966,10 @@ async function cleanupAllProcesses() {
|
|
|
1935
1966
|
if (cleanupDone) return;
|
|
1936
1967
|
cleanupDone = true;
|
|
1937
1968
|
const ids = Array.from(backgroundProcesses.keys());
|
|
1938
|
-
if (ids.length === 0)
|
|
1969
|
+
if (ids.length === 0) {
|
|
1970
|
+
cleanupDone = false;
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1939
1973
|
for (const [_id, proc] of backgroundProcesses) {
|
|
1940
1974
|
if (!proc.hasExited) {
|
|
1941
1975
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
@@ -1997,6 +2031,7 @@ async function cleanupAllProcesses() {
|
|
|
1997
2031
|
} catch {
|
|
1998
2032
|
}
|
|
1999
2033
|
}
|
|
2034
|
+
cleanupDone = false;
|
|
2000
2035
|
}
|
|
2001
2036
|
function getResourceSummary() {
|
|
2002
2037
|
const procs = listBackgroundProcesses();
|
|
@@ -2128,7 +2163,8 @@ var StateSerializer = class {
|
|
|
2128
2163
|
if (important.length > 0) {
|
|
2129
2164
|
lines.push(` Important Findings:`);
|
|
2130
2165
|
for (const f of important.slice(0, DISPLAY_LIMITS.FINDING_PREVIEW)) {
|
|
2131
|
-
|
|
2166
|
+
const tactic = f.attackPattern ? ` [ATT&CK:${f.attackPattern}]` : "";
|
|
2167
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.title} (${f.category || "general"})${tactic}`);
|
|
2132
2168
|
}
|
|
2133
2169
|
}
|
|
2134
2170
|
}
|
|
@@ -2174,11 +2210,54 @@ var GRAPH_LIMITS = {
|
|
|
2174
2210
|
/** Maximum chains to show in prompt */
|
|
2175
2211
|
PROMPT_CHAINS: 5,
|
|
2176
2212
|
/** Maximum unexploited vulns to show in prompt */
|
|
2177
|
-
PROMPT_VULNS: 5
|
|
2213
|
+
PROMPT_VULNS: 5,
|
|
2214
|
+
/** Maximum failed paths to show in prompt */
|
|
2215
|
+
PROMPT_FAILED: 5,
|
|
2216
|
+
/** Maximum DFS depth for chain discovery */
|
|
2217
|
+
MAX_CHAIN_DEPTH: 6,
|
|
2218
|
+
/** Maximum nodes to display in ASCII graph */
|
|
2219
|
+
ASCII_MAX_NODES: 30
|
|
2220
|
+
};
|
|
2221
|
+
var NODE_TYPE = {
|
|
2222
|
+
HOST: "host",
|
|
2223
|
+
SERVICE: "service",
|
|
2224
|
+
CREDENTIAL: "credential",
|
|
2225
|
+
VULNERABILITY: "vulnerability",
|
|
2226
|
+
ACCESS: "access",
|
|
2227
|
+
LOOT: "loot",
|
|
2228
|
+
OSINT: "osint"
|
|
2229
|
+
};
|
|
2230
|
+
var NODE_STATUS = {
|
|
2231
|
+
DISCOVERED: "discovered",
|
|
2232
|
+
ATTEMPTED: "attempted",
|
|
2233
|
+
SUCCEEDED: "succeeded",
|
|
2234
|
+
FAILED: "failed"
|
|
2235
|
+
};
|
|
2236
|
+
var EDGE_STATUS = {
|
|
2237
|
+
UNTESTED: "untested",
|
|
2238
|
+
TESTING: "testing",
|
|
2239
|
+
SUCCEEDED: "succeeded",
|
|
2240
|
+
FAILED: "failed"
|
|
2241
|
+
};
|
|
2242
|
+
var SEVERITY = {
|
|
2243
|
+
CRITICAL: "critical",
|
|
2244
|
+
HIGH: "high",
|
|
2245
|
+
MEDIUM: "medium",
|
|
2246
|
+
LOW: "low",
|
|
2247
|
+
INFO: "info"
|
|
2178
2248
|
};
|
|
2249
|
+
var IMPACT_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
2179
2250
|
var AttackGraph = class {
|
|
2180
2251
|
nodes = /* @__PURE__ */ new Map();
|
|
2181
2252
|
edges = [];
|
|
2253
|
+
failedPaths = [];
|
|
2254
|
+
// ─── Core Graph Operations ──────────────────────────────────
|
|
2255
|
+
/** Reset the graph to an empty state. */
|
|
2256
|
+
reset() {
|
|
2257
|
+
this.nodes.clear();
|
|
2258
|
+
this.edges = [];
|
|
2259
|
+
this.failedPaths = [];
|
|
2260
|
+
}
|
|
2182
2261
|
/**
|
|
2183
2262
|
* Add a node to the attack graph.
|
|
2184
2263
|
*/
|
|
@@ -2190,7 +2269,7 @@ var AttackGraph = class {
|
|
|
2190
2269
|
type,
|
|
2191
2270
|
label,
|
|
2192
2271
|
data,
|
|
2193
|
-
|
|
2272
|
+
status: NODE_STATUS.DISCOVERED,
|
|
2194
2273
|
discoveredAt: Date.now()
|
|
2195
2274
|
});
|
|
2196
2275
|
}
|
|
@@ -2202,34 +2281,106 @@ var AttackGraph = class {
|
|
|
2202
2281
|
addEdge(fromId, toId, relation, confidence = 0.5) {
|
|
2203
2282
|
const exists = this.edges.some((e) => e.from === fromId && e.to === toId && e.relation === relation);
|
|
2204
2283
|
if (!exists) {
|
|
2205
|
-
this.edges.push({ from: fromId, to: toId, relation, confidence });
|
|
2284
|
+
this.edges.push({ from: fromId, to: toId, relation, confidence, status: EDGE_STATUS.UNTESTED });
|
|
2206
2285
|
}
|
|
2207
2286
|
}
|
|
2208
2287
|
/**
|
|
2209
|
-
*
|
|
2288
|
+
* Get node by ID.
|
|
2210
2289
|
*/
|
|
2211
|
-
|
|
2290
|
+
getNode(nodeId) {
|
|
2291
|
+
return this.nodes.get(nodeId);
|
|
2292
|
+
}
|
|
2293
|
+
// ─── Status Management ──────────────────────────────────────
|
|
2294
|
+
/**
|
|
2295
|
+
* Mark a node as being attempted (in-progress attack).
|
|
2296
|
+
*/
|
|
2297
|
+
markAttempted(nodeId) {
|
|
2212
2298
|
const node = this.nodes.get(nodeId);
|
|
2213
|
-
if (node
|
|
2299
|
+
if (node && node.status === NODE_STATUS.DISCOVERED) {
|
|
2300
|
+
node.status = NODE_STATUS.ATTEMPTED;
|
|
2301
|
+
node.attemptedAt = Date.now();
|
|
2302
|
+
}
|
|
2214
2303
|
}
|
|
2215
2304
|
/**
|
|
2216
|
-
*
|
|
2305
|
+
* Mark a node as successfully exploited/achieved.
|
|
2217
2306
|
*/
|
|
2218
|
-
|
|
2219
|
-
|
|
2307
|
+
markSucceeded(nodeId) {
|
|
2308
|
+
const node = this.nodes.get(nodeId);
|
|
2309
|
+
if (node) {
|
|
2310
|
+
node.status = NODE_STATUS.SUCCEEDED;
|
|
2311
|
+
node.resolvedAt = Date.now();
|
|
2312
|
+
}
|
|
2313
|
+
for (const edge of this.edges) {
|
|
2314
|
+
if (edge.to === nodeId && edge.status === EDGE_STATUS.TESTING) {
|
|
2315
|
+
edge.status = EDGE_STATUS.SUCCEEDED;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Mark a node as failed (attack didn't work).
|
|
2321
|
+
*/
|
|
2322
|
+
markFailed(nodeId, reason) {
|
|
2323
|
+
const node = this.nodes.get(nodeId);
|
|
2324
|
+
if (node) {
|
|
2325
|
+
node.status = NODE_STATUS.FAILED;
|
|
2326
|
+
node.resolvedAt = Date.now();
|
|
2327
|
+
node.failReason = reason;
|
|
2328
|
+
this.failedPaths.push(`${node.label}${reason ? ` (${reason})` : ""}`);
|
|
2329
|
+
}
|
|
2330
|
+
for (const edge of this.edges) {
|
|
2331
|
+
if (edge.to === nodeId && (edge.status === EDGE_STATUS.TESTING || edge.status === EDGE_STATUS.UNTESTED)) {
|
|
2332
|
+
edge.status = EDGE_STATUS.FAILED;
|
|
2333
|
+
edge.failReason = reason;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Mark an edge as being tested.
|
|
2339
|
+
*/
|
|
2340
|
+
markEdgeTesting(fromId, toId) {
|
|
2341
|
+
for (const edge of this.edges) {
|
|
2342
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
2343
|
+
edge.status = EDGE_STATUS.TESTING;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Mark an edge as failed.
|
|
2349
|
+
*/
|
|
2350
|
+
markEdgeFailed(fromId, toId, reason) {
|
|
2351
|
+
for (const edge of this.edges) {
|
|
2352
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
2353
|
+
edge.status = EDGE_STATUS.FAILED;
|
|
2354
|
+
edge.failReason = reason;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
|
|
2358
|
+
}
|
|
2359
|
+
// Backward-compatible alias
|
|
2360
|
+
markExploited(nodeId) {
|
|
2361
|
+
this.markSucceeded(nodeId);
|
|
2362
|
+
}
|
|
2363
|
+
// ─── Domain-Specific Registration ───────────────────────────
|
|
2364
|
+
/**
|
|
2365
|
+
* Record a host discovery.
|
|
2366
|
+
*/
|
|
2367
|
+
addHost(ip, hostname) {
|
|
2368
|
+
return this.addNode(NODE_TYPE.HOST, ip, { ip, hostname });
|
|
2220
2369
|
}
|
|
2221
2370
|
/**
|
|
2222
2371
|
* Record a service discovery and auto-create edges.
|
|
2223
2372
|
*/
|
|
2224
2373
|
addService(host, port, service, version) {
|
|
2225
|
-
const
|
|
2374
|
+
const hostId = this.addHost(host);
|
|
2375
|
+
const serviceId = this.addNode(NODE_TYPE.SERVICE, `${host}:${port}`, {
|
|
2226
2376
|
host,
|
|
2227
2377
|
port,
|
|
2228
2378
|
service,
|
|
2229
2379
|
version
|
|
2230
2380
|
});
|
|
2381
|
+
this.addEdge(hostId, serviceId, "has_service", 0.95);
|
|
2231
2382
|
if (version) {
|
|
2232
|
-
const vulnId = this.addNode(
|
|
2383
|
+
const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, `CVE search: ${service} ${version}`, {
|
|
2233
2384
|
service,
|
|
2234
2385
|
version,
|
|
2235
2386
|
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
@@ -2250,7 +2401,7 @@ var AttackGraph = class {
|
|
|
2250
2401
|
for (const [id, node] of this.nodes) {
|
|
2251
2402
|
if (node.type === "service") {
|
|
2252
2403
|
const svc = String(node.data.service || "");
|
|
2253
|
-
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm"].some((s) => svc.includes(s))) {
|
|
2404
|
+
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
|
|
2254
2405
|
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
2255
2406
|
}
|
|
2256
2407
|
}
|
|
@@ -2289,7 +2440,7 @@ var AttackGraph = class {
|
|
|
2289
2440
|
level,
|
|
2290
2441
|
via
|
|
2291
2442
|
});
|
|
2292
|
-
this.
|
|
2443
|
+
this.markSucceeded(accessId);
|
|
2293
2444
|
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
2294
2445
|
const lootId = this.addNode("loot", `flags on ${host}`, {
|
|
2295
2446
|
host,
|
|
@@ -2300,104 +2451,302 @@ var AttackGraph = class {
|
|
|
2300
2451
|
return accessId;
|
|
2301
2452
|
}
|
|
2302
2453
|
/**
|
|
2303
|
-
*
|
|
2304
|
-
* Returns chains sorted by probability (highest first).
|
|
2454
|
+
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
2305
2455
|
*/
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
});
|
|
2456
|
+
addOSINT(category, detail, data = {}) {
|
|
2457
|
+
const osintId = this.addNode("osint", `${category}: ${detail}`, {
|
|
2458
|
+
category,
|
|
2459
|
+
detail,
|
|
2460
|
+
...data
|
|
2461
|
+
});
|
|
2462
|
+
for (const [id, node] of this.nodes) {
|
|
2463
|
+
if (node.type === "host" || node.type === "service") {
|
|
2464
|
+
const hostIp = String(node.data.ip || node.data.host || "");
|
|
2465
|
+
const hostname = String(node.data.hostname || "");
|
|
2466
|
+
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
2467
|
+
this.addEdge(osintId, id, "relates_to", 0.5);
|
|
2319
2468
|
}
|
|
2320
2469
|
}
|
|
2321
2470
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2471
|
+
return osintId;
|
|
2472
|
+
}
|
|
2473
|
+
// ─── Chain Discovery (DFS) ──────────────────────────────────
|
|
2474
|
+
/**
|
|
2475
|
+
* Find all viable attack paths using DFS.
|
|
2476
|
+
* Only follows edges that are untested or succeeded.
|
|
2477
|
+
* Prioritizes by (probability × impact).
|
|
2478
|
+
*/
|
|
2479
|
+
recommendChains() {
|
|
2480
|
+
const chains = [];
|
|
2481
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2482
|
+
const goalTypes = [NODE_TYPE.ACCESS, NODE_TYPE.LOOT];
|
|
2483
|
+
const entryNodes = Array.from(this.nodes.values()).filter(
|
|
2484
|
+
(n) => n.type === NODE_TYPE.HOST || n.type === NODE_TYPE.SERVICE || n.type === NODE_TYPE.CREDENTIAL || n.type === NODE_TYPE.OSINT
|
|
2485
|
+
);
|
|
2486
|
+
for (const entry of entryNodes) {
|
|
2487
|
+
visited.clear();
|
|
2488
|
+
this.dfsChains(entry.id, [], [], visited, chains, goalTypes);
|
|
2335
2489
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2491
|
+
const unique = chains.filter((c) => {
|
|
2492
|
+
if (seen.has(c.description)) return false;
|
|
2493
|
+
seen.add(c.description);
|
|
2494
|
+
return true;
|
|
2495
|
+
});
|
|
2496
|
+
unique.sort(
|
|
2497
|
+
(a, b) => b.probability * (IMPACT_WEIGHT[b.impact] || 1) - a.probability * (IMPACT_WEIGHT[a.impact] || 1)
|
|
2498
|
+
);
|
|
2499
|
+
return unique.slice(0, GRAPH_LIMITS.MAX_CHAINS);
|
|
2500
|
+
}
|
|
2501
|
+
dfsChains(nodeId, path2, pathEdges, visited, results, goalTypes) {
|
|
2502
|
+
if (visited.has(nodeId)) return;
|
|
2503
|
+
if (path2.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH) return;
|
|
2504
|
+
const node = this.nodes.get(nodeId);
|
|
2505
|
+
if (!node) return;
|
|
2506
|
+
if (node.status === NODE_STATUS.FAILED) return;
|
|
2507
|
+
visited.add(nodeId);
|
|
2508
|
+
path2.push(node);
|
|
2509
|
+
if (goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path2.length > 1) {
|
|
2510
|
+
const prob = pathEdges.reduce((acc, e) => acc * e.confidence, 1);
|
|
2511
|
+
const impact = this.estimateImpact(node);
|
|
2512
|
+
results.push({
|
|
2513
|
+
steps: [...path2],
|
|
2514
|
+
edges: [...pathEdges],
|
|
2515
|
+
description: path2.map((n) => n.label).join(" \u2192 "),
|
|
2516
|
+
probability: prob,
|
|
2517
|
+
impact,
|
|
2518
|
+
length: path2.length
|
|
2519
|
+
});
|
|
2349
2520
|
}
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2352
|
-
(a, b) => b.probability * (impactWeight[b.impact] || 1) - a.probability * (impactWeight[a.impact] || 1)
|
|
2521
|
+
const outEdges = this.edges.filter(
|
|
2522
|
+
(e) => e.from === nodeId && e.status !== EDGE_STATUS.FAILED
|
|
2353
2523
|
);
|
|
2354
|
-
|
|
2524
|
+
for (const edge of outEdges) {
|
|
2525
|
+
this.dfsChains(edge.to, path2, [...pathEdges, edge], visited, results, goalTypes);
|
|
2526
|
+
}
|
|
2527
|
+
path2.pop();
|
|
2528
|
+
visited.delete(nodeId);
|
|
2355
2529
|
}
|
|
2530
|
+
estimateImpact(node) {
|
|
2531
|
+
if (node.type === NODE_TYPE.LOOT) return SEVERITY.CRITICAL;
|
|
2532
|
+
if (node.type === NODE_TYPE.ACCESS) {
|
|
2533
|
+
const level = String(node.data.level || "");
|
|
2534
|
+
if (["root", "SYSTEM", "Administrator", "admin"].includes(level)) return SEVERITY.CRITICAL;
|
|
2535
|
+
return SEVERITY.HIGH;
|
|
2536
|
+
}
|
|
2537
|
+
if (node.type === NODE_TYPE.VULNERABILITY) {
|
|
2538
|
+
const sev = String(node.data.severity || "");
|
|
2539
|
+
if (sev === SEVERITY.CRITICAL) return SEVERITY.CRITICAL;
|
|
2540
|
+
if (sev === SEVERITY.HIGH) return SEVERITY.HIGH;
|
|
2541
|
+
if (sev === SEVERITY.MEDIUM) return SEVERITY.MEDIUM;
|
|
2542
|
+
}
|
|
2543
|
+
return SEVERITY.LOW;
|
|
2544
|
+
}
|
|
2545
|
+
// ─── Prompt Generation ──────────────────────────────────────
|
|
2356
2546
|
/**
|
|
2357
2547
|
* Format attack graph status for prompt injection.
|
|
2548
|
+
* Provides LLM with:
|
|
2549
|
+
* - Available paths (sorted by probability)
|
|
2550
|
+
* - Failed paths (DO NOT retry)
|
|
2551
|
+
* - Unexploited vulnerabilities
|
|
2358
2552
|
*/
|
|
2359
2553
|
toPrompt() {
|
|
2360
2554
|
if (this.nodes.size === 0) return "";
|
|
2361
2555
|
const lines = ["<attack-graph>"];
|
|
2362
2556
|
const nodesByType = {};
|
|
2557
|
+
const nodesByStatus = {};
|
|
2363
2558
|
for (const node of this.nodes.values()) {
|
|
2364
2559
|
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
2560
|
+
nodesByStatus[node.status] = (nodesByStatus[node.status] || 0) + 1;
|
|
2365
2561
|
}
|
|
2366
2562
|
lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
|
|
2367
|
-
lines.push(`
|
|
2563
|
+
lines.push(`Status: ${Object.entries(nodesByStatus).map(([s, c]) => `${c} ${s}`).join(", ")}`);
|
|
2564
|
+
lines.push(`Edges: ${this.edges.length} (${this.edges.filter((e) => e.status === EDGE_STATUS.FAILED).length} failed)`);
|
|
2368
2565
|
const chains = this.recommendChains();
|
|
2369
2566
|
if (chains.length > 0) {
|
|
2370
2567
|
lines.push("");
|
|
2371
|
-
lines.push("RECOMMENDED
|
|
2568
|
+
lines.push("RECOMMENDED ATTACK PATHS (try these):");
|
|
2372
2569
|
for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
|
|
2373
2570
|
const c = chains[i];
|
|
2374
2571
|
const prob = (c.probability * 100).toFixed(0);
|
|
2375
|
-
lines.push(` ${i + 1}
|
|
2572
|
+
lines.push(` PATH ${i + 1} [${c.impact.toUpperCase()} | ${prob}%]: ${c.description}`);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (this.failedPaths.length > 0) {
|
|
2576
|
+
lines.push("");
|
|
2577
|
+
lines.push("FAILED PATHS (DO NOT RETRY):");
|
|
2578
|
+
for (const fp of this.failedPaths.slice(-GRAPH_LIMITS.PROMPT_FAILED)) {
|
|
2579
|
+
lines.push(` \u2717 ${fp}`);
|
|
2376
2580
|
}
|
|
2377
2581
|
}
|
|
2378
|
-
const unexploitedVulns = Array.from(this.nodes.values()).filter((n) => n.type ===
|
|
2582
|
+
const unexploitedVulns = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.VULNERABILITY && n.status !== NODE_STATUS.SUCCEEDED && n.status !== NODE_STATUS.FAILED);
|
|
2379
2583
|
if (unexploitedVulns.length > 0) {
|
|
2380
2584
|
lines.push("");
|
|
2381
2585
|
lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
|
|
2382
2586
|
for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
|
|
2383
|
-
|
|
2587
|
+
const sev = v.data.severity || "unknown";
|
|
2588
|
+
const status = v.status === NODE_STATUS.ATTEMPTED ? " \u23F3 testing" : "";
|
|
2589
|
+
lines.push(` - ${v.label} (${sev})${status}`);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
const accessNodes = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.ACCESS && n.status === NODE_STATUS.SUCCEEDED);
|
|
2593
|
+
if (accessNodes.length > 0) {
|
|
2594
|
+
lines.push("");
|
|
2595
|
+
lines.push("ACHIEVED ACCESS:");
|
|
2596
|
+
for (const a of accessNodes) {
|
|
2597
|
+
lines.push(` \u2713 ${a.label}`);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
const credNodes = Array.from(this.nodes.values()).filter((n) => n.type === NODE_TYPE.CREDENTIAL);
|
|
2601
|
+
if (credNodes.length > 0) {
|
|
2602
|
+
lines.push("");
|
|
2603
|
+
lines.push(`CREDENTIALS (${credNodes.length} \u2014 try on all services):`);
|
|
2604
|
+
for (const c of credNodes) {
|
|
2605
|
+
const source = c.data.source || "unknown";
|
|
2606
|
+
const sprayEdges = this.edges.filter((e) => e.from === c.id && e.relation === "can_try_on");
|
|
2607
|
+
const testedCount = sprayEdges.filter((e) => e.status !== EDGE_STATUS.UNTESTED).length;
|
|
2608
|
+
lines.push(` \u{1F511} ${c.label} (from: ${source}) [sprayed: ${testedCount}/${sprayEdges.length}]`);
|
|
2384
2609
|
}
|
|
2385
2610
|
}
|
|
2386
2611
|
lines.push("</attack-graph>");
|
|
2387
2612
|
return lines.join("\n");
|
|
2388
2613
|
}
|
|
2614
|
+
// ─── TUI Visualization ──────────────────────────────────────
|
|
2615
|
+
/**
|
|
2616
|
+
* Generate ASCII visualization for TUI /graph command.
|
|
2617
|
+
*/
|
|
2618
|
+
toASCII() {
|
|
2619
|
+
if (this.nodes.size === 0) return "(Empty attack graph \u2014 no discoveries yet)";
|
|
2620
|
+
const lines = [];
|
|
2621
|
+
lines.push("\u250C\u2500\u2500\u2500 Attack Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
2622
|
+
const groups = {};
|
|
2623
|
+
for (const node of this.nodes.values()) {
|
|
2624
|
+
if (!groups[node.type]) groups[node.type] = [];
|
|
2625
|
+
groups[node.type].push(node);
|
|
2626
|
+
}
|
|
2627
|
+
const typeIcons = {
|
|
2628
|
+
[NODE_TYPE.HOST]: "\u{1F5A5}",
|
|
2629
|
+
[NODE_TYPE.SERVICE]: "\u2699",
|
|
2630
|
+
[NODE_TYPE.VULNERABILITY]: "\u26A0",
|
|
2631
|
+
[NODE_TYPE.CREDENTIAL]: "\u{1F511}",
|
|
2632
|
+
[NODE_TYPE.ACCESS]: "\u{1F513}",
|
|
2633
|
+
[NODE_TYPE.LOOT]: "\u{1F3F4}",
|
|
2634
|
+
[NODE_TYPE.OSINT]: "\u{1F50D}"
|
|
2635
|
+
};
|
|
2636
|
+
const statusIcons = {
|
|
2637
|
+
[NODE_STATUS.DISCOVERED]: "\u25CB",
|
|
2638
|
+
[NODE_STATUS.ATTEMPTED]: "\u25D0",
|
|
2639
|
+
[NODE_STATUS.SUCCEEDED]: "\u25CF",
|
|
2640
|
+
[NODE_STATUS.FAILED]: "\u2717"
|
|
2641
|
+
};
|
|
2642
|
+
let nodeCount = 0;
|
|
2643
|
+
for (const [type, nodes] of Object.entries(groups)) {
|
|
2644
|
+
if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) {
|
|
2645
|
+
lines.push(`\u2502 ... and ${this.nodes.size - nodeCount} more nodes`);
|
|
2646
|
+
break;
|
|
2647
|
+
}
|
|
2648
|
+
const icon = typeIcons[type] || "\xB7";
|
|
2649
|
+
lines.push(`\u2502`);
|
|
2650
|
+
lines.push(`\u2502 ${icon} ${type.toUpperCase()} (${nodes.length})`);
|
|
2651
|
+
for (const node of nodes) {
|
|
2652
|
+
if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) break;
|
|
2653
|
+
const sIcon = statusIcons[node.status] || "?";
|
|
2654
|
+
const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
|
|
2655
|
+
const outEdges = this.edges.filter((e) => e.from === node.id);
|
|
2656
|
+
const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
|
|
2657
|
+
const target = this.nodes.get(e.to);
|
|
2658
|
+
const eName = target ? target.label : e.to;
|
|
2659
|
+
const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
|
|
2660
|
+
return `${eName}${eStatus}`;
|
|
2661
|
+
}).join(", ")}` : "";
|
|
2662
|
+
lines.push(`\u2502 ${sIcon} ${node.label}${fail}${edgeStr}`);
|
|
2663
|
+
nodeCount++;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
const stats = this.getStats();
|
|
2667
|
+
lines.push(`\u2502`);
|
|
2668
|
+
lines.push(`\u251C\u2500\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524`);
|
|
2669
|
+
lines.push(`\u2502 Nodes: ${stats.nodes} | Edges: ${stats.edges} | Succeeded: ${stats.succeeded} | Failed: ${stats.failed} | Chains: ${stats.chains}`);
|
|
2670
|
+
lines.push(`\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`);
|
|
2671
|
+
return lines.join("\n");
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Generate path listing for TUI /paths command.
|
|
2675
|
+
*/
|
|
2676
|
+
toPathsList() {
|
|
2677
|
+
const chains = this.recommendChains();
|
|
2678
|
+
if (chains.length === 0) {
|
|
2679
|
+
if (this.nodes.size === 0) return "No attack graph data yet. Start reconnaissance first.";
|
|
2680
|
+
return "No viable attack paths found. All paths may be exhausted or failed.";
|
|
2681
|
+
}
|
|
2682
|
+
const lines = [];
|
|
2683
|
+
lines.push(`\u2500\u2500\u2500 ${chains.length} Attack Paths (sorted by priority) \u2500\u2500\u2500`);
|
|
2684
|
+
lines.push("");
|
|
2685
|
+
for (let i = 0; i < chains.length; i++) {
|
|
2686
|
+
const c = chains[i];
|
|
2687
|
+
const prob = (c.probability * 100).toFixed(0);
|
|
2688
|
+
const impactColors = {
|
|
2689
|
+
[SEVERITY.CRITICAL]: "\u{1F534}",
|
|
2690
|
+
[SEVERITY.HIGH]: "\u{1F7E0}",
|
|
2691
|
+
[SEVERITY.MEDIUM]: "\u{1F7E1}",
|
|
2692
|
+
[SEVERITY.LOW]: "\u{1F7E2}"
|
|
2693
|
+
};
|
|
2694
|
+
const impactIcon = impactColors[c.impact] || "\u26AA";
|
|
2695
|
+
lines.push(`${impactIcon} PATH ${i + 1} [${c.impact.toUpperCase()} | prob: ${prob}% | steps: ${c.length}]`);
|
|
2696
|
+
lines.push(` ${c.description}`);
|
|
2697
|
+
for (const edge of c.edges) {
|
|
2698
|
+
const statusTag = edge.status === EDGE_STATUS.UNTESTED ? " ?" : edge.status === EDGE_STATUS.TESTING ? " \u23F3" : edge.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : " \u2717";
|
|
2699
|
+
lines.push(` ${statusTag} ${edge.from.split(":").pop()} \u2014[${edge.relation}]\u2192 ${edge.to.split(":").pop()}`);
|
|
2700
|
+
}
|
|
2701
|
+
lines.push("");
|
|
2702
|
+
}
|
|
2703
|
+
if (this.failedPaths.length > 0) {
|
|
2704
|
+
lines.push(`\u2500\u2500\u2500 Failed Paths (${this.failedPaths.length}) \u2500\u2500\u2500`);
|
|
2705
|
+
for (const fp of this.failedPaths) {
|
|
2706
|
+
lines.push(` \u2717 ${fp}`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
return lines.join("\n");
|
|
2710
|
+
}
|
|
2711
|
+
// ─── Stats ──────────────────────────────────────────────────
|
|
2389
2712
|
/**
|
|
2390
2713
|
* Get graph stats for metrics display.
|
|
2391
2714
|
*/
|
|
2392
2715
|
getStats() {
|
|
2393
|
-
const
|
|
2716
|
+
const succeeded = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED).length;
|
|
2717
|
+
const failed = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.FAILED).length;
|
|
2394
2718
|
return {
|
|
2395
2719
|
nodes: this.nodes.size,
|
|
2396
2720
|
edges: this.edges.length,
|
|
2397
|
-
|
|
2721
|
+
succeeded,
|
|
2722
|
+
failed,
|
|
2398
2723
|
chains: this.recommendChains().length
|
|
2399
2724
|
};
|
|
2400
2725
|
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Get all nodes (for serialization or external access).
|
|
2728
|
+
*/
|
|
2729
|
+
getAllNodes() {
|
|
2730
|
+
return Array.from(this.nodes.values());
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Get all edges (for serialization or external access).
|
|
2734
|
+
*/
|
|
2735
|
+
getAllEdges() {
|
|
2736
|
+
return [...this.edges];
|
|
2737
|
+
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Get failed paths list.
|
|
2740
|
+
*/
|
|
2741
|
+
getFailedPaths() {
|
|
2742
|
+
return [...this.failedPaths];
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Check if the graph has any data.
|
|
2746
|
+
*/
|
|
2747
|
+
isEmpty() {
|
|
2748
|
+
return this.nodes.size === 0;
|
|
2749
|
+
}
|
|
2401
2750
|
};
|
|
2402
2751
|
|
|
2403
2752
|
// src/shared/utils/agent-memory.ts
|
|
@@ -2493,6 +2842,10 @@ var WorkingMemory = class {
|
|
|
2493
2842
|
getEntries() {
|
|
2494
2843
|
return [...this.entries];
|
|
2495
2844
|
}
|
|
2845
|
+
/** Clear all working memory entries. */
|
|
2846
|
+
clear() {
|
|
2847
|
+
this.entries = [];
|
|
2848
|
+
}
|
|
2496
2849
|
};
|
|
2497
2850
|
var EpisodicMemory = class {
|
|
2498
2851
|
events = [];
|
|
@@ -2548,6 +2901,10 @@ var EpisodicMemory = class {
|
|
|
2548
2901
|
lines.push("</session-timeline>");
|
|
2549
2902
|
return lines.join("\n");
|
|
2550
2903
|
}
|
|
2904
|
+
/** Clear all episodic events. */
|
|
2905
|
+
clear() {
|
|
2906
|
+
this.events = [];
|
|
2907
|
+
}
|
|
2551
2908
|
};
|
|
2552
2909
|
var MEMORY_DIR = "/tmp/pentesting-memory";
|
|
2553
2910
|
var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
|
|
@@ -2732,6 +3089,10 @@ var DynamicTechniqueLibrary = class {
|
|
|
2732
3089
|
getAll() {
|
|
2733
3090
|
return [...this.techniques];
|
|
2734
3091
|
}
|
|
3092
|
+
/** Clear all learned techniques. */
|
|
3093
|
+
clear() {
|
|
3094
|
+
this.techniques = [];
|
|
3095
|
+
}
|
|
2735
3096
|
/**
|
|
2736
3097
|
* Format for prompt injection.
|
|
2737
3098
|
*/
|
|
@@ -2840,6 +3201,32 @@ var SharedState = class {
|
|
|
2840
3201
|
challengeAnalysis: null
|
|
2841
3202
|
};
|
|
2842
3203
|
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Reset all state to initial defaults.
|
|
3206
|
+
* Used by /clear command to start a fresh session without recreating the agent.
|
|
3207
|
+
*/
|
|
3208
|
+
reset() {
|
|
3209
|
+
this.data = {
|
|
3210
|
+
engagement: null,
|
|
3211
|
+
targets: /* @__PURE__ */ new Map(),
|
|
3212
|
+
findings: [],
|
|
3213
|
+
loot: [],
|
|
3214
|
+
todo: [],
|
|
3215
|
+
actionLog: [],
|
|
3216
|
+
currentPhase: PHASES.RECON,
|
|
3217
|
+
missionSummary: "",
|
|
3218
|
+
missionChecklist: [],
|
|
3219
|
+
ctfMode: true,
|
|
3220
|
+
flags: [],
|
|
3221
|
+
startedAt: Date.now(),
|
|
3222
|
+
deadlineAt: 0,
|
|
3223
|
+
challengeAnalysis: null
|
|
3224
|
+
};
|
|
3225
|
+
this.attackGraph.reset();
|
|
3226
|
+
this.workingMemory.clear();
|
|
3227
|
+
this.episodicMemory.clear();
|
|
3228
|
+
this.dynamicTechniques.clear();
|
|
3229
|
+
}
|
|
2843
3230
|
// --- Mission & Persistent Context ---
|
|
2844
3231
|
setMissionSummary(summary) {
|
|
2845
3232
|
this.data.missionSummary = summary;
|
|
@@ -3350,6 +3737,11 @@ var NOISE_CLASSIFICATION = {
|
|
|
3350
3737
|
};
|
|
3351
3738
|
|
|
3352
3739
|
// src/shared/utils/binary-analysis.ts
|
|
3740
|
+
var RELRO_STATUS = {
|
|
3741
|
+
NO: "no",
|
|
3742
|
+
PARTIAL: "partial",
|
|
3743
|
+
FULL: "full"
|
|
3744
|
+
};
|
|
3353
3745
|
function parseChecksec(output) {
|
|
3354
3746
|
const info = {};
|
|
3355
3747
|
if (/x86-64|amd64/i.test(output)) {
|
|
@@ -3403,10 +3795,10 @@ function suggestExploitTechniques(info) {
|
|
|
3403
3795
|
if (info.pie === true && info.canary === false) {
|
|
3404
3796
|
suggestions.push("PIE enabled \u2192 Need info leak first (partial overwrite, format string)");
|
|
3405
3797
|
}
|
|
3406
|
-
if (info.relro ===
|
|
3798
|
+
if (info.relro === RELRO_STATUS.PARTIAL) {
|
|
3407
3799
|
suggestions.push("Partial RELRO \u2192 GOT overwrite possible");
|
|
3408
3800
|
}
|
|
3409
|
-
if (info.relro ===
|
|
3801
|
+
if (info.relro === RELRO_STATUS.FULL) {
|
|
3410
3802
|
suggestions.push("Full RELRO \u2192 GOT is read-only, try __malloc_hook/__free_hook or stack-based attacks");
|
|
3411
3803
|
}
|
|
3412
3804
|
if (info.canary === true) {
|
|
@@ -3441,6 +3833,11 @@ function formatBinaryAnalysis(info) {
|
|
|
3441
3833
|
}
|
|
3442
3834
|
|
|
3443
3835
|
// src/shared/utils/structured-output.ts
|
|
3836
|
+
var PORT_STATE = {
|
|
3837
|
+
OPEN: "open",
|
|
3838
|
+
CLOSED: "closed",
|
|
3839
|
+
FILTERED: "filtered"
|
|
3840
|
+
};
|
|
3444
3841
|
function extractNmapStructured(output) {
|
|
3445
3842
|
const ports = [];
|
|
3446
3843
|
const hosts = [];
|
|
@@ -3458,8 +3855,8 @@ function extractNmapStructured(output) {
|
|
|
3458
3855
|
while ((match = hostRegex.exec(output)) !== null) {
|
|
3459
3856
|
hosts.push(match[1]);
|
|
3460
3857
|
}
|
|
3461
|
-
const openCount = ports.filter((p) => p.state ===
|
|
3462
|
-
const summary = `${hosts.length} host(s), ${openCount} open port(s): ${ports.filter((p) => p.state ===
|
|
3858
|
+
const openCount = ports.filter((p) => p.state === PORT_STATE.OPEN).length;
|
|
3859
|
+
const summary = `${hosts.length} host(s), ${openCount} open port(s): ${ports.filter((p) => p.state === PORT_STATE.OPEN).map((p) => `${p.port}/${p.service}`).join(", ")}`;
|
|
3463
3860
|
return {
|
|
3464
3861
|
summary,
|
|
3465
3862
|
structured: { openPorts: ports, hosts }
|
|
@@ -4031,7 +4428,7 @@ Resource Safety:
|
|
|
4031
4428
|
const action = params.action;
|
|
4032
4429
|
const processId = params.process_id;
|
|
4033
4430
|
switch (action) {
|
|
4034
|
-
case
|
|
4431
|
+
case PROCESS_ACTIONS.LIST: {
|
|
4035
4432
|
const procs = listBackgroundProcesses();
|
|
4036
4433
|
if (procs.length === 0) {
|
|
4037
4434
|
return { success: true, output: "No background processes running.\nNo ports in use." };
|
|
@@ -4062,7 +4459,7 @@ Ports in use: ${usedPorts.join(", ")}` : "\nNo ports in use.";
|
|
|
4062
4459
|
${lines.join("\n\n")}${portLine}${eventLines}`
|
|
4063
4460
|
};
|
|
4064
4461
|
}
|
|
4065
|
-
case
|
|
4462
|
+
case PROCESS_ACTIONS.STATUS: {
|
|
4066
4463
|
if (!processId) return { success: false, output: "", error: "Missing required parameter: process_id" };
|
|
4067
4464
|
const output = getProcessOutput(processId);
|
|
4068
4465
|
if (!output) return { success: false, output: "", error: `Process ${processId} not found.` };
|
|
@@ -4102,7 +4499,7 @@ ${output.stdout.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) || "(empty)"}
|
|
|
4102
4499
|
${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connectionHint + interactHint
|
|
4103
4500
|
};
|
|
4104
4501
|
}
|
|
4105
|
-
case
|
|
4502
|
+
case PROCESS_ACTIONS.INTERACT: {
|
|
4106
4503
|
if (!processId) return { success: false, output: "", error: "Missing process_id for interact" };
|
|
4107
4504
|
const cmd = params.command;
|
|
4108
4505
|
if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
|
|
@@ -4120,7 +4517,7 @@ ${result2.newOutput}
|
|
|
4120
4517
|
${result2.output}`
|
|
4121
4518
|
};
|
|
4122
4519
|
}
|
|
4123
|
-
case
|
|
4520
|
+
case PROCESS_ACTIONS.PROMOTE: {
|
|
4124
4521
|
if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
|
|
4125
4522
|
const desc = params.description;
|
|
4126
4523
|
const success = promoteToShell(processId, desc);
|
|
@@ -4141,11 +4538,11 @@ Next steps:
|
|
|
4141
4538
|
5. Begin post-exploitation enumeration: whoami, sudo -l, etc.`
|
|
4142
4539
|
};
|
|
4143
4540
|
}
|
|
4144
|
-
case
|
|
4541
|
+
case PROCESS_ACTIONS.STOP: {
|
|
4145
4542
|
if (!processId) return { success: false, output: "", error: "Missing process_id" };
|
|
4146
4543
|
return stopBackgroundProcess(processId);
|
|
4147
4544
|
}
|
|
4148
|
-
case
|
|
4545
|
+
case PROCESS_ACTIONS.STOP_ALL: {
|
|
4149
4546
|
const procs = listBackgroundProcesses();
|
|
4150
4547
|
if (procs.length === 0) return { success: true, output: "No background processes to stop." };
|
|
4151
4548
|
const activeShells = procs.filter((p) => p.role === PROCESS_ROLES.ACTIVE_SHELL && p.isRunning);
|
|
@@ -4515,11 +4912,12 @@ Detail: ${detail}
|
|
|
4515
4912
|
},
|
|
4516
4913
|
{
|
|
4517
4914
|
name: TOOL_NAMES.ADD_FINDING,
|
|
4518
|
-
description: "Add a security finding",
|
|
4915
|
+
description: "Add a security finding. Always include attackPattern for MITRE ATT&CK mapping.",
|
|
4519
4916
|
parameters: {
|
|
4520
4917
|
title: { type: "string", description: "Finding title" },
|
|
4521
|
-
severity: { type: "string", description: "Severity" },
|
|
4522
|
-
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
|
|
4918
|
+
severity: { type: "string", description: "Severity: critical, high, medium, low, info" },
|
|
4919
|
+
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" },
|
|
4920
|
+
attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" }
|
|
4523
4921
|
},
|
|
4524
4922
|
required: ["title", "severity"],
|
|
4525
4923
|
execute: async (p) => {
|
|
@@ -4537,7 +4935,8 @@ Detail: ${detail}
|
|
|
4537
4935
|
evidence,
|
|
4538
4936
|
isVerified: validation.isVerified,
|
|
4539
4937
|
remediation: "",
|
|
4540
|
-
foundAt: Date.now()
|
|
4938
|
+
foundAt: Date.now(),
|
|
4939
|
+
...p.attackPattern ? { attackPattern: p.attackPattern } : {}
|
|
4541
4940
|
});
|
|
4542
4941
|
const hasExploit = validation.isVerified;
|
|
4543
4942
|
const target = affected[0] || "unknown";
|
|
@@ -5214,6 +5613,11 @@ async function searchWithPlaywright(query) {
|
|
|
5214
5613
|
}
|
|
5215
5614
|
|
|
5216
5615
|
// src/engine/tools-mid.ts
|
|
5616
|
+
var PORT_STATE2 = {
|
|
5617
|
+
OPEN: "open",
|
|
5618
|
+
CLOSED: "closed",
|
|
5619
|
+
FILTERED: "filtered"
|
|
5620
|
+
};
|
|
5217
5621
|
function getErrorMessage2(error) {
|
|
5218
5622
|
return error instanceof Error ? error.message : String(error);
|
|
5219
5623
|
}
|
|
@@ -5246,7 +5650,7 @@ async function parseNmap(xmlPath) {
|
|
|
5246
5650
|
const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
|
|
5247
5651
|
const service = serviceMatch ? serviceMatch[1] : void 0;
|
|
5248
5652
|
const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
|
|
5249
|
-
if (state ===
|
|
5653
|
+
if (state === PORT_STATE2.OPEN) {
|
|
5250
5654
|
ports.push({ port, protocol, state, service, version });
|
|
5251
5655
|
results.summary.openPorts++;
|
|
5252
5656
|
if (service) results.summary.servicesFound++;
|
|
@@ -5816,6 +6220,54 @@ For CVEs not in this list, use web_search to find them.`
|
|
|
5816
6220
|
];
|
|
5817
6221
|
|
|
5818
6222
|
// src/shared/utils/payload-mutator.ts
|
|
6223
|
+
var TRANSFORM_TYPE = {
|
|
6224
|
+
URL: "url",
|
|
6225
|
+
DOUBLE_URL: "double_url",
|
|
6226
|
+
TRIPLE_URL: "triple_url",
|
|
6227
|
+
UNICODE: "unicode",
|
|
6228
|
+
HTML_ENTITY_DEC: "html_entity_dec",
|
|
6229
|
+
HTML_ENTITY_HEX: "html_entity_hex",
|
|
6230
|
+
HEX: "hex",
|
|
6231
|
+
OCTAL: "octal",
|
|
6232
|
+
BASE64: "base64",
|
|
6233
|
+
CASE_SWAP: "case_swap",
|
|
6234
|
+
COMMENT_INSERT: "comment_insert",
|
|
6235
|
+
WHITESPACE_ALT: "whitespace_alt",
|
|
6236
|
+
CHAR_FUNCTION: "char_function",
|
|
6237
|
+
CONCAT_SPLIT: "concat_split",
|
|
6238
|
+
NULL_BYTE: "null_byte",
|
|
6239
|
+
REVERSE: "reverse",
|
|
6240
|
+
UTF8_OVERLONG: "utf8_overlong",
|
|
6241
|
+
MIXED_ENCODING: "mixed_encoding",
|
|
6242
|
+
TAG_ALTERNATIVE: "tag_alternative",
|
|
6243
|
+
EVENT_HANDLER: "event_handler",
|
|
6244
|
+
KEYWORD_BYPASS: "keyword_bypass",
|
|
6245
|
+
SPACE_BYPASS: "space_bypass",
|
|
6246
|
+
NO_QUOTES: "no_quotes",
|
|
6247
|
+
JS_ALTERNATIVE: "js_alternative"
|
|
6248
|
+
};
|
|
6249
|
+
var PAYLOAD_CONTEXT = {
|
|
6250
|
+
URL_PATH: "url_path",
|
|
6251
|
+
URL_PARAM: "url_param",
|
|
6252
|
+
POST_BODY: "post_body",
|
|
6253
|
+
JSON_BODY: "json_body",
|
|
6254
|
+
XML_BODY: "xml_body",
|
|
6255
|
+
HTML_BODY: "html_body",
|
|
6256
|
+
HTML_ATTR: "html_attr",
|
|
6257
|
+
JS_STRING: "js_string",
|
|
6258
|
+
SQL_STRING: "sql_string",
|
|
6259
|
+
SQL_NUMERIC: "sql_numeric",
|
|
6260
|
+
SHELL_CMD: "shell_cmd",
|
|
6261
|
+
HTTP_HEADER: "http_header",
|
|
6262
|
+
COOKIE: "cookie",
|
|
6263
|
+
GENERIC: "generic"
|
|
6264
|
+
};
|
|
6265
|
+
var DATABASE_TYPE = {
|
|
6266
|
+
MYSQL: "mysql",
|
|
6267
|
+
MSSQL: "mssql",
|
|
6268
|
+
POSTGRESQL: "pg",
|
|
6269
|
+
ORACLE: "oracle"
|
|
6270
|
+
};
|
|
5819
6271
|
function urlEncode(s) {
|
|
5820
6272
|
return [...s].map((c) => {
|
|
5821
6273
|
const code = c.charCodeAt(0);
|
|
@@ -5876,12 +6328,12 @@ function whitespaceAlt(s) {
|
|
|
5876
6328
|
const alt = alts[Math.floor(Math.random() * alts.length)];
|
|
5877
6329
|
return s.replace(/ /g, alt);
|
|
5878
6330
|
}
|
|
5879
|
-
function charFunction(s, dbType =
|
|
6331
|
+
function charFunction(s, dbType = DATABASE_TYPE.MYSQL) {
|
|
5880
6332
|
const chars = [...s].map((c) => c.charCodeAt(0));
|
|
5881
|
-
if (dbType ===
|
|
6333
|
+
if (dbType === DATABASE_TYPE.MYSQL) {
|
|
5882
6334
|
return `CHAR(${chars.join(",")})`;
|
|
5883
6335
|
}
|
|
5884
|
-
if (dbType ===
|
|
6336
|
+
if (dbType === DATABASE_TYPE.MSSQL) {
|
|
5885
6337
|
return chars.map((c) => `CHAR(${c})`).join("+");
|
|
5886
6338
|
}
|
|
5887
6339
|
return chars.map((c) => `CHR(${c})`).join("||");
|
|
@@ -5966,78 +6418,78 @@ function mutatePayload(request) {
|
|
|
5966
6418
|
const activeTransforms = transforms || getDefaultTransforms(context);
|
|
5967
6419
|
for (const transform of activeTransforms) {
|
|
5968
6420
|
switch (transform) {
|
|
5969
|
-
case
|
|
6421
|
+
case TRANSFORM_TYPE.URL:
|
|
5970
6422
|
variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
|
|
5971
6423
|
variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
|
|
5972
6424
|
break;
|
|
5973
|
-
case
|
|
6425
|
+
case TRANSFORM_TYPE.DOUBLE_URL:
|
|
5974
6426
|
variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
|
|
5975
6427
|
break;
|
|
5976
|
-
case
|
|
6428
|
+
case TRANSFORM_TYPE.TRIPLE_URL:
|
|
5977
6429
|
variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
|
|
5978
6430
|
break;
|
|
5979
|
-
case
|
|
6431
|
+
case TRANSFORM_TYPE.UNICODE:
|
|
5980
6432
|
variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
|
|
5981
6433
|
break;
|
|
5982
|
-
case
|
|
6434
|
+
case TRANSFORM_TYPE.HTML_ENTITY_DEC:
|
|
5983
6435
|
variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
|
|
5984
6436
|
break;
|
|
5985
|
-
case
|
|
6437
|
+
case TRANSFORM_TYPE.HTML_ENTITY_HEX:
|
|
5986
6438
|
variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
|
|
5987
6439
|
break;
|
|
5988
|
-
case
|
|
6440
|
+
case TRANSFORM_TYPE.HEX:
|
|
5989
6441
|
variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
|
|
5990
6442
|
break;
|
|
5991
|
-
case
|
|
6443
|
+
case TRANSFORM_TYPE.OCTAL:
|
|
5992
6444
|
variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
|
|
5993
6445
|
break;
|
|
5994
|
-
case
|
|
6446
|
+
case TRANSFORM_TYPE.BASE64:
|
|
5995
6447
|
variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
|
|
5996
6448
|
break;
|
|
5997
|
-
case
|
|
6449
|
+
case TRANSFORM_TYPE.CASE_SWAP:
|
|
5998
6450
|
for (let i = 0; i < 3; i++) {
|
|
5999
6451
|
variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
|
|
6000
6452
|
}
|
|
6001
6453
|
break;
|
|
6002
|
-
case
|
|
6454
|
+
case TRANSFORM_TYPE.COMMENT_INSERT:
|
|
6003
6455
|
variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
|
|
6004
6456
|
break;
|
|
6005
|
-
case
|
|
6457
|
+
case TRANSFORM_TYPE.WHITESPACE_ALT:
|
|
6006
6458
|
variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
|
|
6007
6459
|
variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
|
|
6008
6460
|
variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
|
|
6009
6461
|
break;
|
|
6010
|
-
case
|
|
6462
|
+
case TRANSFORM_TYPE.CHAR_FUNCTION:
|
|
6011
6463
|
variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
|
|
6012
6464
|
variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
|
|
6013
6465
|
variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
|
|
6014
6466
|
break;
|
|
6015
|
-
case
|
|
6467
|
+
case TRANSFORM_TYPE.CONCAT_SPLIT:
|
|
6016
6468
|
variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
|
|
6017
6469
|
break;
|
|
6018
|
-
case
|
|
6470
|
+
case TRANSFORM_TYPE.NULL_BYTE:
|
|
6019
6471
|
variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
|
|
6020
6472
|
variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
|
|
6021
6473
|
break;
|
|
6022
|
-
case
|
|
6474
|
+
case TRANSFORM_TYPE.REVERSE:
|
|
6023
6475
|
variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
|
|
6024
6476
|
break;
|
|
6025
|
-
case
|
|
6477
|
+
case TRANSFORM_TYPE.UTF8_OVERLONG:
|
|
6026
6478
|
variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
|
|
6027
6479
|
break;
|
|
6028
|
-
case
|
|
6480
|
+
case TRANSFORM_TYPE.MIXED_ENCODING:
|
|
6029
6481
|
variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
|
|
6030
6482
|
break;
|
|
6031
|
-
case
|
|
6032
|
-
case
|
|
6033
|
-
case
|
|
6483
|
+
case TRANSFORM_TYPE.TAG_ALTERNATIVE:
|
|
6484
|
+
case TRANSFORM_TYPE.EVENT_HANDLER:
|
|
6485
|
+
case TRANSFORM_TYPE.JS_ALTERNATIVE:
|
|
6034
6486
|
variants.push(...generateXssAlternatives(payload));
|
|
6035
6487
|
break;
|
|
6036
|
-
case
|
|
6488
|
+
case TRANSFORM_TYPE.KEYWORD_BYPASS:
|
|
6037
6489
|
variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
|
|
6038
6490
|
variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
|
|
6039
6491
|
break;
|
|
6040
|
-
case
|
|
6492
|
+
case TRANSFORM_TYPE.SPACE_BYPASS:
|
|
6041
6493
|
variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
|
|
6042
6494
|
variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
|
|
6043
6495
|
variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
|
|
@@ -6059,52 +6511,52 @@ function mutatePayload(request) {
|
|
|
6059
6511
|
}
|
|
6060
6512
|
function getDefaultTransforms(context) {
|
|
6061
6513
|
switch (context) {
|
|
6062
|
-
case
|
|
6063
|
-
case
|
|
6064
|
-
return [
|
|
6065
|
-
case
|
|
6066
|
-
return [
|
|
6067
|
-
case
|
|
6068
|
-
return [
|
|
6069
|
-
case
|
|
6070
|
-
return [
|
|
6071
|
-
case
|
|
6072
|
-
case
|
|
6073
|
-
return [
|
|
6074
|
-
case
|
|
6075
|
-
return [
|
|
6076
|
-
case
|
|
6077
|
-
return [
|
|
6078
|
-
case
|
|
6079
|
-
return [
|
|
6080
|
-
case
|
|
6081
|
-
case
|
|
6082
|
-
return [
|
|
6514
|
+
case PAYLOAD_CONTEXT.URL_PATH:
|
|
6515
|
+
case PAYLOAD_CONTEXT.URL_PARAM:
|
|
6516
|
+
return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.UTF8_OVERLONG, TRANSFORM_TYPE.MIXED_ENCODING, TRANSFORM_TYPE.NULL_BYTE];
|
|
6517
|
+
case PAYLOAD_CONTEXT.HTML_BODY:
|
|
6518
|
+
return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.TAG_ALTERNATIVE, TRANSFORM_TYPE.EVENT_HANDLER, TRANSFORM_TYPE.JS_ALTERNATIVE];
|
|
6519
|
+
case PAYLOAD_CONTEXT.HTML_ATTR:
|
|
6520
|
+
return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.EVENT_HANDLER];
|
|
6521
|
+
case PAYLOAD_CONTEXT.JS_STRING:
|
|
6522
|
+
return [TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.HEX, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.JS_ALTERNATIVE];
|
|
6523
|
+
case PAYLOAD_CONTEXT.SQL_STRING:
|
|
6524
|
+
case PAYLOAD_CONTEXT.SQL_NUMERIC:
|
|
6525
|
+
return [TRANSFORM_TYPE.CASE_SWAP, TRANSFORM_TYPE.COMMENT_INSERT, TRANSFORM_TYPE.WHITESPACE_ALT, TRANSFORM_TYPE.CHAR_FUNCTION, TRANSFORM_TYPE.CONCAT_SPLIT, TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL];
|
|
6526
|
+
case PAYLOAD_CONTEXT.SHELL_CMD:
|
|
6527
|
+
return [TRANSFORM_TYPE.KEYWORD_BYPASS, TRANSFORM_TYPE.SPACE_BYPASS, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.REVERSE, TRANSFORM_TYPE.HEX, TRANSFORM_TYPE.OCTAL];
|
|
6528
|
+
case PAYLOAD_CONTEXT.XML_BODY:
|
|
6529
|
+
return [TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.HTML_ENTITY_HEX, TRANSFORM_TYPE.UNICODE];
|
|
6530
|
+
case PAYLOAD_CONTEXT.JSON_BODY:
|
|
6531
|
+
return [TRANSFORM_TYPE.UNICODE];
|
|
6532
|
+
case PAYLOAD_CONTEXT.HTTP_HEADER:
|
|
6533
|
+
case PAYLOAD_CONTEXT.COOKIE:
|
|
6534
|
+
return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.BASE64];
|
|
6083
6535
|
default:
|
|
6084
|
-
return [
|
|
6536
|
+
return [TRANSFORM_TYPE.URL, TRANSFORM_TYPE.DOUBLE_URL, TRANSFORM_TYPE.HTML_ENTITY_DEC, TRANSFORM_TYPE.UNICODE, TRANSFORM_TYPE.BASE64, TRANSFORM_TYPE.CASE_SWAP];
|
|
6085
6537
|
}
|
|
6086
6538
|
}
|
|
6087
6539
|
function getContextRecommendations(context, variantCount) {
|
|
6088
6540
|
const recs = [];
|
|
6089
6541
|
recs.push(`Generated ${variantCount} variants for context: ${context}`);
|
|
6090
6542
|
switch (context) {
|
|
6091
|
-
case
|
|
6543
|
+
case PAYLOAD_CONTEXT.URL_PARAM:
|
|
6092
6544
|
recs.push("If all URL encoding fails: try HTTP Parameter Pollution (send same param twice)");
|
|
6093
6545
|
recs.push("Try switching GET \u2192 POST or changing Content-Type");
|
|
6094
6546
|
recs.push("Check if WebSocket endpoint exists (often unfiltered)");
|
|
6095
6547
|
break;
|
|
6096
|
-
case
|
|
6097
|
-
case
|
|
6548
|
+
case PAYLOAD_CONTEXT.SQL_STRING:
|
|
6549
|
+
case PAYLOAD_CONTEXT.SQL_NUMERIC:
|
|
6098
6550
|
recs.push("If inline comments fail: try MySQL version comments /*!50000SELECT*/");
|
|
6099
6551
|
recs.push("Try time-based blind if error/union payloads are blocked");
|
|
6100
6552
|
recs.push("Use sqlmap with --tamper scripts for automated bypass");
|
|
6101
6553
|
break;
|
|
6102
|
-
case
|
|
6554
|
+
case PAYLOAD_CONTEXT.HTML_BODY:
|
|
6103
6555
|
recs.push("If common XSS tags blocked: try SVG, MathML, mutation XSS");
|
|
6104
6556
|
recs.push("Check CSP header \u2014 it determines what JS execution is possible");
|
|
6105
6557
|
recs.push("Try DOM-based XSS via document.location, innerHTML, postMessage");
|
|
6106
6558
|
break;
|
|
6107
|
-
case
|
|
6559
|
+
case PAYLOAD_CONTEXT.SHELL_CMD:
|
|
6108
6560
|
recs.push("Use ${IFS} for space, quotes for keyword bypass, base64 for full obfuscation");
|
|
6109
6561
|
recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
|
|
6110
6562
|
recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
|
|
@@ -6342,6 +6794,9 @@ var globalCredentialHandler = null;
|
|
|
6342
6794
|
function setCredentialHandler(handler) {
|
|
6343
6795
|
globalCredentialHandler = handler;
|
|
6344
6796
|
}
|
|
6797
|
+
function clearCredentialHandler() {
|
|
6798
|
+
globalCredentialHandler = null;
|
|
6799
|
+
}
|
|
6345
6800
|
async function requestCredential(request) {
|
|
6346
6801
|
if (!globalCredentialHandler) {
|
|
6347
6802
|
return {
|
|
@@ -7539,6 +7994,11 @@ var FILE_SHARING_CONFIG = {
|
|
|
7539
7994
|
};
|
|
7540
7995
|
|
|
7541
7996
|
// src/domains/cloud/tools.ts
|
|
7997
|
+
var CLOUD_PROVIDER = {
|
|
7998
|
+
AWS: "aws",
|
|
7999
|
+
AZURE: "azure",
|
|
8000
|
+
GCP: "gcp"
|
|
8001
|
+
};
|
|
7542
8002
|
var CLOUD_TOOLS = [
|
|
7543
8003
|
{
|
|
7544
8004
|
name: TOOL_NAMES.AWS_S3_CHECK,
|
|
@@ -7561,8 +8021,8 @@ var CLOUD_TOOLS = [
|
|
|
7561
8021
|
required: ["provider"],
|
|
7562
8022
|
execute: async (params) => {
|
|
7563
8023
|
const provider = params.provider;
|
|
7564
|
-
if (provider ===
|
|
7565
|
-
if (provider ===
|
|
8024
|
+
if (provider === CLOUD_PROVIDER.AWS) return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
|
|
8025
|
+
if (provider === CLOUD_PROVIDER.AZURE) return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
|
|
7566
8026
|
return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
|
|
7567
8027
|
}
|
|
7568
8028
|
}
|
|
@@ -8220,6 +8680,12 @@ var LLM_ERROR_TYPES = {
|
|
|
8220
8680
|
|
|
8221
8681
|
// src/engine/llm-types.ts
|
|
8222
8682
|
var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
|
|
8683
|
+
var NETWORK_ERROR_CODES = {
|
|
8684
|
+
ECONNRESET: "ECONNRESET",
|
|
8685
|
+
ETIMEDOUT: "ETIMEDOUT",
|
|
8686
|
+
ENOTFOUND: "ENOTFOUND",
|
|
8687
|
+
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
|
|
8688
|
+
};
|
|
8223
8689
|
var LLMError = class extends Error {
|
|
8224
8690
|
/** Structured error information */
|
|
8225
8691
|
errorInfo;
|
|
@@ -8242,10 +8708,10 @@ function classifyError(error) {
|
|
|
8242
8708
|
if (statusCode === HTTP_STATUS.BAD_REQUEST) {
|
|
8243
8709
|
return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
|
|
8244
8710
|
}
|
|
8245
|
-
if (e.code ===
|
|
8711
|
+
if (e.code === NETWORK_ERROR_CODES.ECONNRESET || e.code === NETWORK_ERROR_CODES.ETIMEDOUT || e.code === NETWORK_ERROR_CODES.ENOTFOUND) {
|
|
8246
8712
|
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
8247
8713
|
}
|
|
8248
|
-
if (errorMessage.toLowerCase().includes("timeout") || e.code ===
|
|
8714
|
+
if (errorMessage.toLowerCase().includes("timeout") || e.code === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
|
|
8249
8715
|
return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
|
|
8250
8716
|
}
|
|
8251
8717
|
return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
|
|
@@ -8591,7 +9057,7 @@ var __filename2 = fileURLToPath3(import.meta.url);
|
|
|
8591
9057
|
var __dirname3 = dirname4(__filename2);
|
|
8592
9058
|
|
|
8593
9059
|
// src/engine/state-persistence.ts
|
|
8594
|
-
import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4 } from "fs";
|
|
9060
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
|
|
8595
9061
|
import { join as join9 } from "path";
|
|
8596
9062
|
function saveState(state) {
|
|
8597
9063
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
@@ -8673,6 +9139,28 @@ function loadState(state) {
|
|
|
8673
9139
|
return false;
|
|
8674
9140
|
}
|
|
8675
9141
|
}
|
|
9142
|
+
function clearWorkspace() {
|
|
9143
|
+
const cleared = [];
|
|
9144
|
+
const errors = [];
|
|
9145
|
+
const dirsToClean = [
|
|
9146
|
+
{ path: WORKSPACE.SESSIONS, label: "sessions" },
|
|
9147
|
+
{ path: WORKSPACE.DEBUG, label: "debug logs" },
|
|
9148
|
+
{ path: WORKSPACE.TEMP, label: "temp files" },
|
|
9149
|
+
{ path: WORKSPACE.OUTPUTS, label: "outputs" }
|
|
9150
|
+
];
|
|
9151
|
+
for (const dir of dirsToClean) {
|
|
9152
|
+
try {
|
|
9153
|
+
if (existsSync6(dir.path)) {
|
|
9154
|
+
rmSync(dir.path, { recursive: true, force: true });
|
|
9155
|
+
ensureDirExists(dir.path);
|
|
9156
|
+
cleared.push(dir.label);
|
|
9157
|
+
}
|
|
9158
|
+
} catch (err) {
|
|
9159
|
+
errors.push(`${dir.label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
return { cleared, errors };
|
|
9163
|
+
}
|
|
8676
9164
|
|
|
8677
9165
|
// src/agents/tool-error-enrichment.ts
|
|
8678
9166
|
function enrichToolErrorContext(ctx) {
|
|
@@ -10551,6 +11039,17 @@ var MainAgent = class extends CoreAgent {
|
|
|
10551
11039
|
isCtfMode() {
|
|
10552
11040
|
return this.state.isCtfMode();
|
|
10553
11041
|
}
|
|
11042
|
+
/**
|
|
11043
|
+
* Full session reset — clears state, workspace files, and background processes.
|
|
11044
|
+
* Used by /clear command for a complete fresh start.
|
|
11045
|
+
*/
|
|
11046
|
+
async resetSession() {
|
|
11047
|
+
await cleanupAllProcesses().catch(() => {
|
|
11048
|
+
});
|
|
11049
|
+
this.state.reset();
|
|
11050
|
+
this.userInput = "";
|
|
11051
|
+
return clearWorkspace();
|
|
11052
|
+
}
|
|
10554
11053
|
setScope(allowed, exclusions = []) {
|
|
10555
11054
|
this.state.setScope({
|
|
10556
11055
|
allowedCidrs: allowed.filter((a) => a.includes("/")),
|
|
@@ -10679,10 +11178,10 @@ var THEME = {
|
|
|
10679
11178
|
// Amber
|
|
10680
11179
|
error: "#ef4444",
|
|
10681
11180
|
// Red
|
|
10682
|
-
info: "#
|
|
10683
|
-
//
|
|
10684
|
-
running: "#
|
|
10685
|
-
//
|
|
11181
|
+
info: "#3b82f6",
|
|
11182
|
+
// Blue 500 (distinct from user sky)
|
|
11183
|
+
running: "#60a5fa",
|
|
11184
|
+
// Blue 400 (AI activity — distinct from user sky)
|
|
10686
11185
|
pending: "#64748b"
|
|
10687
11186
|
// Slate
|
|
10688
11187
|
},
|
|
@@ -10702,14 +11201,16 @@ var THEME = {
|
|
|
10702
11201
|
// Border colors
|
|
10703
11202
|
border: {
|
|
10704
11203
|
default: "#1e293b",
|
|
10705
|
-
focus: "#
|
|
11204
|
+
focus: "#7dd3fc",
|
|
11205
|
+
// Sky 300 (softer, UI decorative)
|
|
10706
11206
|
error: "#ef4444",
|
|
10707
11207
|
success: "#22c55e"
|
|
10708
11208
|
},
|
|
10709
11209
|
// Phase colors
|
|
10710
11210
|
phase: {
|
|
10711
11211
|
recon: "#94a3b8",
|
|
10712
|
-
enum: "#
|
|
11212
|
+
enum: "#60a5fa",
|
|
11213
|
+
// Blue 400 (phase indicator)
|
|
10713
11214
|
vuln: "#f59e0b",
|
|
10714
11215
|
exploit: "#ef4444",
|
|
10715
11216
|
privesc: "#8b5cf6",
|
|
@@ -10759,10 +11260,12 @@ var THEME = {
|
|
|
10759
11260
|
gold: ["#f59e0b", "#78350f"],
|
|
10760
11261
|
royal: ["#818cf8", "#312e81"]
|
|
10761
11262
|
},
|
|
10762
|
-
// Spinner color (
|
|
10763
|
-
spinner: "#
|
|
10764
|
-
//
|
|
10765
|
-
|
|
11263
|
+
// Spinner color (soft sky — UI feedback, not user input)
|
|
11264
|
+
spinner: "#7dd3fc",
|
|
11265
|
+
// Sky 300
|
|
11266
|
+
// Identity color (branded accent)
|
|
11267
|
+
identity: "#60a5fa"
|
|
11268
|
+
// Blue 400
|
|
10766
11269
|
};
|
|
10767
11270
|
var ASCII_BANNER = `
|
|
10768
11271
|
____ __ __ _
|
|
@@ -10868,30 +11371,46 @@ var MESSAGE_STYLES = {
|
|
|
10868
11371
|
status: "\u25C8"
|
|
10869
11372
|
}
|
|
10870
11373
|
};
|
|
11374
|
+
var COMMAND_DEFINITIONS = [
|
|
11375
|
+
{ name: "target", alias: "t", args: "<ip>", description: "Set target IP or domain" },
|
|
11376
|
+
{ name: "start", alias: "s", args: "[goal]", description: "Start autonomous pentest" },
|
|
11377
|
+
{ name: "findings", alias: "f", description: "Show discovered vulnerabilities" },
|
|
11378
|
+
{ name: "graph", alias: "g", description: "Visualize the attack graph" },
|
|
11379
|
+
{ name: "paths", alias: "p", description: "Show ranked attack paths" },
|
|
11380
|
+
{ name: "assets", alias: "a", description: "List background processes" },
|
|
11381
|
+
{ name: "logs", alias: "l", args: "<id>", description: "Show logs for an asset" },
|
|
11382
|
+
{ name: "ctf", description: "Toggle CTF mode" },
|
|
11383
|
+
{ name: "auto", description: "Toggle auto-approve mode" },
|
|
11384
|
+
{ name: "clear", alias: "c", description: "Reset session & clean workspace" },
|
|
11385
|
+
{ name: "help", alias: "h", description: "Show detailed help" },
|
|
11386
|
+
{ name: "exit", alias: "q", description: "Exit the application" }
|
|
11387
|
+
];
|
|
11388
|
+
function getMatchingCommands(partial) {
|
|
11389
|
+
const lower = partial.toLowerCase();
|
|
11390
|
+
if (!lower) return COMMAND_DEFINITIONS;
|
|
11391
|
+
return COMMAND_DEFINITIONS.filter(
|
|
11392
|
+
(cmd) => cmd.name.startsWith(lower) || cmd.alias && cmd.alias.startsWith(lower)
|
|
11393
|
+
);
|
|
11394
|
+
}
|
|
10871
11395
|
var HELP_TEXT = `
|
|
10872
11396
|
\u2500\u2500 Commands \u2500\u2500
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
/ctf Toggle CTF mode (default: ON)
|
|
10879
|
-
/auto Toggle auto-approve mode
|
|
10880
|
-
/clear Clear screen
|
|
10881
|
-
/exit Exit
|
|
11397
|
+
${COMMAND_DEFINITIONS.map((cmd) => {
|
|
11398
|
+
const usage = `/${cmd.name}${cmd.args ? " " + cmd.args : ""}`;
|
|
11399
|
+
const alias = cmd.alias ? ` (/${cmd.alias})` : "";
|
|
11400
|
+
return `${usage.padEnd(18)}${alias.padEnd(8)} ${cmd.description}`;
|
|
11401
|
+
}).join("\n")}
|
|
10882
11402
|
|
|
10883
11403
|
\u2500\u2500 Features \u2500\u2500
|
|
10884
11404
|
\u2022 Auto-install missing tools (apt/brew)
|
|
10885
11405
|
\u2022 Transparent command execution
|
|
10886
11406
|
\u2022 Interactive sudo password input
|
|
10887
11407
|
\u2022 CTF mode: Auto flag detection & CTF-specific prompts
|
|
11408
|
+
\u2022 Attack graph: Tracks discovered paths & prevents repeating failures
|
|
10888
11409
|
|
|
10889
|
-
\u2500\u2500
|
|
10890
|
-
/
|
|
10891
|
-
/
|
|
10892
|
-
/
|
|
10893
|
-
/ctf (toggle CTF/standard mode)
|
|
10894
|
-
/auto (toggle approval mode)
|
|
11410
|
+
\u2500\u2500 Tips \u2500\u2500
|
|
11411
|
+
\u2022 Type / and press Tab to autocomplete commands
|
|
11412
|
+
\u2022 /clear resets the entire session (messages, state, .pentesting data)
|
|
11413
|
+
\u2022 /start automatically enables auto-approve for hands-free operation
|
|
10895
11414
|
`;
|
|
10896
11415
|
|
|
10897
11416
|
// src/platform/tui/hooks/useAgentState.ts
|
|
@@ -11110,6 +11629,9 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11110
11629
|
return () => {
|
|
11111
11630
|
events.removeAllListeners();
|
|
11112
11631
|
clearAllTimers();
|
|
11632
|
+
clearInputHandler();
|
|
11633
|
+
clearCredentialHandler();
|
|
11634
|
+
clearCommandEventEmitter();
|
|
11113
11635
|
};
|
|
11114
11636
|
}, [
|
|
11115
11637
|
agent,
|
|
@@ -11215,6 +11737,7 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
11215
11737
|
inputRequest,
|
|
11216
11738
|
setInputRequest,
|
|
11217
11739
|
stats,
|
|
11740
|
+
setStats,
|
|
11218
11741
|
lastResponseMetaRef,
|
|
11219
11742
|
addMessage,
|
|
11220
11743
|
manageTimer,
|
|
@@ -11262,6 +11785,17 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
11262
11785
|
addMessage("system", "Input cancelled");
|
|
11263
11786
|
}
|
|
11264
11787
|
}, [setInputRequest, addMessage]);
|
|
11788
|
+
const refreshStats = useCallback2(() => {
|
|
11789
|
+
const s = agent.getState();
|
|
11790
|
+
setStats({
|
|
11791
|
+
phase: s.getPhase() || PHASES.RECON,
|
|
11792
|
+
targets: s.getTargets().size,
|
|
11793
|
+
findings: s.getFindings().length,
|
|
11794
|
+
todo: s.getTodo().length
|
|
11795
|
+
});
|
|
11796
|
+
resetCumulativeCounters();
|
|
11797
|
+
setCurrentStatus("");
|
|
11798
|
+
}, [agent, setStats, resetCumulativeCounters, setCurrentStatus]);
|
|
11265
11799
|
return {
|
|
11266
11800
|
agent,
|
|
11267
11801
|
messages,
|
|
@@ -11277,7 +11811,8 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
11277
11811
|
executeTask,
|
|
11278
11812
|
abort,
|
|
11279
11813
|
cancelInputRequest,
|
|
11280
|
-
addMessage
|
|
11814
|
+
addMessage,
|
|
11815
|
+
refreshStats
|
|
11281
11816
|
};
|
|
11282
11817
|
};
|
|
11283
11818
|
|
|
@@ -11532,9 +12067,11 @@ var StatusDisplay = ({
|
|
|
11532
12067
|
};
|
|
11533
12068
|
|
|
11534
12069
|
// src/platform/tui/components/ChatInput.tsx
|
|
11535
|
-
import {
|
|
12070
|
+
import { useMemo, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
12071
|
+
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
11536
12072
|
import TextInput from "ink-text-input";
|
|
11537
12073
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
12074
|
+
var MAX_SUGGESTIONS = 6;
|
|
11538
12075
|
var ChatInput = ({
|
|
11539
12076
|
value,
|
|
11540
12077
|
onChange,
|
|
@@ -11545,43 +12082,100 @@ var ChatInput = ({
|
|
|
11545
12082
|
setSecretInput,
|
|
11546
12083
|
onSecretSubmit
|
|
11547
12084
|
}) => {
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
12085
|
+
const isSlashMode = value.startsWith("/");
|
|
12086
|
+
const partialCmd = isSlashMode ? value.slice(1).split(" ")[0] : "";
|
|
12087
|
+
const hasArgs = isSlashMode && value.includes(" ");
|
|
12088
|
+
const suggestions = useMemo(() => {
|
|
12089
|
+
if (!isSlashMode || hasArgs) return [];
|
|
12090
|
+
return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
|
|
12091
|
+
}, [isSlashMode, partialCmd, hasArgs]);
|
|
12092
|
+
const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
|
|
12093
|
+
const suggestionsRef = useRef3(suggestions);
|
|
12094
|
+
suggestionsRef.current = suggestions;
|
|
12095
|
+
const isSlashModeRef = useRef3(isSlashMode);
|
|
12096
|
+
isSlashModeRef.current = isSlashMode;
|
|
12097
|
+
const hasArgsRef = useRef3(hasArgs);
|
|
12098
|
+
hasArgsRef.current = hasArgs;
|
|
12099
|
+
const inputRequestRef = useRef3(inputRequest);
|
|
12100
|
+
inputRequestRef.current = inputRequest;
|
|
12101
|
+
const onChangeRef = useRef3(onChange);
|
|
12102
|
+
onChangeRef.current = onChange;
|
|
12103
|
+
useInput(useCallback3((_input, key) => {
|
|
12104
|
+
if (inputRequestRef.current.isActive) return;
|
|
12105
|
+
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
12106
|
+
const best = suggestionsRef.current[0];
|
|
12107
|
+
const argsHint = best.args ? " " : "";
|
|
12108
|
+
onChangeRef.current(`/${best.name}${argsHint}`);
|
|
12109
|
+
}
|
|
12110
|
+
}, []));
|
|
12111
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
12112
|
+
showPreview && /* @__PURE__ */ jsx5(
|
|
12113
|
+
Box4,
|
|
12114
|
+
{
|
|
12115
|
+
flexDirection: "column",
|
|
12116
|
+
borderStyle: "single",
|
|
12117
|
+
borderColor: THEME.border.default,
|
|
12118
|
+
paddingX: 1,
|
|
12119
|
+
marginBottom: 0,
|
|
12120
|
+
children: suggestions.map((cmd, i) => {
|
|
12121
|
+
const isFirst = i === 0;
|
|
12122
|
+
const nameColor = isFirst ? THEME.text.accent : THEME.text.secondary;
|
|
12123
|
+
const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
|
|
12124
|
+
const argsText = cmd.args ? ` ${cmd.args}` : "";
|
|
12125
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
12126
|
+
/* @__PURE__ */ jsxs4(Text5, { color: nameColor, bold: isFirst, children: [
|
|
12127
|
+
"/",
|
|
12128
|
+
cmd.name
|
|
12129
|
+
] }),
|
|
12130
|
+
/* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: argsText }),
|
|
12131
|
+
aliasText && /* @__PURE__ */ jsx5(Text5, { color: THEME.text.muted, children: aliasText }),
|
|
12132
|
+
/* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
|
|
12133
|
+
" \u2014 ",
|
|
12134
|
+
cmd.description
|
|
12135
|
+
] }),
|
|
12136
|
+
isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.accent.cyan, children: " \u21E5 Tab" })
|
|
12137
|
+
] }, cmd.name);
|
|
12138
|
+
})
|
|
12139
|
+
}
|
|
12140
|
+
),
|
|
12141
|
+
/* @__PURE__ */ jsx5(
|
|
12142
|
+
Box4,
|
|
12143
|
+
{
|
|
12144
|
+
borderStyle: "single",
|
|
12145
|
+
borderColor: inputRequest.isActive ? THEME.status.warning : THEME.border.default,
|
|
12146
|
+
paddingX: 1,
|
|
12147
|
+
children: inputRequest.isActive ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
12148
|
+
/* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "\u{1F512}" }),
|
|
12149
|
+
/* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
|
|
12150
|
+
" ",
|
|
12151
|
+
inputRequest.prompt
|
|
12152
|
+
] }),
|
|
12153
|
+
/* @__PURE__ */ jsx5(
|
|
12154
|
+
TextInput,
|
|
12155
|
+
{
|
|
12156
|
+
value: secretInput,
|
|
12157
|
+
onChange: setSecretInput,
|
|
12158
|
+
onSubmit: onSecretSubmit,
|
|
12159
|
+
placeholder: "...",
|
|
12160
|
+
mask: inputRequest.isPassword ? "\u2022" : void 0
|
|
12161
|
+
}
|
|
12162
|
+
)
|
|
12163
|
+
] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
12164
|
+
/* @__PURE__ */ jsx5(Text5, { color: THEME.text.secondary, children: "\u25B8" }),
|
|
12165
|
+
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
12166
|
+
/* @__PURE__ */ jsx5(
|
|
12167
|
+
TextInput,
|
|
12168
|
+
{
|
|
12169
|
+
value,
|
|
12170
|
+
onChange,
|
|
12171
|
+
onSubmit,
|
|
12172
|
+
placeholder
|
|
12173
|
+
}
|
|
12174
|
+
)
|
|
12175
|
+
] })
|
|
12176
|
+
}
|
|
12177
|
+
)
|
|
12178
|
+
] });
|
|
11585
12179
|
};
|
|
11586
12180
|
|
|
11587
12181
|
// src/platform/tui/components/footer.tsx
|
|
@@ -11655,15 +12249,16 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
11655
12249
|
executeTask,
|
|
11656
12250
|
abort,
|
|
11657
12251
|
cancelInputRequest,
|
|
11658
|
-
addMessage
|
|
12252
|
+
addMessage,
|
|
12253
|
+
refreshStats
|
|
11659
12254
|
} = useAgent(autoApproveMode, target);
|
|
11660
|
-
const isProcessingRef =
|
|
12255
|
+
const isProcessingRef = useRef4(isProcessing);
|
|
11661
12256
|
isProcessingRef.current = isProcessing;
|
|
11662
|
-
const autoApproveModeRef =
|
|
12257
|
+
const autoApproveModeRef = useRef4(autoApproveMode);
|
|
11663
12258
|
autoApproveModeRef.current = autoApproveMode;
|
|
11664
|
-
const inputRequestRef =
|
|
12259
|
+
const inputRequestRef = useRef4(inputRequest);
|
|
11665
12260
|
inputRequestRef.current = inputRequest;
|
|
11666
|
-
const handleExit =
|
|
12261
|
+
const handleExit = useCallback4(() => {
|
|
11667
12262
|
const ir = inputRequestRef.current;
|
|
11668
12263
|
if (ir.isActive && ir.resolve) ir.resolve(null);
|
|
11669
12264
|
cleanupAllProcesses().catch(() => {
|
|
@@ -11671,16 +12266,31 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
11671
12266
|
exit();
|
|
11672
12267
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
11673
12268
|
}, [exit]);
|
|
11674
|
-
const handleCommand =
|
|
12269
|
+
const handleCommand = useCallback4(async (cmd, args) => {
|
|
11675
12270
|
switch (cmd) {
|
|
11676
12271
|
case UI_COMMANDS.HELP:
|
|
11677
12272
|
case UI_COMMANDS.HELP_SHORT:
|
|
11678
12273
|
addMessage("system", HELP_TEXT);
|
|
11679
12274
|
break;
|
|
11680
12275
|
case UI_COMMANDS.CLEAR:
|
|
11681
|
-
case UI_COMMANDS.CLEAR_SHORT:
|
|
12276
|
+
case UI_COMMANDS.CLEAR_SHORT: {
|
|
12277
|
+
if (isProcessingRef.current) {
|
|
12278
|
+
addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
|
|
12279
|
+
break;
|
|
12280
|
+
}
|
|
11682
12281
|
setMessages([]);
|
|
12282
|
+
const result2 = await agent.resetSession();
|
|
12283
|
+
if (result2.cleared.length > 0) {
|
|
12284
|
+
addMessage("system", `\u{1F9F9} Session reset \u2014 cleared: ${result2.cleared.join(", ")}`);
|
|
12285
|
+
} else {
|
|
12286
|
+
addMessage("system", "\u{1F9F9} Session reset \u2014 all clean");
|
|
12287
|
+
}
|
|
12288
|
+
if (result2.errors.length > 0) {
|
|
12289
|
+
addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
12290
|
+
}
|
|
12291
|
+
refreshStats();
|
|
11683
12292
|
break;
|
|
12293
|
+
}
|
|
11684
12294
|
case UI_COMMANDS.TARGET:
|
|
11685
12295
|
case UI_COMMANDS.TARGET_SHORT:
|
|
11686
12296
|
if (!args[0]) {
|
|
@@ -11715,7 +12325,7 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
11715
12325
|
break;
|
|
11716
12326
|
}
|
|
11717
12327
|
addMessage("system", `--- ${findings.length} Findings ---`);
|
|
11718
|
-
findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}`));
|
|
12328
|
+
findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}${f.attackPattern ? ` (ATT&CK: ${f.attackPattern})` : ""}`));
|
|
11719
12329
|
break;
|
|
11720
12330
|
case UI_COMMANDS.ASSETS:
|
|
11721
12331
|
case UI_COMMANDS.ASSETS_SHORT:
|
|
@@ -11740,7 +12350,15 @@ ${procData.stdout || "(no output)"}
|
|
|
11740
12350
|
const ctfEnabled = agent.toggleCtfMode();
|
|
11741
12351
|
addMessage("system", ctfEnabled ? "\u{1F3F4} CTF mode ON \u2014 flag detection active, CTF prompts loaded" : "\u{1F512} CTF mode OFF \u2014 standard pentest mode");
|
|
11742
12352
|
break;
|
|
11743
|
-
case
|
|
12353
|
+
case UI_COMMANDS.GRAPH:
|
|
12354
|
+
case UI_COMMANDS.GRAPH_SHORT:
|
|
12355
|
+
addMessage("system", agent.getState().attackGraph.toASCII());
|
|
12356
|
+
break;
|
|
12357
|
+
case UI_COMMANDS.PATHS:
|
|
12358
|
+
case UI_COMMANDS.PATHS_SHORT:
|
|
12359
|
+
addMessage("system", agent.getState().attackGraph.toPathsList());
|
|
12360
|
+
break;
|
|
12361
|
+
case UI_COMMANDS.AUTO:
|
|
11744
12362
|
setAutoApproveMode((prev) => {
|
|
11745
12363
|
const newVal = !prev;
|
|
11746
12364
|
agent.setAutoApprove(newVal);
|
|
@@ -11756,8 +12374,8 @@ ${procData.stdout || "(no output)"}
|
|
|
11756
12374
|
default:
|
|
11757
12375
|
addMessage("error", `Unknown command: /${cmd}`);
|
|
11758
12376
|
}
|
|
11759
|
-
}, [agent, addMessage, executeTask, setMessages, handleExit]);
|
|
11760
|
-
const handleSubmit =
|
|
12377
|
+
}, [agent, addMessage, executeTask, setMessages, handleExit, refreshStats]);
|
|
12378
|
+
const handleSubmit = useCallback4(async (value) => {
|
|
11761
12379
|
const trimmed = value.trim();
|
|
11762
12380
|
if (!trimmed) return;
|
|
11763
12381
|
setInput("");
|
|
@@ -11769,7 +12387,7 @@ ${procData.stdout || "(no output)"}
|
|
|
11769
12387
|
await executeTask(trimmed);
|
|
11770
12388
|
}
|
|
11771
12389
|
}, [addMessage, executeTask, handleCommand]);
|
|
11772
|
-
const handleSecretSubmit =
|
|
12390
|
+
const handleSecretSubmit = useCallback4((value) => {
|
|
11773
12391
|
const ir = inputRequestRef.current;
|
|
11774
12392
|
if (!ir.isActive || !ir.resolve) return;
|
|
11775
12393
|
const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
|
|
@@ -11779,7 +12397,7 @@ ${procData.stdout || "(no output)"}
|
|
|
11779
12397
|
setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
|
|
11780
12398
|
setSecretInput("");
|
|
11781
12399
|
}, [addMessage, setInputRequest]);
|
|
11782
|
-
|
|
12400
|
+
useInput2(useCallback4((ch, key) => {
|
|
11783
12401
|
if (key.escape) {
|
|
11784
12402
|
if (inputRequestRef.current.isActive) cancelInputRequest();
|
|
11785
12403
|
else if (isProcessingRef.current) abort();
|
|
@@ -11870,7 +12488,7 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
|
|
|
11870
12488
|
console.clear();
|
|
11871
12489
|
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
11872
12490
|
console.log(
|
|
11873
|
-
" " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.
|
|
12491
|
+
" " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.accent.blue)("Type /help for commands") + "\n"
|
|
11874
12492
|
);
|
|
11875
12493
|
if (skipPermissions) {
|
|
11876
12494
|
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
|
|
@@ -11894,7 +12512,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
11894
12512
|
if (skipPermissions) {
|
|
11895
12513
|
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
|
|
11896
12514
|
}
|
|
11897
|
-
console.log(chalk.hex(THEME.
|
|
12515
|
+
console.log(chalk.hex(THEME.accent.blue)(`[target] Objective: ${objective}
|
|
11898
12516
|
`));
|
|
11899
12517
|
const agent = AgentFactory.createMainAgent(skipPermissions);
|
|
11900
12518
|
if (skipPermissions) {
|
|
@@ -11916,7 +12534,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
11916
12534
|
if (options.output) {
|
|
11917
12535
|
const fs = await import("fs/promises");
|
|
11918
12536
|
await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
|
|
11919
|
-
console.log(chalk.hex(THEME.
|
|
12537
|
+
console.log(chalk.hex(THEME.accent.blue)(`
|
|
11920
12538
|
[+] Report saved to: ${options.output}`));
|
|
11921
12539
|
}
|
|
11922
12540
|
await shutdown(0);
|
|
@@ -11931,7 +12549,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
11931
12549
|
const opts = program.opts();
|
|
11932
12550
|
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
11933
12551
|
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
11934
|
-
console.log(chalk.hex(THEME.
|
|
12552
|
+
console.log(chalk.hex(THEME.accent.blue)(`
|
|
11935
12553
|
[scan] Target: ${target} (${options.scanType})
|
|
11936
12554
|
`));
|
|
11937
12555
|
const agent = AgentFactory.createMainAgent(skipPermissions);
|
|
@@ -11955,7 +12573,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
11955
12573
|
program.command("help-extended").description("Show extended help with examples").action(() => {
|
|
11956
12574
|
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
11957
12575
|
console.log(`
|
|
11958
|
-
${chalk.hex(THEME.
|
|
12576
|
+
${chalk.hex(THEME.accent.blue)(APP_NAME + " - Autonomous Penetration Testing AI")}
|
|
11959
12577
|
|
|
11960
12578
|
${chalk.hex(THEME.status.warning)("Usage:")}
|
|
11961
12579
|
|
|
@@ -11965,24 +12583,24 @@ ${chalk.hex(THEME.status.warning)("Usage:")}
|
|
|
11965
12583
|
|
|
11966
12584
|
${chalk.hex(THEME.status.warning)("Commands:")}
|
|
11967
12585
|
|
|
11968
|
-
${chalk.hex(THEME.
|
|
11969
|
-
${chalk.hex(THEME.
|
|
11970
|
-
${chalk.hex(THEME.
|
|
12586
|
+
${chalk.hex(THEME.accent.blue)("pentesting")} Interactive TUI mode
|
|
12587
|
+
${chalk.hex(THEME.accent.blue)("pentesting run <objective>")} Run single objective
|
|
12588
|
+
${chalk.hex(THEME.accent.blue)("pentesting scan <target>")} Quick scan target
|
|
11971
12589
|
|
|
11972
12590
|
${chalk.hex(THEME.status.warning)("Options:")}
|
|
11973
12591
|
|
|
11974
|
-
${chalk.hex(THEME.
|
|
11975
|
-
${chalk.hex(THEME.
|
|
11976
|
-
${chalk.hex(THEME.
|
|
12592
|
+
${chalk.hex(THEME.accent.blue)("--dangerously-skip-permissions")} Skip all permission prompts
|
|
12593
|
+
${chalk.hex(THEME.accent.blue)("-t, --target <ip>")} Set target
|
|
12594
|
+
${chalk.hex(THEME.accent.blue)("-o, --output <file>")} Save results to file
|
|
11977
12595
|
|
|
11978
12596
|
${chalk.hex(THEME.status.warning)("Interactive Commands:")}
|
|
11979
12597
|
|
|
11980
|
-
${chalk.hex(THEME.
|
|
11981
|
-
${chalk.hex(THEME.
|
|
11982
|
-
${chalk.hex(THEME.
|
|
11983
|
-
${chalk.hex(THEME.
|
|
11984
|
-
${chalk.hex(THEME.
|
|
11985
|
-
${chalk.hex(THEME.
|
|
12598
|
+
${chalk.hex(THEME.accent.blue)("/target <ip>")} Set target
|
|
12599
|
+
${chalk.hex(THEME.accent.blue)("/start")} Start autonomous mode
|
|
12600
|
+
${chalk.hex(THEME.accent.blue)("/config")} Manage configuration
|
|
12601
|
+
${chalk.hex(THEME.accent.blue)("/hint <text>")} Provide hint
|
|
12602
|
+
${chalk.hex(THEME.accent.blue)("/findings")} Show findings
|
|
12603
|
+
${chalk.hex(THEME.accent.blue)("/reset")} Reset session
|
|
11986
12604
|
|
|
11987
12605
|
${chalk.hex(THEME.status.warning)("Examples:")}
|
|
11988
12606
|
|
|
@@ -11997,9 +12615,9 @@ ${chalk.hex(THEME.status.warning)("Examples:")}
|
|
|
11997
12615
|
|
|
11998
12616
|
${chalk.hex(THEME.status.warning)("Environment:")}
|
|
11999
12617
|
|
|
12000
|
-
${chalk.hex(THEME.
|
|
12001
|
-
${chalk.hex(THEME.
|
|
12002
|
-
${chalk.hex(THEME.
|
|
12618
|
+
${chalk.hex(THEME.accent.blue)("PENTEST_API_KEY")} Required - LLM API key
|
|
12619
|
+
${chalk.hex(THEME.accent.blue)("PENTEST_BASE_URL")} Optional - AI API base URL
|
|
12620
|
+
${chalk.hex(THEME.accent.blue)("PENTEST_MODEL")} Optional - Model override
|
|
12003
12621
|
`);
|
|
12004
12622
|
});
|
|
12005
12623
|
program.parse();
|