pentesting 0.73.2 → 0.73.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,7 @@ import {
46
46
  debugLog,
47
47
  ensureDirExists,
48
48
  generateId,
49
+ generatePrefixedId,
49
50
  getActiveSessionRuntime,
50
51
  getErrorMessage,
51
52
  getProcessOutput,
@@ -60,11 +61,12 @@ import {
60
61
  startBackgroundProcess,
61
62
  stopBackgroundProcess,
62
63
  writeFileContent
63
- } from "./chunk-KBJPZDIL.js";
64
+ } from "./chunk-UB7RW6LM.js";
64
65
  import {
65
66
  DETECTION_PATTERNS,
66
67
  HEALTH_CONFIG,
67
68
  PROCESS_ACTIONS,
69
+ PROCESS_EVENTS,
68
70
  PROCESS_ICONS,
69
71
  PROCESS_LIMITS,
70
72
  PROCESS_ROLES,
@@ -74,8 +76,9 @@ import {
74
76
  getProcessEventLog,
75
77
  llmNodeCooldownPolicy,
76
78
  llmNodeOutputParsing,
77
- llmNodeSystemPrompt
78
- } from "./chunk-YFDJI3GO.js";
79
+ llmNodeSystemPrompt,
80
+ logEvent
81
+ } from "./chunk-GLO6TOJN.js";
79
82
 
80
83
  // src/shared/utils/config/env.ts
81
84
  var ENV_KEYS = {
@@ -363,88 +366,99 @@ async function makeRequest(baseUrl, apiKey, body, signal) {
363
366
 
364
367
  // src/engine/llm-client/stream-processor.ts
365
368
  function processStreamEvent(event, requestId, context) {
366
- const { toolCallsMap, callbacks, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
367
369
  switch (event.type) {
368
370
  case LLM_SSE_EVENT.CONTENT_BLOCK_START:
369
- if (event.content_block) {
370
- const blockType = event.content_block.type;
371
- if (blockType === LLM_BLOCK_TYPE.TEXT) {
372
- currentBlockRef.value = LLM_BLOCK_TYPE.TEXT;
373
- context.onTextStart?.();
374
- callbacks?.onOutputStart?.();
375
- } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
376
- currentBlockRef.value = LLM_BLOCK_TYPE.REASONING;
377
- context.onReasoningStart?.();
378
- callbacks?.onReasoningStart?.();
379
- } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
380
- currentBlockRef.value = LLM_BLOCK_TYPE.TOOL_USE;
381
- toolCallsMap.set(event.content_block.id || "", {
382
- id: event.content_block.id || "",
383
- name: event.content_block.name || "",
384
- input: {},
385
- _pendingJson: "",
386
- _index: event.index
387
- });
388
- }
389
- }
371
+ handleContentBlockStart(event, context);
390
372
  break;
391
373
  case LLM_SSE_EVENT.CONTENT_BLOCK_DELTA:
392
- if (event.delta) {
393
- if (event.delta.type === LLM_DELTA_TYPE.TEXT_DELTA && event.delta.text) {
394
- onContent(event.delta.text);
395
- callbacks?.onOutputDelta?.(event.delta.text);
396
- } else if (
397
- // Anthropic: thinking_delta / GLM-DeepSeek: reasoning_delta
398
- event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking || event.delta.type === LLM_DELTA_TYPE.REASONING_DELTA && event.delta.reasoning
399
- ) {
400
- const chunk = event.delta.thinking || event.delta.reasoning || "";
401
- onReasoning(chunk);
402
- callbacks?.onReasoningDelta?.(chunk);
403
- } else if (event.delta.type === LLM_DELTA_TYPE.INPUT_JSON_DELTA && event.delta.partial_json) {
404
- const index = event.index;
405
- if (index !== void 0) {
406
- const toolCall = Array.from(toolCallsMap.values()).find((t) => t._index === index);
407
- if (toolCall) {
408
- toolCall._pendingJson = (toolCall._pendingJson || "") + event.delta.partial_json;
409
- debugLog("llm", `[${requestId}] JSON DELTA applied`, { index, toolId: toolCall.id, json: event.delta.partial_json });
410
- } else {
411
- debugLog("llm", `[${requestId}] JSON DELTA no tool found for index`, { index });
412
- }
413
- }
414
- }
415
- const estimatedOutput = Math.ceil(getTotalChars() / LLM_LIMITS.charsPerTokenEstimate);
416
- callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: estimatedOutput });
417
- }
374
+ handleContentBlockDelta(event, requestId, context);
418
375
  break;
419
- case LLM_SSE_EVENT.CONTENT_BLOCK_STOP: {
420
- const stoppedType = currentBlockRef.value;
421
- currentBlockRef.value = null;
422
- if (stoppedType === LLM_BLOCK_TYPE.TEXT) {
423
- context.onTextEnd?.();
424
- callbacks?.onOutputEnd?.();
425
- } else if (stoppedType === LLM_BLOCK_TYPE.REASONING) {
426
- context.onReasoningEnd?.();
427
- callbacks?.onReasoningEnd?.();
428
- }
376
+ case LLM_SSE_EVENT.CONTENT_BLOCK_STOP:
377
+ handleContentBlockStop(context);
429
378
  break;
430
- }
431
379
  case LLM_SSE_EVENT.MESSAGE_START:
432
- if (event.message?.usage) {
433
- onUsage({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
434
- callbacks?.onUsageUpdate?.({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
435
- }
380
+ handleMessageStart(event, context);
436
381
  break;
437
382
  case LLM_SSE_EVENT.MESSAGE_DELTA:
438
- if (event.usage) {
439
- onUsage({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
440
- callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
441
- }
383
+ handleMessageDelta(event, context);
442
384
  break;
443
385
  case LLM_SSE_EVENT.ERROR:
444
386
  if (event.error) throw new Error(`${event.error.type}: ${event.error.message}`);
445
387
  break;
446
388
  }
447
389
  }
390
+ function handleContentBlockStart(event, context) {
391
+ if (!event.content_block) return;
392
+ const { toolCallsMap, callbacks, currentBlockRef } = context;
393
+ const blockType = event.content_block.type;
394
+ if (blockType === LLM_BLOCK_TYPE.TEXT) {
395
+ currentBlockRef.value = LLM_BLOCK_TYPE.TEXT;
396
+ context.onTextStart?.();
397
+ callbacks?.onOutputStart?.();
398
+ } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
399
+ currentBlockRef.value = LLM_BLOCK_TYPE.REASONING;
400
+ context.onReasoningStart?.();
401
+ callbacks?.onReasoningStart?.();
402
+ } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
403
+ currentBlockRef.value = LLM_BLOCK_TYPE.TOOL_USE;
404
+ toolCallsMap.set(event.content_block.id || "", {
405
+ id: event.content_block.id || "",
406
+ name: event.content_block.name || "",
407
+ input: {},
408
+ _pendingJson: "",
409
+ _index: event.index
410
+ });
411
+ }
412
+ }
413
+ function handleContentBlockDelta(event, requestId, context) {
414
+ if (!event.delta) return;
415
+ const { toolCallsMap, callbacks, onContent, onReasoning, getTotalChars } = context;
416
+ if (event.delta.type === LLM_DELTA_TYPE.TEXT_DELTA && event.delta.text) {
417
+ onContent(event.delta.text);
418
+ callbacks?.onOutputDelta?.(event.delta.text);
419
+ } else if (event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking || event.delta.type === LLM_DELTA_TYPE.REASONING_DELTA && event.delta.reasoning) {
420
+ const chunk = event.delta.thinking || event.delta.reasoning || "";
421
+ onReasoning(chunk);
422
+ callbacks?.onReasoningDelta?.(chunk);
423
+ } else if (event.delta.type === LLM_DELTA_TYPE.INPUT_JSON_DELTA && event.delta.partial_json) {
424
+ const index = event.index;
425
+ if (index !== void 0) {
426
+ const toolCall = Array.from(toolCallsMap.values()).find((t) => t._index === index);
427
+ if (toolCall) {
428
+ toolCall._pendingJson = (toolCall._pendingJson || "") + event.delta.partial_json;
429
+ debugLog("llm", `[${requestId}] JSON DELTA applied`, { index, toolId: toolCall.id, json: event.delta.partial_json });
430
+ } else {
431
+ debugLog("llm", `[${requestId}] JSON DELTA no tool found for index`, { index });
432
+ }
433
+ }
434
+ }
435
+ const estimatedOutput = Math.ceil(getTotalChars() / LLM_LIMITS.charsPerTokenEstimate);
436
+ callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: estimatedOutput });
437
+ }
438
+ function handleContentBlockStop(context) {
439
+ const { callbacks, currentBlockRef } = context;
440
+ const stoppedType = currentBlockRef.value;
441
+ currentBlockRef.value = null;
442
+ if (stoppedType === LLM_BLOCK_TYPE.TEXT) {
443
+ context.onTextEnd?.();
444
+ callbacks?.onOutputEnd?.();
445
+ } else if (stoppedType === LLM_BLOCK_TYPE.REASONING) {
446
+ context.onReasoningEnd?.();
447
+ callbacks?.onReasoningEnd?.();
448
+ }
449
+ }
450
+ function handleMessageStart(event, context) {
451
+ if (!event.message?.usage) return;
452
+ const { onUsage, callbacks } = context;
453
+ onUsage({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
454
+ callbacks?.onUsageUpdate?.({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
455
+ }
456
+ function handleMessageDelta(event, context) {
457
+ if (!event.usage) return;
458
+ const { onUsage, callbacks } = context;
459
+ onUsage({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
460
+ callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: event.usage.output_tokens || 0 });
461
+ }
448
462
  function createStreamContext(callbacks) {
449
463
  let fullContent = "";
450
464
  let fullReasoning = "";
@@ -476,7 +490,6 @@ var LLMClient = class {
476
490
  apiKey;
477
491
  baseUrl;
478
492
  model;
479
- requestCount = 0;
480
493
  constructor() {
481
494
  this.apiKey = getApiKey();
482
495
  this.baseUrl = getBaseUrl() || LLM_API.DEFAULT_BASE_URL;
@@ -493,6 +506,7 @@ var LLMClient = class {
493
506
  return this.model;
494
507
  }
495
508
  async executeNonStream(messages, tools, systemPrompt, callbacks) {
509
+ const requestId = incrementGlobalRequestCount();
496
510
  const thinking = isThinkingEnabled() ? { type: "enabled", budget_tokens: getThinkingBudget() } : void 0;
497
511
  const requestBody = {
498
512
  model: this.model,
@@ -502,13 +516,14 @@ var LLMClient = class {
502
516
  tools,
503
517
  ...thinking && { thinking }
504
518
  };
505
- debugLog("llm", "Non-stream request", { model: this.model, toolCount: tools?.length, thinking: !!thinking });
519
+ debugLog("llm", `[${requestId}] Non-stream request START`, { model: this.model, toolCount: tools?.length, thinking: !!thinking });
506
520
  const response = await makeRequest(this.baseUrl, this.apiKey, requestBody, callbacks?.abortSignal);
507
521
  const data = await response.json();
508
522
  const textBlock = data.content.find((b) => b.type === LLM_BLOCK_TYPE.TEXT);
509
523
  const toolBlocks = data.content.filter((b) => b.type === LLM_BLOCK_TYPE.TOOL_USE);
510
- debugLog("llm", "Non-stream response", { toolCount: toolBlocks.length, tools: toolBlocks.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
524
+ debugLog("llm", `[${requestId}] Non-stream response`, { toolCount: toolBlocks.length, tools: toolBlocks.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
511
525
  const usage = { input_tokens: data.usage?.input_tokens || 0, output_tokens: data.usage?.output_tokens || 0 };
526
+ addGlobalTokenUsage(usage);
512
527
  callbacks?.onUsageUpdate?.(usage);
513
528
  return {
514
529
  content: textBlock?.text || "",
@@ -518,8 +533,7 @@ var LLMClient = class {
518
533
  };
519
534
  }
520
535
  async executeStream(messages, tools, systemPrompt, callbacks) {
521
- this.requestCount++;
522
- const requestId = this.requestCount;
536
+ const requestId = incrementGlobalRequestCount();
523
537
  const thinking = isThinkingEnabled() ? { type: LLM_THINKING_MODE.ENABLED, budget_tokens: getThinkingBudget() } : void 0;
524
538
  const requestBody = {
525
539
  model: this.model,
@@ -560,6 +574,9 @@ var LLMClient = class {
560
574
  }
561
575
  const toolCalls = resolveToolCalls(toolCallsMap);
562
576
  const stripped = stripThinkTags(contentChunks.join(""), reasoningChunks.join(""));
577
+ if (usage.input_tokens > 0 || usage.output_tokens > 0) {
578
+ addGlobalTokenUsage(usage);
579
+ }
563
580
  return {
564
581
  content: stripped.cleanText,
565
582
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
@@ -621,12 +638,27 @@ function resolveToolCalls(toolCallsMap) {
621
638
 
622
639
  // src/engine/llm-client/factory.ts
623
640
  var llmInstance = null;
641
+ var globalRequestCount = 0;
642
+ var globalTokenUsage = { input_tokens: 0, output_tokens: 0 };
624
643
  function getLLMClient() {
625
644
  if (!llmInstance) {
626
645
  llmInstance = new LLMClient();
627
646
  }
628
647
  return llmInstance;
629
648
  }
649
+ function incrementGlobalRequestCount() {
650
+ return ++globalRequestCount;
651
+ }
652
+ function getGlobalRequestCount() {
653
+ return globalRequestCount;
654
+ }
655
+ function addGlobalTokenUsage(usage) {
656
+ globalTokenUsage.input_tokens += usage.input_tokens || 0;
657
+ globalTokenUsage.output_tokens += usage.output_tokens || 0;
658
+ }
659
+ function getGlobalTokenUsage() {
660
+ return globalTokenUsage;
661
+ }
630
662
 
631
663
  // src/engine/auxiliary-llm/auxiliary-llm-base.ts
632
664
  var AuxiliaryLLMBase = class {
@@ -1706,19 +1738,8 @@ var CHALLENGE_TYPE_SIGNALS = {
1706
1738
  "escape"
1707
1739
  ]
1708
1740
  };
1709
- function analyzeChallenge(reconData) {
1710
- if (!reconData || reconData.trim().length === 0) {
1711
- return {
1712
- primaryType: "unknown",
1713
- secondaryTypes: [],
1714
- confidence: 0,
1715
- matchedSignals: [],
1716
- recommendedTechniques: TYPE_TECHNIQUE_MAP.unknown,
1717
- recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP.unknown
1718
- };
1719
- }
1720
- const lowerData = reconData.toLowerCase();
1721
- const scores = {
1741
+ function initializeScores() {
1742
+ return {
1722
1743
  web: { score: 0, signals: [] },
1723
1744
  pwn: { score: 0, signals: [] },
1724
1745
  crypto: { score: 0, signals: [] },
@@ -1728,6 +1749,8 @@ function analyzeChallenge(reconData) {
1728
1749
  network: { score: 0, signals: [] },
1729
1750
  unknown: { score: 0, signals: [] }
1730
1751
  };
1752
+ }
1753
+ function matchKeywordSignals(lowerData, scores) {
1731
1754
  for (const [type, signals] of Object.entries(CHALLENGE_TYPE_SIGNALS)) {
1732
1755
  const challengeType = type;
1733
1756
  if (!(challengeType in scores)) continue;
@@ -1738,6 +1761,8 @@ function analyzeChallenge(reconData) {
1738
1761
  }
1739
1762
  }
1740
1763
  }
1764
+ }
1765
+ function applyRegexBoosts(reconData, lowerData, scores) {
1741
1766
  if (WEB_PORT_PATTERN.test(reconData)) {
1742
1767
  scores.web.score += 2;
1743
1768
  scores.web.signals.push("web-port");
@@ -1758,6 +1783,8 @@ function analyzeChallenge(reconData) {
1758
1783
  scores.crypto.score += 3;
1759
1784
  scores.crypto.signals.push("crypto-file");
1760
1785
  }
1786
+ }
1787
+ function calculateAnalysisResult(scores) {
1761
1788
  const sorted = Object.entries(scores).filter(([type]) => type !== "unknown").sort(([, a], [, b]) => b.score - a.score);
1762
1789
  const [primaryType, primaryData] = sorted[0];
1763
1790
  const totalSignals = sorted.reduce((sum, [, data]) => sum + data.score, 0);
@@ -1773,6 +1800,16 @@ function analyzeChallenge(reconData) {
1773
1800
  recommendedPhasePrompt: TYPE_PHASE_PROMPT_MAP[primaryType] || TYPE_PHASE_PROMPT_MAP.unknown
1774
1801
  };
1775
1802
  }
1803
+ function analyzeChallenge(reconData) {
1804
+ if (!reconData || reconData.trim().length === 0) {
1805
+ return calculateAnalysisResult(initializeScores());
1806
+ }
1807
+ const lowerData = reconData.toLowerCase();
1808
+ const scores = initializeScores();
1809
+ matchKeywordSignals(lowerData, scores);
1810
+ applyRegexBoosts(reconData, lowerData, scores);
1811
+ return calculateAnalysisResult(scores);
1812
+ }
1776
1813
 
1777
1814
  // src/shared/utils/knowledge/challenge/formatter.ts
1778
1815
  function formatChallengeAnalysis(analysis) {
@@ -2746,13 +2783,13 @@ var CoreAgent = class _CoreAgent {
2746
2783
  maxIterations;
2747
2784
  abortController = null;
2748
2785
  toolExecutor = null;
2749
- constructor(agentType, state, events, toolRegistry, maxIterations) {
2750
- this.agentType = agentType;
2751
- this.state = state;
2752
- this.events = events;
2753
- this.toolRegistry = toolRegistry;
2786
+ constructor(options) {
2787
+ this.agentType = options.agentType;
2788
+ this.state = options.state;
2789
+ this.events = options.events;
2790
+ this.toolRegistry = options.toolRegistry;
2754
2791
  this.llm = getLLMClient();
2755
- this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
2792
+ this.maxIterations = options.maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
2756
2793
  }
2757
2794
  setToolRegistry(registry) {
2758
2795
  this.toolRegistry = registry;
@@ -2772,14 +2809,19 @@ var CoreAgent = class _CoreAgent {
2772
2809
  // Over 4+ hour sessions this can accumulate GB of tool results in memory.
2773
2810
  // This cap ensures messages never exceed a safe size regardless of extractor health.
2774
2811
  static MAX_MESSAGES_HARD_CAP = 50;
2812
+ static MAX_MESSAGES_CHAR_CAP = 1e6;
2775
2813
  trimMessagesIfNeeded(messages) {
2776
- if (messages.length <= _CoreAgent.MAX_MESSAGES_HARD_CAP) return;
2777
- const firstMsg = messages[0];
2778
- const keptCount = _CoreAgent.MAX_MESSAGES_HARD_CAP - 1;
2779
- const recentMessages = messages.slice(-keptCount);
2780
- messages.length = 0;
2781
- if (firstMsg) messages.push(firstMsg);
2782
- messages.push(...recentMessages);
2814
+ let totalChars = messages.reduce((sum, m) => {
2815
+ const len = typeof m.content === "string" ? m.content.length : JSON.stringify(m.content).length;
2816
+ return sum + len;
2817
+ }, 0);
2818
+ if (messages.length <= _CoreAgent.MAX_MESSAGES_HARD_CAP && totalChars <= _CoreAgent.MAX_MESSAGES_CHAR_CAP) return;
2819
+ while (messages.length > 2 && (messages.length > _CoreAgent.MAX_MESSAGES_HARD_CAP || totalChars > _CoreAgent.MAX_MESSAGES_CHAR_CAP)) {
2820
+ const removed = messages.splice(1, 1)[0];
2821
+ if (removed) {
2822
+ totalChars -= typeof removed.content === "string" ? removed.content.length : JSON.stringify(removed.content).length;
2823
+ }
2824
+ }
2783
2825
  }
2784
2826
  /** The core loop: Think → Act → Observe */
2785
2827
  async run(task, systemPrompt) {
@@ -3232,10 +3274,250 @@ auto-proxied by the system \u2014 no extra work needed for those.`,
3232
3274
  execute: async (params) => writeFileContent(params.path, params.content)
3233
3275
  };
3234
3276
 
3277
+ // src/engine/tools/system/shell-tools.ts
3278
+ function createShellTool(name, description, parameters, required, execute) {
3279
+ return {
3280
+ name,
3281
+ description,
3282
+ parameters,
3283
+ required,
3284
+ execute
3285
+ };
3286
+ }
3287
+ function getShellCheckCommand(profile) {
3288
+ switch (profile) {
3289
+ case "stability":
3290
+ return "echo $TERM && tty && stty -a";
3291
+ case "post":
3292
+ return "id && whoami && hostname && ip a | head -n 20";
3293
+ case "environment":
3294
+ return "pwd && uname -a && cat /etc/os-release 2>/dev/null | head -n 5";
3295
+ case "identity":
3296
+ default:
3297
+ return "id && whoami && hostname";
3298
+ }
3299
+ }
3300
+ function getShellUpgradeCommand(method) {
3301
+ switch (method || "python_pty") {
3302
+ case "python_pty":
3303
+ return `python3 -c 'import pty; pty.spawn("/bin/bash")' || python -c 'import pty; pty.spawn("/bin/bash")'`;
3304
+ case "script":
3305
+ return "script -q /dev/null -c /bin/bash";
3306
+ case "perl":
3307
+ return q(`perl -e 'exec "/bin/bash";'`);
3308
+ case "ruby":
3309
+ return q(`ruby -e 'exec "/bin/bash"'`);
3310
+ case "socat_probe":
3311
+ return "command -v socat && socat -h | head -n 5";
3312
+ case "stty_probe":
3313
+ return "echo $TERM && tty && stty -a";
3314
+ default:
3315
+ return null;
3316
+ }
3317
+ }
3318
+ function outputLooksStabilized(output) {
3319
+ const normalized = output.toLowerCase();
3320
+ return normalized.includes("xterm") || normalized.includes("/dev/pts/") || normalized.includes("rows") || normalized.includes("columns");
3321
+ }
3322
+ function q(value) {
3323
+ return value;
3324
+ }
3325
+ async function executeShellUpgrade(processId, method, waitMs) {
3326
+ const command = getShellUpgradeCommand(method);
3327
+ if (!command) {
3328
+ return {
3329
+ success: false,
3330
+ output: "",
3331
+ error: `Unknown shell upgrade method: ${method}. Use: python_pty, script, perl, ruby, socat_probe, stty_probe`
3332
+ };
3333
+ }
3334
+ logEvent(processId, PROCESS_EVENTS.SHELL_UPGRADE_ATTEMPTED, `Shell upgrade method: ${method || "python_pty"}`);
3335
+ return handleInteractAction(processId, command, waitMs);
3336
+ }
3337
+ function buildListenerCommand(port, bind) {
3338
+ return bind ? `nc -lvnp ${port} -s ${bind}` : `nc -lvnp ${port}`;
3339
+ }
3340
+ function executeListenerStart(port, bind, purpose) {
3341
+ if (!Number.isFinite(port) || port <= 0) {
3342
+ return {
3343
+ success: false,
3344
+ output: "",
3345
+ error: "Invalid port. Provide a positive numeric port."
3346
+ };
3347
+ }
3348
+ const usedPorts = getUsedPorts();
3349
+ if (usedPorts.includes(port)) {
3350
+ return {
3351
+ success: false,
3352
+ output: `[X] PORT CONFLICT: Port ${port} is already in use.
3353
+ Used ports: ${usedPorts.join(", ")}`,
3354
+ error: `Port ${port} already in use`
3355
+ };
3356
+ }
3357
+ const command = buildListenerCommand(port, bind);
3358
+ const portMatch = command.match(DETECTION_PATTERNS.LISTENER);
3359
+ if (!portMatch) {
3360
+ return {
3361
+ success: false,
3362
+ output: "",
3363
+ error: `Failed to build listener command for port ${port}`
3364
+ };
3365
+ }
3366
+ const proc = startBackgroundProcess(command, {
3367
+ description: `listener ${port}`,
3368
+ purpose: purpose || `Reverse shell listener on port ${port}`
3369
+ });
3370
+ return {
3371
+ success: true,
3372
+ output: `Listener started.
3373
+ Process ID: ${proc.id}
3374
+ PID: ${proc.pid}
3375
+ Port: ${proc.listeningPort || port}
3376
+ Role: ${proc.role}
3377
+ Purpose: ${proc.purpose}`
3378
+ };
3379
+ }
3380
+ var shellTools = [
3381
+ createShellTool(
3382
+ TOOL_NAMES.LISTENER_START,
3383
+ "Start exactly one reverse-shell listener as a tracked background process.",
3384
+ {
3385
+ port: { type: "number", description: "Listener port" },
3386
+ bind: { type: "string", description: "Optional local bind address" },
3387
+ purpose: { type: "string", description: "Optional purpose for resource tracking" }
3388
+ },
3389
+ ["port"],
3390
+ async (params) => executeListenerStart(
3391
+ Number(params.port),
3392
+ params.bind,
3393
+ params.purpose
3394
+ )
3395
+ ),
3396
+ createShellTool(
3397
+ TOOL_NAMES.LISTENER_STATUS,
3398
+ "Check a reverse-shell listener in detail. Use this instead of bg_process status when supervising callbacks.",
3399
+ {
3400
+ process_id: { type: "string", description: "Listener process ID" }
3401
+ },
3402
+ ["process_id"],
3403
+ async (params) => handleStatusAction(params.process_id)
3404
+ ),
3405
+ createShellTool(
3406
+ TOOL_NAMES.SHELL_PROMOTE,
3407
+ "Promote a listener with a confirmed callback into the active shell role.",
3408
+ {
3409
+ process_id: { type: "string", description: "Listener process ID" },
3410
+ description: { type: "string", description: "Optional shell description" }
3411
+ },
3412
+ ["process_id"],
3413
+ async (params) => handlePromoteAction(params.process_id, params.description)
3414
+ ),
3415
+ createShellTool(
3416
+ TOOL_NAMES.SHELL_EXEC,
3417
+ "Execute a command through an active reverse shell and return new output.",
3418
+ {
3419
+ process_id: { type: "string", description: "Active shell process ID" },
3420
+ command: { type: "string", description: "Command to execute through the shell" },
3421
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3422
+ },
3423
+ ["process_id", "command"],
3424
+ async (params) => handleInteractAction(
3425
+ params.process_id,
3426
+ params.command,
3427
+ params.wait_ms
3428
+ )
3429
+ ),
3430
+ createShellTool(
3431
+ TOOL_NAMES.SHELL_CHECK,
3432
+ "Run a bounded shell quality or environment check. Profiles: identity, stability, environment, post.",
3433
+ {
3434
+ process_id: { type: "string", description: "Shell process ID" },
3435
+ profile: { type: "string", description: "identity | stability | environment | post" },
3436
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3437
+ },
3438
+ ["process_id"],
3439
+ async (params) => {
3440
+ const processId = params.process_id;
3441
+ const result = await handleInteractAction(
3442
+ processId,
3443
+ getShellCheckCommand(params.profile),
3444
+ params.wait_ms
3445
+ );
3446
+ if (result.success && params.profile === "stability" && outputLooksStabilized(result.output)) {
3447
+ logEvent(processId, PROCESS_EVENTS.SHELL_STABILIZED, "Shell stability confirmed by shell_check");
3448
+ }
3449
+ return result;
3450
+ }
3451
+ ),
3452
+ createShellTool(
3453
+ TOOL_NAMES.SHELL_UPGRADE,
3454
+ "Send a bounded shell-upgrade attempt or upgrade probe through an active shell.",
3455
+ {
3456
+ process_id: { type: "string", description: "Shell process ID" },
3457
+ method: { type: "string", description: "python_pty | script | perl | ruby | socat_probe | stty_probe" },
3458
+ wait_ms: { type: "number", description: "Optional wait time for shell output" }
3459
+ },
3460
+ ["process_id"],
3461
+ async (params) => executeShellUpgrade(
3462
+ params.process_id,
3463
+ params.method,
3464
+ params.wait_ms
3465
+ )
3466
+ )
3467
+ ];
3468
+
3469
+ // src/engine/tools/system/offensive-bounded-tools.ts
3470
+ async function runBoundedForegroundCommand(command, timeout) {
3471
+ return runCommand(command, [], timeout ? { timeout } : {});
3472
+ }
3473
+ function createBoundedCommandTool(name, description) {
3474
+ return {
3475
+ name,
3476
+ description,
3477
+ parameters: {
3478
+ command: { type: "string", description: "Bounded foreground command for this phase-specific operation" },
3479
+ timeout: { type: "number", description: "Optional timeout in ms" }
3480
+ },
3481
+ required: ["command"],
3482
+ execute: async (params) => runBoundedForegroundCommand(
3483
+ params.command,
3484
+ params.timeout
3485
+ )
3486
+ };
3487
+ }
3488
+ var offensiveBoundedTools = [
3489
+ createBoundedCommandTool(
3490
+ TOOL_NAMES.EXPLOIT_CREDENTIAL_CHECK,
3491
+ "Run a bounded exploit credential/token validation probe. Use after a chain dumps credentials or tokens."
3492
+ ),
3493
+ createBoundedCommandTool(
3494
+ TOOL_NAMES.EXPLOIT_ARTIFACT_CHECK,
3495
+ "Run a bounded exploit artifact validation probe. Use to verify the current artifact before replacing it."
3496
+ ),
3497
+ createBoundedCommandTool(
3498
+ TOOL_NAMES.EXPLOIT_FOOTHOLD_CHECK,
3499
+ "Run a bounded foothold confirmation probe after an exploit chain appears to land access."
3500
+ ),
3501
+ createBoundedCommandTool(
3502
+ TOOL_NAMES.PWN_CRASH_REPRO,
3503
+ "Run a bounded pwn crash reproduction command from the preserved crash state."
3504
+ ),
3505
+ createBoundedCommandTool(
3506
+ TOOL_NAMES.PWN_OFFSET_CHECK,
3507
+ "Run a bounded pwn offset verification command when control primitives are believed to be known."
3508
+ ),
3509
+ createBoundedCommandTool(
3510
+ TOOL_NAMES.PWN_PAYLOAD_SMOKE,
3511
+ "Run a bounded pwn payload smoke-test command against the latest exploit revision."
3512
+ )
3513
+ ];
3514
+
3235
3515
  // src/engine/tools/system/index.ts
3236
3516
  var systemTools = [
3237
3517
  runCmdTool,
3238
3518
  bgProcessTool,
3519
+ ...shellTools,
3520
+ ...offensiveBoundedTools,
3239
3521
  readFileTool,
3240
3522
  writeFileTool
3241
3523
  ];
@@ -4253,9 +4535,7 @@ function isTransientHttpError(status) {
4253
4535
  async function parseNmap(xmlPath) {
4254
4536
  try {
4255
4537
  const fileResult = await readFileContent(xmlPath);
4256
- if (!fileResult.success) {
4257
- return fileResult;
4258
- }
4538
+ if (!fileResult.success) return fileResult;
4259
4539
  const xmlContent = fileResult.output;
4260
4540
  const results = {
4261
4541
  targets: [],
@@ -4264,31 +4544,7 @@ async function parseNmap(xmlPath) {
4264
4544
  const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
4265
4545
  const hosts = xmlContent.match(hostRegex) || [];
4266
4546
  for (const hostBlock of hosts) {
4267
- const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
4268
- const ip = ipMatch ? ipMatch[1] : "";
4269
- const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
4270
- const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
4271
- const ports = [];
4272
- const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
4273
- let portMatch;
4274
- while ((portMatch = portRegex.exec(hostBlock)) !== null) {
4275
- const protocol = portMatch[1];
4276
- const port = parseInt(portMatch[2]);
4277
- const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
4278
- const state = stateMatch ? stateMatch[1] : "";
4279
- const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
4280
- const service = serviceMatch ? serviceMatch[1] : void 0;
4281
- const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
4282
- if (state === PORT_STATE.OPEN) {
4283
- ports.push({ port, protocol, state, service, version });
4284
- results.summary.openPorts++;
4285
- if (service) results.summary.servicesFound++;
4286
- }
4287
- }
4288
- if (ip) {
4289
- results.targets.push({ ip, hostname, ports });
4290
- results.summary.totalTargets++;
4291
- }
4547
+ parseHost(hostBlock, results);
4292
4548
  }
4293
4549
  return {
4294
4550
  success: true,
@@ -4302,6 +4558,37 @@ async function parseNmap(xmlPath) {
4302
4558
  };
4303
4559
  }
4304
4560
  }
4561
+ function parseHost(hostBlock, results) {
4562
+ const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
4563
+ const ip = ipMatch ? ipMatch[1] : "";
4564
+ const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
4565
+ const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
4566
+ const ports = parsePorts(hostBlock, results);
4567
+ if (ip) {
4568
+ results.targets.push({ ip, hostname, ports });
4569
+ results.summary.totalTargets++;
4570
+ }
4571
+ }
4572
+ function parsePorts(hostBlock, results) {
4573
+ const ports = [];
4574
+ const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
4575
+ let portMatch;
4576
+ while ((portMatch = portRegex.exec(hostBlock)) !== null) {
4577
+ const protocol = portMatch[1];
4578
+ const port = parseInt(portMatch[2]);
4579
+ const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
4580
+ const state = stateMatch ? stateMatch[1] : "";
4581
+ const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
4582
+ const service = serviceMatch ? serviceMatch[1] : void 0;
4583
+ const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
4584
+ if (state === PORT_STATE.OPEN) {
4585
+ ports.push({ port, protocol, state, service, version });
4586
+ results.summary.openPorts++;
4587
+ if (service) results.summary.servicesFound++;
4588
+ }
4589
+ }
4590
+ return ports;
4591
+ }
4305
4592
 
4306
4593
  // src/engine/tools/intel-utils/cve-search.ts
4307
4594
  import { execFileSync } from "child_process";
@@ -4861,54 +5148,57 @@ function getStorageAndAuthDumpSnippet(capturedVarName = "_capturedHeaders", safe
4861
5148
  }
4862
5149
 
4863
5150
  // src/engine/tools/web-browser/scripts/builder.ts
4864
- function buildBrowseScript(url, options, screenshotPath, sessionPath) {
4865
- const safeUrl = safeJsString(url);
4866
- const safeUserAgent = safeJsString(options.userAgent || BROWSER_CONFIG.DEFAULT_USER_AGENT);
4867
- const safeScreenshotPath = screenshotPath ? safeJsString(screenshotPath) : "null";
4868
- const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
4869
- const playwrightPath = getPlaywrightPath();
4870
- const safePlaywrightPath = safeJsString(playwrightPath);
4871
- const safeSessionPath = sessionPath ? safeJsString(sessionPath) : null;
4872
- return `
4873
- const { chromium } = require(${safePlaywrightPath});
5151
+ function buildCommonHeader(safePlaywrightPath) {
5152
+ return `const { chromium } = require(${safePlaywrightPath});
4874
5153
  const fs = require('fs');
4875
5154
 
4876
5155
  (async () => {
4877
5156
  const browser = await chromium.launch({
4878
5157
  headless: true,
4879
5158
  args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
4880
- });
4881
-
4882
- // \xA74 Browser Session: load existing session if requested
5159
+ });`;
5160
+ }
5161
+ function buildSessionContext(options, safeSessionPath) {
5162
+ const safeUserAgent = options.userAgent ? safeJsString(options.userAgent) : "undefined";
5163
+ const safeExtraHeaders = options.extraHeaders ? JSON.stringify(options.extraHeaders) : "{}";
5164
+ const viewportStr = options.viewport ? `viewport: { width: ${options.viewport.width}, height: ${options.viewport.height} },` : "";
5165
+ return `
4883
5166
  const contextOptions = {
4884
- userAgent: ${safeUserAgent},
4885
- viewport: { width: ${options.viewport.width}, height: ${options.viewport.height} },
4886
- extraHTTPHeaders: ${safeExtraHeaders}
5167
+ ${options.userAgent ? `userAgent: ${safeUserAgent},` : ""}
5168
+ ${viewportStr}
5169
+ ${options.extraHeaders ? `extraHTTPHeaders: ${safeExtraHeaders},` : ""}
4887
5170
  };
4888
5171
  ${safeSessionPath && options.useSession ? `
4889
5172
  if (fs.existsSync(${safeSessionPath})) {
4890
5173
  contextOptions.storageState = ${safeSessionPath};
4891
5174
  }` : ""}
4892
-
4893
5175
  const context = await browser.newContext(contextOptions);
5176
+ const page = await context.newPage();`;
5177
+ }
5178
+ function buildSessionSave(saveSession, safeSessionPath, targetObj, authHeadersProp) {
5179
+ if (!safeSessionPath || !saveSession) return "";
5180
+ return `
5181
+ await context.storageState({ path: ${safeSessionPath} });
5182
+ ${targetObj}.sessionSaved = ${safeSessionPath};
4894
5183
 
4895
- const page = await context.newPage();
4896
-
4897
- ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
4898
-
4899
- try {
4900
- await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${options.timeout} });
4901
-
4902
- // Wait for dynamic content
4903
- await page.waitForTimeout(${options.waitAfterLoad});
4904
-
4905
- const result = { _interceptedAuth: _capturedHeaders };
4906
-
4907
- // Extract title
4908
- result.title = await page.title();
4909
-
4910
- // Extract text content
4911
- ${options.extractContent ? `
5184
+ ${getStorageAndAuthDumpSnippet(authHeadersProp, safeSessionPath)}
5185
+ ${targetObj}.authHeadersSaved = _authHeadersPath;
5186
+ ${targetObj}.authHeadersNote = 'auth-headers.json contains intercepted network headers + full storage dump. LLM should inspect _storage field for unlabeled tokens.';
5187
+ `;
5188
+ }
5189
+ function buildCommonFooter() {
5190
+ return `
5191
+ } catch (error) {
5192
+ console.log(JSON.stringify({ error: error.message }));
5193
+ } finally {
5194
+ await browser.close();
5195
+ }
5196
+ })();`;
5197
+ }
5198
+ function buildBrowseExtraction(options) {
5199
+ let extraction = "";
5200
+ if (options.extractContent) {
5201
+ extraction += `
4912
5202
  result.text = await page.evaluate(() => {
4913
5203
  const body = document.body;
4914
5204
  const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null, false);
@@ -4919,19 +5209,19 @@ const fs = require('fs');
4919
5209
  if (content) text += content + '\\n';
4920
5210
  }
4921
5211
  return text.slice(0, ${BROWSER_LIMITS.MAX_TEXT_EXTRACTION});
4922
- });` : ""}
4923
-
4924
- // Extract links
4925
- ${options.extractLinks ? `
5212
+ });`;
5213
+ }
5214
+ if (options.extractLinks) {
5215
+ extraction += `
4926
5216
  result.links = await page.evaluate(() => {
4927
5217
  return Array.from(document.querySelectorAll('a[href]')).map(a => ({
4928
5218
  href: a.href,
4929
5219
  text: a.textContent.trim().slice(0, ${DISPLAY_LIMITS.LINK_TEXT_PREVIEW})
4930
5220
  })).slice(0, ${BROWSER_LIMITS.MAX_LINKS_EXTRACTION});
4931
- });` : ""}
4932
-
4933
- // Extract forms
4934
- ${options.extractForms ? `
5221
+ });`;
5222
+ }
5223
+ if (options.extractForms) {
5224
+ extraction += `
4935
5225
  result.forms = await page.evaluate(() => {
4936
5226
  return Array.from(document.querySelectorAll('form')).map(form => ({
4937
5227
  action: form.action,
@@ -4944,69 +5234,42 @@ const fs = require('fs');
4944
5234
  isRequired: input.required
4945
5235
  }))
4946
5236
  }));
4947
- });` : ""}
4948
-
4949
- // Take screenshot
4950
- ${screenshotPath ? `await page.screenshot({ path: ${safeScreenshotPath}, fullPage: false });` : ""}
5237
+ });`;
5238
+ }
5239
+ return extraction;
5240
+ }
5241
+ function buildBrowseScript(url, options, screenshotPath, sessionPath) {
5242
+ const safeUrl = safeJsString(url);
5243
+ const safeScreenshotPath = screenshotPath ? safeJsString(screenshotPath) : "null";
5244
+ const safePlaywrightPath = safeJsString(getPlaywrightPath());
5245
+ const safeSessionPath = sessionPath ? safeJsString(sessionPath) : null;
5246
+ options.userAgent = options.userAgent || BROWSER_CONFIG.DEFAULT_USER_AGENT;
5247
+ return `
5248
+ ${buildCommonHeader(safePlaywrightPath)}
5249
+ ${buildSessionContext(options, safeSessionPath)}
5250
+ ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
5251
+ try {
5252
+ await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${options.timeout} });
5253
+ await page.waitForTimeout(${options.waitAfterLoad});
4951
5254
 
4952
- // \xA74 Browser Session: save state + capture auth headers (LLM-independent, no hardcoded keys)
4953
- ${safeSessionPath && options.saveSession ? `
4954
- await context.storageState({ path: ${safeSessionPath} });
4955
- result.sessionSaved = ${safeSessionPath};
5255
+ const result = { _interceptedAuth: _capturedHeaders };
5256
+ result.title = await page.title();
4956
5257
 
4957
- ${getStorageAndAuthDumpSnippet("result._interceptedAuth", safeSessionPath)}
4958
- result.authHeadersSaved = _authHeadersPath;
4959
- result.authHeadersNote = 'auth-headers.json contains intercepted network headers + full storage dump. LLM should inspect _storage field for unlabeled tokens.';
4960
- ` : ""}
5258
+ ${buildBrowseExtraction(options)}
5259
+ ${screenshotPath ? `await page.screenshot({ path: ${safeScreenshotPath}, fullPage: false });` : ""}
5260
+ ${buildSessionSave(!!options.saveSession, safeSessionPath, "result", "result._interceptedAuth")}
4961
5261
 
4962
5262
  console.log(JSON.stringify(result));
4963
- } catch (error) {
4964
- console.log(JSON.stringify({ error: error.message }));
4965
- } finally {
4966
- await browser.close();
4967
- }
4968
- })();
5263
+ ${buildCommonFooter()}
4969
5264
  `;
4970
5265
  }
4971
- function buildFormScript(params) {
4972
- const { safeUrl, safeFormData, safePlaywrightPath, timeout, useSession, saveSession, safeSessionPath } = params;
5266
+ function buildFormFillingLogic(safeFormData) {
4973
5267
  return `
4974
- const { chromium } = require(${safePlaywrightPath});
4975
- const fs = require('fs');
4976
-
4977
- (async () => {
4978
- const browser = await chromium.launch({
4979
- headless: true,
4980
- args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}', ${JSON.stringify(getTorBrowserArgs()).slice(1, -1)}].filter(Boolean)
4981
- });
4982
-
4983
- // \xA74 Session: load existing session if requested
4984
- const contextOptions = {};
4985
- ${safeSessionPath && useSession ? `
4986
- if (fs.existsSync(${safeSessionPath})) {
4987
- contextOptions.storageState = ${safeSessionPath};
4988
- }` : ""}
4989
- const context = await browser.newContext(contextOptions);
4990
- const page = await context.newPage();
4991
-
4992
- ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
4993
-
4994
- try {
4995
- await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${timeout} });
4996
-
4997
- // \xA7LLM-AUTONOMY: Semantic form field matching via Accessibility Tree + multiple strategies.
4998
- // No hardcoded CSS selector rules. Tries in priority order:
4999
- // 1. name/id attribute (exact)
5000
- // 2. aria-label / placeholder semantic match (case-insensitive)
5001
- // 3. input[type] semantic inference (username\u2192text, password\u2192password)
5002
5268
  const formData = ${safeFormData};
5003
5269
  for (const [fieldKey, fieldValue] of Object.entries(formData)) {
5004
5270
  const lk = fieldKey.toLowerCase();
5005
-
5006
- // Strategy 1: exact name/id/aria-label attribute
5007
5271
  let el = await page.$(\`[name="\${fieldKey}"], #\${fieldKey}\`);
5008
5272
 
5009
- // Strategy 2: semantic aria-label or placeholder match
5010
5273
  if (!el) {
5011
5274
  el = await page.evaluateHandle((key) => {
5012
5275
  const inputs = [...document.querySelectorAll('input, textarea, select')];
@@ -5019,22 +5282,16 @@ const fs = require('fs');
5019
5282
  }, lk).then(h => h.asElement());
5020
5283
  }
5021
5284
 
5022
- // Strategy 3: type-based inference (username/email/user \u2192 type=text/email, password \u2192 type=password)
5023
5285
  if (!el) {
5024
5286
  let inferredType = null;
5025
5287
  if (['username', 'user', 'email', 'login'].includes(lk)) inferredType = ['text', 'email'];
5026
5288
  if (['password', 'pass', 'pwd'].includes(lk)) inferredType = ['password'];
5027
- if (inferredType) {
5028
- el = await page.$(\`input[type="\${inferredType[0]}"\${inferredType[1] ? \`, input[type="\${inferredType[1]}"\` : ''}]\`);
5029
- }
5289
+ if (inferredType) el = await page.$(\`input[type="\${inferredType[0]}"\${inferredType[1] ? \`, input[type="\${inferredType[1]}"\` : ''}]\`);
5030
5290
  }
5031
5291
 
5032
- if (el) {
5033
- await el.fill(String(fieldValue));
5034
- }
5292
+ if (el) await el.fill(String(fieldValue));
5035
5293
  }
5036
5294
 
5037
- // \xA7LLM-AUTONOMY: Submit via semantic search \u2014 finds any element that looks like a submit action.
5038
5295
  const submitEl = await page.$([
5039
5296
  'button[type="submit"]',
5040
5297
  'input[type="submit"]',
@@ -5047,27 +5304,29 @@ const fs = require('fs');
5047
5304
  await submitEl.click();
5048
5305
  await page.waitForLoadState('networkidle').catch(() => {});
5049
5306
  }
5307
+ `;
5308
+ }
5309
+ function buildFormScript(params) {
5310
+ const { safeUrl, safeFormData, safePlaywrightPath, timeout, useSession, saveSession, safeSessionPath } = params;
5311
+ return `
5312
+ ${buildCommonHeader(safePlaywrightPath)}
5313
+ ${buildSessionContext({ useSession }, safeSessionPath)}
5314
+ ${getNetworkAuthInterceptSnippet("_capturedHeaders")}
5315
+ try {
5316
+ await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${timeout} });
5050
5317
 
5051
- // \xA74 Session: save state + capture real auth headers (no hardcoded keys)
5052
- ${safeSessionPath && saveSession ? `
5053
- await context.storageState({ path: ${safeSessionPath} });
5054
-
5055
- ${getStorageAndAuthDumpSnippet("_capturedHeaders", safeSessionPath)}
5056
- ` : ""}
5318
+ ${buildFormFillingLogic(safeFormData)}
5057
5319
 
5058
5320
  const result = {
5059
5321
  url: page.url(),
5060
5322
  title: await page.title(),
5061
- ${safeSessionPath && saveSession ? `authHeadersSaved: ${safeSessionPath}.replace('${BROWSER_PATHS.SESSION_FILE}', '${BROWSER_PATHS.AUTH_HEADERS_FILE}'),` : ""}
5323
+ _interceptedAuth: _capturedHeaders
5062
5324
  };
5063
5325
 
5326
+ ${buildSessionSave(saveSession, safeSessionPath, "result", "result._interceptedAuth")}
5327
+
5064
5328
  console.log(JSON.stringify(result));
5065
- } catch (error) {
5066
- console.log(JSON.stringify({ error: error.message }));
5067
- } finally {
5068
- await browser.close();
5069
- }
5070
- })();
5329
+ ${buildCommonFooter()}
5071
5330
  `;
5072
5331
  }
5073
5332
 
@@ -7950,6 +8209,7 @@ var SessionState = class {
7950
8209
  };
7951
8210
 
7952
8211
  // src/engine/state/core/shared-state.ts
8212
+ var MAX_DELEGATED_TASKS = 50;
7953
8213
  var SharedState = class {
7954
8214
  attackGraph = new AttackGraph();
7955
8215
  targetState;
@@ -7985,6 +8245,7 @@ var SharedState = class {
7985
8245
  * Current objective for flexible task management.
7986
8246
  */
7987
8247
  currentObjective = null;
8248
+ delegatedTasks = [];
7988
8249
  constructor() {
7989
8250
  this.targetState = new TargetState(this.attackGraph);
7990
8251
  this.findingState = new FindingState();
@@ -8010,6 +8271,7 @@ var SharedState = class {
8010
8271
  this.dynamicTechniques.clear();
8011
8272
  this.artifacts = [];
8012
8273
  this.currentObjective = null;
8274
+ this.delegatedTasks = [];
8013
8275
  this.lastReflection = "";
8014
8276
  this.lastTriageMemo = "";
8015
8277
  }
@@ -8127,6 +8389,38 @@ var SharedState = class {
8127
8389
  getTimeStatus() {
8128
8390
  return this.sessionState.getTimeStatus();
8129
8391
  }
8392
+ recordDelegatedTask(task) {
8393
+ const now = Date.now();
8394
+ const parentTask = task.resumeTaskId ? this.delegatedTasks.find((existing) => existing.id === task.resumeTaskId) : void 0;
8395
+ const record = {
8396
+ ...task,
8397
+ parentTaskId: task.resumeTaskId || void 0,
8398
+ rootTaskId: parentTask ? parentTask.rootTaskId || parentTask.id : task.resumeTaskId || void 0,
8399
+ id: generatePrefixedId("delegated_task"),
8400
+ createdAt: now,
8401
+ updatedAt: now
8402
+ };
8403
+ this.delegatedTasks.push(record);
8404
+ if (this.delegatedTasks.length > MAX_DELEGATED_TASKS) {
8405
+ this.delegatedTasks = this.delegatedTasks.slice(this.delegatedTasks.length - MAX_DELEGATED_TASKS);
8406
+ }
8407
+ return record;
8408
+ }
8409
+ restoreDelegatedTask(task) {
8410
+ this.delegatedTasks.push(task);
8411
+ if (this.delegatedTasks.length > MAX_DELEGATED_TASKS) {
8412
+ this.delegatedTasks = this.delegatedTasks.slice(this.delegatedTasks.length - MAX_DELEGATED_TASKS);
8413
+ }
8414
+ }
8415
+ getDelegatedTasks() {
8416
+ return [...this.delegatedTasks];
8417
+ }
8418
+ getActiveDelegatedTasks() {
8419
+ return this.delegatedTasks.filter((task) => task.status === "waiting" || task.status === "running");
8420
+ }
8421
+ getDelegatedTask(taskId) {
8422
+ return this.delegatedTasks.find((task) => task.id === taskId);
8423
+ }
8130
8424
  };
8131
8425
 
8132
8426
  // src/shared/utils/policy/document.ts
@@ -8168,6 +8462,37 @@ function writePolicyDocument(content) {
8168
8462
  );
8169
8463
  }
8170
8464
 
8465
+ // src/engine/auxiliary-llm/strategist-prompt.ts
8466
+ function buildStrategistPrompt(input) {
8467
+ const state = input.state;
8468
+ const sections = ["## Engagement State", StateSerializer.toPrompt(state)];
8469
+ const failures = state.workingMemory.toPrompt();
8470
+ if (failures) sections.push("", "## Failed Attempts (DO NOT REPEAT THESE)", failures);
8471
+ try {
8472
+ const journalSummary = readJournalSummary();
8473
+ if (journalSummary) sections.push("", "## Session Journal (past turns summary)", journalSummary);
8474
+ } catch {
8475
+ }
8476
+ const policyDocument = readPolicyDocument();
8477
+ if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
8478
+ const graph = state.attackGraph.toPrompt();
8479
+ if (graph) sections.push("", "## Attack Graph", graph);
8480
+ const timeline = state.episodicMemory.toPrompt();
8481
+ if (timeline) sections.push("", "## Recent Actions", timeline);
8482
+ const techniques = state.dynamicTechniques.toPrompt();
8483
+ if (techniques) sections.push("", "## Learned Techniques", techniques);
8484
+ sections.push("", "## Time", state.getTimeStatus());
8485
+ const analysis = state.getChallengeAnalysis?.();
8486
+ if (analysis && analysis.primaryType !== "unknown") {
8487
+ sections.push(
8488
+ "",
8489
+ "## Challenge Type",
8490
+ `${analysis.primaryType.toUpperCase()} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`
8491
+ );
8492
+ }
8493
+ return sections.join("\n");
8494
+ }
8495
+
8171
8496
  // src/engine/auxiliary-llm/strategist.ts
8172
8497
  var Strategist = class extends AuxiliaryLLMBase {
8173
8498
  lastDirective = null;
@@ -8184,33 +8509,7 @@ var Strategist = class extends AuxiliaryLLMBase {
8184
8509
  }
8185
8510
  // ─── Abstract impl ───────────────────────────────────────────────────────
8186
8511
  formatInput(input) {
8187
- const state = input.state;
8188
- const sections = ["## Engagement State", StateSerializer.toPrompt(state)];
8189
- const failures = state.workingMemory.toPrompt();
8190
- if (failures) sections.push("", "## Failed Attempts (DO NOT REPEAT THESE)", failures);
8191
- try {
8192
- const journalSummary = readJournalSummary();
8193
- if (journalSummary) sections.push("", "## Session Journal (past turns summary)", journalSummary);
8194
- } catch {
8195
- }
8196
- const policyDocument = readPolicyDocument();
8197
- if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
8198
- const graph = state.attackGraph.toPrompt();
8199
- if (graph) sections.push("", "## Attack Graph", graph);
8200
- const timeline = state.episodicMemory.toPrompt();
8201
- if (timeline) sections.push("", "## Recent Actions", timeline);
8202
- const techniques = state.dynamicTechniques.toPrompt();
8203
- if (techniques) sections.push("", "## Learned Techniques", techniques);
8204
- sections.push("", "## Time", state.getTimeStatus());
8205
- const analysis = state.getChallengeAnalysis?.();
8206
- if (analysis && analysis.primaryType !== "unknown") {
8207
- sections.push(
8208
- "",
8209
- "## Challenge Type",
8210
- `${analysis.primaryType.toUpperCase()} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`
8211
- );
8212
- }
8213
- return sections.join("\n");
8512
+ return buildStrategistPrompt(input);
8214
8513
  }
8215
8514
  parseOutput(_response) {
8216
8515
  throw new Error("Unused \u2014 execute() is overridden in Strategist");
@@ -8631,7 +8930,7 @@ function isPortArray(value) {
8631
8930
  (item) => typeof item === "object" && item !== null && typeof item.port === "number"
8632
8931
  );
8633
8932
  }
8634
- function parsePorts(value) {
8933
+ function parsePorts2(value) {
8635
8934
  return isPortArray(value) ? value : [];
8636
8935
  }
8637
8936
  function isValidSeverity(value) {
@@ -8663,7 +8962,7 @@ var SPRAY_LOOT_TYPES = /* @__PURE__ */ new Set([
8663
8962
  // src/domains/engagement/handlers/target.ts
8664
8963
  async function executeAddTarget(p, state) {
8665
8964
  const ip = p.ip;
8666
- const ports = parsePorts(p.ports);
8965
+ const ports = parsePorts2(p.ports);
8667
8966
  const os = p.os;
8668
8967
  const hostname = p.hostname;
8669
8968
  const existing = state.getTarget(ip);
@@ -9172,99 +9471,70 @@ function getContextRecommendations(context, variantCount) {
9172
9471
  }
9173
9472
 
9174
9473
  // src/shared/utils/payload-mutator/core.ts
9474
+ var TRANSFORM_STRATEGIES = {
9475
+ [TRANSFORM_TYPE.URL]: (p, v) => {
9476
+ v.push({ payload: urlEncode(p), transform: "url", description: "URL encoded (special chars only)" });
9477
+ v.push({ payload: urlEncodeAll(p), transform: "url_full", description: "URL encoded (all chars)" });
9478
+ },
9479
+ [TRANSFORM_TYPE.DOUBLE_URL]: (p, v) => v.push({ payload: doubleUrlEncode(p), transform: "double_url", description: "Double URL encoded" }),
9480
+ [TRANSFORM_TYPE.TRIPLE_URL]: (p, v) => v.push({ payload: tripleUrlEncode(p), transform: "triple_url", description: "Triple URL encoded" }),
9481
+ [TRANSFORM_TYPE.UNICODE]: (p, v) => v.push({ payload: unicodeEncode(p), transform: "unicode", description: "Unicode escape sequences" }),
9482
+ [TRANSFORM_TYPE.HTML_ENTITY_DEC]: (p, v) => v.push({ payload: htmlEntityDec(p), transform: "html_entity_dec", description: "HTML decimal entities" }),
9483
+ [TRANSFORM_TYPE.HTML_ENTITY_HEX]: (p, v) => v.push({ payload: htmlEntityHex(p), transform: "html_entity_hex", description: "HTML hex entities" }),
9484
+ [TRANSFORM_TYPE.HEX]: (p, v) => v.push({ payload: hexEncode(p), transform: "hex", description: "Hex encoded" }),
9485
+ [TRANSFORM_TYPE.OCTAL]: (p, v) => v.push({ payload: octalEncode(p), transform: "octal", description: "Octal encoded" }),
9486
+ [TRANSFORM_TYPE.BASE64]: (p, v) => v.push({ payload: base64Encode(p), transform: "base64", description: "Base64 encoded" }),
9487
+ [TRANSFORM_TYPE.CASE_SWAP]: (p, v) => {
9488
+ for (let i = 0; i < 3; i++) v.push({ payload: caseSwap(p), transform: "case_swap", description: `Case variation ${i + 1}` });
9489
+ },
9490
+ [TRANSFORM_TYPE.COMMENT_INSERT]: (p, v) => v.push({ payload: commentInsert(p), transform: "comment_insert", description: "SQL comments in keywords" }),
9491
+ [TRANSFORM_TYPE.WHITESPACE_ALT]: (p, v) => {
9492
+ v.push({ payload: whitespaceAlt(p), transform: "whitespace_alt", description: "Alternative whitespace chars" });
9493
+ v.push({ payload: p.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
9494
+ v.push({ payload: p.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
9495
+ },
9496
+ [TRANSFORM_TYPE.CHAR_FUNCTION]: (p, v) => {
9497
+ v.push({ payload: charFunction(p, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
9498
+ v.push({ payload: charFunction(p, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
9499
+ v.push({ payload: charFunction(p, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
9500
+ },
9501
+ [TRANSFORM_TYPE.CONCAT_SPLIT]: (p, v) => v.push({ payload: concatSplit(p), transform: "concat_split", description: "String split via CONCAT" }),
9502
+ [TRANSFORM_TYPE.NULL_BYTE]: (p, v) => {
9503
+ v.push({ payload: nullByteAppend(p), transform: "null_byte", description: "Null byte appended" });
9504
+ v.push({ payload: p + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
9505
+ },
9506
+ [TRANSFORM_TYPE.REVERSE]: (p, v) => v.push({ payload: reversePayload(p), transform: "reverse", description: "Reversed (use with rev | sh)" }),
9507
+ [TRANSFORM_TYPE.UTF8_OVERLONG]: (p, v) => v.push({ payload: utf8Overlong(p), transform: "utf8_overlong", description: "UTF-8 overlong sequences" }),
9508
+ [TRANSFORM_TYPE.MIXED_ENCODING]: (p, v) => v.push({ payload: mixedEncoding(p), transform: "mixed_encoding", description: "Mixed encoding (partial)" }),
9509
+ [TRANSFORM_TYPE.TAG_ALTERNATIVE]: (p, v) => v.push(...generateXssAlternatives(p)),
9510
+ [TRANSFORM_TYPE.EVENT_HANDLER]: (p, v) => v.push(...generateXssAlternatives(p)),
9511
+ [TRANSFORM_TYPE.JS_ALTERNATIVE]: (p, v) => v.push(...generateXssAlternatives(p)),
9512
+ [TRANSFORM_TYPE.KEYWORD_BYPASS]: (p, v) => {
9513
+ v.push({ payload: keywordBypass(p), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
9514
+ v.push({ payload: spaceBypass(p), transform: "space_bypass", description: "$IFS space bypass" });
9515
+ },
9516
+ [TRANSFORM_TYPE.SPACE_BYPASS]: (p, v) => {
9517
+ v.push({ payload: p.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
9518
+ v.push({ payload: p.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
9519
+ v.push({ payload: p.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
9520
+ const parts = p.split(" ");
9521
+ if (parts.length >= 2) v.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
9522
+ }
9523
+ };
9175
9524
  function mutatePayload(request) {
9176
9525
  const { payload, transforms, context = "generic", maxVariants = 20 } = request;
9177
9526
  const variants = [];
9178
- const recommendations = [];
9179
9527
  const activeTransforms = transforms || getDefaultTransforms(context);
9180
9528
  for (const transform of activeTransforms) {
9181
- switch (transform) {
9182
- case TRANSFORM_TYPE.URL:
9183
- variants.push({ payload: urlEncode(payload), transform: "url", description: "URL encoded (special chars only)" });
9184
- variants.push({ payload: urlEncodeAll(payload), transform: "url_full", description: "URL encoded (all chars)" });
9185
- break;
9186
- case TRANSFORM_TYPE.DOUBLE_URL:
9187
- variants.push({ payload: doubleUrlEncode(payload), transform: "double_url", description: "Double URL encoded" });
9188
- break;
9189
- case TRANSFORM_TYPE.TRIPLE_URL:
9190
- variants.push({ payload: tripleUrlEncode(payload), transform: "triple_url", description: "Triple URL encoded" });
9191
- break;
9192
- case TRANSFORM_TYPE.UNICODE:
9193
- variants.push({ payload: unicodeEncode(payload), transform: "unicode", description: "Unicode escape sequences" });
9194
- break;
9195
- case TRANSFORM_TYPE.HTML_ENTITY_DEC:
9196
- variants.push({ payload: htmlEntityDec(payload), transform: "html_entity_dec", description: "HTML decimal entities" });
9197
- break;
9198
- case TRANSFORM_TYPE.HTML_ENTITY_HEX:
9199
- variants.push({ payload: htmlEntityHex(payload), transform: "html_entity_hex", description: "HTML hex entities" });
9200
- break;
9201
- case TRANSFORM_TYPE.HEX:
9202
- variants.push({ payload: hexEncode(payload), transform: "hex", description: "Hex encoded" });
9203
- break;
9204
- case TRANSFORM_TYPE.OCTAL:
9205
- variants.push({ payload: octalEncode(payload), transform: "octal", description: "Octal encoded" });
9206
- break;
9207
- case TRANSFORM_TYPE.BASE64:
9208
- variants.push({ payload: base64Encode(payload), transform: "base64", description: "Base64 encoded" });
9209
- break;
9210
- case TRANSFORM_TYPE.CASE_SWAP:
9211
- for (let i = 0; i < 3; i++) {
9212
- variants.push({ payload: caseSwap(payload), transform: "case_swap", description: `Case variation ${i + 1}` });
9213
- }
9214
- break;
9215
- case TRANSFORM_TYPE.COMMENT_INSERT:
9216
- variants.push({ payload: commentInsert(payload), transform: "comment_insert", description: "SQL comments in keywords" });
9217
- break;
9218
- case TRANSFORM_TYPE.WHITESPACE_ALT:
9219
- variants.push({ payload: whitespaceAlt(payload), transform: "whitespace_alt", description: "Alternative whitespace chars" });
9220
- variants.push({ payload: payload.replace(/ /g, "/**/"), transform: "whitespace_comment", description: "Comment as whitespace" });
9221
- variants.push({ payload: payload.replace(/ /g, "+"), transform: "whitespace_plus", description: "Plus as whitespace" });
9222
- break;
9223
- case TRANSFORM_TYPE.CHAR_FUNCTION:
9224
- variants.push({ payload: charFunction(payload, "mysql"), transform: "char_mysql", description: "MySQL CHAR() encoding" });
9225
- variants.push({ payload: charFunction(payload, "mssql"), transform: "char_mssql", description: "MSSQL CHAR() encoding" });
9226
- variants.push({ payload: charFunction(payload, "pg"), transform: "char_pg", description: "PostgreSQL CHR() encoding" });
9227
- break;
9228
- case TRANSFORM_TYPE.CONCAT_SPLIT:
9229
- variants.push({ payload: concatSplit(payload), transform: "concat_split", description: "String split via CONCAT" });
9230
- break;
9231
- case TRANSFORM_TYPE.NULL_BYTE:
9232
- variants.push({ payload: nullByteAppend(payload), transform: "null_byte", description: "Null byte appended" });
9233
- variants.push({ payload: payload + "%00.jpg", transform: "null_byte_ext", description: "Null byte + fake extension" });
9234
- break;
9235
- case TRANSFORM_TYPE.REVERSE:
9236
- variants.push({ payload: reversePayload(payload), transform: "reverse", description: "Reversed (use with rev | sh)" });
9237
- break;
9238
- case TRANSFORM_TYPE.UTF8_OVERLONG:
9239
- variants.push({ payload: utf8Overlong(payload), transform: "utf8_overlong", description: "UTF-8 overlong sequences" });
9240
- break;
9241
- case TRANSFORM_TYPE.MIXED_ENCODING:
9242
- variants.push({ payload: mixedEncoding(payload), transform: "mixed_encoding", description: "Mixed encoding (partial)" });
9243
- break;
9244
- case TRANSFORM_TYPE.TAG_ALTERNATIVE:
9245
- case TRANSFORM_TYPE.EVENT_HANDLER:
9246
- case TRANSFORM_TYPE.JS_ALTERNATIVE:
9247
- variants.push(...generateXssAlternatives(payload));
9248
- break;
9249
- case TRANSFORM_TYPE.KEYWORD_BYPASS:
9250
- variants.push({ payload: keywordBypass(payload), transform: "keyword_bypass", description: "Quote-inserted keyword bypass" });
9251
- variants.push({ payload: spaceBypass(payload), transform: "space_bypass", description: "$IFS space bypass" });
9252
- break;
9253
- case TRANSFORM_TYPE.SPACE_BYPASS:
9254
- variants.push({ payload: payload.replace(/ /g, "${IFS}"), transform: "ifs", description: "$IFS space bypass" });
9255
- variants.push({ payload: payload.replace(/ /g, "%09"), transform: "tab", description: "Tab space bypass" });
9256
- variants.push({ payload: payload.replace(/ /g, "<"), transform: "redirect", description: "Redirect as separator" });
9257
- const parts = payload.split(" ");
9258
- if (parts.length >= 2) {
9259
- variants.push({ payload: `{${parts.join(",")}}`, transform: "brace", description: "Brace expansion" });
9260
- }
9261
- break;
9529
+ const handler = TRANSFORM_STRATEGIES[transform];
9530
+ if (handler) {
9531
+ handler(payload, variants);
9262
9532
  }
9263
9533
  }
9264
9534
  if (context.startsWith("sql")) {
9265
9535
  variants.push(...generateSqlAlternatives(payload));
9266
9536
  }
9267
- recommendations.push(...getContextRecommendations(context, variants.length));
9537
+ const recommendations = getContextRecommendations(context, variants.length);
9268
9538
  return {
9269
9539
  variants: variants.slice(0, maxVariants),
9270
9540
  recommendations
@@ -9324,8 +9594,8 @@ async function executeGetWordlists(p) {
9324
9594
  };
9325
9595
  const matchesSearch = (filePath, fileName) => {
9326
9596
  if (!search) return true;
9327
- const q = search.toLowerCase();
9328
- return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
9597
+ const q2 = search.toLowerCase();
9598
+ return fileName.toLowerCase().includes(q2) || filePath.toLowerCase().includes(q2);
9329
9599
  };
9330
9600
  const results = ["# Available Wordlists on System\n"];
9331
9601
  let totalCount = 0;
@@ -10653,7 +10923,7 @@ async function executeGoogleDork(args) {
10653
10923
  const output = [
10654
10924
  `=== Google Dork Queries for ${d} ===`,
10655
10925
  "",
10656
- ...selected.map((q, i) => `${i + 1}. https://www.google.com/search?q=${encodeURIComponent(q)}`),
10926
+ ...selected.map((q2, i) => `${i + 1}. https://www.google.com/search?q=${encodeURIComponent(q2)}`),
10657
10927
  "",
10658
10928
  "Paste these URLs into a browser to search manually.",
10659
10929
  "Tip: Also try: https://pentest-tools.com/information-gathering/google-hacking"
@@ -10908,6 +11178,51 @@ function logSuccessfulAction(state, toolCall, approval, result) {
10908
11178
  });
10909
11179
  }
10910
11180
 
11181
+ // src/engine/parsers/security.ts
11182
+ function extractCredentials(output) {
11183
+ const creds = [];
11184
+ const patterns = [
11185
+ // user:password from hydra, medusa, etc.
11186
+ { regex: /login:\s*(\S+)\s+password:\s*(\S+)/gi, type: "password" },
11187
+ // user:hash from hashdump, secretsdump, etc.
11188
+ { regex: /(\S+):(\d+):([a-f0-9]{32}):([a-f0-9]{32})/g, type: "hash" },
11189
+ // Generic user:pass pattern
11190
+ { regex: /(?:username|user|login)\s*[:=]\s*['"]?(\S+?)['"]?\s+(?:password|pass|pwd)\s*[:=]\s*['"]?(\S+?)['"]?/gi, type: "password" },
11191
+ // Database connection strings
11192
+ { regex: /(?:postgres|mysql|mongodb):\/\/([^:]+):([^@]+)@/g, type: "password" },
11193
+ // API tokens/keys
11194
+ { regex: /(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: "token" }
11195
+ ];
11196
+ for (const { regex, type } of patterns) {
11197
+ let match;
11198
+ while ((match = regex.exec(output)) !== null) {
11199
+ const username = match[1] || "unknown";
11200
+ const credential = match[2] || match[1];
11201
+ if (credential && !creds.some((c) => c.username === username && c.credential === credential)) {
11202
+ creds.push({ username, credential, type, source: "auto-extracted" });
11203
+ }
11204
+ }
11205
+ }
11206
+ return creds;
11207
+ }
11208
+ function extractVulnerabilities(output) {
11209
+ const vulns = [];
11210
+ const cveRegex = /CVE-\d{4}-\d{4,}/g;
11211
+ let match;
11212
+ while ((match = cveRegex.exec(output)) !== null) {
11213
+ const cveId = match[0];
11214
+ if (!vulns.some((v) => v.id === cveId)) {
11215
+ vulns.push({
11216
+ id: cveId,
11217
+ severity: "unknown",
11218
+ description: `${cveId} referenced in output`,
11219
+ hasExploit: /exploit|poc|proof.of.concept/i.test(output)
11220
+ });
11221
+ }
11222
+ }
11223
+ return vulns;
11224
+ }
11225
+
10911
11226
  // src/engine/parsers/types.ts
10912
11227
  var PORT_STATE2 = {
10913
11228
  OPEN: "open",
@@ -10977,51 +11292,6 @@ function extractFuzzStructured(output) {
10977
11292
  };
10978
11293
  }
10979
11294
 
10980
- // src/engine/parsers/security.ts
10981
- function extractCredentials(output) {
10982
- const creds = [];
10983
- const patterns = [
10984
- // user:password from hydra, medusa, etc.
10985
- { regex: /login:\s*(\S+)\s+password:\s*(\S+)/gi, type: "password" },
10986
- // user:hash from hashdump, secretsdump, etc.
10987
- { regex: /(\S+):(\d+):([a-f0-9]{32}):([a-f0-9]{32})/g, type: "hash" },
10988
- // Generic user:pass pattern
10989
- { regex: /(?:username|user|login)\s*[:=]\s*['"]?(\S+?)['"]?\s+(?:password|pass|pwd)\s*[:=]\s*['"]?(\S+?)['"]?/gi, type: "password" },
10990
- // Database connection strings
10991
- { regex: /(?:postgres|mysql|mongodb):\/\/([^:]+):([^@]+)@/g, type: "password" },
10992
- // API tokens/keys
10993
- { regex: /(?:api[_-]?key|token|secret|bearer)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: "token" }
10994
- ];
10995
- for (const { regex, type } of patterns) {
10996
- let match;
10997
- while ((match = regex.exec(output)) !== null) {
10998
- const username = match[1] || "unknown";
10999
- const credential = match[2] || match[1];
11000
- if (credential && !creds.some((c) => c.username === username && c.credential === credential)) {
11001
- creds.push({ username, credential, type, source: "auto-extracted" });
11002
- }
11003
- }
11004
- }
11005
- return creds;
11006
- }
11007
- function extractVulnerabilities(output) {
11008
- const vulns = [];
11009
- const cveRegex = /CVE-\d{4}-\d{4,}/g;
11010
- let match;
11011
- while ((match = cveRegex.exec(output)) !== null) {
11012
- const cveId = match[0];
11013
- if (!vulns.some((v) => v.id === cveId)) {
11014
- vulns.push({
11015
- id: cveId,
11016
- severity: "unknown",
11017
- description: `${cveId} referenced in output`,
11018
- hasExploit: /exploit|poc|proof.of.concept/i.test(output)
11019
- });
11020
- }
11021
- }
11022
- return vulns;
11023
- }
11024
-
11025
11295
  // src/shared/utils/binary-analysis/types.ts
11026
11296
  var RELRO_STATUS = {
11027
11297
  NO: "no",
@@ -11124,45 +11394,42 @@ function formatBinaryAnalysis(info) {
11124
11394
  return lines.join("\n");
11125
11395
  }
11126
11396
 
11127
- // src/engine/parsers/index.ts
11128
- function autoExtractStructured(toolName, output) {
11129
- if (!output || output.length < 10) return null;
11130
- const data = {};
11131
- let hasData = false;
11132
- const creds = extractCredentials(output);
11133
- if (creds && creds.length > 0) {
11134
- data.credentials = creds;
11135
- hasData = true;
11136
- }
11137
- const vulns = extractVulnerabilities(output);
11138
- if (vulns && vulns.length > 0) {
11139
- data.vulnerabilities = vulns;
11140
- hasData = true;
11141
- }
11397
+ // src/engine/parsers/auto-extractors.ts
11398
+ function extractNmapData(toolName, output, data) {
11142
11399
  if (toolName === TOOL_NAMES.PARSE_NMAP || /nmap scan report/i.test(output)) {
11143
11400
  const nmap = extractNmapStructured(output);
11144
11401
  if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
11145
11402
  data.openPorts = nmap.structured.openPorts;
11146
11403
  data.hosts = nmap.structured.hosts;
11147
- hasData = true;
11404
+ return true;
11148
11405
  }
11149
11406
  }
11407
+ return false;
11408
+ }
11409
+ function extractFuzzData(output, data) {
11150
11410
  if (/gobuster|ffuf|feroxbuster|dirbuster/i.test(output)) {
11151
11411
  const fuzz = extractFuzzStructured(output);
11152
11412
  if (fuzz.structured.paths && fuzz.structured.paths.length > 0) {
11153
11413
  data.paths = fuzz.structured.paths;
11154
- hasData = true;
11414
+ return true;
11155
11415
  }
11156
11416
  }
11417
+ return false;
11418
+ }
11419
+ function extractBinaryData(output, data) {
11157
11420
  if (/canary|RELRO|NX|PIE|Stack|FORTIFY/i.test(output) && /enabled|disabled|found|no /i.test(output)) {
11158
11421
  const binaryInfo = parseChecksec(output);
11159
11422
  if (binaryInfo.arch || binaryInfo.canary !== void 0 || binaryInfo.nx !== void 0) {
11160
11423
  data.binaryInfo = binaryInfo;
11161
11424
  data.binaryAnalysisSummary = formatBinaryAnalysis(binaryInfo);
11162
- hasData = true;
11425
+ return true;
11163
11426
  }
11164
11427
  }
11428
+ return false;
11429
+ }
11430
+ function extractOsintData(toolName, output, data) {
11165
11431
  if (toolName === TOOL_NAMES.WHOIS_LOOKUP || toolName === TOOL_NAMES.DNS_RECON || toolName === TOOL_NAMES.SUBDOMAIN_ENUM || toolName === TOOL_NAMES.HARVESTER || toolName === TOOL_NAMES.CERT_TRANSPARENCY) {
11432
+ let hasData = false;
11166
11433
  const ipMatches = output.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g);
11167
11434
  if (ipMatches && ipMatches.length > 0) {
11168
11435
  data.discoveredIPs = [...new Set(ipMatches)].slice(0, 50);
@@ -11173,21 +11440,52 @@ function autoExtractStructured(toolName, output) {
11173
11440
  data.discoveredSubdomains = [...new Set(subMatches)].slice(0, 100);
11174
11441
  hasData = true;
11175
11442
  }
11443
+ return hasData;
11176
11444
  }
11445
+ return false;
11446
+ }
11447
+ function extractCryptoData(toolName, output, data) {
11177
11448
  if (toolName === TOOL_NAMES.RSA_ANALYZE || toolName === TOOL_NAMES.HASH_IDENTIFY || toolName === TOOL_NAMES.PADDING_ORACLE_CHECK) {
11178
11449
  const weaknessMatches = output.match(/(?:FERMAT_FACTORED|SMALL_E|WEAK_N|Padding Oracle|hash type|cracked)[^\n]*/gi);
11179
11450
  if (weaknessMatches && weaknessMatches.length > 0) {
11180
11451
  data.cryptoWeaknesses = weaknessMatches.slice(0, 10);
11181
- hasData = true;
11452
+ return true;
11182
11453
  }
11183
11454
  }
11455
+ return false;
11456
+ }
11457
+ function extractForensicsData(toolName, output, data) {
11184
11458
  if (toolName === TOOL_NAMES.BINWALK_ANALYZE || toolName === TOOL_NAMES.STEGHIDE_EXTRACT || toolName === TOOL_NAMES.ZSTEG_ANALYZE || toolName === TOOL_NAMES.STEGSEEK) {
11185
11459
  const stegoMatches = output.match(/(?:JPEG|PNG|ZIP|embedded|hidden|extracted|passphrase|found)[^\n]*/gi);
11186
11460
  if (stegoMatches && stegoMatches.length > 0) {
11187
11461
  data.stegoHints = stegoMatches.slice(0, 10);
11188
- hasData = true;
11462
+ return true;
11189
11463
  }
11190
11464
  }
11465
+ return false;
11466
+ }
11467
+
11468
+ // src/engine/parsers/index.ts
11469
+ function autoExtractStructured(toolName, output) {
11470
+ if (!output || output.length < 10) return null;
11471
+ const data = {};
11472
+ let hasData = false;
11473
+ const creds = extractCredentials(output);
11474
+ if (creds && creds.length > 0) {
11475
+ data.credentials = creds;
11476
+ hasData = true;
11477
+ }
11478
+ const vulns = extractVulnerabilities(output);
11479
+ if (vulns && vulns.length > 0) {
11480
+ data.vulnerabilities = vulns;
11481
+ hasData = true;
11482
+ }
11483
+ if (extractNmapData(toolName, output, data)) hasData = true;
11484
+ if (extractFuzzData(output, data)) hasData = true;
11485
+ if (extractBinaryData(output, data)) hasData = true;
11486
+ if (extractOsintData(toolName, output, data)) hasData = true;
11487
+ if (extractCryptoData(toolName, output, data)) hasData = true;
11488
+ if (extractForensicsData(toolName, output, data)) hasData = true;
11191
11489
  return hasData ? data : null;
11192
11490
  }
11193
11491
 
@@ -11345,7 +11643,7 @@ function runPostExecutionPipeline(state, toolCall, result) {
11345
11643
  // src/engine/tool-registry/core.ts
11346
11644
  function validateRequiredParams(tool, input) {
11347
11645
  if (!tool.required || tool.required.length === 0) return [];
11348
- return tool.required.filter((param2) => !(param2 in input) || input[param2] === void 0);
11646
+ return tool.required.filter((param2) => !(param2 in input) || input[param2] == null);
11349
11647
  }
11350
11648
  var ToolRegistry = class {
11351
11649
  constructor(state, scopeGuard, approvalGate, events) {
@@ -11553,7 +11851,16 @@ After completion: record key loot/findings from the sub-agent output to canonica
11553
11851
  task: { type: "string", description: "Delegated task goal" },
11554
11852
  target: { type: "string", description: "Optional target IP/URL/path" },
11555
11853
  context: { type: "string", description: "Optional extra context" },
11556
- time_limit_min: { type: "number", description: "Optional hint only" }
11854
+ time_limit_min: { type: "number", description: "Optional hint only" },
11855
+ worker_type: {
11856
+ type: "string",
11857
+ enum: ["general", "shell-supervisor", "exploit", "pwn"],
11858
+ description: "Optional worker specialization for the delegated task"
11859
+ },
11860
+ resume_task_id: {
11861
+ type: "string",
11862
+ description: "Optional delegated task ID to resume/continue instead of creating a fresh chain"
11863
+ }
11557
11864
  },
11558
11865
  required: ["task"],
11559
11866
  execute: async (params) => {
@@ -11568,18 +11875,43 @@ After completion: record key loot/findings from the sub-agent output to canonica
11568
11875
  task: params["task"],
11569
11876
  target: params["target"],
11570
11877
  context: params["context"],
11571
- timeLimitMin: params["time_limit_min"]
11878
+ timeLimitMin: params["time_limit_min"],
11879
+ workerType: params["worker_type"],
11880
+ resumeTaskId: params["resume_task_id"]
11572
11881
  };
11573
- const { AgentTool } = await import("./agent-tool-HYQGTZC4.js");
11882
+ const { AgentTool } = await import("./agent-tool-JEFUBDZE.js");
11574
11883
  const executor = new AgentTool(state, events, scopeGuard, approvalGate);
11575
11884
  const result = await executor.execute(input);
11885
+ state.recordDelegatedTask({
11886
+ task: input.task,
11887
+ target: input.target,
11888
+ context: input.context,
11889
+ resumeTaskId: input.resumeTaskId,
11890
+ workerType: input.workerType,
11891
+ status: result.status,
11892
+ summary: result.summary,
11893
+ tried: result.tried,
11894
+ findings: result.findings,
11895
+ loot: result.loot,
11896
+ sessions: result.sessions,
11897
+ assets: result.assets,
11898
+ suggestedNext: result.suggestedNext,
11899
+ waitingOn: result.waitingOn,
11900
+ resumeHint: result.resumeHint,
11901
+ nextWorkerType: result.nextWorkerType
11902
+ });
11576
11903
  const lines = [
11577
11904
  `[Status] ${result.status}`,
11578
11905
  `[Summary] ${result.summary}`,
11579
11906
  result.findings.length ? `[Findings] ${result.findings.join(" | ")}` : "",
11580
11907
  result.loot.length ? `[Loot] ${result.loot.join(" | ")}` : "",
11581
11908
  result.sessions.length ? `[Sessions] ${result.sessions.join(", ")}` : "",
11909
+ result.assets.length ? `[Assets] ${result.assets.join(" | ")}` : "",
11582
11910
  result.tried.length ? `[Tried] ${result.tried.join(" | ")}` : "",
11911
+ input.resumeTaskId ? `[ResumedFrom] ${input.resumeTaskId}` : "",
11912
+ result.waitingOn ? `[Waiting] ${result.waitingOn}` : "",
11913
+ result.resumeHint ? `[Resume] ${result.resumeHint}` : "",
11914
+ result.nextWorkerType ? `[NextWorker] ${result.nextWorkerType}` : "",
11583
11915
  result.suggestedNext ? `[Next] ${result.suggestedNext}` : ""
11584
11916
  ].filter(Boolean);
11585
11917
  return { success: true, output: lines.join("\n") };
@@ -11618,6 +11950,18 @@ var CategorizedToolRegistry = class extends ToolRegistry {
11618
11950
  TOOL_NAMES.READ_FILE,
11619
11951
  TOOL_NAMES.WRITE_FILE,
11620
11952
  TOOL_NAMES.BG_PROCESS,
11953
+ TOOL_NAMES.LISTENER_START,
11954
+ TOOL_NAMES.LISTENER_STATUS,
11955
+ TOOL_NAMES.SHELL_PROMOTE,
11956
+ TOOL_NAMES.SHELL_EXEC,
11957
+ TOOL_NAMES.SHELL_CHECK,
11958
+ TOOL_NAMES.SHELL_UPGRADE,
11959
+ TOOL_NAMES.EXPLOIT_CREDENTIAL_CHECK,
11960
+ TOOL_NAMES.EXPLOIT_ARTIFACT_CHECK,
11961
+ TOOL_NAMES.EXPLOIT_FOOTHOLD_CHECK,
11962
+ TOOL_NAMES.PWN_CRASH_REPRO,
11963
+ TOOL_NAMES.PWN_OFFSET_CHECK,
11964
+ TOOL_NAMES.PWN_PAYLOAD_SMOKE,
11621
11965
  TOOL_NAMES.PARSE_NMAP,
11622
11966
  TOOL_NAMES.SEARCH_CVE,
11623
11967
  TOOL_NAMES.WEB_SEARCH,
@@ -11666,6 +12010,110 @@ var CategorizedToolRegistry = class extends ToolRegistry {
11666
12010
  }
11667
12011
  };
11668
12012
 
12013
+ // src/engine/agent-tool/shell-supervisor-lifecycle.ts
12014
+ function hasConnectionSignal(processId, stdout) {
12015
+ if (DETECTION_PATTERNS.CONNECTION.some((pattern) => pattern.test(stdout))) {
12016
+ return true;
12017
+ }
12018
+ return getProcessEventLog().some(
12019
+ (event) => event.processId === processId && event.event === PROCESS_EVENTS.CONNECTION_DETECTED
12020
+ );
12021
+ }
12022
+ function getRecentCommandDetails(processId) {
12023
+ return getProcessEventLog().filter((event) => event.processId === processId && event.event === PROCESS_EVENTS.COMMAND_SENT).slice(-10).map((event) => event.detail.toLowerCase());
12024
+ }
12025
+ function hasEvent(processId, eventName) {
12026
+ return getProcessEventLog().some(
12027
+ (event) => event.processId === processId && event.event === eventName
12028
+ );
12029
+ }
12030
+ function isPtyUpgradeCommand(detail) {
12031
+ return detail.includes("pty.spawn(") || detail.includes("import pty; pty.spawn(") || detail.includes("script -qc") || detail.includes("script -q /dev/null -c /bin/bash") || detail.includes("script /dev/null -c bash") || detail.includes("stty raw -echo") || detail.includes("export term=") || detail.includes("export shell=") || detail.includes("stty rows") || detail.includes("stty columns") || detail.includes("tty") || detail.includes("/usr/bin/expect -c") || detail.includes('exec "/bin/bash"');
12032
+ }
12033
+ function isShellStabilized(stdout, commandDetails) {
12034
+ const normalized = stdout.toLowerCase();
12035
+ return normalized.includes("xterm") || normalized.includes("rows") || normalized.includes("columns") || commandDetails.some(
12036
+ (detail) => detail.includes("export term=") || detail.includes("export shell=") || detail.includes("stty rows") || detail.includes("stty columns")
12037
+ );
12038
+ }
12039
+ function isPostExploitationCommand(detail) {
12040
+ return detail.includes("sudo -l") || detail.includes("ip a") || detail.includes("ip route") || detail.includes("ps aux") || detail.includes("ss -tlnp") || detail.includes("netstat -tlnp") || detail.includes("env | grep") || detail.includes("find / -perm -4000") || detail.includes("getcap -r /") || detail.includes("cat /etc/os-release") || detail.includes("uname -a") || detail.includes("whoami && hostname");
12041
+ }
12042
+ function getShellSupervisorLifecycleSnapshot() {
12043
+ const processes = listBackgroundProcesses().filter(
12044
+ (process2) => process2.isRunning && (process2.role === PROCESS_ROLES.ACTIVE_SHELL || process2.role === PROCESS_ROLES.LISTENER)
12045
+ );
12046
+ const activeShell = processes.find((process2) => process2.role === PROCESS_ROLES.ACTIVE_SHELL);
12047
+ if (activeShell) {
12048
+ const output = getProcessOutput(activeShell.id);
12049
+ const commandDetails = getRecentCommandDetails(activeShell.id);
12050
+ if (commandDetails.some(isPostExploitationCommand)) {
12051
+ return {
12052
+ phase: "post_exploitation_active",
12053
+ activeShellId: activeShell.id,
12054
+ recommendation: `Active shell ${activeShell.id} is already in post-exploitation flow. Keep reusing it for controlled enumeration, privilege escalation checks, and pivot preparation.`
12055
+ };
12056
+ }
12057
+ if (hasEvent(activeShell.id, PROCESS_EVENTS.SHELL_STABILIZED) || output && isShellStabilized(output.stdout, commandDetails)) {
12058
+ return {
12059
+ phase: "active_shell_stabilized",
12060
+ activeShellId: activeShell.id,
12061
+ recommendation: `Active shell ${activeShell.id} appears stabilized. Reuse it for controlled enumeration and follow-up operations.`
12062
+ };
12063
+ }
12064
+ if (hasEvent(activeShell.id, PROCESS_EVENTS.SHELL_UPGRADE_ATTEMPTED) || commandDetails.some(isPtyUpgradeCommand)) {
12065
+ return {
12066
+ phase: "active_shell_stabilizing",
12067
+ activeShellId: activeShell.id,
12068
+ recommendation: `Active shell ${activeShell.id} is in PTY stabilization flow. Verify TERM/TTY quality, then continue controlled enumeration.`
12069
+ };
12070
+ }
12071
+ return {
12072
+ phase: "active_shell_ready",
12073
+ activeShellId: activeShell.id,
12074
+ recommendation: `Reuse active shell ${activeShell.id}, validate stability, and upgrade PTY before broad enumeration.`
12075
+ };
12076
+ }
12077
+ const listeners = processes.filter((process2) => process2.role === PROCESS_ROLES.LISTENER);
12078
+ for (const listener of listeners) {
12079
+ const output = getProcessOutput(listener.id);
12080
+ if (!output) continue;
12081
+ if (hasConnectionSignal(listener.id, output.stdout)) {
12082
+ return {
12083
+ phase: "listener_callback_detected",
12084
+ listenerId: listener.id,
12085
+ recommendation: `Listener ${listener.id} has a callback signal. Confirm status, promote immediately, then validate and stabilize the shell.`
12086
+ };
12087
+ }
12088
+ }
12089
+ if (listeners.length > 0) {
12090
+ return {
12091
+ phase: "listener_waiting",
12092
+ listenerId: listeners[0]?.id,
12093
+ recommendation: `Reuse listener ${listeners[0]?.id} and keep polling for a callback instead of opening a duplicate listener.`
12094
+ };
12095
+ }
12096
+ return {
12097
+ phase: "no_asset",
12098
+ recommendation: "No shell or listener asset is active. Create exactly one listener only if the callback path still requires it."
12099
+ };
12100
+ }
12101
+ function buildShellSupervisorLifecycleSection() {
12102
+ const snapshot = getShellSupervisorLifecycleSnapshot();
12103
+ const lines = [
12104
+ "## Shell Supervisor Lifecycle",
12105
+ `Current phase: ${snapshot.phase}`,
12106
+ `Recommendation: ${snapshot.recommendation}`
12107
+ ];
12108
+ if (snapshot.activeShellId) {
12109
+ lines.push(`Active shell: ${snapshot.activeShellId}`);
12110
+ }
12111
+ if (snapshot.listenerId) {
12112
+ lines.push(`Tracked listener: ${snapshot.listenerId}`);
12113
+ }
12114
+ return lines.join("\n");
12115
+ }
12116
+
11669
12117
  export {
11670
12118
  ENV_KEYS,
11671
12119
  DEFAULT_MODEL,
@@ -11680,6 +12128,8 @@ export {
11680
12128
  HealthMonitor,
11681
12129
  ZombieHunter,
11682
12130
  getLLMClient,
12131
+ getGlobalRequestCount,
12132
+ getGlobalTokenUsage,
11683
12133
  createContextExtractor,
11684
12134
  parseTurnNumbers,
11685
12135
  rotateTurnRecords,
@@ -11706,5 +12156,7 @@ export {
11706
12156
  formatChallengeAnalysis,
11707
12157
  setCurrentTurn,
11708
12158
  CoreAgent,
12159
+ getShellSupervisorLifecycleSnapshot,
12160
+ buildShellSupervisorLifecycleSection,
11709
12161
  CategorizedToolRegistry
11710
12162
  };