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.
Files changed (2) hide show
  1. package/dist/main.js +1841 -412
  2. 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.24.4";
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, "died", "Process no longer alive (PID check failed)");
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 === "listener") {
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(-10);
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(-3).join(" | ").replace(/\n/g, " ");
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(-5)) {
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
- * Check if a tool call is within allowed scope
3113
+ * Always allow Docker container is the security boundary.
3114
+ * The agent should try everything; observability > restriction.
2447
3115
  */
2448
- check(toolCall) {
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
- * Public helper to check if a specific target is in scope
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) || []).forEach((d) => targets.add(d.toLowerCase()));
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
- function getApprovalLevel(toolCall) {
2599
- if (toolCall.metadata?.approval) return toolCall.metadata.approval;
2600
- if (toolCall.metadata?.category) {
2601
- const categoryLevel = CATEGORY_APPROVAL[toolCall.metadata.category];
2602
- if (categoryLevel === APPROVAL_LEVELS.BLOCK) return APPROVAL_LEVELS.BLOCK;
2603
- }
2604
- const tool = toolCall.name;
2605
- const input = toolCall.input;
2606
- if (tool === TOOL_NAMES.RUN_CMD) {
2607
- const command = String(input.command || "").toLowerCase();
2608
- if (PASSIVE_BINARIES.some((p) => command.includes(p))) {
2609
- return APPROVAL_LEVELS.AUTO;
2610
- }
2611
- if ([CORE_BINARIES.NMAP, CORE_BINARIES.FFUF, CORE_BINARIES.NUCLEI].some((p) => command.includes(p))) {
2612
- if (toolCall.metadata?.category && CATEGORY_APPROVAL[toolCall.metadata.category] === APPROVAL_LEVELS.REVIEW) {
2613
- return APPROVAL_LEVELS.REVIEW;
2614
- }
2615
- return APPROVAL_LEVELS.CONFIRM;
2616
- }
2617
- if ([CORE_BINARIES.SQLMAP, CORE_BINARIES.METASPLOIT, ...EXPLOIT_BINARIES].some((p) => command.includes(p))) {
2618
- return APPROVAL_LEVELS.REVIEW;
2619
- }
2620
- }
2621
- const autoTools = [
2622
- // Intelligence & research (read-only)
2623
- TOOL_NAMES.READ_FILE,
2624
- TOOL_NAMES.SEARCH_CVE,
2625
- TOOL_NAMES.PARSE_NMAP,
2626
- TOOL_NAMES.WEB_SEARCH,
2627
- TOOL_NAMES.BROWSE_URL,
2628
- TOOL_NAMES.GET_CVE_INFO,
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
- * Set auto-approve mode
2654
- */
2655
- setAutoApprove(enabled) {
2656
- this.shouldAutoApprove = enabled;
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
- * Get current auto-approve mode
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
- async request(toolCall) {
2665
- if (this.shouldAutoApprove) return { isApproved: true, reason: "Auto-approve enabled" };
2666
- const level = getApprovalLevel(toolCall);
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
- // src/shared/constants/pentest.ts
2674
- var WORDLISTS = {
2675
- // Most commonly used password wordlists
2676
- ROCKYOU: "/usr/share/wordlists/rockyou.txt",
2677
- COMMON_PASSWORDS: "/usr/share/seclists/Passwords/common-passwords.txt",
2678
- PASSWORDS_10K: "/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt",
2679
- // Username lists
2680
- USERNAMES: "/usr/share/seclists/Usernames/top-usernames-shortlist.txt",
2681
- // Web content discovery
2682
- DIRB_COMMON: "/usr/share/wordlists/dirb/common.txt",
2683
- RAFT_MEDIUM_DIRS: "/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt",
2684
- // DNS/Subdomain discovery
2685
- SUBDOMAINS: "/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt",
2686
- // API testing
2687
- API_ENDPOINTS: "/usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt",
2688
- API_FUZZ: "/usr/share/seclists/Fuzzing/api-fuzz.txt"
2689
- };
2690
- var HASHCAT_MODES = {
2691
- MD5: "0",
2692
- SHA1: "100",
2693
- NTLM: "1000",
2694
- SHA256: "1400",
2695
- bcrypt: "3200",
2696
- WPA: "2500"
2697
- };
2698
- var NOISE_CLASSIFICATION = {
2699
- HIGH: [
2700
- "arp_spoof",
2701
- "mitm_proxy",
2702
- "packet_sniff",
2703
- "dns_spoof",
2704
- "traffic_intercept",
2705
- "nmap",
2706
- "masscan",
2707
- "nuclei",
2708
- "nikto"
2709
- ],
2710
- MEDIUM: [
2711
- "ffuf",
2712
- "gobuster",
2713
- "dirsearch",
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: p.host,
3138
- detail: p.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 ${p.host}
3146
- Detail: ${p.detail}
3147
- ` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
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: p.title,
3164
- severity: p.severity,
3165
- affected: p.affected || [],
4468
+ title,
4469
+ severity,
4470
+ affected,
3166
4471
  description: p.description || "",
3167
- evidence: [],
3168
- isVerified: false,
4472
+ evidence,
4473
+ isVerified: validation.isVerified,
3169
4474
  remediation: "",
3170
4475
  foundAt: Date.now()
3171
4476
  });
3172
- return { success: true, output: `Added: ${p.title}` };
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 join5 } from "path";
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 existsSync4 } from "fs";
3281
- import { join as join3, dirname as dirname2 } from "path";
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 (existsSync4(dockerPath)) return dockerPath;
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 = join3(dir, "playwright");
3293
- if (existsSync4(candidate)) return candidate;
4608
+ const candidate = join4(dir, "playwright");
4609
+ if (existsSync5(candidate)) return candidate;
3294
4610
  }
3295
- return join3(process.cwd(), "node_modules", "playwright");
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 writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
3347
- import { join as join4 } from "path";
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 = join4(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
4670
+ const tempDir = join5(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
3355
4671
  ensureDirExists(tempDir);
3356
- const scriptPath = join4(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
4672
+ const scriptPath = join5(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
3357
4673
  try {
3358
- writeFileSync4(scriptPath, script, "utf-8");
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
- join4(process.cwd(), "node_modules"),
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 ? join5(join5(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
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: existsSync8, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
4857
- const { join: join10 } = await import("path");
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 || !existsSync8(dirPath)) return;
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 = join10(dirPath, entry.name);
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: writeFileSync7 } = await import("fs");
5287
- writeFileSync7(hostsFile, `${spoofIp} ${domain}
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 safety pipeline (Scope -> Approval -> Execution -> Log)
5831
- * Implements §8-1 (Safe Execution Pattern).
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 join6, dirname as dirname3 } from "path";
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: join6(__dirname2, "network/prompt.md")
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: join6(__dirname2, "web/prompt.md")
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: join6(__dirname2, "database/prompt.md")
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: join6(__dirname2, "ad/prompt.md")
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: join6(__dirname2, "email/prompt.md")
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: join6(__dirname2, "remote-access/prompt.md")
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: join6(__dirname2, "file-sharing/prompt.md")
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: join6(__dirname2, "cloud/prompt.md")
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: join6(__dirname2, "container/prompt.md")
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: join6(__dirname2, "api/prompt.md")
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: join6(__dirname2, "wireless/prompt.md")
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: join6(__dirname2, "ics/prompt.md")
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 join7 } from "path";
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 writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
6617
- import { join as join8 } from "path";
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 = join8(sessionsDir, `${sessionId}.json`);
6636
- writeFileSync5(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
6637
- const latestFile = join8(sessionsDir, "latest.json");
6638
- writeFileSync5(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
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: join8(sessionsDir, f),
6647
- mtime: statSync(join8(sessionsDir, f)).mtimeMs
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 = join8(WORKSPACE.SESSIONS, "latest.json");
6658
- if (!existsSync5(latestFile)) {
8015
+ const latestFile = join9(WORKSPACE.SESSIONS, "latest.json");
8016
+ if (!existsSync6(latestFile)) {
6659
8017
  return false;
6660
8018
  }
6661
8019
  try {
6662
- const raw = readFileSync3(latestFile, "utf-8");
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, 30)) {
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, 10)) {
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, 500)}`);
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, 300)}`);
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, 5).forEach((c) => intel.push(` ${c.trim()}`));
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, 5).forEach((p) => intel.push(` ${p.trim()}`));
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, 10).join(", ")}`);
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, 20).forEach((p) => intel.push(` ${p}`));
7054
- if (interestingPaths.length > 20) {
7055
- intel.push(` ... and ${interestingPaths.length - 20} more`);
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, 5).forEach((t) => intel.push(` ${t.trim()}`));
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, 5).forEach((h) => intel.push(` ${h.trim().slice(0, 100)}`));
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, 5).forEach((c) => intel.push(` ${c.trim()}`));
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, 10);
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 writeFileSync6, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
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 (!existsSync6(outputDir)) {
7356
- mkdirSync2(outputDir, { recursive: true });
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
- writeFileSync6(filePath, output, "utf-8");
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, 200),
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 readFileSync4, existsSync as existsSync7, readdirSync as readdirSync2 } from "fs";
7884
- import { join as join9, dirname as dirname5 } from "path";
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 = join9(__dirname4, "prompts");
7941
- var TECHNIQUES_DIR = join9(PROMPTS_DIR, PROMPT_PATHS.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, ~25-30K tokens vs previous ~53K):
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 (elapsed + deadline)
7999
- * 9. User context
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 = join9(PROMPTS_DIR, filename);
8032
- return existsSync7(path2) ? readFileSync4(path2, PROMPT_CONFIG.ENCODING) : "";
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 (!existsSync7(TECHNIQUES_DIR)) return "";
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 = join9(TECHNIQUES_DIR, `${technique}.md`);
9469
+ const filePath = join10(TECHNIQUES_DIR, `${technique}.md`);
8084
9470
  try {
8085
- if (!existsSync7(filePath)) continue;
8086
- const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
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 = join9(TECHNIQUES_DIR, file);
8100
- const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
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
- return `<time-status>
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