pentesting 0.24.4 → 0.40.1
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 +1841 -412
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -97,6 +97,8 @@ var DISPLAY_LIMITS = {
|
|
|
97
97
|
HASH_PREVIEW_LENGTH: 20,
|
|
98
98
|
/** Max characters for loot detail preview */
|
|
99
99
|
LOOT_DETAIL_PREVIEW: 30,
|
|
100
|
+
/** Max characters for episodic memory event detail preview */
|
|
101
|
+
MEMORY_EVENT_PREVIEW: 50,
|
|
100
102
|
/** Max characters for link text preview in browser */
|
|
101
103
|
LINK_TEXT_PREVIEW: 100,
|
|
102
104
|
/** Prefix length for API key redaction in logs */
|
|
@@ -154,6 +156,20 @@ var AGENT_LIMITS = {
|
|
|
154
156
|
*/
|
|
155
157
|
MAX_SESSION_FILES: 10
|
|
156
158
|
};
|
|
159
|
+
var MEMORY_LIMITS = {
|
|
160
|
+
/** Maximum entries in working memory (hot context) */
|
|
161
|
+
WORKING_MEMORY_MAX_ENTRIES: 20,
|
|
162
|
+
/** Maximum events in episodic memory (session timeline) */
|
|
163
|
+
EPISODIC_MEMORY_MAX_EVENTS: 100,
|
|
164
|
+
/** Consecutive failures before SWITCH VECTOR warning */
|
|
165
|
+
CONSECUTIVE_FAIL_THRESHOLD: 3,
|
|
166
|
+
/** Maximum learned techniques in DynamicTechniqueLibrary */
|
|
167
|
+
DYNAMIC_TECHNIQUES_MAX: 50,
|
|
168
|
+
/** Number of leading words to match for duplicate command detection */
|
|
169
|
+
COMMAND_MATCH_WORDS: 3,
|
|
170
|
+
/** Maximum unverified techniques to show in prompt */
|
|
171
|
+
PROMPT_UNVERIFIED_TECHNIQUES: 10
|
|
172
|
+
};
|
|
157
173
|
|
|
158
174
|
// src/shared/constants/patterns.ts
|
|
159
175
|
var INPUT_PROMPT_PATTERNS = [
|
|
@@ -253,7 +269,13 @@ var SYSTEM_LIMITS = {
|
|
|
253
269
|
/** Port range for web services (development servers) */
|
|
254
270
|
WEB_PORT_RANGE: { MIN: 8e3, MAX: 9e3 },
|
|
255
271
|
/** Port range for API services */
|
|
256
|
-
API_PORT_RANGE: { MIN: 3e3, MAX: 3500 }
|
|
272
|
+
API_PORT_RANGE: { MIN: 3e3, MAX: 3500 },
|
|
273
|
+
/** Number of recent events to fetch for resource summary */
|
|
274
|
+
RECENT_EVENTS_IN_SUMMARY: 10,
|
|
275
|
+
/** Number of events to display in resource summary */
|
|
276
|
+
RECENT_EVENTS_DISPLAY: 5,
|
|
277
|
+
/** Number of recent output lines to show per process */
|
|
278
|
+
RECENT_OUTPUT_LINES: 3
|
|
257
279
|
};
|
|
258
280
|
var DETECTION_PATTERNS = {
|
|
259
281
|
LISTENER: /-(?:lvnp|nlvp|lp|p)\s+(\d+)/,
|
|
@@ -286,7 +308,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
286
308
|
var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
|
|
287
309
|
var ID_RADIX = AGENT_LIMITS.ID_RADIX;
|
|
288
310
|
var APP_NAME = "Pentest AI";
|
|
289
|
-
var APP_VERSION = "0.
|
|
311
|
+
var APP_VERSION = "0.40.1";
|
|
290
312
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
291
313
|
var LLM_ROLES = {
|
|
292
314
|
SYSTEM: "system",
|
|
@@ -487,16 +509,6 @@ var TODO_STATUSES = {
|
|
|
487
509
|
DONE: "done",
|
|
488
510
|
SKIPPED: "skipped"
|
|
489
511
|
};
|
|
490
|
-
var CORE_BINARIES = {
|
|
491
|
-
NMAP: "nmap",
|
|
492
|
-
MASSCAN: "masscan",
|
|
493
|
-
NUCLEI: "nuclei",
|
|
494
|
-
NIKTO: "nikto",
|
|
495
|
-
FFUF: "ffuf",
|
|
496
|
-
GOBUSTER: "gobuster",
|
|
497
|
-
SQLMAP: "sqlmap",
|
|
498
|
-
METASPLOIT: "msfconsole"
|
|
499
|
-
};
|
|
500
512
|
var APPROVAL_STATUSES = {
|
|
501
513
|
AUTO: "auto",
|
|
502
514
|
USER_CONFIRMED: "user_confirmed",
|
|
@@ -558,8 +570,6 @@ var UI_COMMANDS = {
|
|
|
558
570
|
QUIT: "quit",
|
|
559
571
|
EXIT_SHORT: "q"
|
|
560
572
|
};
|
|
561
|
-
var PASSIVE_BINARIES = ["whois", "dig", "curl -i"];
|
|
562
|
-
var EXPLOIT_BINARIES = ["impacket"];
|
|
563
573
|
var COMMAND_EVENT_TYPES = {
|
|
564
574
|
TOOL_MISSING: "tool_missing",
|
|
565
575
|
TOOL_INSTALL: "tool_install",
|
|
@@ -1759,11 +1769,11 @@ function isProcessRunning(processId) {
|
|
|
1759
1769
|
if (proc.hasExited) return false;
|
|
1760
1770
|
if (!isPidAlive(proc.pid)) {
|
|
1761
1771
|
proc.hasExited = true;
|
|
1762
|
-
logEvent(processId,
|
|
1772
|
+
logEvent(processId, PROCESS_EVENTS.DIED, "Process no longer alive (PID check failed)");
|
|
1763
1773
|
return false;
|
|
1764
1774
|
}
|
|
1765
1775
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
1766
|
-
if (proc.role ===
|
|
1776
|
+
if (proc.role === PROCESS_ROLES.LISTENER) {
|
|
1767
1777
|
try {
|
|
1768
1778
|
if (existsSync3(proc.stdoutFile)) {
|
|
1769
1779
|
const stdout = readFileSync2(proc.stdoutFile, "utf-8");
|
|
@@ -1954,7 +1964,7 @@ function getResourceSummary() {
|
|
|
1954
1964
|
const running = procs.filter((p) => p.isRunning);
|
|
1955
1965
|
const exited = procs.filter((p) => !p.isRunning);
|
|
1956
1966
|
const ports = getUsedPorts();
|
|
1957
|
-
const recentEvents = processEventLog.slice(-
|
|
1967
|
+
const recentEvents = processEventLog.slice(-SYSTEM_LIMITS.RECENT_EVENTS_IN_SUMMARY);
|
|
1958
1968
|
if (running.length === 0 && exited.length === 0 && recentEvents.length === 0) return "";
|
|
1959
1969
|
const lines = [];
|
|
1960
1970
|
if (running.length > 0) {
|
|
@@ -1970,7 +1980,7 @@ function getResourceSummary() {
|
|
|
1970
1980
|
if (p.stdoutFile && existsSync3(p.stdoutFile)) {
|
|
1971
1981
|
const content = readFileSync2(p.stdoutFile, "utf-8");
|
|
1972
1982
|
const outputLines = content.trim().split("\n");
|
|
1973
|
-
lastOutput = outputLines.slice(-
|
|
1983
|
+
lastOutput = outputLines.slice(-SYSTEM_LIMITS.RECENT_OUTPUT_LINES).join(" | ").replace(/\n/g, " ");
|
|
1974
1984
|
}
|
|
1975
1985
|
} catch {
|
|
1976
1986
|
}
|
|
@@ -2016,7 +2026,7 @@ Ports In Use: ${ports.join(", ")}`);
|
|
|
2016
2026
|
}
|
|
2017
2027
|
if (recentEvents.length > 0) {
|
|
2018
2028
|
lines.push(`Recent Process Events:`);
|
|
2019
|
-
for (const e of recentEvents.slice(-
|
|
2029
|
+
for (const e of recentEvents.slice(-SYSTEM_LIMITS.RECENT_EVENTS_DISPLAY)) {
|
|
2020
2030
|
const ago = Math.round((Date.now() - e.timestamp) / 1e3);
|
|
2021
2031
|
lines.push(` ${ago}s ago: [${e.event}] ${e.detail}`);
|
|
2022
2032
|
}
|
|
@@ -2147,9 +2157,657 @@ var StateSerializer = class {
|
|
|
2147
2157
|
}
|
|
2148
2158
|
};
|
|
2149
2159
|
|
|
2160
|
+
// src/shared/utils/attack-graph.ts
|
|
2161
|
+
var GRAPH_STATUS = {
|
|
2162
|
+
NEEDS_SEARCH: "needs_search",
|
|
2163
|
+
POTENTIAL: "potential"
|
|
2164
|
+
};
|
|
2165
|
+
var GRAPH_LIMITS = {
|
|
2166
|
+
/** Maximum recommended attack chains to return */
|
|
2167
|
+
MAX_CHAINS: 10,
|
|
2168
|
+
/** Maximum chains to show in prompt */
|
|
2169
|
+
PROMPT_CHAINS: 5,
|
|
2170
|
+
/** Maximum unexploited vulns to show in prompt */
|
|
2171
|
+
PROMPT_VULNS: 5
|
|
2172
|
+
};
|
|
2173
|
+
var AttackGraph = class {
|
|
2174
|
+
nodes = /* @__PURE__ */ new Map();
|
|
2175
|
+
edges = [];
|
|
2176
|
+
/**
|
|
2177
|
+
* Add a node to the attack graph.
|
|
2178
|
+
*/
|
|
2179
|
+
addNode(type, label, data = {}) {
|
|
2180
|
+
const id = `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
|
|
2181
|
+
if (!this.nodes.has(id)) {
|
|
2182
|
+
this.nodes.set(id, {
|
|
2183
|
+
id,
|
|
2184
|
+
type,
|
|
2185
|
+
label,
|
|
2186
|
+
data,
|
|
2187
|
+
exploited: false,
|
|
2188
|
+
discoveredAt: Date.now()
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
return id;
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Add an edge (relationship) between two nodes.
|
|
2195
|
+
*/
|
|
2196
|
+
addEdge(fromId, toId, relation, confidence = 0.5) {
|
|
2197
|
+
const exists = this.edges.some((e) => e.from === fromId && e.to === toId && e.relation === relation);
|
|
2198
|
+
if (!exists) {
|
|
2199
|
+
this.edges.push({ from: fromId, to: toId, relation, confidence });
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Mark a node as exploited.
|
|
2204
|
+
*/
|
|
2205
|
+
markExploited(nodeId) {
|
|
2206
|
+
const node = this.nodes.get(nodeId);
|
|
2207
|
+
if (node) node.exploited = true;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Get node by ID.
|
|
2211
|
+
*/
|
|
2212
|
+
getNode(nodeId) {
|
|
2213
|
+
return this.nodes.get(nodeId);
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Record a service discovery and auto-create edges.
|
|
2217
|
+
*/
|
|
2218
|
+
addService(host, port, service, version) {
|
|
2219
|
+
const serviceId = this.addNode("service", `${host}:${port}`, {
|
|
2220
|
+
host,
|
|
2221
|
+
port,
|
|
2222
|
+
service,
|
|
2223
|
+
version
|
|
2224
|
+
});
|
|
2225
|
+
if (version) {
|
|
2226
|
+
const vulnId = this.addNode("vulnerability", `CVE search: ${service} ${version}`, {
|
|
2227
|
+
service,
|
|
2228
|
+
version,
|
|
2229
|
+
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
2230
|
+
});
|
|
2231
|
+
this.addEdge(serviceId, vulnId, "may_have", 0.3);
|
|
2232
|
+
}
|
|
2233
|
+
return serviceId;
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Record a credential discovery and create spray edges.
|
|
2237
|
+
*/
|
|
2238
|
+
addCredential(username, password, source) {
|
|
2239
|
+
const credId = this.addNode("credential", `${username}:***`, {
|
|
2240
|
+
username,
|
|
2241
|
+
password,
|
|
2242
|
+
source
|
|
2243
|
+
});
|
|
2244
|
+
for (const [id, node] of this.nodes) {
|
|
2245
|
+
if (node.type === "service") {
|
|
2246
|
+
const svc = String(node.data.service || "");
|
|
2247
|
+
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm"].some((s) => svc.includes(s))) {
|
|
2248
|
+
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return credId;
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Record a vulnerability finding.
|
|
2256
|
+
*/
|
|
2257
|
+
addVulnerability(title, target, severity, hasExploit = false) {
|
|
2258
|
+
const vulnId = this.addNode("vulnerability", title, {
|
|
2259
|
+
target,
|
|
2260
|
+
severity,
|
|
2261
|
+
hasExploit
|
|
2262
|
+
});
|
|
2263
|
+
for (const [id, node] of this.nodes) {
|
|
2264
|
+
if (node.type === "service" && node.label.includes(target)) {
|
|
2265
|
+
this.addEdge(id, vulnId, "has_vulnerability", 0.8);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
if (hasExploit) {
|
|
2269
|
+
const accessId = this.addNode("access", `shell via ${title}`, {
|
|
2270
|
+
via: title,
|
|
2271
|
+
status: GRAPH_STATUS.POTENTIAL
|
|
2272
|
+
});
|
|
2273
|
+
this.addEdge(vulnId, accessId, "leads_to", 0.7);
|
|
2274
|
+
}
|
|
2275
|
+
return vulnId;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Record gained access.
|
|
2279
|
+
*/
|
|
2280
|
+
addAccess(host, level, via) {
|
|
2281
|
+
const accessId = this.addNode("access", `${level}@${host}`, {
|
|
2282
|
+
host,
|
|
2283
|
+
level,
|
|
2284
|
+
via
|
|
2285
|
+
});
|
|
2286
|
+
this.markExploited(accessId);
|
|
2287
|
+
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
2288
|
+
const lootId = this.addNode("loot", `flags on ${host}`, {
|
|
2289
|
+
host,
|
|
2290
|
+
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
2291
|
+
});
|
|
2292
|
+
this.addEdge(accessId, lootId, "can_access", 0.9);
|
|
2293
|
+
}
|
|
2294
|
+
return accessId;
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Recommend attack chains based on current graph state.
|
|
2298
|
+
* Returns chains sorted by probability (highest first).
|
|
2299
|
+
*/
|
|
2300
|
+
recommendChains() {
|
|
2301
|
+
const chains = [];
|
|
2302
|
+
for (const edge of this.edges) {
|
|
2303
|
+
if (edge.relation === "can_try_on") {
|
|
2304
|
+
const cred = this.nodes.get(edge.from);
|
|
2305
|
+
const service = this.nodes.get(edge.to);
|
|
2306
|
+
if (cred && service && !service.exploited) {
|
|
2307
|
+
chains.push({
|
|
2308
|
+
steps: [cred, service],
|
|
2309
|
+
description: `Credential spray: ${cred.label} \u2192 ${service.label}`,
|
|
2310
|
+
probability: edge.confidence,
|
|
2311
|
+
impact: "high"
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
for (const edge of this.edges) {
|
|
2317
|
+
if (edge.relation === "leads_to") {
|
|
2318
|
+
const vuln = this.nodes.get(edge.from);
|
|
2319
|
+
const access = this.nodes.get(edge.to);
|
|
2320
|
+
if (vuln && access && !vuln.exploited) {
|
|
2321
|
+
chains.push({
|
|
2322
|
+
steps: [vuln, access],
|
|
2323
|
+
description: `Exploit: ${vuln.label} \u2192 ${access.label}`,
|
|
2324
|
+
probability: edge.confidence,
|
|
2325
|
+
impact: "critical"
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
for (const edge of this.edges) {
|
|
2331
|
+
if (edge.relation === "can_access") {
|
|
2332
|
+
const access = this.nodes.get(edge.from);
|
|
2333
|
+
const loot = this.nodes.get(edge.to);
|
|
2334
|
+
if (access && loot && access.exploited && !loot.exploited) {
|
|
2335
|
+
chains.push({
|
|
2336
|
+
steps: [access, loot],
|
|
2337
|
+
description: `Collect: ${access.label} \u2192 ${loot.label}`,
|
|
2338
|
+
probability: edge.confidence,
|
|
2339
|
+
impact: "high"
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
const impactWeight = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
2345
|
+
chains.sort(
|
|
2346
|
+
(a, b) => b.probability * (impactWeight[b.impact] || 1) - a.probability * (impactWeight[a.impact] || 1)
|
|
2347
|
+
);
|
|
2348
|
+
return chains.slice(0, GRAPH_LIMITS.MAX_CHAINS);
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Format attack graph status for prompt injection.
|
|
2352
|
+
*/
|
|
2353
|
+
toPrompt() {
|
|
2354
|
+
if (this.nodes.size === 0) return "";
|
|
2355
|
+
const lines = ["<attack-graph>"];
|
|
2356
|
+
const nodesByType = {};
|
|
2357
|
+
for (const node of this.nodes.values()) {
|
|
2358
|
+
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
2359
|
+
}
|
|
2360
|
+
lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
|
|
2361
|
+
lines.push(`Edges: ${this.edges.length} relationships`);
|
|
2362
|
+
const chains = this.recommendChains();
|
|
2363
|
+
if (chains.length > 0) {
|
|
2364
|
+
lines.push("");
|
|
2365
|
+
lines.push("RECOMMENDED NEXT ACTIONS:");
|
|
2366
|
+
for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
|
|
2367
|
+
const c = chains[i];
|
|
2368
|
+
const prob = (c.probability * 100).toFixed(0);
|
|
2369
|
+
lines.push(` ${i + 1}. [${c.impact.toUpperCase()} | ${prob}%] ${c.description}`);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
const unexploitedVulns = Array.from(this.nodes.values()).filter((n) => n.type === "vulnerability" && !n.exploited);
|
|
2373
|
+
if (unexploitedVulns.length > 0) {
|
|
2374
|
+
lines.push("");
|
|
2375
|
+
lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
|
|
2376
|
+
for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
|
|
2377
|
+
lines.push(` - ${v.label} (${v.data.severity || "unknown"})`);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
lines.push("</attack-graph>");
|
|
2381
|
+
return lines.join("\n");
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Get graph stats for metrics display.
|
|
2385
|
+
*/
|
|
2386
|
+
getStats() {
|
|
2387
|
+
const exploited = Array.from(this.nodes.values()).filter((n) => n.exploited).length;
|
|
2388
|
+
return {
|
|
2389
|
+
nodes: this.nodes.size,
|
|
2390
|
+
edges: this.edges.length,
|
|
2391
|
+
exploited,
|
|
2392
|
+
chains: this.recommendChains().length
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
|
|
2397
|
+
// src/shared/utils/agent-memory.ts
|
|
2398
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
2399
|
+
import { join as join3 } from "path";
|
|
2400
|
+
var WorkingMemory = class {
|
|
2401
|
+
entries = [];
|
|
2402
|
+
maxEntries = MEMORY_LIMITS.WORKING_MEMORY_MAX_ENTRIES;
|
|
2403
|
+
add(category, content, importance = 0.5, context = {}) {
|
|
2404
|
+
this.entries.push({
|
|
2405
|
+
id: `wm_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
2406
|
+
timestamp: Date.now(),
|
|
2407
|
+
category,
|
|
2408
|
+
content,
|
|
2409
|
+
context,
|
|
2410
|
+
importance
|
|
2411
|
+
});
|
|
2412
|
+
if (this.entries.length > this.maxEntries) {
|
|
2413
|
+
this.entries.sort((a, b) => {
|
|
2414
|
+
const aScore = a.importance * 0.7 + (1 - (Date.now() - a.timestamp) / 6e5) * 0.3;
|
|
2415
|
+
const bScore = b.importance * 0.7 + (1 - (Date.now() - b.timestamp) / 6e5) * 0.3;
|
|
2416
|
+
return bScore - aScore;
|
|
2417
|
+
});
|
|
2418
|
+
this.entries = this.entries.slice(0, this.maxEntries);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Record a failed attempt to avoid repeating it.
|
|
2423
|
+
*/
|
|
2424
|
+
recordFailure(tool, command, error) {
|
|
2425
|
+
this.add("failure", `FAILED: ${tool} \u2192 ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)} \u2192 ${error.slice(0, DISPLAY_LIMITS.ERROR_PREVIEW)}`, 0.8, { tool, command });
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2428
|
+
* Record a successful action for reference.
|
|
2429
|
+
*/
|
|
2430
|
+
recordSuccess(tool, command, result2) {
|
|
2431
|
+
this.add("success", `SUCCESS: ${tool} \u2192 ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}`, 0.6, { tool, result: result2.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY) });
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Check if a similar command has already failed.
|
|
2435
|
+
*/
|
|
2436
|
+
hasFailedBefore(command) {
|
|
2437
|
+
const lower = command.toLowerCase().split(/\s+/).slice(0, MEMORY_LIMITS.COMMAND_MATCH_WORDS).join(" ");
|
|
2438
|
+
return this.entries.find(
|
|
2439
|
+
(e) => e.category === "failure" && e.content.toLowerCase().includes(lower)
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Get count of consecutive failures (for vector switch detection).
|
|
2444
|
+
*/
|
|
2445
|
+
getConsecutiveFailures() {
|
|
2446
|
+
let count = 0;
|
|
2447
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2448
|
+
if (this.entries[i].category === "failure") count++;
|
|
2449
|
+
else break;
|
|
2450
|
+
}
|
|
2451
|
+
return count;
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Format for prompt injection.
|
|
2455
|
+
*/
|
|
2456
|
+
toPrompt() {
|
|
2457
|
+
if (this.entries.length === 0) return "";
|
|
2458
|
+
const failures = this.entries.filter((e) => e.category === "failure");
|
|
2459
|
+
const successes = this.entries.filter((e) => e.category === "success");
|
|
2460
|
+
const insights = this.entries.filter((e) => e.category === "insight" || e.category === "discovery");
|
|
2461
|
+
const lines = ["<working-memory>"];
|
|
2462
|
+
if (failures.length > 0) {
|
|
2463
|
+
lines.push(`\u26A0\uFE0F FAILED ATTEMPTS (${failures.length} \u2014 DO NOT REPEAT):`);
|
|
2464
|
+
for (const f of failures.slice(-5)) {
|
|
2465
|
+
lines.push(` \u2717 ${f.content}`);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const consecutiveFails = this.getConsecutiveFailures();
|
|
2469
|
+
if (consecutiveFails >= MEMORY_LIMITS.CONSECUTIVE_FAIL_THRESHOLD) {
|
|
2470
|
+
lines.push(`\u{1F534} ${consecutiveFails} CONSECUTIVE FAILURES \u2014 SWITCH ATTACK VECTOR NOW`);
|
|
2471
|
+
}
|
|
2472
|
+
if (successes.length > 0) {
|
|
2473
|
+
lines.push(`\u2705 RECENT SUCCESSES (${successes.length}):`);
|
|
2474
|
+
for (const s of successes.slice(-3)) {
|
|
2475
|
+
lines.push(` \u2713 ${s.content}`);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
if (insights.length > 0) {
|
|
2479
|
+
lines.push(`\u{1F4A1} INSIGHTS:`);
|
|
2480
|
+
for (const i of insights.slice(-3)) {
|
|
2481
|
+
lines.push(` \u2192 ${i.content}`);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
lines.push("</working-memory>");
|
|
2485
|
+
return lines.join("\n");
|
|
2486
|
+
}
|
|
2487
|
+
getEntries() {
|
|
2488
|
+
return [...this.entries];
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
var EpisodicMemory = class {
|
|
2492
|
+
events = [];
|
|
2493
|
+
maxEvents = MEMORY_LIMITS.EPISODIC_MEMORY_MAX_EVENTS;
|
|
2494
|
+
record(type, summary, details = {}) {
|
|
2495
|
+
this.events.push({
|
|
2496
|
+
timestamp: Date.now(),
|
|
2497
|
+
type,
|
|
2498
|
+
summary,
|
|
2499
|
+
details
|
|
2500
|
+
});
|
|
2501
|
+
if (this.events.length > this.maxEvents) {
|
|
2502
|
+
this.events = this.events.slice(-this.maxEvents);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Get timeline of key events (for strategic overview).
|
|
2507
|
+
*/
|
|
2508
|
+
getTimeline() {
|
|
2509
|
+
return [...this.events];
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Get events of a specific type.
|
|
2513
|
+
*/
|
|
2514
|
+
getByType(type) {
|
|
2515
|
+
return this.events.filter((e) => e.type === type);
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Count how many times a vector has been switched (indicator of difficulty).
|
|
2519
|
+
*/
|
|
2520
|
+
getVectorSwitchCount() {
|
|
2521
|
+
return this.events.filter((e) => e.type === "vector_switch").length;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Format for prompt (condensed timeline).
|
|
2525
|
+
*/
|
|
2526
|
+
toPrompt() {
|
|
2527
|
+
if (this.events.length === 0) return "";
|
|
2528
|
+
const lines = ["<session-timeline>"];
|
|
2529
|
+
const recent = this.events.slice(-10);
|
|
2530
|
+
for (const e of recent) {
|
|
2531
|
+
const mins = Math.floor((Date.now() - e.timestamp) / 6e4);
|
|
2532
|
+
const icon = {
|
|
2533
|
+
tool_success: "\u2705",
|
|
2534
|
+
tool_failure: "\u274C",
|
|
2535
|
+
phase_change: "\u{1F504}",
|
|
2536
|
+
flag_found: "\u{1F3C1}",
|
|
2537
|
+
access_gained: "\u{1F513}",
|
|
2538
|
+
vector_switch: "\u21AA\uFE0F"
|
|
2539
|
+
}[e.type] || "\u2022";
|
|
2540
|
+
lines.push(` ${icon} [${mins}min ago] ${e.summary}`);
|
|
2541
|
+
}
|
|
2542
|
+
lines.push("</session-timeline>");
|
|
2543
|
+
return lines.join("\n");
|
|
2544
|
+
}
|
|
2545
|
+
};
|
|
2546
|
+
var MEMORY_DIR = "/tmp/pentesting-memory";
|
|
2547
|
+
var MEMORY_FILE = join3(MEMORY_DIR, "persistent-knowledge.json");
|
|
2548
|
+
var PersistentMemory = class {
|
|
2549
|
+
knowledge;
|
|
2550
|
+
constructor() {
|
|
2551
|
+
this.knowledge = this.load();
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Record a successful technique.
|
|
2555
|
+
*/
|
|
2556
|
+
recordSuccess(service, version, technique) {
|
|
2557
|
+
const existing = this.knowledge.successfulTechniques.find(
|
|
2558
|
+
(t) => t.service === service && t.technique === technique
|
|
2559
|
+
);
|
|
2560
|
+
if (existing) {
|
|
2561
|
+
existing.successCount++;
|
|
2562
|
+
existing.lastUsed = Date.now();
|
|
2563
|
+
} else {
|
|
2564
|
+
this.knowledge.successfulTechniques.push({
|
|
2565
|
+
service,
|
|
2566
|
+
version,
|
|
2567
|
+
technique,
|
|
2568
|
+
successCount: 1,
|
|
2569
|
+
lastUsed: Date.now()
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
this.save();
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Record a failure pattern to avoid.
|
|
2576
|
+
*/
|
|
2577
|
+
recordFailurePattern(pattern, reason) {
|
|
2578
|
+
const existing = this.knowledge.failurePatterns.find((f) => f.pattern === pattern);
|
|
2579
|
+
if (existing) {
|
|
2580
|
+
existing.avoidCount++;
|
|
2581
|
+
} else {
|
|
2582
|
+
this.knowledge.failurePatterns.push({ pattern, reason, avoidCount: 1 });
|
|
2583
|
+
}
|
|
2584
|
+
this.save();
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Learn a fact about a technology.
|
|
2588
|
+
*/
|
|
2589
|
+
learnFact(technology, fact) {
|
|
2590
|
+
if (!this.knowledge.techFacts.some((f) => f.technology === technology && f.fact === fact)) {
|
|
2591
|
+
this.knowledge.techFacts.push({ technology, fact, learnedAt: Date.now() });
|
|
2592
|
+
this.save();
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Get known successful techniques for a service.
|
|
2597
|
+
*/
|
|
2598
|
+
getSuccessfulTechniques(service) {
|
|
2599
|
+
return this.knowledge.successfulTechniques.filter((t) => t.service.toLowerCase().includes(service.toLowerCase())).sort((a, b) => b.successCount - a.successCount);
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Format for prompt injection (most relevant persistent knowledge).
|
|
2603
|
+
*/
|
|
2604
|
+
toPrompt(services) {
|
|
2605
|
+
const relevant = [];
|
|
2606
|
+
for (const svc of services) {
|
|
2607
|
+
const techniques = this.getSuccessfulTechniques(svc);
|
|
2608
|
+
if (techniques.length > 0) {
|
|
2609
|
+
relevant.push(`${svc}: ${techniques.map((t) => `${t.technique} (${t.successCount}x)`).join(", ")}`);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
if (relevant.length === 0) return "";
|
|
2613
|
+
return [
|
|
2614
|
+
"<persistent-memory>",
|
|
2615
|
+
"PREVIOUSLY SUCCESSFUL TECHNIQUES:",
|
|
2616
|
+
...relevant.map((r) => ` \u{1F4DA} ${r}`),
|
|
2617
|
+
"</persistent-memory>"
|
|
2618
|
+
].join("\n");
|
|
2619
|
+
}
|
|
2620
|
+
load() {
|
|
2621
|
+
try {
|
|
2622
|
+
if (existsSync4(MEMORY_FILE)) {
|
|
2623
|
+
return JSON.parse(readFileSync3(MEMORY_FILE, "utf-8"));
|
|
2624
|
+
}
|
|
2625
|
+
} catch {
|
|
2626
|
+
}
|
|
2627
|
+
return { successfulTechniques: [], failurePatterns: [], techFacts: [] };
|
|
2628
|
+
}
|
|
2629
|
+
save() {
|
|
2630
|
+
try {
|
|
2631
|
+
mkdirSync2(MEMORY_DIR, { recursive: true });
|
|
2632
|
+
writeFileSync4(MEMORY_FILE, JSON.stringify(this.knowledge, null, 2));
|
|
2633
|
+
} catch {
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
|
|
2638
|
+
// src/shared/utils/dynamic-techniques.ts
|
|
2639
|
+
var DynamicTechniqueLibrary = class {
|
|
2640
|
+
techniques = [];
|
|
2641
|
+
maxTechniques = MEMORY_LIMITS.DYNAMIC_TECHNIQUES_MAX;
|
|
2642
|
+
/**
|
|
2643
|
+
* Learn a new technique from a search result or successful exploit.
|
|
2644
|
+
*/
|
|
2645
|
+
learn(technique) {
|
|
2646
|
+
const exists = this.techniques.some(
|
|
2647
|
+
(t) => t.technique.toLowerCase() === technique.technique.toLowerCase()
|
|
2648
|
+
);
|
|
2649
|
+
if (exists) return;
|
|
2650
|
+
this.techniques.push({
|
|
2651
|
+
...technique,
|
|
2652
|
+
learnedAt: Date.now()
|
|
2653
|
+
});
|
|
2654
|
+
if (this.techniques.length > this.maxTechniques) {
|
|
2655
|
+
this.techniques.sort((a, b) => {
|
|
2656
|
+
if (a.verified !== b.verified) return a.verified ? -1 : 1;
|
|
2657
|
+
return b.learnedAt - a.learnedAt;
|
|
2658
|
+
});
|
|
2659
|
+
this.techniques = this.techniques.slice(0, this.maxTechniques);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Extract and learn techniques from a web search result.
|
|
2664
|
+
*/
|
|
2665
|
+
learnFromSearchResult(query, resultText) {
|
|
2666
|
+
if (!resultText || resultText.length < 50) return;
|
|
2667
|
+
const patterns = [
|
|
2668
|
+
// "Step 1: ...", "1. Run ..."
|
|
2669
|
+
/(?:step\s*\d+|^\d+\.\s)[:.]?\s*(.{20,150})/gim,
|
|
2670
|
+
// "Exploit: ...", "Payload: ...", "Command: ..."
|
|
2671
|
+
/(?:exploit|payload|command|technique|bypass|trick):\s*(.{10,150})/gi,
|
|
2672
|
+
// Code blocks with commands
|
|
2673
|
+
/(?:```|`)((?:curl|wget|python|nmap|sqlmap|nikto|gobuster|ffuf)\s+.{10,100})(?:```|`)/g
|
|
2674
|
+
];
|
|
2675
|
+
const extracted = [];
|
|
2676
|
+
for (const pattern of patterns) {
|
|
2677
|
+
pattern.lastIndex = 0;
|
|
2678
|
+
let match;
|
|
2679
|
+
while ((match = pattern.exec(resultText)) !== null && extracted.length < 5) {
|
|
2680
|
+
const technique = (match[1] || match[0]).trim();
|
|
2681
|
+
if (technique.length > 10 && technique.length < 200) {
|
|
2682
|
+
extracted.push(technique);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
const applicableTo = this.extractTechnologies(query);
|
|
2687
|
+
for (const tech of extracted) {
|
|
2688
|
+
this.learn({
|
|
2689
|
+
source: `Web search: "${query}"`,
|
|
2690
|
+
technique: tech,
|
|
2691
|
+
applicableTo,
|
|
2692
|
+
verified: false,
|
|
2693
|
+
fromQuery: query
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Mark a technique as verified (it worked in practice).
|
|
2699
|
+
*/
|
|
2700
|
+
verify(techniqueSubstring) {
|
|
2701
|
+
for (const t of this.techniques) {
|
|
2702
|
+
if (t.technique.toLowerCase().includes(techniqueSubstring.toLowerCase())) {
|
|
2703
|
+
t.verified = true;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Get techniques relevant to a specific service/technology.
|
|
2709
|
+
*/
|
|
2710
|
+
getRelevant(service) {
|
|
2711
|
+
const lower = service.toLowerCase();
|
|
2712
|
+
return this.techniques.filter(
|
|
2713
|
+
(t) => t.applicableTo.some((a) => a.toLowerCase().includes(lower) || lower.includes(a.toLowerCase()))
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Get all learned techniques.
|
|
2718
|
+
*/
|
|
2719
|
+
getAll() {
|
|
2720
|
+
return [...this.techniques];
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Format for prompt injection.
|
|
2724
|
+
*/
|
|
2725
|
+
toPrompt() {
|
|
2726
|
+
if (this.techniques.length === 0) return "";
|
|
2727
|
+
const verified = this.techniques.filter((t) => t.verified);
|
|
2728
|
+
const unverified = this.techniques.filter((t) => !t.verified);
|
|
2729
|
+
const lines = ["<learned-techniques>"];
|
|
2730
|
+
if (verified.length > 0) {
|
|
2731
|
+
lines.push("VERIFIED (worked in this session):");
|
|
2732
|
+
for (const t of verified) {
|
|
2733
|
+
lines.push(` \u2705 [${t.applicableTo.join(",")}] ${t.technique}`);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (unverified.length > 0) {
|
|
2737
|
+
lines.push(`DISCOVERED (${unverified.length} unverified):`);
|
|
2738
|
+
for (const t of unverified.slice(0, MEMORY_LIMITS.PROMPT_UNVERIFIED_TECHNIQUES)) {
|
|
2739
|
+
lines.push(` \u{1F4A1} [${t.applicableTo.join(",")}] ${t.technique} (from: ${t.source})`);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
lines.push("</learned-techniques>");
|
|
2743
|
+
return lines.join("\n");
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Extract technology/service names from a search query.
|
|
2747
|
+
*/
|
|
2748
|
+
extractTechnologies(query) {
|
|
2749
|
+
const techs = [];
|
|
2750
|
+
const knownTechs = [
|
|
2751
|
+
"apache",
|
|
2752
|
+
"nginx",
|
|
2753
|
+
"iis",
|
|
2754
|
+
"tomcat",
|
|
2755
|
+
"nodejs",
|
|
2756
|
+
"express",
|
|
2757
|
+
"flask",
|
|
2758
|
+
"django",
|
|
2759
|
+
"rails",
|
|
2760
|
+
"php",
|
|
2761
|
+
"wordpress",
|
|
2762
|
+
"joomla",
|
|
2763
|
+
"drupal",
|
|
2764
|
+
"mysql",
|
|
2765
|
+
"postgresql",
|
|
2766
|
+
"mssql",
|
|
2767
|
+
"mongodb",
|
|
2768
|
+
"redis",
|
|
2769
|
+
"memcached",
|
|
2770
|
+
"ssh",
|
|
2771
|
+
"ftp",
|
|
2772
|
+
"smb",
|
|
2773
|
+
"rdp",
|
|
2774
|
+
"ldap",
|
|
2775
|
+
"kerberos",
|
|
2776
|
+
"dns",
|
|
2777
|
+
"docker",
|
|
2778
|
+
"kubernetes",
|
|
2779
|
+
"jenkins",
|
|
2780
|
+
"gitlab",
|
|
2781
|
+
"grafana",
|
|
2782
|
+
"spring",
|
|
2783
|
+
"struts",
|
|
2784
|
+
"log4j",
|
|
2785
|
+
"jackson",
|
|
2786
|
+
"fastjson"
|
|
2787
|
+
];
|
|
2788
|
+
const lower = query.toLowerCase();
|
|
2789
|
+
for (const tech of knownTechs) {
|
|
2790
|
+
if (lower.includes(tech)) techs.push(tech);
|
|
2791
|
+
}
|
|
2792
|
+
const versionMatch = query.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
2793
|
+
if (versionMatch && techs.length > 0) {
|
|
2794
|
+
techs[0] = `${techs[0]}/${versionMatch[1]}`;
|
|
2795
|
+
}
|
|
2796
|
+
return techs.length > 0 ? techs : ["general"];
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2150
2800
|
// src/engine/state.ts
|
|
2151
2801
|
var SharedState = class {
|
|
2152
2802
|
data;
|
|
2803
|
+
// ─── Improvement #6: Attack Graph ──────────────────────────────
|
|
2804
|
+
attackGraph = new AttackGraph();
|
|
2805
|
+
// ─── Improvement #12: Multi-Tier Memory ───────────────────────
|
|
2806
|
+
workingMemory = new WorkingMemory();
|
|
2807
|
+
episodicMemory = new EpisodicMemory();
|
|
2808
|
+
persistentMemory = new PersistentMemory();
|
|
2809
|
+
// ─── Improvement #7: Dynamic Technique Library ────────────────
|
|
2810
|
+
dynamicTechniques = new DynamicTechniqueLibrary();
|
|
2153
2811
|
constructor() {
|
|
2154
2812
|
this.data = {
|
|
2155
2813
|
engagement: null,
|
|
@@ -2165,7 +2823,8 @@ var SharedState = class {
|
|
|
2165
2823
|
// CTF mode ON by default
|
|
2166
2824
|
flags: [],
|
|
2167
2825
|
startedAt: Date.now(),
|
|
2168
|
-
deadlineAt: 0
|
|
2826
|
+
deadlineAt: 0,
|
|
2827
|
+
challengeAnalysis: null
|
|
2169
2828
|
};
|
|
2170
2829
|
}
|
|
2171
2830
|
// --- Mission & Persistent Context ---
|
|
@@ -2318,11 +2977,19 @@ var SharedState = class {
|
|
|
2318
2977
|
addFlag(flag) {
|
|
2319
2978
|
if (this.data.flags.includes(flag)) return false;
|
|
2320
2979
|
this.data.flags.push(flag);
|
|
2980
|
+
this.episodicMemory.record("flag_found", `Flag captured: ${flag.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}...`);
|
|
2321
2981
|
return true;
|
|
2322
2982
|
}
|
|
2323
2983
|
getFlags() {
|
|
2324
2984
|
return this.data.flags;
|
|
2325
2985
|
}
|
|
2986
|
+
// --- Challenge Analysis (#2: Auto-Prompter) ---
|
|
2987
|
+
setChallengeAnalysis(analysis) {
|
|
2988
|
+
this.data.challengeAnalysis = analysis;
|
|
2989
|
+
}
|
|
2990
|
+
getChallengeAnalysis() {
|
|
2991
|
+
return this.data.challengeAnalysis;
|
|
2992
|
+
}
|
|
2326
2993
|
// --- Time Management ---
|
|
2327
2994
|
/** Set a deadline for time-aware strategy (e.g., CTF competition end time) */
|
|
2328
2995
|
setDeadline(deadlineMs) {
|
|
@@ -2443,77 +3110,15 @@ var ScopeGuard = class {
|
|
|
2443
3110
|
this.state = state;
|
|
2444
3111
|
}
|
|
2445
3112
|
/**
|
|
2446
|
-
*
|
|
3113
|
+
* Always allow — Docker container is the security boundary.
|
|
3114
|
+
* The agent should try everything; observability > restriction.
|
|
2447
3115
|
*/
|
|
2448
|
-
check(
|
|
2449
|
-
const passiveTools = [
|
|
2450
|
-
TOOL_NAMES.READ_FILE,
|
|
2451
|
-
TOOL_NAMES.SEARCH_CVE,
|
|
2452
|
-
TOOL_NAMES.PARSE_NMAP,
|
|
2453
|
-
TOOL_NAMES.WEB_SEARCH,
|
|
2454
|
-
TOOL_NAMES.ADD_FINDING,
|
|
2455
|
-
TOOL_NAMES.UPDATE_TODO,
|
|
2456
|
-
TOOL_NAMES.GET_STATE,
|
|
2457
|
-
TOOL_NAMES.HELP,
|
|
2458
|
-
// Browser tools are reconnaissance — allow freely
|
|
2459
|
-
TOOL_NAMES.BROWSE_URL,
|
|
2460
|
-
TOOL_NAMES.FILL_FORM,
|
|
2461
|
-
// State mutation tools
|
|
2462
|
-
TOOL_NAMES.ADD_TARGET,
|
|
2463
|
-
TOOL_NAMES.ADD_LOOT,
|
|
2464
|
-
TOOL_NAMES.UPDATE_MISSION,
|
|
2465
|
-
TOOL_NAMES.WRITE_FILE
|
|
2466
|
-
];
|
|
2467
|
-
if (passiveTools.includes(toolCall.name)) {
|
|
2468
|
-
return { isAllowed: true };
|
|
2469
|
-
}
|
|
2470
|
-
let scope = this.state.getScope();
|
|
2471
|
-
if (!scope) {
|
|
2472
|
-
const targets2 = this.state.getTargets();
|
|
2473
|
-
if (targets2.size > 0) {
|
|
2474
|
-
const cidrs = [];
|
|
2475
|
-
const domains = [];
|
|
2476
|
-
for (const [ip, target] of targets2) {
|
|
2477
|
-
cidrs.push(ip);
|
|
2478
|
-
if (target.hostname) domains.push(target.hostname);
|
|
2479
|
-
}
|
|
2480
|
-
scope = {
|
|
2481
|
-
allowedCidrs: cidrs,
|
|
2482
|
-
allowedDomains: domains,
|
|
2483
|
-
exclusions: [],
|
|
2484
|
-
isDOSAllowed: false,
|
|
2485
|
-
isSocialAllowed: false
|
|
2486
|
-
};
|
|
2487
|
-
this.state.setScope(scope);
|
|
2488
|
-
} else {
|
|
2489
|
-
return { isAllowed: true, reason: "No scope defined. Proceeding with caution." };
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
let command = "";
|
|
2493
|
-
if (toolCall.name === TOOL_NAMES.RUN_CMD) {
|
|
2494
|
-
command = String(toolCall.input.command || "");
|
|
2495
|
-
}
|
|
2496
|
-
if (!command) {
|
|
2497
|
-
return { isAllowed: true };
|
|
2498
|
-
}
|
|
2499
|
-
const targets = this.extractTargets(command);
|
|
2500
|
-
const violations = [];
|
|
2501
|
-
for (const target of targets) {
|
|
2502
|
-
if (!this.isTargetInScope(target)) {
|
|
2503
|
-
violations.push(target);
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
if (violations.length > 0) {
|
|
2507
|
-
return {
|
|
2508
|
-
isAllowed: false,
|
|
2509
|
-
reason: `Target(s) outside approved scope: ${violations.join(", ")}`,
|
|
2510
|
-
violations
|
|
2511
|
-
};
|
|
2512
|
-
}
|
|
3116
|
+
check(_toolCall) {
|
|
2513
3117
|
return { isAllowed: true };
|
|
2514
3118
|
}
|
|
2515
3119
|
/**
|
|
2516
|
-
*
|
|
3120
|
+
* Advisory check: is a specific target in scope?
|
|
3121
|
+
* Still functional for attack graph analysis and recommendations.
|
|
2517
3122
|
*/
|
|
2518
3123
|
isTargetInScope(target) {
|
|
2519
3124
|
const scope = this.state.getScope();
|
|
@@ -2532,7 +3137,6 @@ var ScopeGuard = class {
|
|
|
2532
3137
|
/**
|
|
2533
3138
|
* Accurate CIDR matching using bitwise arithmetic.
|
|
2534
3139
|
* Supports any prefix length /0 through /32.
|
|
2535
|
-
* No external dependencies — pure math.
|
|
2536
3140
|
*/
|
|
2537
3141
|
matchesCidr(target, cidr) {
|
|
2538
3142
|
if (target === cidr) return true;
|
|
@@ -2554,7 +3158,6 @@ var ScopeGuard = class {
|
|
|
2554
3158
|
}
|
|
2555
3159
|
/**
|
|
2556
3160
|
* Convert IPv4 dotted-decimal string to unsigned 32-bit integer.
|
|
2557
|
-
* Returns null if the string is not a valid IPv4 address.
|
|
2558
3161
|
*/
|
|
2559
3162
|
ipToUint32(ip) {
|
|
2560
3163
|
const parts = ip.split(".");
|
|
@@ -2568,152 +3171,732 @@ var ScopeGuard = class {
|
|
|
2568
3171
|
return result2;
|
|
2569
3172
|
}
|
|
2570
3173
|
/**
|
|
2571
|
-
* Extract IPs and Domains from string
|
|
3174
|
+
* Extract IPs and Domains from string.
|
|
3175
|
+
* Refined to avoid false positives on filenames and Python modules.
|
|
2572
3176
|
*/
|
|
2573
3177
|
extractTargets(text) {
|
|
2574
3178
|
const targets = /* @__PURE__ */ new Set();
|
|
2575
3179
|
const ipv4Regex = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
|
|
2576
3180
|
(text.match(ipv4Regex) || []).forEach((ip) => targets.add(ip));
|
|
3181
|
+
const fileExtensions = /* @__PURE__ */ new Set([
|
|
3182
|
+
"py",
|
|
3183
|
+
"txt",
|
|
3184
|
+
"sh",
|
|
3185
|
+
"js",
|
|
3186
|
+
"ts",
|
|
3187
|
+
"c",
|
|
3188
|
+
"cpp",
|
|
3189
|
+
"h",
|
|
3190
|
+
"rb",
|
|
3191
|
+
"pl",
|
|
3192
|
+
"go",
|
|
3193
|
+
"rs",
|
|
3194
|
+
"java",
|
|
3195
|
+
"class",
|
|
3196
|
+
"jar",
|
|
3197
|
+
"php",
|
|
3198
|
+
"html",
|
|
3199
|
+
"css",
|
|
3200
|
+
"xml",
|
|
3201
|
+
"json",
|
|
3202
|
+
"yaml",
|
|
3203
|
+
"yml",
|
|
3204
|
+
"toml",
|
|
3205
|
+
"conf",
|
|
3206
|
+
"cfg",
|
|
3207
|
+
"ini",
|
|
3208
|
+
"log",
|
|
3209
|
+
"tmp",
|
|
3210
|
+
"bak",
|
|
3211
|
+
"sql",
|
|
3212
|
+
"db",
|
|
3213
|
+
"csv",
|
|
3214
|
+
"md",
|
|
3215
|
+
"rst",
|
|
3216
|
+
"elf",
|
|
3217
|
+
"bin",
|
|
3218
|
+
"exe",
|
|
3219
|
+
"dll",
|
|
3220
|
+
"so",
|
|
3221
|
+
"o",
|
|
3222
|
+
"a",
|
|
3223
|
+
"png",
|
|
3224
|
+
"jpg",
|
|
3225
|
+
"gif",
|
|
3226
|
+
"pdf",
|
|
3227
|
+
"zip",
|
|
3228
|
+
"gz",
|
|
3229
|
+
"tar",
|
|
3230
|
+
"pcap",
|
|
3231
|
+
"cap",
|
|
3232
|
+
"pem",
|
|
3233
|
+
"key",
|
|
3234
|
+
"crt",
|
|
3235
|
+
"csr",
|
|
3236
|
+
"der",
|
|
3237
|
+
"p12",
|
|
3238
|
+
"enc",
|
|
3239
|
+
"dec"
|
|
3240
|
+
]);
|
|
2577
3241
|
const domainRegex = /\b[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}\b/gi;
|
|
2578
|
-
(text.match(domainRegex) || [])
|
|
3242
|
+
for (const match of text.match(domainRegex) || []) {
|
|
3243
|
+
const lower = match.toLowerCase();
|
|
3244
|
+
const ext = lower.split(".").pop() || "";
|
|
3245
|
+
if (fileExtensions.has(ext)) continue;
|
|
3246
|
+
if (/^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/i.test(match)) continue;
|
|
3247
|
+
targets.add(lower);
|
|
3248
|
+
}
|
|
2579
3249
|
return targets;
|
|
2580
3250
|
}
|
|
2581
3251
|
};
|
|
2582
3252
|
|
|
2583
|
-
// src/engine/approval.ts
|
|
2584
|
-
var CATEGORY_APPROVAL = {
|
|
2585
|
-
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
2586
|
-
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
2587
|
-
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
2588
|
-
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
2589
|
-
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
2590
|
-
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
2591
|
-
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
2592
|
-
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
2593
|
-
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
2594
|
-
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
2595
|
-
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
2596
|
-
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
3253
|
+
// src/engine/approval.ts
|
|
3254
|
+
var CATEGORY_APPROVAL = {
|
|
3255
|
+
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
3256
|
+
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
3257
|
+
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
3258
|
+
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
3259
|
+
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
3260
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
3261
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
3262
|
+
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
3263
|
+
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
3264
|
+
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
3265
|
+
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
3266
|
+
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
3267
|
+
};
|
|
3268
|
+
var ApprovalGate = class {
|
|
3269
|
+
constructor(shouldAutoApprove = false) {
|
|
3270
|
+
this.shouldAutoApprove = shouldAutoApprove;
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Set auto-approve mode
|
|
3274
|
+
*/
|
|
3275
|
+
setAutoApprove(enabled) {
|
|
3276
|
+
this.shouldAutoApprove = enabled;
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Get current auto-approve mode
|
|
3280
|
+
*/
|
|
3281
|
+
isAutoApprove() {
|
|
3282
|
+
return this.shouldAutoApprove;
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Always approve — Docker container is the security boundary.
|
|
3286
|
+
* The agent must have unrestricted freedom to exploit targets.
|
|
3287
|
+
* Category/level system retained for audit trail only.
|
|
3288
|
+
*/
|
|
3289
|
+
async request(_toolCall) {
|
|
3290
|
+
return { isApproved: true };
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
|
|
3294
|
+
// src/shared/constants/pentest.ts
|
|
3295
|
+
var WORDLISTS = {
|
|
3296
|
+
// Most commonly used password wordlists
|
|
3297
|
+
ROCKYOU: "/usr/share/wordlists/rockyou.txt",
|
|
3298
|
+
COMMON_PASSWORDS: "/usr/share/seclists/Passwords/common-passwords.txt",
|
|
3299
|
+
PASSWORDS_10K: "/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt",
|
|
3300
|
+
// Username lists
|
|
3301
|
+
USERNAMES: "/usr/share/seclists/Usernames/top-usernames-shortlist.txt",
|
|
3302
|
+
// Web content discovery
|
|
3303
|
+
DIRB_COMMON: "/usr/share/wordlists/dirb/common.txt",
|
|
3304
|
+
RAFT_MEDIUM_DIRS: "/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt",
|
|
3305
|
+
// DNS/Subdomain discovery
|
|
3306
|
+
SUBDOMAINS: "/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt",
|
|
3307
|
+
// API testing
|
|
3308
|
+
API_ENDPOINTS: "/usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt",
|
|
3309
|
+
API_FUZZ: "/usr/share/seclists/Fuzzing/api-fuzz.txt"
|
|
3310
|
+
};
|
|
3311
|
+
var HASHCAT_MODES = {
|
|
3312
|
+
MD5: "0",
|
|
3313
|
+
SHA1: "100",
|
|
3314
|
+
NTLM: "1000",
|
|
3315
|
+
SHA256: "1400",
|
|
3316
|
+
bcrypt: "3200",
|
|
3317
|
+
WPA: "2500"
|
|
3318
|
+
};
|
|
3319
|
+
var NOISE_CLASSIFICATION = {
|
|
3320
|
+
HIGH: [
|
|
3321
|
+
"arp_spoof",
|
|
3322
|
+
"mitm_proxy",
|
|
3323
|
+
"packet_sniff",
|
|
3324
|
+
"dns_spoof",
|
|
3325
|
+
"traffic_intercept",
|
|
3326
|
+
"nmap",
|
|
3327
|
+
"masscan",
|
|
3328
|
+
"nuclei",
|
|
3329
|
+
"nikto"
|
|
3330
|
+
],
|
|
3331
|
+
MEDIUM: [
|
|
3332
|
+
"ffuf",
|
|
3333
|
+
"gobuster",
|
|
3334
|
+
"dirsearch",
|
|
3335
|
+
"feroxbuster"
|
|
3336
|
+
]
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3339
|
+
// src/shared/utils/binary-analysis.ts
|
|
3340
|
+
function parseChecksec(output) {
|
|
3341
|
+
const info = {};
|
|
3342
|
+
if (/x86-64|amd64/i.test(output)) {
|
|
3343
|
+
info.arch = "amd64";
|
|
3344
|
+
info.bits = 64;
|
|
3345
|
+
info.endian = "little";
|
|
3346
|
+
} else if (/x86|i[3-6]86/i.test(output)) {
|
|
3347
|
+
info.arch = "i386";
|
|
3348
|
+
info.bits = 32;
|
|
3349
|
+
info.endian = "little";
|
|
3350
|
+
} else if (/aarch64|arm64/i.test(output)) {
|
|
3351
|
+
info.arch = "aarch64";
|
|
3352
|
+
info.bits = 64;
|
|
3353
|
+
info.endian = "little";
|
|
3354
|
+
} else if (/arm/i.test(output)) {
|
|
3355
|
+
info.arch = "arm";
|
|
3356
|
+
info.bits = 32;
|
|
3357
|
+
info.endian = "little";
|
|
3358
|
+
} else if (/mips/i.test(output)) {
|
|
3359
|
+
info.arch = "mips";
|
|
3360
|
+
info.bits = 32;
|
|
3361
|
+
info.endian = "big";
|
|
3362
|
+
}
|
|
3363
|
+
if (/no\s+canary|canary\s*(?:not\s+found|disabled|no)/i.test(output)) info.canary = false;
|
|
3364
|
+
else info.canary = /canary\s*(?:found|enabled|yes)/i.test(output);
|
|
3365
|
+
if (/nx\s*(?:disabled|no)|NX:\s*NX disabled/i.test(output)) info.nx = false;
|
|
3366
|
+
else info.nx = /nx\s*(?:enabled|yes)|NX:\s*NX enabled/i.test(output);
|
|
3367
|
+
if (/no\s+pie|pie\s*(?:disabled|not|no)/i.test(output)) info.pie = false;
|
|
3368
|
+
else info.pie = /pie\s*(?:enabled|yes)|Type:\s*DYN/i.test(output);
|
|
3369
|
+
info.stripped = /stripped/i.test(output) && !/not stripped/i.test(output);
|
|
3370
|
+
info.staticallyLinked = /statically linked/i.test(output);
|
|
3371
|
+
if (/full relro/i.test(output)) info.relro = "full";
|
|
3372
|
+
else if (/partial relro/i.test(output)) info.relro = "partial";
|
|
3373
|
+
else info.relro = "no";
|
|
3374
|
+
return info;
|
|
3375
|
+
}
|
|
3376
|
+
function suggestExploitTechniques(info) {
|
|
3377
|
+
const suggestions = [];
|
|
3378
|
+
if (info.nx === false) {
|
|
3379
|
+
suggestions.push("NX disabled \u2192 Direct shellcode injection on stack/heap");
|
|
3380
|
+
suggestions.push("jmp esp / call esp gadget for stack pivot");
|
|
3381
|
+
}
|
|
3382
|
+
if (info.canary === false) {
|
|
3383
|
+
suggestions.push("No stack canary \u2192 Classic buffer overflow, overwrite return address");
|
|
3384
|
+
if (info.nx) suggestions.push("ROP chain required (NX enabled)");
|
|
3385
|
+
}
|
|
3386
|
+
if (info.pie === false) {
|
|
3387
|
+
suggestions.push("No PIE \u2192 Fixed binary base address, use binary gadgets directly");
|
|
3388
|
+
suggestions.push("ret2plt possible (call functions via PLT)");
|
|
3389
|
+
}
|
|
3390
|
+
if (info.pie === true && info.canary === false) {
|
|
3391
|
+
suggestions.push("PIE enabled \u2192 Need info leak first (partial overwrite, format string)");
|
|
3392
|
+
}
|
|
3393
|
+
if (info.relro === "partial") {
|
|
3394
|
+
suggestions.push("Partial RELRO \u2192 GOT overwrite possible");
|
|
3395
|
+
}
|
|
3396
|
+
if (info.relro === "full") {
|
|
3397
|
+
suggestions.push("Full RELRO \u2192 GOT is read-only, try __malloc_hook/__free_hook or stack-based attacks");
|
|
3398
|
+
}
|
|
3399
|
+
if (info.canary === true) {
|
|
3400
|
+
suggestions.push("Stack canary present \u2192 Look for format string leak, brute force (forking server), or canary bypass");
|
|
3401
|
+
}
|
|
3402
|
+
if (info.bits === 32) {
|
|
3403
|
+
suggestions.push('32-bit binary \u2192 ret2libc straightforward (system + "/bin/sh")');
|
|
3404
|
+
suggestions.push("Smaller address space \u2192 Brute force ASLR feasible");
|
|
3405
|
+
}
|
|
3406
|
+
if (info.bits === 64) {
|
|
3407
|
+
suggestions.push("64-bit \u2192 Arguments passed in registers (RDI, RSI, RDX). Need pop rdi; ret gadget");
|
|
3408
|
+
suggestions.push("One-gadget may work \u2192 one_gadget libc.so");
|
|
3409
|
+
}
|
|
3410
|
+
if (info.staticallyLinked) {
|
|
3411
|
+
suggestions.push("Statically linked \u2192 Lots of ROP gadgets available, ROPchain via ropper/ROPgadget");
|
|
3412
|
+
}
|
|
3413
|
+
return suggestions;
|
|
3414
|
+
}
|
|
3415
|
+
function formatBinaryAnalysis(info) {
|
|
3416
|
+
const suggestions = suggestExploitTechniques(info);
|
|
3417
|
+
const lines = [
|
|
3418
|
+
"<binary-analysis>",
|
|
3419
|
+
`Arch: ${info.arch || "?"} | Bits: ${info.bits || "?"} | Endian: ${info.endian || "?"}`,
|
|
3420
|
+
`Protections: Canary=${info.canary ?? "?"} NX=${info.nx ?? "?"} PIE=${info.pie ?? "?"} RELRO=${info.relro ?? "?"}`,
|
|
3421
|
+
`Stripped: ${info.stripped ?? "?"} | Static: ${info.staticallyLinked ?? "?"}`,
|
|
3422
|
+
"",
|
|
3423
|
+
"EXPLOIT SUGGESTIONS:",
|
|
3424
|
+
...suggestions.map((s) => ` \u2192 ${s}`),
|
|
3425
|
+
"</binary-analysis>"
|
|
3426
|
+
];
|
|
3427
|
+
return lines.join("\n");
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
// src/shared/utils/structured-output.ts
|
|
3431
|
+
function extractNmapStructured(output) {
|
|
3432
|
+
const ports = [];
|
|
3433
|
+
const hosts = [];
|
|
3434
|
+
const portRegex = /(\d+)\/(tcp|udp)\s+(open|filtered)\s+(\S+)[ \t]*([^\n]*)/g;
|
|
3435
|
+
let match;
|
|
3436
|
+
while ((match = portRegex.exec(output)) !== null) {
|
|
3437
|
+
ports.push({
|
|
3438
|
+
port: parseInt(match[1], 10),
|
|
3439
|
+
service: match[4],
|
|
3440
|
+
version: match[5]?.trim() || "",
|
|
3441
|
+
state: match[3]
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
const hostRegex = /Nmap scan report for\s+(\S+)/g;
|
|
3445
|
+
while ((match = hostRegex.exec(output)) !== null) {
|
|
3446
|
+
hosts.push(match[1]);
|
|
3447
|
+
}
|
|
3448
|
+
const openCount = ports.filter((p) => p.state === "open").length;
|
|
3449
|
+
const summary = `${hosts.length} host(s), ${openCount} open port(s): ${ports.filter((p) => p.state === "open").map((p) => `${p.port}/${p.service}`).join(", ")}`;
|
|
3450
|
+
return {
|
|
3451
|
+
summary,
|
|
3452
|
+
structured: { openPorts: ports, hosts }
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
function extractFuzzStructured(output) {
|
|
3456
|
+
const paths = [];
|
|
3457
|
+
const pathPatterns = [
|
|
3458
|
+
/\/\S+\s+\(Status:\s*(?:200|301|302|403)\)/g,
|
|
3459
|
+
// gobuster
|
|
3460
|
+
/\|\s*URL\s*\|\s*(\/\S+)/g,
|
|
3461
|
+
// ffuf table
|
|
3462
|
+
/^(\/\S+)\s+\[\d+\]/gm,
|
|
3463
|
+
// feroxbuster
|
|
3464
|
+
/Status:\s*(200|301|302|403)\s+.*?\s+(\/\S+)/g
|
|
3465
|
+
// generic
|
|
3466
|
+
];
|
|
3467
|
+
for (const pattern of pathPatterns) {
|
|
3468
|
+
let match;
|
|
3469
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
3470
|
+
const path2 = match[1] || match[0];
|
|
3471
|
+
const cleanPath = path2.replace(/\s.*$/, "").trim();
|
|
3472
|
+
if (cleanPath.startsWith("/") && !paths.includes(cleanPath)) {
|
|
3473
|
+
paths.push(cleanPath);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
const lines = output.split("\n");
|
|
3478
|
+
for (const line of lines) {
|
|
3479
|
+
const pathMatch = line.match(/^\s*(\/\S+)\s/);
|
|
3480
|
+
if (pathMatch && !paths.includes(pathMatch[1])) {
|
|
3481
|
+
paths.push(pathMatch[1]);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
summary: `${paths.length} path(s) discovered: ${paths.slice(0, 10).join(", ")}${paths.length > 10 ? "..." : ""}`,
|
|
3486
|
+
structured: { paths }
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
function extractCredentials(output) {
|
|
3490
|
+
const creds = [];
|
|
3491
|
+
const patterns = [
|
|
3492
|
+
// user:password from hydra, medusa, etc.
|
|
3493
|
+
{ regex: /login:\s*(\S+)\s+password:\s*(\S+)/gi, type: "password" },
|
|
3494
|
+
// user:hash from hashdump, secretsdump, etc.
|
|
3495
|
+
{ regex: /(\S+):(\d+):([a-f0-9]{32}):([a-f0-9]{32})/g, type: "hash" },
|
|
3496
|
+
// Generic user:pass pattern
|
|
3497
|
+
{ regex: /(?:username|user|login)\s*[:=]\s*['"]?(\S+?)['"]?\s+(?:password|pass|pwd)\s*[:=]\s*['"]?(\S+?)['"]?/gi, type: "password" },
|
|
3498
|
+
// Database connection strings
|
|
3499
|
+
{ regex: /(?:postgres|mysql|mongodb):\/\/([^:]+):([^@]+)@/g, type: "password" },
|
|
3500
|
+
// API tokens/keys
|
|
3501
|
+
{ regex: /(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: "token" }
|
|
3502
|
+
];
|
|
3503
|
+
for (const { regex, type } of patterns) {
|
|
3504
|
+
let match;
|
|
3505
|
+
while ((match = regex.exec(output)) !== null) {
|
|
3506
|
+
const username = match[1] || "unknown";
|
|
3507
|
+
const credential = match[2] || match[1];
|
|
3508
|
+
if (credential && !creds.some((c) => c.username === username && c.credential === credential)) {
|
|
3509
|
+
creds.push({ username, credential, type, source: "auto-extracted" });
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
return creds;
|
|
3514
|
+
}
|
|
3515
|
+
function extractVulnerabilities(output) {
|
|
3516
|
+
const vulns = [];
|
|
3517
|
+
const cveRegex = /CVE-\d{4}-\d{4,}/g;
|
|
3518
|
+
let match;
|
|
3519
|
+
while ((match = cveRegex.exec(output)) !== null) {
|
|
3520
|
+
const cveId = match[0];
|
|
3521
|
+
if (!vulns.some((v) => v.id === cveId)) {
|
|
3522
|
+
vulns.push({
|
|
3523
|
+
id: cveId,
|
|
3524
|
+
severity: "unknown",
|
|
3525
|
+
description: `${cveId} referenced in output`,
|
|
3526
|
+
hasExploit: /exploit|poc|proof.of.concept/i.test(output)
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
return vulns;
|
|
3531
|
+
}
|
|
3532
|
+
function autoExtractStructured(toolName, output) {
|
|
3533
|
+
if (!output || output.length < 10) return null;
|
|
3534
|
+
const data = {};
|
|
3535
|
+
let hasData = false;
|
|
3536
|
+
const creds = extractCredentials(output);
|
|
3537
|
+
if (creds && creds.length > 0) {
|
|
3538
|
+
data.credentials = creds;
|
|
3539
|
+
hasData = true;
|
|
3540
|
+
}
|
|
3541
|
+
const vulns = extractVulnerabilities(output);
|
|
3542
|
+
if (vulns && vulns.length > 0) {
|
|
3543
|
+
data.vulnerabilities = vulns;
|
|
3544
|
+
hasData = true;
|
|
3545
|
+
}
|
|
3546
|
+
if (toolName === "parse_nmap" || /nmap scan report/i.test(output)) {
|
|
3547
|
+
const nmap = extractNmapStructured(output);
|
|
3548
|
+
if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
|
|
3549
|
+
data.openPorts = nmap.structured.openPorts;
|
|
3550
|
+
data.hosts = nmap.structured.hosts;
|
|
3551
|
+
hasData = true;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
if (/gobuster|ffuf|feroxbuster|dirbuster/i.test(output)) {
|
|
3555
|
+
const fuzz = extractFuzzStructured(output);
|
|
3556
|
+
if (fuzz.structured.paths && fuzz.structured.paths.length > 0) {
|
|
3557
|
+
data.paths = fuzz.structured.paths;
|
|
3558
|
+
hasData = true;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
if (/canary|RELRO|NX|PIE|Stack|FORTIFY/i.test(output) && /enabled|disabled|found|no /i.test(output)) {
|
|
3562
|
+
const binaryInfo = parseChecksec(output);
|
|
3563
|
+
if (binaryInfo.arch || binaryInfo.canary !== void 0 || binaryInfo.nx !== void 0) {
|
|
3564
|
+
data.binaryInfo = binaryInfo;
|
|
3565
|
+
data.binaryAnalysisSummary = formatBinaryAnalysis(binaryInfo);
|
|
3566
|
+
hasData = true;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
return hasData ? data : null;
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
// src/shared/utils/ctf-knowledge.ts
|
|
3573
|
+
var FLAG_PATTERNS = {
|
|
3574
|
+
// Generic CTF flag formats
|
|
3575
|
+
generic: /flag\{[^}]+\}/gi,
|
|
3576
|
+
curly_upper: /FLAG\{[^}]+\}/g,
|
|
3577
|
+
// Platform-specific — Major International Competitions
|
|
3578
|
+
htb: /HTB\{[^}]+\}/g,
|
|
3579
|
+
thm: /THM\{[^}]+\}/g,
|
|
3580
|
+
picoCTF: /picoCTF\{[^}]+\}/g,
|
|
3581
|
+
defcon_ooo: /OOO\{[^}]+\}/g,
|
|
3582
|
+
// DEF CON CTF (Order of the Overflow)
|
|
3583
|
+
csaw: /CSAW\{[^}]+\}/g,
|
|
3584
|
+
// CSAW CTF
|
|
3585
|
+
google: /CTF\{[^}]+\}/g,
|
|
3586
|
+
// Google CTF
|
|
3587
|
+
dragon: /DrgnS\{[^}]+\}/g,
|
|
3588
|
+
// Dragon Sector
|
|
3589
|
+
hxp: /hxp\{[^}]+\}/g,
|
|
3590
|
+
// hxp CTF
|
|
3591
|
+
zer0pts: /zer0pts\{[^}]+\}/g,
|
|
3592
|
+
// zer0pts CTF
|
|
3593
|
+
// Asia-Pacific Major Competitions
|
|
3594
|
+
seccon: /SECCON\{[^}]+\}/g,
|
|
3595
|
+
// SECCON CTF (Japan)
|
|
3596
|
+
hitcon: /hitcon\{[^}]+\}/gi,
|
|
3597
|
+
// HITCON CTF (Taiwan)
|
|
3598
|
+
codegate: /codegate\{[^}]+\}/gi,
|
|
3599
|
+
// Codegate CTF (Korea)
|
|
3600
|
+
wacon: /WACON\{[^}]+\}/g,
|
|
3601
|
+
// WACON CTF (Korea)
|
|
3602
|
+
linectf: /LINECTF\{[^}]+\}/g,
|
|
3603
|
+
// LINE CTF (Korea/Japan)
|
|
3604
|
+
tsgctf: /TSGCTF\{[^}]+\}/g,
|
|
3605
|
+
// TSG CTF (Japan)
|
|
3606
|
+
kosenctf: /KosenCTF\{[^}]+\}/g,
|
|
3607
|
+
// Kosen CTF (Japan)
|
|
3608
|
+
asis: /ASIS\{[^}]+\}/g,
|
|
3609
|
+
// ASIS CTF (Iran)
|
|
3610
|
+
// Americas / Europe Major Competitions
|
|
3611
|
+
plaidctf: /PCTF\{[^}]+\}/g,
|
|
3612
|
+
// PlaidCTF (PPP)
|
|
3613
|
+
dicectf: /dice\{[^}]+\}/gi,
|
|
3614
|
+
// DiceCTF
|
|
3615
|
+
rwctf: /rwctf\{[^}]+\}/gi,
|
|
3616
|
+
// Real World CTF
|
|
3617
|
+
rctf: /RCTF\{[^}]+\}/g,
|
|
3618
|
+
// RCTF
|
|
3619
|
+
n1ctf: /N1CTF\{[^}]+\}/g,
|
|
3620
|
+
// N1CTF (Nu1L)
|
|
3621
|
+
bctf: /BCTF\{[^}]+\}/g,
|
|
3622
|
+
// BCTF
|
|
3623
|
+
_0ctf: /0CTF\{[^}]+\}/g,
|
|
3624
|
+
// 0CTF (Shanghai)
|
|
3625
|
+
defcamp: /CTF\{[^}]+\}/g,
|
|
3626
|
+
// DefCamp
|
|
3627
|
+
insomnihack: /INS\{[^}]+\}/g,
|
|
3628
|
+
// Insomni'hack
|
|
3629
|
+
justctf: /justCTF\{[^}]+\}/g,
|
|
3630
|
+
// justCTF
|
|
3631
|
+
lactf: /lactf\{[^}]+\}/gi,
|
|
3632
|
+
// LA CTF (UCLA)
|
|
3633
|
+
damctf: /dam\{[^}]+\}/gi,
|
|
3634
|
+
// DamCTF
|
|
3635
|
+
tjctf: /tjctf\{[^}]+\}/gi,
|
|
3636
|
+
// TJCTF
|
|
3637
|
+
buckeye: /buckeye\{[^}]+\}/gi,
|
|
3638
|
+
// BuckeyeCTF
|
|
3639
|
+
uiuctf: /uiuctf\{[^}]+\}/gi,
|
|
3640
|
+
// UIUCTF
|
|
3641
|
+
// Platform-specific
|
|
3642
|
+
cyber_apocalypse: /HTB\{[^}]+\}/g,
|
|
3643
|
+
// HTB Cyber Apocalypse (same as HTB)
|
|
3644
|
+
offshift: /oS\{[^}]+\}/g,
|
|
3645
|
+
// Offshift CTF
|
|
3646
|
+
imaginaryctf: /ictf\{[^}]+\}/gi,
|
|
3647
|
+
// ImaginaryCTF
|
|
3648
|
+
corctf: /corctf\{[^}]+\}/gi,
|
|
3649
|
+
// corCTF
|
|
3650
|
+
// Generic CTFd format — catches most custom competitions
|
|
3651
|
+
ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
|
|
3652
|
+
// Hash-style flags (HTB user.txt / root.txt — 32-char hex)
|
|
3653
|
+
hash_flag: /\b[a-f0-9]{32}\b/g,
|
|
3654
|
+
// Base64-encoded flag detection (common in steganography/forensics)
|
|
3655
|
+
base64_flag: /flag\{[A-Za-z0-9_+\-/=]+\}/gi
|
|
3656
|
+
};
|
|
3657
|
+
var MIN_FLAG_SCAN_LENGTH = 5;
|
|
3658
|
+
function detectFlags(output) {
|
|
3659
|
+
if (!output || output.length < MIN_FLAG_SCAN_LENGTH) return [];
|
|
3660
|
+
const found = /* @__PURE__ */ new Set();
|
|
3661
|
+
for (const pattern of Object.values(FLAG_PATTERNS)) {
|
|
3662
|
+
pattern.lastIndex = 0;
|
|
3663
|
+
const matches = output.match(pattern);
|
|
3664
|
+
if (matches) {
|
|
3665
|
+
for (const m of matches) found.add(m);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
return Array.from(found);
|
|
3669
|
+
}
|
|
3670
|
+
var CHALLENGE_TYPE_SIGNALS = {
|
|
3671
|
+
web: [
|
|
3672
|
+
"http",
|
|
3673
|
+
"https",
|
|
3674
|
+
"nginx",
|
|
3675
|
+
"apache",
|
|
3676
|
+
"flask",
|
|
3677
|
+
"express",
|
|
3678
|
+
"php",
|
|
3679
|
+
"node",
|
|
3680
|
+
"django",
|
|
3681
|
+
"rails",
|
|
3682
|
+
"wordpress",
|
|
3683
|
+
"login form",
|
|
3684
|
+
"graphql",
|
|
3685
|
+
"websocket",
|
|
3686
|
+
"oauth",
|
|
3687
|
+
"jwt",
|
|
3688
|
+
"cookie",
|
|
3689
|
+
"grpc"
|
|
3690
|
+
],
|
|
3691
|
+
pwn: [
|
|
3692
|
+
"buffer overflow",
|
|
3693
|
+
"SUID",
|
|
3694
|
+
"stack",
|
|
3695
|
+
"heap",
|
|
3696
|
+
"format string",
|
|
3697
|
+
"ELF",
|
|
3698
|
+
"binary",
|
|
3699
|
+
"pwntools",
|
|
3700
|
+
"libc",
|
|
3701
|
+
"canary",
|
|
3702
|
+
"NX",
|
|
3703
|
+
"PIE",
|
|
3704
|
+
"seccomp",
|
|
3705
|
+
"shellcode",
|
|
3706
|
+
"tcache",
|
|
3707
|
+
"fastbin",
|
|
3708
|
+
"house of"
|
|
3709
|
+
],
|
|
3710
|
+
crypto: [
|
|
3711
|
+
"RSA",
|
|
3712
|
+
"AES",
|
|
3713
|
+
"cipher",
|
|
3714
|
+
"decrypt",
|
|
3715
|
+
"encrypt",
|
|
3716
|
+
"key",
|
|
3717
|
+
"modulus",
|
|
3718
|
+
"prime",
|
|
3719
|
+
"hash",
|
|
3720
|
+
"base64",
|
|
3721
|
+
"XOR",
|
|
3722
|
+
"padding oracle",
|
|
3723
|
+
"elliptic curve",
|
|
3724
|
+
"lattice",
|
|
3725
|
+
"LLL",
|
|
3726
|
+
"ECDSA",
|
|
3727
|
+
"nonce"
|
|
3728
|
+
],
|
|
3729
|
+
forensics: [
|
|
3730
|
+
"pcap",
|
|
3731
|
+
"memory dump",
|
|
3732
|
+
"disk image",
|
|
3733
|
+
"steganography",
|
|
3734
|
+
"exif",
|
|
3735
|
+
"binwalk",
|
|
3736
|
+
"volatility",
|
|
3737
|
+
"autopsy",
|
|
3738
|
+
"file carving",
|
|
3739
|
+
"wireshark",
|
|
3740
|
+
"firmware",
|
|
3741
|
+
"ext4",
|
|
3742
|
+
"ntfs",
|
|
3743
|
+
"registry"
|
|
3744
|
+
],
|
|
3745
|
+
reversing: [
|
|
3746
|
+
"reverse",
|
|
3747
|
+
"disassemble",
|
|
3748
|
+
"decompile",
|
|
3749
|
+
"ghidra",
|
|
3750
|
+
"ida",
|
|
3751
|
+
"radare2",
|
|
3752
|
+
"upx",
|
|
3753
|
+
"packed",
|
|
3754
|
+
"obfuscated",
|
|
3755
|
+
"android",
|
|
3756
|
+
"apk",
|
|
3757
|
+
"angr",
|
|
3758
|
+
"z3",
|
|
3759
|
+
"frida",
|
|
3760
|
+
"crackme",
|
|
3761
|
+
"keygen",
|
|
3762
|
+
"dnspy"
|
|
3763
|
+
],
|
|
3764
|
+
misc: [
|
|
3765
|
+
"OSINT",
|
|
3766
|
+
"trivia",
|
|
3767
|
+
"scripting",
|
|
3768
|
+
"pyjail",
|
|
3769
|
+
"sandbox escape",
|
|
3770
|
+
"qr code",
|
|
3771
|
+
"morse",
|
|
3772
|
+
"braille",
|
|
3773
|
+
"jail",
|
|
3774
|
+
"rbash",
|
|
3775
|
+
"restricted",
|
|
3776
|
+
"seccomp",
|
|
3777
|
+
"filter bypass",
|
|
3778
|
+
"escape"
|
|
3779
|
+
]
|
|
3780
|
+
};
|
|
3781
|
+
|
|
3782
|
+
// src/shared/utils/auto-prompter.ts
|
|
3783
|
+
var SECONDARY_TYPE_RATIO = 0.5;
|
|
3784
|
+
var MIN_CHALLENGE_CONFIDENCE = 0.2;
|
|
3785
|
+
var MIN_RECON_OUTPUT_LENGTH = 100;
|
|
3786
|
+
var TYPE_TECHNIQUE_MAP = {
|
|
3787
|
+
web: ["injection", "auth-access", "file-attacks", "crypto"],
|
|
3788
|
+
pwn: ["pwn", "shells", "reversing"],
|
|
3789
|
+
crypto: ["crypto"],
|
|
3790
|
+
forensics: ["forensics", "reversing", "crypto"],
|
|
3791
|
+
reversing: ["reversing", "pwn"],
|
|
3792
|
+
misc: ["sandbox-escape", "crypto", "forensics"],
|
|
3793
|
+
network: ["network-svc", "shells", "lateral", "ad-attack"],
|
|
3794
|
+
unknown: ["network-svc", "injection", "shells", "file-attacks"]
|
|
2597
3795
|
};
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
2630
|
-
TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
2631
|
-
TOOL_NAMES.FILL_FORM,
|
|
2632
|
-
TOOL_NAMES.EXTRACT_URLS,
|
|
2633
|
-
// State recording (internal state mutations only)
|
|
2634
|
-
TOOL_NAMES.ADD_FINDING,
|
|
2635
|
-
TOOL_NAMES.ADD_TARGET,
|
|
2636
|
-
TOOL_NAMES.ADD_LOOT,
|
|
2637
|
-
TOOL_NAMES.UPDATE_MISSION,
|
|
2638
|
-
TOOL_NAMES.UPDATE_TODO,
|
|
2639
|
-
TOOL_NAMES.GET_STATE,
|
|
2640
|
-
TOOL_NAMES.HELP,
|
|
2641
|
-
TOOL_NAMES.HASH_CRACK,
|
|
2642
|
-
// Payload generation (no external effect until used)
|
|
2643
|
-
TOOL_NAMES.PAYLOAD_MUTATE
|
|
2644
|
-
];
|
|
2645
|
-
if (autoTools.includes(tool)) return APPROVAL_LEVELS.AUTO;
|
|
2646
|
-
return APPROVAL_LEVELS.CONFIRM;
|
|
2647
|
-
}
|
|
2648
|
-
var ApprovalGate = class {
|
|
2649
|
-
constructor(shouldAutoApprove = false) {
|
|
2650
|
-
this.shouldAutoApprove = shouldAutoApprove;
|
|
3796
|
+
var TYPE_PHASE_PROMPT_MAP = {
|
|
3797
|
+
web: "web.md",
|
|
3798
|
+
pwn: "exploit.md",
|
|
3799
|
+
crypto: "recon.md",
|
|
3800
|
+
forensics: "recon.md",
|
|
3801
|
+
reversing: "exploit.md",
|
|
3802
|
+
misc: "recon.md",
|
|
3803
|
+
network: "recon.md",
|
|
3804
|
+
unknown: "recon.md"
|
|
3805
|
+
};
|
|
3806
|
+
var TYPE_STRATEGY_MAP = {
|
|
3807
|
+
web: "Web challenge detected. Priority: directory fuzzing \u2192 injection testing \u2192 authentication bypass \u2192 file upload \u2192 SSRF/SSTI. Check source code, cookies, JWT tokens.",
|
|
3808
|
+
pwn: "Binary exploitation challenge. Priority: checksec \u2192 binary analysis \u2192 find vulnerability class \u2192 develop exploit. Tools: gdb, pwntools, radare2.",
|
|
3809
|
+
crypto: "Cryptography challenge. Priority: identify algorithm \u2192 find weakness \u2192 write solver script. Check for weak keys, reused nonces, padding oracle, ECB mode.",
|
|
3810
|
+
forensics: "Forensics challenge. Priority: file identification \u2192 extract data \u2192 analyze artifacts. Tools: binwalk, volatility, wireshark, exiftool, foremost.",
|
|
3811
|
+
reversing: "Reverse engineering challenge. Priority: identify binary type \u2192 static analysis \u2192 dynamic analysis \u2192 extract flag/keygen. Tools: ghidra, radare2, ltrace, strace.",
|
|
3812
|
+
misc: "Miscellaneous challenge. Priority: understand the puzzle \u2192 check for sandbox escape, scripting, OSINT, encoding. Think creatively.",
|
|
3813
|
+
network: "Network/infrastructure challenge. Priority: service enumeration \u2192 version CVE search \u2192 exploit \u2192 privilege escalation \u2192 lateral movement.",
|
|
3814
|
+
unknown: "Challenge type unclear. Run broad reconnaissance first, then adapt based on findings."
|
|
3815
|
+
};
|
|
3816
|
+
function analyzeChallenge(reconData) {
|
|
3817
|
+
if (!reconData || reconData.trim().length === 0) {
|
|
3818
|
+
return {
|
|
3819
|
+
primaryType: "unknown",
|
|
3820
|
+
secondaryTypes: [],
|
|
3821
|
+
confidence: 0,
|
|
3822
|
+
matchedSignals: [],
|
|
3823
|
+
recommendedTechniques: TYPE_TECHNIQUE_MAP.unknown,
|
|
3824
|
+
recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP.unknown,
|
|
3825
|
+
strategySuggestion: TYPE_STRATEGY_MAP.unknown
|
|
3826
|
+
};
|
|
2651
3827
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
3828
|
+
const lowerData = reconData.toLowerCase();
|
|
3829
|
+
const scores = {
|
|
3830
|
+
web: { score: 0, signals: [] },
|
|
3831
|
+
pwn: { score: 0, signals: [] },
|
|
3832
|
+
crypto: { score: 0, signals: [] },
|
|
3833
|
+
forensics: { score: 0, signals: [] },
|
|
3834
|
+
reversing: { score: 0, signals: [] },
|
|
3835
|
+
misc: { score: 0, signals: [] },
|
|
3836
|
+
network: { score: 0, signals: [] },
|
|
3837
|
+
unknown: { score: 0, signals: [] }
|
|
3838
|
+
};
|
|
3839
|
+
for (const [type, signals] of Object.entries(CHALLENGE_TYPE_SIGNALS)) {
|
|
3840
|
+
const challengeType = type;
|
|
3841
|
+
if (!(challengeType in scores)) continue;
|
|
3842
|
+
for (const signal of signals) {
|
|
3843
|
+
if (lowerData.includes(signal.toLowerCase())) {
|
|
3844
|
+
scores[challengeType].score++;
|
|
3845
|
+
scores[challengeType].signals.push(signal);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
2657
3848
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
isAutoApprove() {
|
|
2662
|
-
return this.shouldAutoApprove;
|
|
3849
|
+
if (/\b(80|443|8080|8443|3000|5000|8000)\b/.test(reconData)) {
|
|
3850
|
+
scores.web.score += 2;
|
|
3851
|
+
scores.web.signals.push("web-port");
|
|
2663
3852
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
if (level === APPROVAL_LEVELS.AUTO) return { isApproved: true };
|
|
2668
|
-
if (level === APPROVAL_LEVELS.BLOCK) return { isApproved: false, reason: "Policy blocked execution" };
|
|
2669
|
-
return { isApproved: true, reason: `Auto-approved in non-interactive: ${toolCall.name}` };
|
|
3853
|
+
if (/\b(21|22|23|25|53|445|139|3389|5985)\b/.test(reconData)) {
|
|
3854
|
+
scores.network.score += 2;
|
|
3855
|
+
scores.network.signals.push("infra-port");
|
|
2670
3856
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
};
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
"
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
"feroxbuster"
|
|
2715
|
-
]
|
|
2716
|
-
};
|
|
3857
|
+
if (/\.(elf|bin|out|exe)\b/.test(lowerData)) {
|
|
3858
|
+
scores.pwn.score += 3;
|
|
3859
|
+
scores.pwn.signals.push("binary-file");
|
|
3860
|
+
}
|
|
3861
|
+
if (/\.(pcap|pcapng|mem|raw|img|dd)\b/.test(lowerData)) {
|
|
3862
|
+
scores.forensics.score += 3;
|
|
3863
|
+
scores.forensics.signals.push("forensic-file");
|
|
3864
|
+
}
|
|
3865
|
+
if (/\.(py|sage|pem|pub)\b.*(?:encrypt|decrypt|rsa|aes)/i.test(reconData)) {
|
|
3866
|
+
scores.crypto.score += 3;
|
|
3867
|
+
scores.crypto.signals.push("crypto-file");
|
|
3868
|
+
}
|
|
3869
|
+
const sorted = Object.entries(scores).filter(([type]) => type !== "unknown").sort(([, a], [, b]) => b.score - a.score);
|
|
3870
|
+
const [primaryType, primaryData] = sorted[0];
|
|
3871
|
+
const totalSignals = sorted.reduce((sum, [, data]) => sum + data.score, 0);
|
|
3872
|
+
const confidence = totalSignals > 0 ? Math.min(1, primaryData.score / Math.max(totalSignals, 1)) : 0;
|
|
3873
|
+
const threshold = primaryData.score * SECONDARY_TYPE_RATIO;
|
|
3874
|
+
const secondaryTypes = sorted.slice(1).filter(([, data]) => data.score >= threshold && data.score > 0).map(([type]) => type);
|
|
3875
|
+
return {
|
|
3876
|
+
primaryType: primaryData.score > 0 ? primaryType : "unknown",
|
|
3877
|
+
secondaryTypes,
|
|
3878
|
+
confidence,
|
|
3879
|
+
matchedSignals: primaryData.signals,
|
|
3880
|
+
recommendedTechniques: TYPE_TECHNIQUE_MAP[primaryType] || TYPE_TECHNIQUE_MAP.unknown,
|
|
3881
|
+
recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP[primaryType] || TYPE_PHASE_PROMPT_MAP.unknown,
|
|
3882
|
+
strategySuggestion: TYPE_STRATEGY_MAP[primaryType] || TYPE_STRATEGY_MAP.unknown
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
function formatChallengeAnalysis(analysis) {
|
|
3886
|
+
if (analysis.primaryType === "unknown") return "";
|
|
3887
|
+
const lines = [
|
|
3888
|
+
`<challenge-analysis>`,
|
|
3889
|
+
`Type: ${analysis.primaryType.toUpperCase()} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`
|
|
3890
|
+
];
|
|
3891
|
+
if (analysis.secondaryTypes.length > 0) {
|
|
3892
|
+
lines.push(`Secondary: ${analysis.secondaryTypes.join(", ")}`);
|
|
3893
|
+
}
|
|
3894
|
+
lines.push(`Signals: ${analysis.matchedSignals.join(", ")}`);
|
|
3895
|
+
lines.push(`Strategy: ${analysis.strategySuggestion}`);
|
|
3896
|
+
lines.push(`Focus techniques: ${analysis.recommendedTechniques.join(", ")}`);
|
|
3897
|
+
lines.push(`</challenge-analysis>`);
|
|
3898
|
+
return lines.join("\n");
|
|
3899
|
+
}
|
|
2717
3900
|
|
|
2718
3901
|
// src/engine/tools/system.ts
|
|
2719
3902
|
var systemTools = [
|
|
@@ -3048,6 +4231,111 @@ Checklist Items: ${state.getMissionChecklist().length}`
|
|
|
3048
4231
|
}
|
|
3049
4232
|
];
|
|
3050
4233
|
|
|
4234
|
+
// src/shared/utils/finding-validator.ts
|
|
4235
|
+
var VALIDATION_THRESHOLDS = {
|
|
4236
|
+
/** Divisor for base confidence (N+ pattern matches = 100%) */
|
|
4237
|
+
CONFIDENCE_DIVISOR: 2,
|
|
4238
|
+
/** Penalty per false-positive indicator */
|
|
4239
|
+
FALSE_POSITIVE_PENALTY: 0.15,
|
|
4240
|
+
/** Confidence breakpoints for quality classification */
|
|
4241
|
+
QUALITY_STRONG: 0.8,
|
|
4242
|
+
QUALITY_MODERATE: 0.5,
|
|
4243
|
+
/** Minimum confidence for verification */
|
|
4244
|
+
VERIFICATION_MIN: 0.5
|
|
4245
|
+
};
|
|
4246
|
+
var SUCCESS_PATTERNS = [
|
|
4247
|
+
// Shell access indicators
|
|
4248
|
+
{ pattern: /uid=\d+\([^)]+\)\s+gid=\d+/, description: "Unix id output", weight: 1 },
|
|
4249
|
+
{ pattern: /root:x:0:0/, description: "/etc/passwd root entry", weight: 0.9 },
|
|
4250
|
+
{ pattern: /NT AUTHORITY\\SYSTEM/i, description: "Windows SYSTEM access", weight: 1 },
|
|
4251
|
+
{ pattern: /nt authority\\system/i, description: "Windows SYSTEM access (lowercase)", weight: 1 },
|
|
4252
|
+
{ pattern: /\$ whoami\s*\n\s*root/, description: "root whoami output", weight: 1 },
|
|
4253
|
+
{ pattern: /Administrator/, description: "Windows Administrator", weight: 0.7 },
|
|
4254
|
+
// Database access
|
|
4255
|
+
{ pattern: /\d+ rows? in set/, description: "SQL query result", weight: 0.8 },
|
|
4256
|
+
{ pattern: /mysql>|postgres[=#]|sqlite>/, description: "Database shell prompt", weight: 0.9 },
|
|
4257
|
+
{ pattern: /CREATE TABLE|INSERT INTO|SELECT \*/i, description: "SQL DDL/DML output", weight: 0.7 },
|
|
4258
|
+
// File read success
|
|
4259
|
+
{ pattern: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/, description: "Private key exposure", weight: 1 },
|
|
4260
|
+
{ pattern: /-----BEGIN CERTIFICATE-----/, description: "Certificate exposure", weight: 0.6 },
|
|
4261
|
+
{ pattern: /DB_PASSWORD|DATABASE_URL|SECRET_KEY/i, description: "Credential in config", weight: 0.8 },
|
|
4262
|
+
// RCE indicators
|
|
4263
|
+
{ pattern: /Linux\s+\S+\s+\d+\.\d+/, description: "Linux uname output", weight: 0.7 },
|
|
4264
|
+
{ pattern: /Windows\s+(Server\s+)?\d{4}/i, description: "Windows systeminfo", weight: 0.7 },
|
|
4265
|
+
{ pattern: /\bwww-data\b/, description: "Web server user context", weight: 0.6 },
|
|
4266
|
+
// Network access
|
|
4267
|
+
{ pattern: /Nmap scan report for/, description: "Internal nmap scan (pivoting)", weight: 0.5 },
|
|
4268
|
+
{ pattern: /meterpreter\s*>/, description: "Meterpreter session", weight: 1 },
|
|
4269
|
+
// Credential extraction
|
|
4270
|
+
{ pattern: /\b[a-f0-9]{32}\b:\b[a-f0-9]{32}\b/, description: "Hash:hash pair", weight: 0.7 },
|
|
4271
|
+
{ pattern: /password\s*[:=]\s*\S+/i, description: "Password in output", weight: 0.6 }
|
|
4272
|
+
];
|
|
4273
|
+
var FALSE_POSITIVE_PATTERNS = [
|
|
4274
|
+
{ pattern: /connection refused/i, description: "Connection refused" },
|
|
4275
|
+
{ pattern: /access denied/i, description: "Access denied" },
|
|
4276
|
+
{ pattern: /permission denied/i, description: "Permission denied" },
|
|
4277
|
+
{ pattern: /404 not found/i, description: "404 response" },
|
|
4278
|
+
{ pattern: /401 unauthorized/i, description: "Unauthorized" },
|
|
4279
|
+
{ pattern: /timeout|timed out/i, description: "Timeout" },
|
|
4280
|
+
{ pattern: /error:|exception:/i, description: "Error/Exception" }
|
|
4281
|
+
];
|
|
4282
|
+
function validateFinding(evidence, severity) {
|
|
4283
|
+
if (!evidence || evidence.length === 0) {
|
|
4284
|
+
return {
|
|
4285
|
+
isVerified: false,
|
|
4286
|
+
confidence: 0,
|
|
4287
|
+
verificationNote: "No evidence provided \u2014 finding is unverified.",
|
|
4288
|
+
evidenceQuality: "none"
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
const combinedEvidence = evidence.join("\n");
|
|
4292
|
+
const flags = detectFlags(combinedEvidence);
|
|
4293
|
+
if (flags.length > 0) {
|
|
4294
|
+
return {
|
|
4295
|
+
isVerified: true,
|
|
4296
|
+
confidence: 1,
|
|
4297
|
+
verificationNote: `CTF flag detected in evidence: ${flags[0]}`,
|
|
4298
|
+
evidenceQuality: "strong"
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
let totalWeight = 0;
|
|
4302
|
+
const matchedPatterns = [];
|
|
4303
|
+
for (const { pattern, description, weight } of SUCCESS_PATTERNS) {
|
|
4304
|
+
pattern.lastIndex = 0;
|
|
4305
|
+
if (pattern.test(combinedEvidence)) {
|
|
4306
|
+
totalWeight += weight;
|
|
4307
|
+
matchedPatterns.push(description);
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
let falsePositiveCount = 0;
|
|
4311
|
+
for (const { pattern } of FALSE_POSITIVE_PATTERNS) {
|
|
4312
|
+
pattern.lastIndex = 0;
|
|
4313
|
+
if (pattern.test(combinedEvidence)) {
|
|
4314
|
+
falsePositiveCount++;
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
const baseConfidence = Math.min(1, totalWeight / VALIDATION_THRESHOLDS.CONFIDENCE_DIVISOR);
|
|
4318
|
+
const fpPenalty = falsePositiveCount * VALIDATION_THRESHOLDS.FALSE_POSITIVE_PENALTY;
|
|
4319
|
+
const confidence = Math.max(0, baseConfidence - fpPenalty);
|
|
4320
|
+
let evidenceQuality;
|
|
4321
|
+
if (confidence >= VALIDATION_THRESHOLDS.QUALITY_STRONG) evidenceQuality = "strong";
|
|
4322
|
+
else if (confidence >= VALIDATION_THRESHOLDS.QUALITY_MODERATE) evidenceQuality = "moderate";
|
|
4323
|
+
else if (confidence > 0) evidenceQuality = "weak";
|
|
4324
|
+
else evidenceQuality = "none";
|
|
4325
|
+
const isVerified = confidence >= VALIDATION_THRESHOLDS.VERIFICATION_MIN;
|
|
4326
|
+
const note = matchedPatterns.length > 0 ? `Evidence matches: ${matchedPatterns.join(", ")}. Confidence: ${(confidence * 100).toFixed(0)}%` : `No recognized success patterns in evidence. ${falsePositiveCount > 0 ? `${falsePositiveCount} potential false-positive indicators found.` : "Manual review recommended."}`;
|
|
4327
|
+
return {
|
|
4328
|
+
isVerified,
|
|
4329
|
+
confidence,
|
|
4330
|
+
verificationNote: note,
|
|
4331
|
+
evidenceQuality
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
function formatValidation(result2) {
|
|
4335
|
+
const icon = result2.isVerified ? "\u2705" : "\u26A0\uFE0F";
|
|
4336
|
+
return `${icon} Verified: ${result2.isVerified} | Quality: ${result2.evidenceQuality} | ${result2.verificationNote}`;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
3051
4339
|
// src/engine/tools/pentest-target-tools.ts
|
|
3052
4340
|
var createTargetTools = (state) => [
|
|
3053
4341
|
{
|
|
@@ -3114,6 +4402,9 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
3114
4402
|
tags: p.tags || [],
|
|
3115
4403
|
firstSeen: Date.now()
|
|
3116
4404
|
});
|
|
4405
|
+
for (const port of ports) {
|
|
4406
|
+
state.attackGraph.addService(ip, port.port, port.service, port.version);
|
|
4407
|
+
}
|
|
3117
4408
|
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
3118
4409
|
}
|
|
3119
4410
|
},
|
|
@@ -3132,19 +4423,28 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
3132
4423
|
execute: async (p) => {
|
|
3133
4424
|
const lootType = p.type;
|
|
3134
4425
|
const crackableTypes = ["hash"];
|
|
4426
|
+
const detail = p.detail;
|
|
4427
|
+
const host = p.host;
|
|
3135
4428
|
state.addLoot({
|
|
3136
4429
|
type: lootType,
|
|
3137
|
-
host
|
|
3138
|
-
detail
|
|
4430
|
+
host,
|
|
4431
|
+
detail,
|
|
3139
4432
|
obtainedAt: Date.now(),
|
|
3140
4433
|
isCrackable: crackableTypes.includes(lootType),
|
|
3141
4434
|
isCracked: false
|
|
3142
4435
|
});
|
|
4436
|
+
if (["credential", "token", "ssh_key", "api_key"].includes(lootType)) {
|
|
4437
|
+
const parts = detail.split(":");
|
|
4438
|
+
if (parts.length >= 2) {
|
|
4439
|
+
state.attackGraph.addCredential(parts[0], parts.slice(1).join(":"), host);
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
state.episodicMemory.record("access_gained", `Loot [${lootType}] from ${host}: ${detail.slice(0, DISPLAY_LIMITS.MEMORY_EVENT_PREVIEW)}`);
|
|
3143
4443
|
return {
|
|
3144
4444
|
success: true,
|
|
3145
|
-
output: `Loot recorded: [${lootType}] from ${
|
|
3146
|
-
Detail: ${
|
|
3147
|
-
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${
|
|
4445
|
+
output: `Loot recorded: [${lootType}] from ${host}
|
|
4446
|
+
Detail: ${detail}
|
|
4447
|
+
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
3148
4448
|
};
|
|
3149
4449
|
}
|
|
3150
4450
|
},
|
|
@@ -3158,18 +4458,34 @@ Detail: ${p.detail}
|
|
|
3158
4458
|
},
|
|
3159
4459
|
required: ["title", "severity"],
|
|
3160
4460
|
execute: async (p) => {
|
|
4461
|
+
const evidence = p.evidence || [];
|
|
4462
|
+
const title = p.title;
|
|
4463
|
+
const severity = p.severity;
|
|
4464
|
+
const affected = p.affected || [];
|
|
4465
|
+
const validation = validateFinding(evidence, severity);
|
|
3161
4466
|
state.addFinding({
|
|
3162
4467
|
id: generateId(ID_RADIX, ID_LENGTH),
|
|
3163
|
-
title
|
|
3164
|
-
severity
|
|
3165
|
-
affected
|
|
4468
|
+
title,
|
|
4469
|
+
severity,
|
|
4470
|
+
affected,
|
|
3166
4471
|
description: p.description || "",
|
|
3167
|
-
evidence
|
|
3168
|
-
isVerified:
|
|
4472
|
+
evidence,
|
|
4473
|
+
isVerified: validation.isVerified,
|
|
3169
4474
|
remediation: "",
|
|
3170
4475
|
foundAt: Date.now()
|
|
3171
4476
|
});
|
|
3172
|
-
|
|
4477
|
+
const hasExploit = validation.isVerified;
|
|
4478
|
+
const target = affected[0] || "unknown";
|
|
4479
|
+
state.attackGraph.addVulnerability(title, target, severity, hasExploit);
|
|
4480
|
+
state.episodicMemory.record(
|
|
4481
|
+
validation.isVerified ? "tool_success" : "tool_failure",
|
|
4482
|
+
`Finding: ${title} (${severity}) \u2014 ${formatValidation(validation)}`
|
|
4483
|
+
);
|
|
4484
|
+
return {
|
|
4485
|
+
success: true,
|
|
4486
|
+
output: `Added: ${title}
|
|
4487
|
+
${formatValidation(validation)}`
|
|
4488
|
+
};
|
|
3173
4489
|
}
|
|
3174
4490
|
}
|
|
3175
4491
|
];
|
|
@@ -3226,7 +4542,7 @@ var SEARCH_LIMIT = {
|
|
|
3226
4542
|
};
|
|
3227
4543
|
|
|
3228
4544
|
// src/engine/tools/web-browser.ts
|
|
3229
|
-
import { join as
|
|
4545
|
+
import { join as join6 } from "path";
|
|
3230
4546
|
import { tmpdir as tmpdir3 } from "os";
|
|
3231
4547
|
|
|
3232
4548
|
// src/shared/constants/browser/config.ts
|
|
@@ -3277,8 +4593,8 @@ var PLAYWRIGHT_SCRIPT = {
|
|
|
3277
4593
|
|
|
3278
4594
|
// src/engine/tools/web-browser-setup.ts
|
|
3279
4595
|
import { spawn as spawn4 } from "child_process";
|
|
3280
|
-
import { existsSync as
|
|
3281
|
-
import { join as
|
|
4596
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4597
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
3282
4598
|
function getPlaywrightPath() {
|
|
3283
4599
|
try {
|
|
3284
4600
|
const mainPath = __require.resolve("playwright");
|
|
@@ -3286,13 +4602,13 @@ function getPlaywrightPath() {
|
|
|
3286
4602
|
} catch {
|
|
3287
4603
|
}
|
|
3288
4604
|
const dockerPath = "/app/node_modules/playwright";
|
|
3289
|
-
if (
|
|
4605
|
+
if (existsSync5(dockerPath)) return dockerPath;
|
|
3290
4606
|
const nodePath = process.env.NODE_PATH || "";
|
|
3291
4607
|
for (const dir of nodePath.split(":").filter(Boolean)) {
|
|
3292
|
-
const candidate =
|
|
3293
|
-
if (
|
|
4608
|
+
const candidate = join4(dir, "playwright");
|
|
4609
|
+
if (existsSync5(candidate)) return candidate;
|
|
3294
4610
|
}
|
|
3295
|
-
return
|
|
4611
|
+
return join4(process.cwd(), "node_modules", "playwright");
|
|
3296
4612
|
}
|
|
3297
4613
|
async function checkPlaywright() {
|
|
3298
4614
|
try {
|
|
@@ -3343,19 +4659,19 @@ async function installPlaywright() {
|
|
|
3343
4659
|
|
|
3344
4660
|
// src/engine/tools/web-browser-script.ts
|
|
3345
4661
|
import { spawn as spawn5 } from "child_process";
|
|
3346
|
-
import { writeFileSync as
|
|
3347
|
-
import { join as
|
|
4662
|
+
import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
4663
|
+
import { join as join5 } from "path";
|
|
3348
4664
|
import { tmpdir as tmpdir2 } from "os";
|
|
3349
4665
|
function safeJsString(str) {
|
|
3350
4666
|
return JSON.stringify(str);
|
|
3351
4667
|
}
|
|
3352
4668
|
function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
3353
4669
|
return new Promise((resolve) => {
|
|
3354
|
-
const tempDir =
|
|
4670
|
+
const tempDir = join5(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
|
|
3355
4671
|
ensureDirExists(tempDir);
|
|
3356
|
-
const scriptPath =
|
|
4672
|
+
const scriptPath = join5(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
|
|
3357
4673
|
try {
|
|
3358
|
-
|
|
4674
|
+
writeFileSync5(scriptPath, script, "utf-8");
|
|
3359
4675
|
} catch (err) {
|
|
3360
4676
|
resolve({
|
|
3361
4677
|
success: false,
|
|
@@ -3366,7 +4682,7 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
|
3366
4682
|
}
|
|
3367
4683
|
const nodePathDirs = [
|
|
3368
4684
|
"/app/node_modules",
|
|
3369
|
-
|
|
4685
|
+
join5(process.cwd(), "node_modules"),
|
|
3370
4686
|
process.env.NODE_PATH || ""
|
|
3371
4687
|
].filter(Boolean).join(":");
|
|
3372
4688
|
const child = spawn5(PLAYWRIGHT_CMD.NODE, [scriptPath], {
|
|
@@ -3573,7 +4889,7 @@ async function browseUrl(url, options = {}) {
|
|
|
3573
4889
|
};
|
|
3574
4890
|
}
|
|
3575
4891
|
}
|
|
3576
|
-
const screenshotPath = browserOptions.screenshot ?
|
|
4892
|
+
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
3577
4893
|
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
3578
4894
|
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
3579
4895
|
if (!result2.success) {
|
|
@@ -4853,8 +6169,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4853
6169
|
}
|
|
4854
6170
|
},
|
|
4855
6171
|
execute: async (p) => {
|
|
4856
|
-
const { existsSync:
|
|
4857
|
-
const { join:
|
|
6172
|
+
const { existsSync: existsSync9, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
|
|
6173
|
+
const { join: join11 } = await import("path");
|
|
4858
6174
|
const category = p.category || "";
|
|
4859
6175
|
const search = p.search || "";
|
|
4860
6176
|
const minSize = p.min_size || 0;
|
|
@@ -4900,7 +6216,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4900
6216
|
results.push("");
|
|
4901
6217
|
};
|
|
4902
6218
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
4903
|
-
if (depth > maxDepth || !
|
|
6219
|
+
if (depth > maxDepth || !existsSync9(dirPath)) return;
|
|
4904
6220
|
let entries;
|
|
4905
6221
|
try {
|
|
4906
6222
|
entries = readdirSync3(dirPath, { withFileTypes: true });
|
|
@@ -4909,7 +6225,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4909
6225
|
}
|
|
4910
6226
|
for (const entry of entries) {
|
|
4911
6227
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
4912
|
-
const fullPath =
|
|
6228
|
+
const fullPath = join11(dirPath, entry.name);
|
|
4913
6229
|
if (entry.isDirectory()) {
|
|
4914
6230
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
4915
6231
|
continue;
|
|
@@ -5283,8 +6599,8 @@ Requires root/sudo privileges.`,
|
|
|
5283
6599
|
const iface = p.interface || "";
|
|
5284
6600
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
5285
6601
|
const hostsFile = createTempFile(".hosts");
|
|
5286
|
-
const { writeFileSync:
|
|
5287
|
-
|
|
6602
|
+
const { writeFileSync: writeFileSync8 } = await import("fs");
|
|
6603
|
+
writeFileSync8(hostsFile, `${spoofIp} ${domain}
|
|
5288
6604
|
${spoofIp} *.${domain}
|
|
5289
6605
|
`);
|
|
5290
6606
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -5827,8 +7143,13 @@ var ToolRegistry = class {
|
|
|
5827
7143
|
return this.tools.get(name);
|
|
5828
7144
|
}
|
|
5829
7145
|
/**
|
|
5830
|
-
* Execute tool with integrated
|
|
5831
|
-
*
|
|
7146
|
+
* Execute tool with integrated pipeline:
|
|
7147
|
+
* Scope → Approval → Execution → Memory → Structured Extract → Log
|
|
7148
|
+
*
|
|
7149
|
+
* Enhancements integrated:
|
|
7150
|
+
* - #2: Auto-Prompter trigger after initial recon
|
|
7151
|
+
* - #10: Structured output extraction
|
|
7152
|
+
* - #12: Working memory (success/failure tracking)
|
|
5832
7153
|
*/
|
|
5833
7154
|
async execute(toolCall) {
|
|
5834
7155
|
const tool = this.getTool(toolCall.name);
|
|
@@ -5841,6 +7162,43 @@ var ToolRegistry = class {
|
|
|
5841
7162
|
return { success: false, output: "", error: approval.reason || "Denied by policy" };
|
|
5842
7163
|
}
|
|
5843
7164
|
const result2 = await tool.execute(toolCall.input);
|
|
7165
|
+
const command = String(toolCall.input.command || toolCall.input.url || toolCall.input.query || "");
|
|
7166
|
+
if (result2.success) {
|
|
7167
|
+
this.state.workingMemory.recordSuccess(toolCall.name, command, result2.output || "");
|
|
7168
|
+
} else {
|
|
7169
|
+
this.state.workingMemory.recordFailure(toolCall.name, command, result2.error || "Unknown error");
|
|
7170
|
+
}
|
|
7171
|
+
if (result2.success && result2.output) {
|
|
7172
|
+
const structured = autoExtractStructured(toolCall.name, result2.output);
|
|
7173
|
+
if (structured) {
|
|
7174
|
+
const additions = [];
|
|
7175
|
+
if (structured.credentials && structured.credentials.length > 0) {
|
|
7176
|
+
additions.push(`
|
|
7177
|
+
[AUTO-EXTRACTED] ${structured.credentials.length} credential(s) found`);
|
|
7178
|
+
}
|
|
7179
|
+
if (structured.vulnerabilities && structured.vulnerabilities.length > 0) {
|
|
7180
|
+
additions.push(`
|
|
7181
|
+
[AUTO-EXTRACTED] ${structured.vulnerabilities.length} CVE reference(s) found`);
|
|
7182
|
+
}
|
|
7183
|
+
if (structured.binaryAnalysisSummary) {
|
|
7184
|
+
additions.push(`
|
|
7185
|
+
[AUTO-EXTRACTED] Binary analysis:
|
|
7186
|
+
${structured.binaryAnalysisSummary}`);
|
|
7187
|
+
}
|
|
7188
|
+
if (additions.length > 0) {
|
|
7189
|
+
result2.output += additions.join("");
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
if (result2.success && !this.state.getChallengeAnalysis()) {
|
|
7194
|
+
const reconTools = [TOOL_NAMES.RUN_CMD, TOOL_NAMES.BROWSE_URL, TOOL_NAMES.PARSE_NMAP];
|
|
7195
|
+
if (reconTools.includes(toolCall.name) && result2.output && result2.output.length > MIN_RECON_OUTPUT_LENGTH) {
|
|
7196
|
+
const analysis = analyzeChallenge(result2.output);
|
|
7197
|
+
if (analysis.confidence > MIN_CHALLENGE_CONFIDENCE) {
|
|
7198
|
+
this.state.setChallengeAnalysis(analysis);
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
5844
7202
|
this.logSuccessfulAction(toolCall, approval, result2);
|
|
5845
7203
|
return result2;
|
|
5846
7204
|
}
|
|
@@ -5977,7 +7335,7 @@ var ServiceParser = class {
|
|
|
5977
7335
|
};
|
|
5978
7336
|
|
|
5979
7337
|
// src/domains/registry.ts
|
|
5980
|
-
import { join as
|
|
7338
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
5981
7339
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5982
7340
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
5983
7341
|
var DOMAINS = {
|
|
@@ -5985,73 +7343,73 @@ var DOMAINS = {
|
|
|
5985
7343
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
5986
7344
|
name: "Network Infrastructure",
|
|
5987
7345
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
5988
|
-
promptPath:
|
|
7346
|
+
promptPath: join7(__dirname2, "network/prompt.md")
|
|
5989
7347
|
},
|
|
5990
7348
|
[SERVICE_CATEGORIES.WEB]: {
|
|
5991
7349
|
id: SERVICE_CATEGORIES.WEB,
|
|
5992
7350
|
name: "Web Application",
|
|
5993
7351
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
5994
|
-
promptPath:
|
|
7352
|
+
promptPath: join7(__dirname2, "web/prompt.md")
|
|
5995
7353
|
},
|
|
5996
7354
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
5997
7355
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
5998
7356
|
name: "Database Security",
|
|
5999
7357
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
6000
|
-
promptPath:
|
|
7358
|
+
promptPath: join7(__dirname2, "database/prompt.md")
|
|
6001
7359
|
},
|
|
6002
7360
|
[SERVICE_CATEGORIES.AD]: {
|
|
6003
7361
|
id: SERVICE_CATEGORIES.AD,
|
|
6004
7362
|
name: "Active Directory",
|
|
6005
7363
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
6006
|
-
promptPath:
|
|
7364
|
+
promptPath: join7(__dirname2, "ad/prompt.md")
|
|
6007
7365
|
},
|
|
6008
7366
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
6009
7367
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
6010
7368
|
name: "Email Services",
|
|
6011
7369
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
6012
|
-
promptPath:
|
|
7370
|
+
promptPath: join7(__dirname2, "email/prompt.md")
|
|
6013
7371
|
},
|
|
6014
7372
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
6015
7373
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
6016
7374
|
name: "Remote Access",
|
|
6017
7375
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
6018
|
-
promptPath:
|
|
7376
|
+
promptPath: join7(__dirname2, "remote-access/prompt.md")
|
|
6019
7377
|
},
|
|
6020
7378
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
6021
7379
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
6022
7380
|
name: "File Sharing",
|
|
6023
7381
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
6024
|
-
promptPath:
|
|
7382
|
+
promptPath: join7(__dirname2, "file-sharing/prompt.md")
|
|
6025
7383
|
},
|
|
6026
7384
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
6027
7385
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
6028
7386
|
name: "Cloud Infrastructure",
|
|
6029
7387
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
6030
|
-
promptPath:
|
|
7388
|
+
promptPath: join7(__dirname2, "cloud/prompt.md")
|
|
6031
7389
|
},
|
|
6032
7390
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
6033
7391
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
6034
7392
|
name: "Container Systems",
|
|
6035
7393
|
description: "Docker and Kubernetes security testing.",
|
|
6036
|
-
promptPath:
|
|
7394
|
+
promptPath: join7(__dirname2, "container/prompt.md")
|
|
6037
7395
|
},
|
|
6038
7396
|
[SERVICE_CATEGORIES.API]: {
|
|
6039
7397
|
id: SERVICE_CATEGORIES.API,
|
|
6040
7398
|
name: "API Security",
|
|
6041
7399
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
6042
|
-
promptPath:
|
|
7400
|
+
promptPath: join7(__dirname2, "api/prompt.md")
|
|
6043
7401
|
},
|
|
6044
7402
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
6045
7403
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
6046
7404
|
name: "Wireless Networks",
|
|
6047
7405
|
description: "WiFi and Bluetooth security testing.",
|
|
6048
|
-
promptPath:
|
|
7406
|
+
promptPath: join7(__dirname2, "wireless/prompt.md")
|
|
6049
7407
|
},
|
|
6050
7408
|
[SERVICE_CATEGORIES.ICS]: {
|
|
6051
7409
|
id: SERVICE_CATEGORIES.ICS,
|
|
6052
7410
|
name: "Industrial Systems",
|
|
6053
7411
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
6054
|
-
promptPath:
|
|
7412
|
+
promptPath: join7(__dirname2, "ics/prompt.md")
|
|
6055
7413
|
}
|
|
6056
7414
|
};
|
|
6057
7415
|
|
|
@@ -6608,13 +7966,13 @@ function logLLM(message, data) {
|
|
|
6608
7966
|
|
|
6609
7967
|
// src/engine/orchestrator/orchestrator.ts
|
|
6610
7968
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6611
|
-
import { dirname as dirname4, join as
|
|
7969
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
6612
7970
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
6613
7971
|
var __dirname3 = dirname4(__filename2);
|
|
6614
7972
|
|
|
6615
7973
|
// src/engine/state-persistence.ts
|
|
6616
|
-
import { writeFileSync as
|
|
6617
|
-
import { join as
|
|
7974
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
|
|
7975
|
+
import { join as join9 } from "path";
|
|
6618
7976
|
function saveState(state) {
|
|
6619
7977
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
6620
7978
|
ensureDirExists(sessionsDir);
|
|
@@ -6632,10 +7990,10 @@ function saveState(state) {
|
|
|
6632
7990
|
missionChecklist: state.getMissionChecklist()
|
|
6633
7991
|
};
|
|
6634
7992
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6635
|
-
const sessionFile =
|
|
6636
|
-
|
|
6637
|
-
const latestFile =
|
|
6638
|
-
|
|
7993
|
+
const sessionFile = join9(sessionsDir, `${sessionId}.json`);
|
|
7994
|
+
writeFileSync6(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
7995
|
+
const latestFile = join9(sessionsDir, "latest.json");
|
|
7996
|
+
writeFileSync6(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
6639
7997
|
pruneOldSessions(sessionsDir);
|
|
6640
7998
|
return sessionFile;
|
|
6641
7999
|
}
|
|
@@ -6643,8 +8001,8 @@ function pruneOldSessions(sessionsDir) {
|
|
|
6643
8001
|
try {
|
|
6644
8002
|
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".json") && f !== "latest.json").map((f) => ({
|
|
6645
8003
|
name: f,
|
|
6646
|
-
path:
|
|
6647
|
-
mtime: statSync(
|
|
8004
|
+
path: join9(sessionsDir, f),
|
|
8005
|
+
mtime: statSync(join9(sessionsDir, f)).mtimeMs
|
|
6648
8006
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
6649
8007
|
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
6650
8008
|
for (const file of toDelete) {
|
|
@@ -6654,12 +8012,12 @@ function pruneOldSessions(sessionsDir) {
|
|
|
6654
8012
|
}
|
|
6655
8013
|
}
|
|
6656
8014
|
function loadState(state) {
|
|
6657
|
-
const latestFile =
|
|
6658
|
-
if (!
|
|
8015
|
+
const latestFile = join9(WORKSPACE.SESSIONS, "latest.json");
|
|
8016
|
+
if (!existsSync6(latestFile)) {
|
|
6659
8017
|
return false;
|
|
6660
8018
|
}
|
|
6661
8019
|
try {
|
|
6662
|
-
const raw =
|
|
8020
|
+
const raw = readFileSync4(latestFile, "utf-8");
|
|
6663
8021
|
const snapshot = JSON.parse(raw);
|
|
6664
8022
|
if (snapshot.version !== 1) {
|
|
6665
8023
|
debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
|
|
@@ -6696,105 +8054,6 @@ function loadState(state) {
|
|
|
6696
8054
|
}
|
|
6697
8055
|
}
|
|
6698
8056
|
|
|
6699
|
-
// src/shared/utils/ctf-knowledge.ts
|
|
6700
|
-
var FLAG_PATTERNS = {
|
|
6701
|
-
// Generic CTF flag formats
|
|
6702
|
-
generic: /flag\{[^}]+\}/gi,
|
|
6703
|
-
curly_upper: /FLAG\{[^}]+\}/g,
|
|
6704
|
-
// Platform-specific — Major International Competitions
|
|
6705
|
-
htb: /HTB\{[^}]+\}/g,
|
|
6706
|
-
thm: /THM\{[^}]+\}/g,
|
|
6707
|
-
picoCTF: /picoCTF\{[^}]+\}/g,
|
|
6708
|
-
defcon_ooo: /OOO\{[^}]+\}/g,
|
|
6709
|
-
// DEF CON CTF (Order of the Overflow)
|
|
6710
|
-
csaw: /CSAW\{[^}]+\}/g,
|
|
6711
|
-
// CSAW CTF
|
|
6712
|
-
google: /CTF\{[^}]+\}/g,
|
|
6713
|
-
// Google CTF
|
|
6714
|
-
dragon: /DrgnS\{[^}]+\}/g,
|
|
6715
|
-
// Dragon Sector
|
|
6716
|
-
hxp: /hxp\{[^}]+\}/g,
|
|
6717
|
-
// hxp CTF
|
|
6718
|
-
zer0pts: /zer0pts\{[^}]+\}/g,
|
|
6719
|
-
// zer0pts CTF
|
|
6720
|
-
// Asia-Pacific Major Competitions
|
|
6721
|
-
seccon: /SECCON\{[^}]+\}/g,
|
|
6722
|
-
// SECCON CTF (Japan)
|
|
6723
|
-
hitcon: /hitcon\{[^}]+\}/gi,
|
|
6724
|
-
// HITCON CTF (Taiwan)
|
|
6725
|
-
codegate: /codegate\{[^}]+\}/gi,
|
|
6726
|
-
// Codegate CTF (Korea)
|
|
6727
|
-
wacon: /WACON\{[^}]+\}/g,
|
|
6728
|
-
// WACON CTF (Korea)
|
|
6729
|
-
linectf: /LINECTF\{[^}]+\}/g,
|
|
6730
|
-
// LINE CTF (Korea/Japan)
|
|
6731
|
-
tsgctf: /TSGCTF\{[^}]+\}/g,
|
|
6732
|
-
// TSG CTF (Japan)
|
|
6733
|
-
kosenctf: /KosenCTF\{[^}]+\}/g,
|
|
6734
|
-
// Kosen CTF (Japan)
|
|
6735
|
-
asis: /ASIS\{[^}]+\}/g,
|
|
6736
|
-
// ASIS CTF (Iran)
|
|
6737
|
-
// Americas / Europe Major Competitions
|
|
6738
|
-
plaidctf: /PCTF\{[^}]+\}/g,
|
|
6739
|
-
// PlaidCTF (PPP)
|
|
6740
|
-
dicectf: /dice\{[^}]+\}/gi,
|
|
6741
|
-
// DiceCTF
|
|
6742
|
-
rwctf: /rwctf\{[^}]+\}/gi,
|
|
6743
|
-
// Real World CTF
|
|
6744
|
-
rctf: /RCTF\{[^}]+\}/g,
|
|
6745
|
-
// RCTF
|
|
6746
|
-
n1ctf: /N1CTF\{[^}]+\}/g,
|
|
6747
|
-
// N1CTF (Nu1L)
|
|
6748
|
-
bctf: /BCTF\{[^}]+\}/g,
|
|
6749
|
-
// BCTF
|
|
6750
|
-
_0ctf: /0CTF\{[^}]+\}/g,
|
|
6751
|
-
// 0CTF (Shanghai)
|
|
6752
|
-
defcamp: /CTF\{[^}]+\}/g,
|
|
6753
|
-
// DefCamp
|
|
6754
|
-
insomnihack: /INS\{[^}]+\}/g,
|
|
6755
|
-
// Insomni'hack
|
|
6756
|
-
justctf: /justCTF\{[^}]+\}/g,
|
|
6757
|
-
// justCTF
|
|
6758
|
-
lactf: /lactf\{[^}]+\}/gi,
|
|
6759
|
-
// LA CTF (UCLA)
|
|
6760
|
-
damctf: /dam\{[^}]+\}/gi,
|
|
6761
|
-
// DamCTF
|
|
6762
|
-
tjctf: /tjctf\{[^}]+\}/gi,
|
|
6763
|
-
// TJCTF
|
|
6764
|
-
buckeye: /buckeye\{[^}]+\}/gi,
|
|
6765
|
-
// BuckeyeCTF
|
|
6766
|
-
uiuctf: /uiuctf\{[^}]+\}/gi,
|
|
6767
|
-
// UIUCTF
|
|
6768
|
-
// Platform-specific
|
|
6769
|
-
cyber_apocalypse: /HTB\{[^}]+\}/g,
|
|
6770
|
-
// HTB Cyber Apocalypse (same as HTB)
|
|
6771
|
-
offshift: /oS\{[^}]+\}/g,
|
|
6772
|
-
// Offshift CTF
|
|
6773
|
-
imaginaryctf: /ictf\{[^}]+\}/gi,
|
|
6774
|
-
// ImaginaryCTF
|
|
6775
|
-
corctf: /corctf\{[^}]+\}/gi,
|
|
6776
|
-
// corCTF
|
|
6777
|
-
// Generic CTFd format — catches most custom competitions
|
|
6778
|
-
ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
|
|
6779
|
-
// Hash-style flags (HTB user.txt / root.txt — 32-char hex)
|
|
6780
|
-
hash_flag: /\b[a-f0-9]{32}\b/g,
|
|
6781
|
-
// Base64-encoded flag detection (common in steganography/forensics)
|
|
6782
|
-
base64_flag: /flag\{[A-Za-z0-9_+\-/=]+\}/gi
|
|
6783
|
-
};
|
|
6784
|
-
var MIN_FLAG_SCAN_LENGTH = 5;
|
|
6785
|
-
function detectFlags(output) {
|
|
6786
|
-
if (!output || output.length < MIN_FLAG_SCAN_LENGTH) return [];
|
|
6787
|
-
const found = /* @__PURE__ */ new Set();
|
|
6788
|
-
for (const pattern of Object.values(FLAG_PATTERNS)) {
|
|
6789
|
-
pattern.lastIndex = 0;
|
|
6790
|
-
const matches = output.match(pattern);
|
|
6791
|
-
if (matches) {
|
|
6792
|
-
for (const m of matches) found.add(m);
|
|
6793
|
-
}
|
|
6794
|
-
}
|
|
6795
|
-
return Array.from(found);
|
|
6796
|
-
}
|
|
6797
|
-
|
|
6798
8057
|
// src/agents/tool-error-enrichment.ts
|
|
6799
8058
|
function enrichToolErrorContext(ctx) {
|
|
6800
8059
|
const { toolName, input, error, originalOutput, progress } = ctx;
|
|
@@ -6876,6 +8135,21 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
6876
8135
|
var MIN_COMPRESS_LENGTH = 3e3;
|
|
6877
8136
|
var SUMMARY_HEADER = "\u2550\u2550\u2550 INTELLIGENCE SUMMARY (auto-extracted) \u2550\u2550\u2550";
|
|
6878
8137
|
var SUMMARY_FOOTER = "\u2550\u2550\u2550 END SUMMARY \u2014 Full output follows \u2550\u2550\u2550";
|
|
8138
|
+
var EXTRACT_LIMITS = {
|
|
8139
|
+
NMAP_PORTS: 30,
|
|
8140
|
+
NMAP_VULNS: 10,
|
|
8141
|
+
LINPEAS_SUDO: 500,
|
|
8142
|
+
LINPEAS_WRITABLE: 300,
|
|
8143
|
+
LINPEAS_CRON: 5,
|
|
8144
|
+
LINPEAS_PASSWORDS: 5,
|
|
8145
|
+
ENUM4LINUX_SHARES: 10,
|
|
8146
|
+
DIRBUST_PATHS: 20,
|
|
8147
|
+
SQLMAP_INJECTIONS: 5,
|
|
8148
|
+
HASH_NTLM: 5,
|
|
8149
|
+
HASH_PREVIEW_LEN: 100,
|
|
8150
|
+
GENERIC_CREDS: 5,
|
|
8151
|
+
GENERIC_PATHS: 10
|
|
8152
|
+
};
|
|
6879
8153
|
var TOOL_SIGNATURES = [
|
|
6880
8154
|
{
|
|
6881
8155
|
name: "nmap",
|
|
@@ -6950,7 +8224,7 @@ function extractNmapIntel(output) {
|
|
|
6950
8224
|
const portLines = lines.filter((l) => /^\d+\/\w+\s+open\s+/.test(l.trim()));
|
|
6951
8225
|
if (portLines.length > 0) {
|
|
6952
8226
|
intel.push("Open ports:");
|
|
6953
|
-
for (const pl of portLines.slice(0,
|
|
8227
|
+
for (const pl of portLines.slice(0, EXTRACT_LIMITS.NMAP_PORTS)) {
|
|
6954
8228
|
intel.push(` ${pl.trim()}`);
|
|
6955
8229
|
}
|
|
6956
8230
|
}
|
|
@@ -6959,7 +8233,7 @@ function extractNmapIntel(output) {
|
|
|
6959
8233
|
);
|
|
6960
8234
|
if (vulnLines.length > 0) {
|
|
6961
8235
|
intel.push("\u26A0\uFE0F Vulnerability indicators:");
|
|
6962
|
-
for (const vl of vulnLines.slice(0,
|
|
8236
|
+
for (const vl of vulnLines.slice(0, EXTRACT_LIMITS.NMAP_VULNS)) {
|
|
6963
8237
|
intel.push(` ${vl.trim()}`);
|
|
6964
8238
|
}
|
|
6965
8239
|
}
|
|
@@ -6983,18 +8257,18 @@ function extractLinpeasIntel(output) {
|
|
|
6983
8257
|
}
|
|
6984
8258
|
const sudoMatch = output.match(/User \S+ may run[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6985
8259
|
if (sudoMatch) {
|
|
6986
|
-
intel.push(`\u{1F534} sudo -l: ${sudoMatch[0].trim().slice(0,
|
|
8260
|
+
intel.push(`\u{1F534} sudo -l: ${sudoMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_SUDO)}`);
|
|
6987
8261
|
}
|
|
6988
8262
|
const writableMatch = output.match(/Interesting writable[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6989
8263
|
if (writableMatch) {
|
|
6990
|
-
intel.push(`\u{1F4DD} Writable: ${writableMatch[0].trim().slice(0,
|
|
8264
|
+
intel.push(`\u{1F4DD} Writable: ${writableMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_WRITABLE)}`);
|
|
6991
8265
|
}
|
|
6992
8266
|
const cronMatch = output.match(/Cron[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6993
8267
|
if (cronMatch) {
|
|
6994
8268
|
const cronLines = cronMatch[0].split("\n").filter((l) => l.includes("*") || /\/(root|cron)/i.test(l));
|
|
6995
8269
|
if (cronLines.length > 0) {
|
|
6996
8270
|
intel.push("\u23F0 Cron entries:");
|
|
6997
|
-
cronLines.slice(0,
|
|
8271
|
+
cronLines.slice(0, EXTRACT_LIMITS.LINPEAS_CRON).forEach((c) => intel.push(` ${c.trim()}`));
|
|
6998
8272
|
}
|
|
6999
8273
|
}
|
|
7000
8274
|
const kernelMatch = output.match(/Linux version\s+(\S+)/i) || output.match(/Kernel:\s*(\S+)/i);
|
|
@@ -7004,7 +8278,7 @@ function extractLinpeasIntel(output) {
|
|
|
7004
8278
|
);
|
|
7005
8279
|
if (passLines.length > 0) {
|
|
7006
8280
|
intel.push("\u{1F511} Potential credentials found:");
|
|
7007
|
-
passLines.slice(0,
|
|
8281
|
+
passLines.slice(0, EXTRACT_LIMITS.LINPEAS_PASSWORDS).forEach((p) => intel.push(` ${p.trim()}`));
|
|
7008
8282
|
}
|
|
7009
8283
|
const cveMatches = output.match(/CVE-\d{4}-\d+/gi);
|
|
7010
8284
|
if (cveMatches) {
|
|
@@ -7022,7 +8296,7 @@ function extractEnum4linuxIntel(output) {
|
|
|
7022
8296
|
}
|
|
7023
8297
|
const shareMatches = output.match(/Mapping: (\S+),\s*Listing: (\S+)/gi) || output.match(/\\\\\S+\\\\\S+/g);
|
|
7024
8298
|
if (shareMatches) {
|
|
7025
|
-
intel.push(`\u{1F4C2} Shares: ${shareMatches.slice(0,
|
|
8299
|
+
intel.push(`\u{1F4C2} Shares: ${shareMatches.slice(0, EXTRACT_LIMITS.ENUM4LINUX_SHARES).join(", ")}`);
|
|
7026
8300
|
}
|
|
7027
8301
|
const domainMatch = output.match(/Domain:\s*\[(\S+?)\]/i) || output.match(/Workgroup:\s*\[(\S+?)\]/i);
|
|
7028
8302
|
if (domainMatch) intel.push(`\u{1F3E2} Domain: ${domainMatch[1]}`);
|
|
@@ -7050,9 +8324,9 @@ function extractDirBustIntel(output) {
|
|
|
7050
8324
|
}
|
|
7051
8325
|
if (interestingPaths.length > 0) {
|
|
7052
8326
|
intel.push(`\u{1F4C1} Discovered paths (${interestingPaths.length}):`);
|
|
7053
|
-
interestingPaths.slice(0,
|
|
7054
|
-
if (interestingPaths.length >
|
|
7055
|
-
intel.push(` ... and ${interestingPaths.length -
|
|
8327
|
+
interestingPaths.slice(0, EXTRACT_LIMITS.DIRBUST_PATHS).forEach((p) => intel.push(` ${p}`));
|
|
8328
|
+
if (interestingPaths.length > EXTRACT_LIMITS.DIRBUST_PATHS) {
|
|
8329
|
+
intel.push(` ... and ${interestingPaths.length - EXTRACT_LIMITS.DIRBUST_PATHS} more`);
|
|
7056
8330
|
}
|
|
7057
8331
|
}
|
|
7058
8332
|
return intel;
|
|
@@ -7062,7 +8336,7 @@ function extractSqlmapIntel(output) {
|
|
|
7062
8336
|
const injectionTypes = output.match(/Type:\s*(\S.*?)(?:\n|$)/gi);
|
|
7063
8337
|
if (injectionTypes) {
|
|
7064
8338
|
intel.push("\u{1F489} SQL injection found:");
|
|
7065
|
-
injectionTypes.slice(0,
|
|
8339
|
+
injectionTypes.slice(0, EXTRACT_LIMITS.SQLMAP_INJECTIONS).forEach((t) => intel.push(` ${t.trim()}`));
|
|
7066
8340
|
}
|
|
7067
8341
|
const dbMatch = output.match(/back-end DBMS:\s*(.+)/i);
|
|
7068
8342
|
if (dbMatch) intel.push(`\u{1F5C4}\uFE0F DBMS: ${dbMatch[1].trim()}`);
|
|
@@ -7088,7 +8362,7 @@ function extractHashIntel(output) {
|
|
|
7088
8362
|
if (unixHashes.length > 0) intel.push(`#\uFE0F\u20E3 Unix crypt hashes: ${unixHashes.length}`);
|
|
7089
8363
|
if (ntlmHashes.length > 0) {
|
|
7090
8364
|
intel.push(`#\uFE0F\u20E3 NTLM hashes: ${ntlmHashes.length}`);
|
|
7091
|
-
ntlmHashes.slice(0,
|
|
8365
|
+
ntlmHashes.slice(0, EXTRACT_LIMITS.HASH_NTLM).forEach((h) => intel.push(` ${h.trim().slice(0, EXTRACT_LIMITS.HASH_PREVIEW_LEN)}`));
|
|
7092
8366
|
}
|
|
7093
8367
|
return intel;
|
|
7094
8368
|
}
|
|
@@ -7097,7 +8371,7 @@ function extractGenericIntel(output) {
|
|
|
7097
8371
|
const credPatterns = output.match(/(?:password|passwd|pwd|credentials?)\s*[=:]\s*\S+/gi);
|
|
7098
8372
|
if (credPatterns) {
|
|
7099
8373
|
intel.push("\u{1F511} Potential credentials detected:");
|
|
7100
|
-
credPatterns.slice(0,
|
|
8374
|
+
credPatterns.slice(0, EXTRACT_LIMITS.GENERIC_CREDS).forEach((c) => intel.push(` ${c.trim()}`));
|
|
7101
8375
|
}
|
|
7102
8376
|
const cves = output.match(/CVE-\d{4}-\d+/gi);
|
|
7103
8377
|
if (cves) {
|
|
@@ -7115,7 +8389,7 @@ function extractGenericIntel(output) {
|
|
|
7115
8389
|
}
|
|
7116
8390
|
const paths = output.match(/\/(?:etc\/(?:shadow|passwd|sudoers)|root\/|home\/\S+|var\/www\/\S+|opt\/\S+)\S*/g);
|
|
7117
8391
|
if (paths) {
|
|
7118
|
-
const uniquePaths = [...new Set(paths)].slice(0,
|
|
8392
|
+
const uniquePaths = [...new Set(paths)].slice(0, EXTRACT_LIMITS.GENERIC_PATHS);
|
|
7119
8393
|
intel.push(`\u{1F4C2} Interesting paths: ${uniquePaths.join(", ")}`);
|
|
7120
8394
|
}
|
|
7121
8395
|
const flagPatterns = output.match(/(?:flag|secret|key|token)\{[^}]+\}/gi);
|
|
@@ -7127,7 +8401,7 @@ function extractGenericIntel(output) {
|
|
|
7127
8401
|
}
|
|
7128
8402
|
|
|
7129
8403
|
// src/shared/utils/context-digest.ts
|
|
7130
|
-
import { writeFileSync as
|
|
8404
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
7131
8405
|
var PASSTHROUGH_THRESHOLD = 3e3;
|
|
7132
8406
|
var LAYER2_THRESHOLD = 8e3;
|
|
7133
8407
|
var LAYER3_THRESHOLD = 5e4;
|
|
@@ -7352,13 +8626,13 @@ function normalizeLine(line) {
|
|
|
7352
8626
|
function saveFullOutput(output, toolName) {
|
|
7353
8627
|
try {
|
|
7354
8628
|
const outputDir = getOutputDir();
|
|
7355
|
-
if (!
|
|
7356
|
-
|
|
8629
|
+
if (!existsSync7(outputDir)) {
|
|
8630
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
7357
8631
|
}
|
|
7358
8632
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
7359
8633
|
const safeName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 30);
|
|
7360
8634
|
const filePath = `${outputDir}/${timestamp}_${safeName}.txt`;
|
|
7361
|
-
|
|
8635
|
+
writeFileSync7(filePath, output, "utf-8");
|
|
7362
8636
|
return filePath;
|
|
7363
8637
|
} catch (err) {
|
|
7364
8638
|
debugLog("general", "Failed to save full output to file", { toolName, error: String(err) });
|
|
@@ -7790,7 +9064,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7790
9064
|
const digestResult = await digestToolOutput(
|
|
7791
9065
|
outputText,
|
|
7792
9066
|
call.name,
|
|
7793
|
-
JSON.stringify(call.input).slice(0,
|
|
9067
|
+
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
7794
9068
|
llmDigestFn
|
|
7795
9069
|
);
|
|
7796
9070
|
outputText = digestResult.digestedOutput;
|
|
@@ -7880,8 +9154,8 @@ Please decide how to handle this error and continue.`;
|
|
|
7880
9154
|
};
|
|
7881
9155
|
|
|
7882
9156
|
// src/agents/prompt-builder.ts
|
|
7883
|
-
import { readFileSync as
|
|
7884
|
-
import { join as
|
|
9157
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
|
|
9158
|
+
import { join as join10, dirname as dirname5 } from "path";
|
|
7885
9159
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7886
9160
|
|
|
7887
9161
|
// src/shared/constants/prompts.ts
|
|
@@ -7935,10 +9209,104 @@ var INITIAL_TASKS = {
|
|
|
7935
9209
|
RECON: "Initial reconnaissance and target discovery"
|
|
7936
9210
|
};
|
|
7937
9211
|
|
|
9212
|
+
// src/shared/utils/time-strategy.ts
|
|
9213
|
+
var TIME_CONSTANTS = {
|
|
9214
|
+
/** Milliseconds per minute */
|
|
9215
|
+
MS_PER_MINUTE: 6e4,
|
|
9216
|
+
/** Minutes threshold for sprint-to-exploit switch (no deadline mode) */
|
|
9217
|
+
SPRINT_MINUTES: 10,
|
|
9218
|
+
/** Deadline percentage thresholds for phase transitions */
|
|
9219
|
+
PHASE_SPRINT: 0.25,
|
|
9220
|
+
PHASE_EXPLOIT: 0.5,
|
|
9221
|
+
PHASE_CREATIVE: 0.75
|
|
9222
|
+
};
|
|
9223
|
+
function getTimeAdaptiveStrategy(elapsedMs, deadlineMs) {
|
|
9224
|
+
if (deadlineMs === 0) {
|
|
9225
|
+
const mins = Math.floor(elapsedMs / TIME_CONSTANTS.MS_PER_MINUTE);
|
|
9226
|
+
if (mins < TIME_CONSTANTS.SPRINT_MINUTES) {
|
|
9227
|
+
return {
|
|
9228
|
+
phase: "sprint",
|
|
9229
|
+
label: "SPRINT",
|
|
9230
|
+
urgency: "low",
|
|
9231
|
+
directive: `\u26A1 SPRINT MODE (${mins}min elapsed): Parallel reconnaissance. Full port scan + service enumeration + default credentials. Attack immediately on any finding.`
|
|
9232
|
+
};
|
|
9233
|
+
}
|
|
9234
|
+
return {
|
|
9235
|
+
phase: "exploit",
|
|
9236
|
+
label: "EXPLOIT",
|
|
9237
|
+
urgency: "low",
|
|
9238
|
+
directive: `\u{1F3AF} EXPLOIT MODE (${mins}min elapsed): Focus on discovered vectors. Chain findings. Build custom tools if needed.`
|
|
9239
|
+
};
|
|
9240
|
+
}
|
|
9241
|
+
const totalMs = deadlineMs - (deadlineMs > Date.now() ? Date.now() - elapsedMs : 0);
|
|
9242
|
+
const remainingMs = Math.max(0, deadlineMs - Date.now());
|
|
9243
|
+
const pct = totalMs > 0 ? 1 - remainingMs / totalMs : 1;
|
|
9244
|
+
const remainingMins = Math.floor(remainingMs / TIME_CONSTANTS.MS_PER_MINUTE);
|
|
9245
|
+
if (pct < TIME_CONSTANTS.PHASE_SPRINT) {
|
|
9246
|
+
return {
|
|
9247
|
+
phase: "sprint",
|
|
9248
|
+
label: "SPRINT",
|
|
9249
|
+
urgency: "low",
|
|
9250
|
+
directive: [
|
|
9251
|
+
`\u26A1 SPRINT MODE (${remainingMins}min remaining):`,
|
|
9252
|
+
"- Run ALL scans in parallel (nmap TCP/UDP, web fuzzing, CVE search)",
|
|
9253
|
+
"- Try default/weak credentials on every discovered service IMMEDIATELY",
|
|
9254
|
+
"- Check for exposed files (.env, .git, backup.sql, phpinfo)",
|
|
9255
|
+
"- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
|
|
9256
|
+
"- Goal: Maximum attack surface discovery + instant wins"
|
|
9257
|
+
].join("\n")
|
|
9258
|
+
};
|
|
9259
|
+
}
|
|
9260
|
+
if (pct < TIME_CONSTANTS.PHASE_EXPLOIT) {
|
|
9261
|
+
return {
|
|
9262
|
+
phase: "exploit",
|
|
9263
|
+
label: "EXPLOIT",
|
|
9264
|
+
urgency: "medium",
|
|
9265
|
+
directive: [
|
|
9266
|
+
`\u{1F3AF} EXPLOIT MODE (${remainingMins}min remaining):`,
|
|
9267
|
+
"- Concentrate on discovered vectors with highest success probability",
|
|
9268
|
+
"- Run parallel exploitation attempts on multiple targets",
|
|
9269
|
+
"- Search for CVE PoCs for every identified service+version",
|
|
9270
|
+
"- Chain: credentials \u2192 spray everywhere, LFI \u2192 config \u2192 DB creds",
|
|
9271
|
+
"- Keep background scans running for new targets"
|
|
9272
|
+
].join("\n")
|
|
9273
|
+
};
|
|
9274
|
+
}
|
|
9275
|
+
if (pct < TIME_CONSTANTS.PHASE_CREATIVE) {
|
|
9276
|
+
return {
|
|
9277
|
+
phase: "creative",
|
|
9278
|
+
label: "CREATIVE",
|
|
9279
|
+
urgency: "high",
|
|
9280
|
+
directive: [
|
|
9281
|
+
`\u{1F52C} CREATIVE MODE (${remainingMins}min remaining):`,
|
|
9282
|
+
"- Try advanced techniques: chained exploits, custom tools, race conditions",
|
|
9283
|
+
"- Binary analysis for SUID/custom binaries, kernel exploits for privesc",
|
|
9284
|
+
"- Protocol-level attacks: SMB relay, Kerberoasting, ADCS abuse",
|
|
9285
|
+
'- Search for latest bypasses: web_search("{defense} bypass 2025")',
|
|
9286
|
+
"- If stuck >5min on any vector, SWITCH immediately"
|
|
9287
|
+
].join("\n")
|
|
9288
|
+
};
|
|
9289
|
+
}
|
|
9290
|
+
return {
|
|
9291
|
+
phase: "harvest",
|
|
9292
|
+
label: "HARVEST",
|
|
9293
|
+
urgency: "critical",
|
|
9294
|
+
directive: [
|
|
9295
|
+
`\u{1F3C1} HARVEST MODE (${remainingMins}min remaining \u2014 FINAL PUSH):`,
|
|
9296
|
+
"- STOP exploring new vectors. Focus on what you HAVE.",
|
|
9297
|
+
"- Submit ALL discovered flags immediately",
|
|
9298
|
+
"- Check obvious flag locations: /root/flag.txt, env vars, DB tables",
|
|
9299
|
+
"- Re-check previously accessed systems for missed flags",
|
|
9300
|
+
"- Collect and record ALL evidence and proof files",
|
|
9301
|
+
"- Write final report with all findings"
|
|
9302
|
+
].join("\n")
|
|
9303
|
+
};
|
|
9304
|
+
}
|
|
9305
|
+
|
|
7938
9306
|
// src/agents/prompt-builder.ts
|
|
7939
9307
|
var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
|
|
7940
|
-
var PROMPTS_DIR =
|
|
7941
|
-
var TECHNIQUES_DIR =
|
|
9308
|
+
var PROMPTS_DIR = join10(__dirname4, "prompts");
|
|
9309
|
+
var TECHNIQUES_DIR = join10(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
7942
9310
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
7943
9311
|
var PHASE_PROMPT_MAP = {
|
|
7944
9312
|
// Direct mappings — phase has its own prompt file
|
|
@@ -7987,7 +9355,7 @@ var PromptBuilder = class {
|
|
|
7987
9355
|
/**
|
|
7988
9356
|
* Build complete prompt for LLM iteration.
|
|
7989
9357
|
*
|
|
7990
|
-
* Layers (phase-aware,
|
|
9358
|
+
* Layers (phase-aware, enhanced with improvements #2,#3,#6,#7,#8,#12,#14):
|
|
7991
9359
|
* 1. base.md — Core identity, rules, autonomy, shell management (~7.6K tok)
|
|
7992
9360
|
* 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
|
|
7993
9361
|
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~12K tok)
|
|
@@ -7995,8 +9363,14 @@ var PromptBuilder = class {
|
|
|
7995
9363
|
* 5. Scope constraints
|
|
7996
9364
|
* 6. Current state (targets, findings, loot, active processes)
|
|
7997
9365
|
* 7. TODO list
|
|
7998
|
-
* 8. Time awareness
|
|
7999
|
-
* 9.
|
|
9366
|
+
* 8. Time awareness + adaptive strategy (#8)
|
|
9367
|
+
* 9. Challenge analysis (#2: Auto-Prompter)
|
|
9368
|
+
* 10. Attack graph (#6: recommended attack chains)
|
|
9369
|
+
* 11. Working memory (#12: failed/succeeded attempts)
|
|
9370
|
+
* 12. Session timeline (#12: episodic memory)
|
|
9371
|
+
* 13. Learned techniques (#7: dynamic technique library)
|
|
9372
|
+
* 14. Persistent memory (#12: cross-session knowledge)
|
|
9373
|
+
* 15. User context
|
|
8000
9374
|
*/
|
|
8001
9375
|
build(userInput, phase) {
|
|
8002
9376
|
const fragments = [
|
|
@@ -8009,6 +9383,18 @@ var PromptBuilder = class {
|
|
|
8009
9383
|
this.getStateFragment(),
|
|
8010
9384
|
this.getTodoFragment(),
|
|
8011
9385
|
this.getTimeFragment(),
|
|
9386
|
+
this.getChallengeAnalysisFragment(),
|
|
9387
|
+
// #2
|
|
9388
|
+
this.getAttackGraphFragment(),
|
|
9389
|
+
// #6
|
|
9390
|
+
this.getWorkingMemoryFragment(),
|
|
9391
|
+
// #12
|
|
9392
|
+
this.getEpisodicMemoryFragment(),
|
|
9393
|
+
// #12
|
|
9394
|
+
this.getDynamicTechniquesFragment(),
|
|
9395
|
+
// #7
|
|
9396
|
+
this.getPersistentMemoryFragment(),
|
|
9397
|
+
// #12
|
|
8012
9398
|
PROMPT_DEFAULTS.USER_CONTEXT(userInput)
|
|
8013
9399
|
];
|
|
8014
9400
|
return fragments.filter((f) => !!f).join("\n\n");
|
|
@@ -8028,8 +9414,8 @@ ${content}
|
|
|
8028
9414
|
* Load a prompt file from src/agents/prompts/
|
|
8029
9415
|
*/
|
|
8030
9416
|
loadPromptFile(filename) {
|
|
8031
|
-
const path2 =
|
|
8032
|
-
return
|
|
9417
|
+
const path2 = join10(PROMPTS_DIR, filename);
|
|
9418
|
+
return existsSync8(path2) ? readFileSync5(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
8033
9419
|
}
|
|
8034
9420
|
/**
|
|
8035
9421
|
* Load phase-specific prompt.
|
|
@@ -8075,15 +9461,15 @@ ${content}
|
|
|
8075
9461
|
* "마크다운 파일 하나를 폴더에 넣으면, PromptBuilder가 자동으로 발견하고 로드한다."
|
|
8076
9462
|
*/
|
|
8077
9463
|
loadPhaseRelevantTechniques(phase) {
|
|
8078
|
-
if (!
|
|
9464
|
+
if (!existsSync8(TECHNIQUES_DIR)) return "";
|
|
8079
9465
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
8080
9466
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
8081
9467
|
const fragments = [];
|
|
8082
9468
|
for (const technique of priorityTechniques) {
|
|
8083
|
-
const filePath =
|
|
9469
|
+
const filePath = join10(TECHNIQUES_DIR, `${technique}.md`);
|
|
8084
9470
|
try {
|
|
8085
|
-
if (!
|
|
8086
|
-
const content =
|
|
9471
|
+
if (!existsSync8(filePath)) continue;
|
|
9472
|
+
const content = readFileSync5(filePath, PROMPT_CONFIG.ENCODING);
|
|
8087
9473
|
if (content) {
|
|
8088
9474
|
fragments.push(`<technique-reference category="${technique}">
|
|
8089
9475
|
${content}
|
|
@@ -8096,8 +9482,8 @@ ${content}
|
|
|
8096
9482
|
try {
|
|
8097
9483
|
const allFiles = readdirSync2(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
8098
9484
|
for (const file of allFiles) {
|
|
8099
|
-
const filePath =
|
|
8100
|
-
const content =
|
|
9485
|
+
const filePath = join10(TECHNIQUES_DIR, file);
|
|
9486
|
+
const content = readFileSync5(filePath, PROMPT_CONFIG.ENCODING);
|
|
8101
9487
|
if (content) {
|
|
8102
9488
|
const category = file.replace(".md", "");
|
|
8103
9489
|
fragments.push(`<technique-reference category="${category}">
|
|
@@ -8129,9 +9515,52 @@ ${content}
|
|
|
8129
9515
|
return PROMPT_XML.TODO(list || PROMPT_DEFAULTS.EMPTY_TODO);
|
|
8130
9516
|
}
|
|
8131
9517
|
getTimeFragment() {
|
|
8132
|
-
|
|
9518
|
+
const elapsed = this.state.getElapsedMs();
|
|
9519
|
+
const remaining = this.state.getRemainingMs();
|
|
9520
|
+
const baseTime = `<time-status>
|
|
8133
9521
|
${this.state.getTimeStatus()}
|
|
8134
9522
|
</time-status>`;
|
|
9523
|
+
const strategy = getTimeAdaptiveStrategy(
|
|
9524
|
+
elapsed,
|
|
9525
|
+
remaining > 0 ? Date.now() + remaining : 0
|
|
9526
|
+
);
|
|
9527
|
+
return `${baseTime}
|
|
9528
|
+
|
|
9529
|
+
<time-strategy phase="${strategy.phase}" urgency="${strategy.urgency}">
|
|
9530
|
+
${strategy.directive}
|
|
9531
|
+
</time-strategy>`;
|
|
9532
|
+
}
|
|
9533
|
+
// --- Improvement #2: Auto-Prompter Challenge Analysis ---
|
|
9534
|
+
getChallengeAnalysisFragment() {
|
|
9535
|
+
const analysis = this.state.getChallengeAnalysis();
|
|
9536
|
+
if (!analysis) return "";
|
|
9537
|
+
return formatChallengeAnalysis(analysis);
|
|
9538
|
+
}
|
|
9539
|
+
// --- Improvement #6: Attack Graph ---
|
|
9540
|
+
getAttackGraphFragment() {
|
|
9541
|
+
return this.state.attackGraph.toPrompt();
|
|
9542
|
+
}
|
|
9543
|
+
// --- Improvement #12: Working Memory ---
|
|
9544
|
+
getWorkingMemoryFragment() {
|
|
9545
|
+
return this.state.workingMemory.toPrompt();
|
|
9546
|
+
}
|
|
9547
|
+
// --- Improvement #12: Episodic Memory ---
|
|
9548
|
+
getEpisodicMemoryFragment() {
|
|
9549
|
+
return this.state.episodicMemory.toPrompt();
|
|
9550
|
+
}
|
|
9551
|
+
// --- Improvement #7: Dynamic Technique Library ---
|
|
9552
|
+
getDynamicTechniquesFragment() {
|
|
9553
|
+
return this.state.dynamicTechniques.toPrompt();
|
|
9554
|
+
}
|
|
9555
|
+
// --- Improvement #12: Persistent Memory ---
|
|
9556
|
+
getPersistentMemoryFragment() {
|
|
9557
|
+
const services = [];
|
|
9558
|
+
for (const target of this.state.getAllTargets()) {
|
|
9559
|
+
for (const port of target.ports) {
|
|
9560
|
+
if (port.service) services.push(port.service);
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
return this.state.persistentMemory.toPrompt(services);
|
|
8135
9564
|
}
|
|
8136
9565
|
};
|
|
8137
9566
|
|