pentesting 0.50.0 → 0.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -7
- package/dist/main.js +1201 -1235
- package/package.json +1 -1
- package/dist/orchestrator/orchestrator.md +0 -71
package/dist/main.js
CHANGED
|
@@ -228,7 +228,9 @@ var EXIT_CODES = {
|
|
|
228
228
|
/** Process killed by SIGTERM */
|
|
229
229
|
SIGTERM: 143,
|
|
230
230
|
/** Process killed by SIGKILL */
|
|
231
|
-
SIGKILL: 137
|
|
231
|
+
SIGKILL: 137,
|
|
232
|
+
/** Invalid or missing configuration (Unix EX_CONFIG convention = 78) */
|
|
233
|
+
CONFIG_ERROR: 78
|
|
232
234
|
};
|
|
233
235
|
var PROCESS_ACTIONS = {
|
|
234
236
|
LIST: "list",
|
|
@@ -343,7 +345,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
343
345
|
|
|
344
346
|
// src/shared/constants/agent.ts
|
|
345
347
|
var APP_NAME = "Pentest AI";
|
|
346
|
-
var APP_VERSION = "0.
|
|
348
|
+
var APP_VERSION = "0.51.1";
|
|
347
349
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
348
350
|
var LLM_ROLES = {
|
|
349
351
|
SYSTEM: "system",
|
|
@@ -532,14 +534,7 @@ var PHASES = {
|
|
|
532
534
|
REPORT: "report"
|
|
533
535
|
};
|
|
534
536
|
var AGENT_ROLES = {
|
|
535
|
-
ORCHESTRATOR: "orchestrator"
|
|
536
|
-
RECON: "recon",
|
|
537
|
-
WEB: "web",
|
|
538
|
-
EXPLOITER: "exploit",
|
|
539
|
-
DATABASE: "database",
|
|
540
|
-
INFRA: "infra",
|
|
541
|
-
VULN: "vulnerability_analysis",
|
|
542
|
-
POST_EXPLOIT: "post_exploitation"
|
|
537
|
+
ORCHESTRATOR: "orchestrator"
|
|
543
538
|
};
|
|
544
539
|
var SERVICES = {
|
|
545
540
|
HTTP: "http",
|
|
@@ -772,7 +767,6 @@ var EVENT_TYPES = {
|
|
|
772
767
|
RETRY: "retry",
|
|
773
768
|
USAGE_UPDATE: "usage_update",
|
|
774
769
|
INPUT_REQUEST: "input_request",
|
|
775
|
-
LOG: "log",
|
|
776
770
|
FLAG_FOUND: "flag_found"
|
|
777
771
|
};
|
|
778
772
|
var UI_COMMANDS = {
|
|
@@ -823,7 +817,7 @@ var DEFAULTS = {
|
|
|
823
817
|
PORT_STATE_OPEN: "open"
|
|
824
818
|
};
|
|
825
819
|
|
|
826
|
-
// src/engine/process-manager.ts
|
|
820
|
+
// src/engine/process/process-manager.ts
|
|
827
821
|
import { spawn as spawn3, execSync as execSync2 } from "child_process";
|
|
828
822
|
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
829
823
|
|
|
@@ -1333,12 +1327,7 @@ function validateCommand(command) {
|
|
|
1333
1327
|
return result2;
|
|
1334
1328
|
}
|
|
1335
1329
|
}
|
|
1336
|
-
|
|
1337
|
-
return {
|
|
1338
|
-
isSafe: true,
|
|
1339
|
-
binary: primaryBinary || void 0,
|
|
1340
|
-
args: normalizedCommand.split(/\s+/).slice(1)
|
|
1341
|
-
};
|
|
1330
|
+
return { isSafe: true };
|
|
1342
1331
|
}
|
|
1343
1332
|
function splitChainedCommands(command) {
|
|
1344
1333
|
const parts = [];
|
|
@@ -1596,6 +1585,7 @@ var TOOL_PACKAGE_MAP = {
|
|
|
1596
1585
|
"seclists": { apt: "seclists", brew: "seclists" },
|
|
1597
1586
|
"wfuzz": { apt: "wfuzz", brew: "wfuzz", pip: "wfuzz" },
|
|
1598
1587
|
"dirsearch": { apt: "dirsearch", brew: "dirsearch", pip: "dirsearch" },
|
|
1588
|
+
"wafw00f": { apt: "wafw00f", brew: "wafw00f", pip: "wafw00f" },
|
|
1599
1589
|
"feroxbuster": { apt: "feroxbuster", brew: "feroxbuster" },
|
|
1600
1590
|
"subfinder": { apt: "subfinder", brew: "subfinder" },
|
|
1601
1591
|
"amass": { apt: "amass", brew: "amass" },
|
|
@@ -1637,20 +1627,16 @@ async function detectPackageManager() {
|
|
|
1637
1627
|
}
|
|
1638
1628
|
function isCommandNotFound(stderr, stdout) {
|
|
1639
1629
|
const combined = (stderr + stdout).toLowerCase();
|
|
1640
|
-
|
|
1630
|
+
return [
|
|
1641
1631
|
/command not found/,
|
|
1642
|
-
/not found
|
|
1632
|
+
/not found/,
|
|
1643
1633
|
/no such file or directory/,
|
|
1644
1634
|
/is not recognized/,
|
|
1645
1635
|
/cannot find/,
|
|
1646
1636
|
/not installed/,
|
|
1647
|
-
/unable to locate package
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
if (!missing) return { missing: false, toolName: null };
|
|
1651
|
-
const toolMatch = combined.match(/(?:command not found|not found):\s*([a-zA-Z0-9_-]+)/i);
|
|
1652
|
-
const toolName = toolMatch ? toolMatch[1] : null;
|
|
1653
|
-
return { missing: true, toolName };
|
|
1637
|
+
/unable to locate package/,
|
|
1638
|
+
/enoent/
|
|
1639
|
+
].some((p) => p.test(combined));
|
|
1654
1640
|
}
|
|
1655
1641
|
async function installTool(toolName, eventEmitter, inputHandler) {
|
|
1656
1642
|
const pkgInfo = TOOL_PACKAGE_MAP[toolName];
|
|
@@ -1769,14 +1755,15 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1769
1755
|
}
|
|
1770
1756
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1771
1757
|
const maxRetries = options.maxRetries ?? AGENT_LIMITS.MAX_INSTALL_RETRIES;
|
|
1758
|
+
const toolName = command.split(/[\s/]/).pop() || command;
|
|
1772
1759
|
let lastResult = { success: false, output: "", error: "Unknown error" };
|
|
1773
1760
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1774
|
-
const result2 = await executeCommandOnce(
|
|
1761
|
+
const result2 = await executeCommandOnce(fullCommand, { ...options, timeout });
|
|
1775
1762
|
if (result2.success) {
|
|
1776
1763
|
return result2;
|
|
1777
1764
|
}
|
|
1778
|
-
const
|
|
1779
|
-
if (!missing ||
|
|
1765
|
+
const missing = isCommandNotFound(result2.error || "", result2.output);
|
|
1766
|
+
if (!missing || attempt >= maxRetries) {
|
|
1780
1767
|
return result2;
|
|
1781
1768
|
}
|
|
1782
1769
|
lastResult = result2;
|
|
@@ -1803,7 +1790,7 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1803
1790
|
}
|
|
1804
1791
|
return lastResult;
|
|
1805
1792
|
}
|
|
1806
|
-
async function executeCommandOnce(command,
|
|
1793
|
+
async function executeCommandOnce(command, options = {}) {
|
|
1807
1794
|
return new Promise((resolve) => {
|
|
1808
1795
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1809
1796
|
globalEventEmitter?.({
|
|
@@ -1934,10 +1921,10 @@ function createTempFile(suffix = "") {
|
|
|
1934
1921
|
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1935
1922
|
}
|
|
1936
1923
|
|
|
1937
|
-
// src/engine/process-cleanup.ts
|
|
1924
|
+
// src/engine/process/process-cleanup.ts
|
|
1938
1925
|
import { unlinkSync } from "fs";
|
|
1939
1926
|
|
|
1940
|
-
// src/engine/process-tree.ts
|
|
1927
|
+
// src/engine/process/process-tree.ts
|
|
1941
1928
|
import { execSync } from "child_process";
|
|
1942
1929
|
function discoverChildPids(parentPid) {
|
|
1943
1930
|
try {
|
|
@@ -2021,7 +2008,7 @@ function killProcessTreeSync(pid, childPids) {
|
|
|
2021
2008
|
}
|
|
2022
2009
|
}
|
|
2023
2010
|
|
|
2024
|
-
// src/engine/process-cleanup.ts
|
|
2011
|
+
// src/engine/process/process-cleanup.ts
|
|
2025
2012
|
function syncCleanupAllProcesses(processMap) {
|
|
2026
2013
|
for (const [, proc] of processMap) {
|
|
2027
2014
|
if (!proc.hasExited) {
|
|
@@ -2057,7 +2044,7 @@ function registerExitHandlers(processMap) {
|
|
|
2057
2044
|
});
|
|
2058
2045
|
}
|
|
2059
2046
|
|
|
2060
|
-
// src/engine/process-detector.ts
|
|
2047
|
+
// src/engine/process/process-detector.ts
|
|
2061
2048
|
function detectProcessRole(command) {
|
|
2062
2049
|
const tags = [];
|
|
2063
2050
|
let port;
|
|
@@ -2110,7 +2097,7 @@ function detectConnection(stdout) {
|
|
|
2110
2097
|
return DETECTION_PATTERNS.CONNECTION.some((p) => p.test(stdout));
|
|
2111
2098
|
}
|
|
2112
2099
|
|
|
2113
|
-
// src/engine/process-manager.ts
|
|
2100
|
+
// src/engine/process/process-manager.ts
|
|
2114
2101
|
var backgroundProcesses = /* @__PURE__ */ new Map();
|
|
2115
2102
|
var cleanupDone = false;
|
|
2116
2103
|
registerExitHandlers(backgroundProcesses);
|
|
@@ -3666,6 +3653,22 @@ var SharedState = class {
|
|
|
3666
3653
|
this.data.missionChecklist.push({ id, text, isCompleted: false });
|
|
3667
3654
|
}
|
|
3668
3655
|
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Restore mission checklist from persistence (direct injection).
|
|
3658
|
+
* WHY: avoids the add-then-update roundtrip that loadState previously needed.
|
|
3659
|
+
*/
|
|
3660
|
+
restoreMissionChecklist(items) {
|
|
3661
|
+
for (const item of items) {
|
|
3662
|
+
this.data.missionChecklist.push({ ...item });
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
/**
|
|
3666
|
+
* Restore a todo item from persistence (direct injection with original status).
|
|
3667
|
+
* WHY: addTodo always creates PENDING; this preserves the saved status.
|
|
3668
|
+
*/
|
|
3669
|
+
restoreTodoItem(item) {
|
|
3670
|
+
this.data.todo.push({ ...item });
|
|
3671
|
+
}
|
|
3669
3672
|
// --- Engagement & Scope ---
|
|
3670
3673
|
setEngagement(engagement) {
|
|
3671
3674
|
this.data.engagement = engagement;
|
|
@@ -3708,26 +3711,6 @@ var SharedState = class {
|
|
|
3708
3711
|
getTargets() {
|
|
3709
3712
|
return this.data.targets;
|
|
3710
3713
|
}
|
|
3711
|
-
addServiceFingerprint(ip, fingerprint) {
|
|
3712
|
-
const target = this.getTarget(ip);
|
|
3713
|
-
if (!target) return;
|
|
3714
|
-
target.services = target.services || [];
|
|
3715
|
-
target.services.push(fingerprint);
|
|
3716
|
-
target.primaryCategory = this.calculatePrimaryCategory(target.services);
|
|
3717
|
-
}
|
|
3718
|
-
calculatePrimaryCategory(services) {
|
|
3719
|
-
const counts = {};
|
|
3720
|
-
let winner = SERVICE_CATEGORIES.NETWORK;
|
|
3721
|
-
let max = 0;
|
|
3722
|
-
for (const s of services) {
|
|
3723
|
-
counts[s.category] = (counts[s.category] || 0) + 1;
|
|
3724
|
-
if (counts[s.category] > max) {
|
|
3725
|
-
max = counts[s.category];
|
|
3726
|
-
winner = s.category;
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
return winner;
|
|
3730
|
-
}
|
|
3731
3714
|
// --- Findings & Loot ---
|
|
3732
3715
|
addFinding(finding) {
|
|
3733
3716
|
this.data.findings.push(finding);
|
|
@@ -3738,14 +3721,6 @@ var SharedState = class {
|
|
|
3738
3721
|
getFindingsBySeverity(severity) {
|
|
3739
3722
|
return this.data.findings.filter((f) => f.severity === severity);
|
|
3740
3723
|
}
|
|
3741
|
-
/** Returns findings with confidence >= threshold (default: CONFIRMED = 80) */
|
|
3742
|
-
getFindingsByConfidence(threshold = CONFIDENCE_THRESHOLDS.CONFIRMED) {
|
|
3743
|
-
return this.data.findings.filter((f) => f.confidence >= threshold);
|
|
3744
|
-
}
|
|
3745
|
-
/** True if confidence >= CONFIRMED (80) */
|
|
3746
|
-
isConfirmedFinding(finding) {
|
|
3747
|
-
return finding.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
3748
|
-
}
|
|
3749
3724
|
addLoot(loot) {
|
|
3750
3725
|
this.data.loot.push(loot);
|
|
3751
3726
|
}
|
|
@@ -5460,7 +5435,8 @@ var ENV_KEYS = {
|
|
|
5460
5435
|
THINKING: "PENTEST_THINKING",
|
|
5461
5436
|
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
5462
5437
|
};
|
|
5463
|
-
var DEFAULT_SEARCH_API_URL = "https://
|
|
5438
|
+
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
5439
|
+
var DEFAULT_MODEL = "glm-4.7";
|
|
5464
5440
|
function getApiKey() {
|
|
5465
5441
|
return process.env[ENV_KEYS.API_KEY] || "";
|
|
5466
5442
|
}
|
|
@@ -5479,6 +5455,10 @@ function getSearchApiKey() {
|
|
|
5479
5455
|
function getSearchApiUrl() {
|
|
5480
5456
|
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
5481
5457
|
}
|
|
5458
|
+
function isZaiProvider() {
|
|
5459
|
+
const baseUrl = getBaseUrl() || "";
|
|
5460
|
+
return baseUrl.includes("z.ai");
|
|
5461
|
+
}
|
|
5482
5462
|
function isThinkingEnabled() {
|
|
5483
5463
|
return process.env[ENV_KEYS.THINKING] === "true";
|
|
5484
5464
|
}
|
|
@@ -5486,8 +5466,26 @@ function getThinkingBudget() {
|
|
|
5486
5466
|
const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
|
|
5487
5467
|
return isNaN(val) ? 8e3 : Math.max(1024, val);
|
|
5488
5468
|
}
|
|
5489
|
-
function
|
|
5490
|
-
|
|
5469
|
+
function validateRequiredConfig() {
|
|
5470
|
+
const errors = [];
|
|
5471
|
+
if (!getApiKey()) {
|
|
5472
|
+
errors.push(
|
|
5473
|
+
`[config] PENTEST_API_KEY is required.
|
|
5474
|
+
Export it before running:
|
|
5475
|
+
export PENTEST_API_KEY=your_api_key
|
|
5476
|
+
For z.ai: get your key at https://api.z.ai`
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
|
|
5480
|
+
if (budgetRaw !== void 0 && budgetRaw !== "") {
|
|
5481
|
+
const parsed = parseInt(budgetRaw, 10);
|
|
5482
|
+
if (isNaN(parsed) || parsed < 1024) {
|
|
5483
|
+
errors.push(
|
|
5484
|
+
`[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
|
|
5485
|
+
);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
return errors;
|
|
5491
5489
|
}
|
|
5492
5490
|
|
|
5493
5491
|
// src/shared/constants/search-api.const.ts
|
|
@@ -5582,10 +5580,8 @@ function getPlaywrightPath() {
|
|
|
5582
5580
|
}
|
|
5583
5581
|
async function checkPlaywright() {
|
|
5584
5582
|
try {
|
|
5585
|
-
|
|
5583
|
+
return await new Promise((resolve) => {
|
|
5586
5584
|
const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
|
|
5587
|
-
let stdout = "";
|
|
5588
|
-
child.stdout.on("data", (data) => stdout += data);
|
|
5589
5585
|
child.on("close", (code) => {
|
|
5590
5586
|
if (code === 0) {
|
|
5591
5587
|
const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
|
|
@@ -5599,7 +5595,6 @@ async function checkPlaywright() {
|
|
|
5599
5595
|
});
|
|
5600
5596
|
child.on("error", () => resolve({ installed: false, browserInstalled: false }));
|
|
5601
5597
|
});
|
|
5602
|
-
return result2;
|
|
5603
5598
|
} catch {
|
|
5604
5599
|
return { installed: false, browserInstalled: false };
|
|
5605
5600
|
}
|
|
@@ -5710,13 +5705,12 @@ function buildBrowseScript(url, options, screenshotPath) {
|
|
|
5710
5705
|
const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
|
|
5711
5706
|
const playwrightPath = getPlaywrightPath();
|
|
5712
5707
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5713
|
-
const headlessMode = isBrowserHeadless();
|
|
5714
5708
|
return `
|
|
5715
5709
|
const { chromium } = require(${safePlaywrightPath});
|
|
5716
5710
|
|
|
5717
5711
|
(async () => {
|
|
5718
5712
|
const browser = await chromium.launch({
|
|
5719
|
-
headless:
|
|
5713
|
+
headless: true,
|
|
5720
5714
|
args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}']
|
|
5721
5715
|
});
|
|
5722
5716
|
|
|
@@ -5860,7 +5854,7 @@ async function browseUrl(url, options = {}) {
|
|
|
5860
5854
|
};
|
|
5861
5855
|
}
|
|
5862
5856
|
}
|
|
5863
|
-
const screenshotPath = browserOptions.screenshot ? join6(
|
|
5857
|
+
const screenshotPath = browserOptions.screenshot ? join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME, `screenshot-${Date.now()}.png`) : void 0;
|
|
5864
5858
|
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5865
5859
|
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5866
5860
|
if (!result2.success) {
|
|
@@ -5899,12 +5893,11 @@ async function fillAndSubmitForm(url, formData, options = {}) {
|
|
|
5899
5893
|
const safeFormData = JSON.stringify(formData);
|
|
5900
5894
|
const playwrightPath = getPlaywrightPath();
|
|
5901
5895
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5902
|
-
const headlessMode = process.env.HEADLESS !== "false";
|
|
5903
5896
|
const script = `
|
|
5904
5897
|
const { chromium } = require(${safePlaywrightPath});
|
|
5905
5898
|
|
|
5906
5899
|
(async () => {
|
|
5907
|
-
const browser = await chromium.launch({ headless:
|
|
5900
|
+
const browser = await chromium.launch({ headless: true });
|
|
5908
5901
|
const page = await browser.newPage();
|
|
5909
5902
|
|
|
5910
5903
|
try {
|
|
@@ -5989,12 +5982,16 @@ var SEARCH_TIMEOUT_MS = 15e3;
|
|
|
5989
5982
|
function getErrorMessage(error) {
|
|
5990
5983
|
return error instanceof Error ? error.message : String(error);
|
|
5991
5984
|
}
|
|
5985
|
+
function generateRequestId() {
|
|
5986
|
+
return crypto.randomUUID();
|
|
5987
|
+
}
|
|
5992
5988
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
5993
5989
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5994
5990
|
const requestBody = {
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
stream: false
|
|
5991
|
+
request_id: generateRequestId(),
|
|
5992
|
+
tool: "web-search-pro",
|
|
5993
|
+
stream: false,
|
|
5994
|
+
messages: [{ role: LLM_ROLES.USER, content: query }]
|
|
5998
5995
|
};
|
|
5999
5996
|
debugLog("search", "GLM request body", requestBody);
|
|
6000
5997
|
let response;
|
|
@@ -6039,11 +6036,7 @@ async function searchWithGLM(query, apiKey, apiUrl) {
|
|
|
6039
6036
|
debugLog("search", "GLM has tool_calls", { count: data.choices[0].message.tool_calls.length });
|
|
6040
6037
|
for (const tc of data.choices[0].message.tool_calls) {
|
|
6041
6038
|
if (tc.function?.arguments) {
|
|
6042
|
-
|
|
6043
|
-
const args = JSON.parse(tc.function.arguments);
|
|
6044
|
-
results += JSON.stringify(args, null, 2) + "\n";
|
|
6045
|
-
} catch {
|
|
6046
|
-
}
|
|
6039
|
+
results += tc.function.arguments + "\n";
|
|
6047
6040
|
}
|
|
6048
6041
|
}
|
|
6049
6042
|
}
|
|
@@ -6245,55 +6238,40 @@ async function parseNmap(xmlPath) {
|
|
|
6245
6238
|
}
|
|
6246
6239
|
}
|
|
6247
6240
|
async function searchCVE(service, version) {
|
|
6241
|
+
const query = version ? `${service} ${version}` : service;
|
|
6248
6242
|
try {
|
|
6249
|
-
|
|
6250
|
-
|
|
6243
|
+
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
6244
|
+
encoding: "utf-8",
|
|
6245
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6246
|
+
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
6247
|
+
});
|
|
6248
|
+
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
6251
6249
|
return {
|
|
6252
|
-
success:
|
|
6253
|
-
output: ""
|
|
6254
|
-
error: getErrorMessage2(error)
|
|
6250
|
+
success: true,
|
|
6251
|
+
output: lines.join("\n") || `No exploits found for ${query}`
|
|
6255
6252
|
};
|
|
6256
|
-
}
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
try {
|
|
6262
|
-
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
6263
|
-
encoding: "utf-8",
|
|
6264
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
6265
|
-
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
6266
|
-
});
|
|
6267
|
-
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
6268
|
-
return {
|
|
6269
|
-
success: true,
|
|
6270
|
-
output: lines.join("\n") || `No exploits found for ${query}`
|
|
6271
|
-
};
|
|
6272
|
-
} catch (e) {
|
|
6273
|
-
const execError = e;
|
|
6274
|
-
const stderr = String(execError.stderr || "");
|
|
6275
|
-
const stdout = String(execError.stdout || "");
|
|
6276
|
-
if (stderr.includes("No results")) {
|
|
6277
|
-
return {
|
|
6278
|
-
success: true,
|
|
6279
|
-
output: `No exploits found for ${query}`
|
|
6280
|
-
};
|
|
6281
|
-
}
|
|
6253
|
+
} catch (e) {
|
|
6254
|
+
const execError = e;
|
|
6255
|
+
const stderr = String(execError.stderr || "");
|
|
6256
|
+
const stdout = String(execError.stdout || "");
|
|
6257
|
+
if (stderr.includes("No results")) {
|
|
6282
6258
|
return {
|
|
6283
6259
|
success: true,
|
|
6284
|
-
output:
|
|
6260
|
+
output: `No exploits found for ${query}`
|
|
6285
6261
|
};
|
|
6286
6262
|
}
|
|
6287
|
-
} catch (error) {
|
|
6288
6263
|
return {
|
|
6289
|
-
success:
|
|
6290
|
-
output:
|
|
6291
|
-
error: getErrorMessage2(error)
|
|
6264
|
+
success: true,
|
|
6265
|
+
output: stdout || `No exploits found for ${query}`
|
|
6292
6266
|
};
|
|
6293
6267
|
}
|
|
6294
6268
|
}
|
|
6295
|
-
async function webSearch(query
|
|
6269
|
+
async function webSearch(query) {
|
|
6296
6270
|
debugLog("search", "webSearch START", { query });
|
|
6271
|
+
if (isZaiProvider()) {
|
|
6272
|
+
debugLog("search", "Using z.ai LLM-native web search (web_search_20250305)");
|
|
6273
|
+
return await searchWithZai(query);
|
|
6274
|
+
}
|
|
6297
6275
|
const apiKey = getSearchApiKey();
|
|
6298
6276
|
const apiUrl = getSearchApiUrl();
|
|
6299
6277
|
debugLog("search", "Search API config", {
|
|
@@ -6321,7 +6299,7 @@ async function webSearch(query, _engine) {
|
|
|
6321
6299
|
};
|
|
6322
6300
|
}
|
|
6323
6301
|
try {
|
|
6324
|
-
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)
|
|
6302
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
6325
6303
|
debugLog("search", "Using GLM search");
|
|
6326
6304
|
return await searchWithGLM(query, apiKey, apiUrl);
|
|
6327
6305
|
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
@@ -6343,6 +6321,111 @@ async function webSearch(query, _engine) {
|
|
|
6343
6321
|
};
|
|
6344
6322
|
}
|
|
6345
6323
|
}
|
|
6324
|
+
var ZAI_SEARCH = {
|
|
6325
|
+
TIMEOUT_MS: 2e4,
|
|
6326
|
+
MAX_TOKENS: 1024,
|
|
6327
|
+
MAX_USES: 3,
|
|
6328
|
+
TOOL_TYPE: "web_search_20250305",
|
|
6329
|
+
TOOL_NAME: "web_search",
|
|
6330
|
+
ANTHROPIC_VERSION: "2023-06-01",
|
|
6331
|
+
// §25-1 Retry constants
|
|
6332
|
+
MAX_RETRIES: 2,
|
|
6333
|
+
RETRY_BASE_MS: 500
|
|
6334
|
+
};
|
|
6335
|
+
function isTransientHttpError(status) {
|
|
6336
|
+
return status === 429 || status >= 500;
|
|
6337
|
+
}
|
|
6338
|
+
async function searchWithZai(query) {
|
|
6339
|
+
const apiKey = getApiKey();
|
|
6340
|
+
const baseUrl = (getBaseUrl() || "https://api.z.ai/api/anthropic").replace(/\/+$/, "");
|
|
6341
|
+
const url = `${baseUrl}/v1/messages`;
|
|
6342
|
+
const requestBody = JSON.stringify({
|
|
6343
|
+
model: getModel() || DEFAULT_MODEL,
|
|
6344
|
+
max_tokens: ZAI_SEARCH.MAX_TOKENS,
|
|
6345
|
+
tools: [{ type: ZAI_SEARCH.TOOL_TYPE, name: ZAI_SEARCH.TOOL_NAME, max_uses: ZAI_SEARCH.MAX_USES }],
|
|
6346
|
+
messages: [{ role: "user", content: `Search the web for: ${query}` }]
|
|
6347
|
+
});
|
|
6348
|
+
let lastError = "";
|
|
6349
|
+
for (let attempt = 0; attempt <= ZAI_SEARCH.MAX_RETRIES; attempt++) {
|
|
6350
|
+
if (attempt > 0) {
|
|
6351
|
+
const baseWait = ZAI_SEARCH.RETRY_BASE_MS * Math.pow(2, attempt - 1);
|
|
6352
|
+
const jitter = baseWait * 0.25 * (Math.random() * 2 - 1);
|
|
6353
|
+
await new Promise((r) => setTimeout(r, Math.round(baseWait + jitter)));
|
|
6354
|
+
debugLog("search", `z.ai web search RETRY attempt=${attempt}`);
|
|
6355
|
+
}
|
|
6356
|
+
let response;
|
|
6357
|
+
try {
|
|
6358
|
+
response = await fetch(url, {
|
|
6359
|
+
method: "POST",
|
|
6360
|
+
headers: {
|
|
6361
|
+
"Content-Type": "application/json",
|
|
6362
|
+
"x-api-key": apiKey,
|
|
6363
|
+
"anthropic-version": ZAI_SEARCH.ANTHROPIC_VERSION
|
|
6364
|
+
},
|
|
6365
|
+
body: requestBody,
|
|
6366
|
+
signal: AbortSignal.timeout(ZAI_SEARCH.TIMEOUT_MS)
|
|
6367
|
+
});
|
|
6368
|
+
} catch (err) {
|
|
6369
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
6370
|
+
debugLog("search", "z.ai web search fetch FAILED", { attempt, error: lastError });
|
|
6371
|
+
continue;
|
|
6372
|
+
}
|
|
6373
|
+
if (response.ok) {
|
|
6374
|
+
let data;
|
|
6375
|
+
try {
|
|
6376
|
+
data = await response.json();
|
|
6377
|
+
} catch (err) {
|
|
6378
|
+
return { success: false, output: "", error: `z.ai web search invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
6379
|
+
}
|
|
6380
|
+
const lines = [];
|
|
6381
|
+
for (const block of data.content || []) {
|
|
6382
|
+
if (block.type === "text" && block.text) {
|
|
6383
|
+
lines.push(block.text);
|
|
6384
|
+
} else if (block.type === "server_tool_use" && block.input?.results) {
|
|
6385
|
+
const refs = block.input.results.filter((r) => !!r.url && !!r.title).map((r, i) => `[${i + 1}] ${r.title}
|
|
6386
|
+
${r.url}`);
|
|
6387
|
+
if (refs.length > 0) {
|
|
6388
|
+
lines.push("\n--- Search Sources ---\n" + refs.join("\n"));
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
const output = lines.join("\n").trim();
|
|
6393
|
+
debugLog("search", "z.ai web search COMPLETE", { attempt, outputLength: output.length });
|
|
6394
|
+
return { success: true, output: output || `No results found for: ${query}` };
|
|
6395
|
+
}
|
|
6396
|
+
let errorText = "";
|
|
6397
|
+
try {
|
|
6398
|
+
errorText = await response.text();
|
|
6399
|
+
} catch {
|
|
6400
|
+
}
|
|
6401
|
+
lastError = `z.ai web search API error ${response.status}: ${errorText.slice(0, 500)}`;
|
|
6402
|
+
debugLog("search", "z.ai web search ERROR", { attempt, status: response.status, error: errorText });
|
|
6403
|
+
if (!isTransientHttpError(response.status)) {
|
|
6404
|
+
break;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
return { success: false, output: "", error: lastError };
|
|
6408
|
+
}
|
|
6409
|
+
async function extractURLs(text) {
|
|
6410
|
+
try {
|
|
6411
|
+
const urlRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g;
|
|
6412
|
+
const urls = text.match(urlRegex) || [];
|
|
6413
|
+
const uniqueURLs = [...new Set(urls)];
|
|
6414
|
+
return {
|
|
6415
|
+
success: true,
|
|
6416
|
+
output: JSON.stringify({
|
|
6417
|
+
count: uniqueURLs.length,
|
|
6418
|
+
urls: uniqueURLs
|
|
6419
|
+
}, null, 2)
|
|
6420
|
+
};
|
|
6421
|
+
} catch (error) {
|
|
6422
|
+
return {
|
|
6423
|
+
success: false,
|
|
6424
|
+
output: "",
|
|
6425
|
+
error: getErrorMessage2(error)
|
|
6426
|
+
};
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6346
6429
|
|
|
6347
6430
|
// src/shared/utils/owasp-knowledge.ts
|
|
6348
6431
|
var OWASP_2017 = {
|
|
@@ -6543,8 +6626,83 @@ Use 'get_owasp_knowledge' with edition="2023" or similar to see specific details
|
|
|
6543
6626
|
`.trim();
|
|
6544
6627
|
}
|
|
6545
6628
|
|
|
6629
|
+
// src/shared/utils/domain-tools-factory.ts
|
|
6630
|
+
function param(params, name, defaultValue) {
|
|
6631
|
+
const value = params[name];
|
|
6632
|
+
if (value === void 0 || value === null) {
|
|
6633
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
6634
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
6635
|
+
}
|
|
6636
|
+
return String(value);
|
|
6637
|
+
}
|
|
6638
|
+
function paramNumber(params, name, defaultValue) {
|
|
6639
|
+
const value = params[name];
|
|
6640
|
+
if (value === void 0 || value === null) {
|
|
6641
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
6642
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
6643
|
+
}
|
|
6644
|
+
return Number(value);
|
|
6645
|
+
}
|
|
6646
|
+
function ensureUrlProtocol(target) {
|
|
6647
|
+
if (/^https?:\/\//i.test(target)) return target;
|
|
6648
|
+
return `http://${target}`;
|
|
6649
|
+
}
|
|
6650
|
+
function createCommandTool(def) {
|
|
6651
|
+
const parameters = {};
|
|
6652
|
+
for (const paramName of def.params) {
|
|
6653
|
+
parameters[paramName] = { type: "string", description: paramName };
|
|
6654
|
+
}
|
|
6655
|
+
if (def.optionalParams) {
|
|
6656
|
+
for (const paramName of Object.keys(def.optionalParams)) {
|
|
6657
|
+
parameters[paramName] = { type: "string", description: `${paramName} (optional)` };
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
return {
|
|
6661
|
+
name: def.name,
|
|
6662
|
+
description: def.description,
|
|
6663
|
+
parameters,
|
|
6664
|
+
required: def.params,
|
|
6665
|
+
execute: async (params) => {
|
|
6666
|
+
const resolvedArgs = def.args.map((arg) => {
|
|
6667
|
+
const match = arg.match(/^\{(\w+)\}$/);
|
|
6668
|
+
if (match) {
|
|
6669
|
+
const paramName = match[1];
|
|
6670
|
+
let value;
|
|
6671
|
+
if (def.optionalParams && def.optionalParams[paramName] !== void 0) {
|
|
6672
|
+
value = param(params, paramName, String(def.optionalParams[paramName]));
|
|
6673
|
+
} else {
|
|
6674
|
+
value = param(params, paramName);
|
|
6675
|
+
}
|
|
6676
|
+
if (def.requiresUrl && paramName === "target") {
|
|
6677
|
+
value = ensureUrlProtocol(value);
|
|
6678
|
+
}
|
|
6679
|
+
return value;
|
|
6680
|
+
}
|
|
6681
|
+
return arg;
|
|
6682
|
+
});
|
|
6683
|
+
return await runCommand(def.cmd, resolvedArgs);
|
|
6684
|
+
}
|
|
6685
|
+
};
|
|
6686
|
+
}
|
|
6687
|
+
function createTool(name, description, parameters, required, execute) {
|
|
6688
|
+
return { name, description, parameters, required, execute };
|
|
6689
|
+
}
|
|
6690
|
+
function createDomain(def) {
|
|
6691
|
+
const tools = def.commands?.map(createCommandTool) ?? [];
|
|
6692
|
+
const config = {
|
|
6693
|
+
name: def.category,
|
|
6694
|
+
description: def.description,
|
|
6695
|
+
tools,
|
|
6696
|
+
dangerLevel: def.dangerLevel,
|
|
6697
|
+
defaultApproval: def.defaultApproval,
|
|
6698
|
+
commonPorts: def.commonPorts,
|
|
6699
|
+
commonServices: def.commonServices
|
|
6700
|
+
};
|
|
6701
|
+
return { tools, config };
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6546
6704
|
// src/engine/tools/pentest-intel-tools.ts
|
|
6547
|
-
var createIntelTools = (
|
|
6705
|
+
var createIntelTools = () => [
|
|
6548
6706
|
{
|
|
6549
6707
|
name: TOOL_NAMES.PARSE_NMAP,
|
|
6550
6708
|
description: "Parse nmap XML output to structured JSON",
|
|
@@ -6630,7 +6788,8 @@ Can extract forms and inputs for security testing.`,
|
|
|
6630
6788
|
},
|
|
6631
6789
|
required: ["url"],
|
|
6632
6790
|
execute: async (p) => {
|
|
6633
|
-
const
|
|
6791
|
+
const url = ensureUrlProtocol(p.url);
|
|
6792
|
+
const result2 = await browseUrl(url, {
|
|
6634
6793
|
extractForms: p.extract_forms,
|
|
6635
6794
|
extractLinks: p.extract_links,
|
|
6636
6795
|
screenshot: p.screenshot,
|
|
@@ -6660,7 +6819,8 @@ Can extract forms and inputs for security testing.`,
|
|
|
6660
6819
|
},
|
|
6661
6820
|
required: ["url", "fields"],
|
|
6662
6821
|
execute: async (p) => {
|
|
6663
|
-
const
|
|
6822
|
+
const url = ensureUrlProtocol(p.url);
|
|
6823
|
+
const result2 = await fillAndSubmitForm(url, p.fields);
|
|
6664
6824
|
return {
|
|
6665
6825
|
success: result2.success,
|
|
6666
6826
|
output: result2.output,
|
|
@@ -6803,6 +6963,19 @@ For CVEs not in this list, use web_search to find them.`
|
|
|
6803
6963
|
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
6804
6964
|
};
|
|
6805
6965
|
}
|
|
6966
|
+
},
|
|
6967
|
+
{
|
|
6968
|
+
name: TOOL_NAMES.EXTRACT_URLS,
|
|
6969
|
+
description: `Extract all URLs from a block of text.
|
|
6970
|
+
Use this to:
|
|
6971
|
+
- Pull links from command output, HTML, or tool results
|
|
6972
|
+
- Discover subdomains and endpoints from scan results
|
|
6973
|
+
- Find API endpoints buried in verbose output`,
|
|
6974
|
+
parameters: {
|
|
6975
|
+
text: { type: "string", description: "Text to extract URLs from" }
|
|
6976
|
+
},
|
|
6977
|
+
required: ["text"],
|
|
6978
|
+
execute: async (p) => extractURLs(p.text)
|
|
6806
6979
|
}
|
|
6807
6980
|
];
|
|
6808
6981
|
|
|
@@ -7154,7 +7327,9 @@ function getContextRecommendations(context, variantCount) {
|
|
|
7154
7327
|
}
|
|
7155
7328
|
|
|
7156
7329
|
// src/engine/tools/pentest-attack-tools.ts
|
|
7157
|
-
|
|
7330
|
+
import { existsSync as existsSync6, statSync, readdirSync } from "fs";
|
|
7331
|
+
import { join as join7 } from "path";
|
|
7332
|
+
var createAttackTools = () => [
|
|
7158
7333
|
{
|
|
7159
7334
|
name: TOOL_NAMES.HASH_CRACK,
|
|
7160
7335
|
description: `Crack password hashes using hashcat or john.
|
|
@@ -7273,8 +7448,6 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7273
7448
|
}
|
|
7274
7449
|
},
|
|
7275
7450
|
execute: async (p) => {
|
|
7276
|
-
const { existsSync: existsSync12, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7277
|
-
const { join: join13 } = await import("path");
|
|
7278
7451
|
const category = p.category || "";
|
|
7279
7452
|
const search = p.search || "";
|
|
7280
7453
|
const minSize = p.min_size || 0;
|
|
@@ -7305,12 +7478,12 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7305
7478
|
const q = search.toLowerCase();
|
|
7306
7479
|
return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
|
|
7307
7480
|
};
|
|
7308
|
-
const results = ["# Available Wordlists on System
|
|
7481
|
+
const results = ["# Available Wordlists on System\n"];
|
|
7309
7482
|
let totalCount = 0;
|
|
7310
7483
|
const processFile = (fullPath, fileName) => {
|
|
7311
7484
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
7312
7485
|
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
7313
|
-
const stats =
|
|
7486
|
+
const stats = statSync(fullPath);
|
|
7314
7487
|
if (stats.size < minSize) return;
|
|
7315
7488
|
if (!matchesCategory(fullPath)) return;
|
|
7316
7489
|
if (!matchesSearch(fullPath, fileName)) return;
|
|
@@ -7320,16 +7493,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7320
7493
|
results.push("");
|
|
7321
7494
|
};
|
|
7322
7495
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
7323
|
-
if (depth > maxDepth || !
|
|
7496
|
+
if (depth > maxDepth || !existsSync6(dirPath)) return;
|
|
7324
7497
|
let entries;
|
|
7325
7498
|
try {
|
|
7326
|
-
entries =
|
|
7499
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
7327
7500
|
} catch {
|
|
7328
7501
|
return;
|
|
7329
7502
|
}
|
|
7330
7503
|
for (const entry of entries) {
|
|
7331
7504
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
7332
|
-
const fullPath =
|
|
7505
|
+
const fullPath = join7(dirPath, entry.name);
|
|
7333
7506
|
if (entry.isDirectory()) {
|
|
7334
7507
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
7335
7508
|
continue;
|
|
@@ -7342,19 +7515,26 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7342
7515
|
}
|
|
7343
7516
|
};
|
|
7344
7517
|
for (const basePath of scanPaths) {
|
|
7345
|
-
results.push(
|
|
7518
|
+
results.push(`
|
|
7519
|
+
## ${basePath}
|
|
7520
|
+
`);
|
|
7346
7521
|
scanDir(basePath);
|
|
7347
7522
|
}
|
|
7348
7523
|
if (totalCount === 0) {
|
|
7349
|
-
results.push(
|
|
7350
|
-
|
|
7524
|
+
results.push(`
|
|
7525
|
+
No wordlists found matching your criteria.`);
|
|
7526
|
+
results.push(`
|
|
7527
|
+
Tips:`);
|
|
7351
7528
|
results.push(`- Try without filters to see all available wordlists`);
|
|
7352
7529
|
results.push(`- Ensure seclists/wordlists packages are installed`);
|
|
7353
7530
|
results.push(`- Common locations: /usr/share/seclists/, /usr/share/wordlists/`);
|
|
7354
7531
|
}
|
|
7355
|
-
results.push(
|
|
7356
|
-
|
|
7357
|
-
results.push(
|
|
7532
|
+
results.push(`
|
|
7533
|
+
---`);
|
|
7534
|
+
results.push(`
|
|
7535
|
+
Total wordlists found: ${totalCount}`);
|
|
7536
|
+
results.push(`
|
|
7537
|
+
## Commonly Used Wordlists (check if available)`);
|
|
7358
7538
|
results.push(`- RockYou: /usr/share/wordlists/rockyou.txt (14M passwords)`);
|
|
7359
7539
|
results.push(`- Dirb common: /usr/share/wordlists/dirb/common.txt`);
|
|
7360
7540
|
results.push(`- SecLists passwords: /usr/share/seclists/Passwords/Common-Credentials/`);
|
|
@@ -7362,7 +7542,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7362
7542
|
results.push(`- SecLists DNS: /usr/share/seclists/Discovery/DNS/`);
|
|
7363
7543
|
return {
|
|
7364
7544
|
success: true,
|
|
7365
|
-
output: results.join("
|
|
7545
|
+
output: results.join("\n")
|
|
7366
7546
|
};
|
|
7367
7547
|
}
|
|
7368
7548
|
}
|
|
@@ -7372,8 +7552,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7372
7552
|
var createPentestTools = (state, events) => [
|
|
7373
7553
|
...createStateTools(state, events),
|
|
7374
7554
|
...createTargetTools(state),
|
|
7375
|
-
...createIntelTools(
|
|
7376
|
-
...createAttackTools(
|
|
7555
|
+
...createIntelTools(),
|
|
7556
|
+
...createAttackTools()
|
|
7377
7557
|
];
|
|
7378
7558
|
|
|
7379
7559
|
// src/engine/tools/agents.ts
|
|
@@ -7531,6 +7711,7 @@ var NETWORK_FILTERS = {
|
|
|
7531
7711
|
};
|
|
7532
7712
|
|
|
7533
7713
|
// src/engine/tools/network-attack.ts
|
|
7714
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
7534
7715
|
var createNetworkAttackTools = () => [
|
|
7535
7716
|
{
|
|
7536
7717
|
name: TOOL_NAMES.ARP_SPOOF,
|
|
@@ -7706,8 +7887,7 @@ Requires root/sudo privileges.`,
|
|
|
7706
7887
|
const iface = p.interface || "";
|
|
7707
7888
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7708
7889
|
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7709
|
-
|
|
7710
|
-
writeFileSync10(hostsFile, `${spoofIp} ${domain}
|
|
7890
|
+
writeFileSync6(hostsFile, `${spoofIp} ${domain}
|
|
7711
7891
|
${spoofIp} *.${domain}
|
|
7712
7892
|
`);
|
|
7713
7893
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -7957,19 +8137,6 @@ var ZombieHunter = class {
|
|
|
7957
8137
|
}
|
|
7958
8138
|
return lines.join("\n");
|
|
7959
8139
|
}
|
|
7960
|
-
/**
|
|
7961
|
-
* Get cleanup command suggestions for Orchestrator
|
|
7962
|
-
*/
|
|
7963
|
-
getCleanupCommands(zombies) {
|
|
7964
|
-
return zombies.map((z) => `bg_cleanup({ processId: "${z.processId}", killOrphans: true })`);
|
|
7965
|
-
}
|
|
7966
|
-
/**
|
|
7967
|
-
* Check if any critical processes are among the zombies
|
|
7968
|
-
* (shells, listeners that might be active shells)
|
|
7969
|
-
*/
|
|
7970
|
-
hasCriticalZombies(zombies) {
|
|
7971
|
-
return false;
|
|
7972
|
-
}
|
|
7973
8140
|
};
|
|
7974
8141
|
|
|
7975
8142
|
// src/engine/resource/health-monitor.ts
|
|
@@ -8054,28 +8221,6 @@ var HealthMonitor = class {
|
|
|
8054
8221
|
if (data.recommendations.length > MAX_RECOMMENDATIONS_FOR_HEALTHY) return HEALTH_STATUS.WARNING;
|
|
8055
8222
|
return HEALTH_STATUS.HEALTHY;
|
|
8056
8223
|
}
|
|
8057
|
-
/**
|
|
8058
|
-
* Generate summary string for Orchestrator context
|
|
8059
|
-
*/
|
|
8060
|
-
generateSummary() {
|
|
8061
|
-
const status = this.check();
|
|
8062
|
-
const lines = [];
|
|
8063
|
-
lines.push(`Health: ${status.overall.toUpperCase()}`);
|
|
8064
|
-
lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
|
|
8065
|
-
if (status.ports.inUse.length > 0) {
|
|
8066
|
-
lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
|
|
8067
|
-
}
|
|
8068
|
-
if (status.ports.conflicts.length > 0) {
|
|
8069
|
-
lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
|
|
8070
|
-
}
|
|
8071
|
-
if (status.recommendations.length > 0) {
|
|
8072
|
-
lines.push(`Recommendations:`);
|
|
8073
|
-
for (const r of status.recommendations) {
|
|
8074
|
-
lines.push(` - ${r}`);
|
|
8075
|
-
}
|
|
8076
|
-
}
|
|
8077
|
-
return lines.join("\n");
|
|
8078
|
-
}
|
|
8079
8224
|
};
|
|
8080
8225
|
|
|
8081
8226
|
// src/engine/tools/resource.ts
|
|
@@ -8090,10 +8235,8 @@ var resourceTools = [
|
|
|
8090
8235
|
description: `Query the status of all background processes.
|
|
8091
8236
|
Shows running tasks, port usage, and health status.
|
|
8092
8237
|
Used by the Orchestrator for resource management.`,
|
|
8093
|
-
parameters: {
|
|
8094
|
-
|
|
8095
|
-
properties: {}
|
|
8096
|
-
},
|
|
8238
|
+
parameters: {},
|
|
8239
|
+
// No parameters needed
|
|
8097
8240
|
async execute() {
|
|
8098
8241
|
const processes = listBackgroundProcesses();
|
|
8099
8242
|
const health = healthMonitor.check();
|
|
@@ -8145,19 +8288,17 @@ Used by the Orchestrator for resource management.`,
|
|
|
8145
8288
|
If processId is specified, terminates that process and its children.
|
|
8146
8289
|
If killOrphans is true, also cleans up child processes whose parent has died.`,
|
|
8147
8290
|
parameters: {
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
description: "Also clean up orphan child processes (default: true)",
|
|
8157
|
-
default: true
|
|
8158
|
-
}
|
|
8291
|
+
processId: {
|
|
8292
|
+
type: "string",
|
|
8293
|
+
description: "Process ID to clean up (optional)"
|
|
8294
|
+
},
|
|
8295
|
+
killOrphans: {
|
|
8296
|
+
type: "boolean",
|
|
8297
|
+
description: "Also clean up orphan child processes (default: true)",
|
|
8298
|
+
default: true
|
|
8159
8299
|
}
|
|
8160
8300
|
},
|
|
8301
|
+
required: [],
|
|
8161
8302
|
async execute(params) {
|
|
8162
8303
|
const results = [];
|
|
8163
8304
|
if (params.processId) {
|
|
@@ -8198,14 +8339,27 @@ If killOrphans is true, also cleans up child processes whose parent has died.`,
|
|
|
8198
8339
|
name: TOOL_NAMES.HEALTH_CHECK,
|
|
8199
8340
|
description: `Check system resource health.
|
|
8200
8341
|
Returns recommendations on process status, port conflicts, long-running tasks, etc.`,
|
|
8201
|
-
parameters: {
|
|
8202
|
-
|
|
8203
|
-
properties: {}
|
|
8204
|
-
},
|
|
8342
|
+
parameters: {},
|
|
8343
|
+
// No parameters needed
|
|
8205
8344
|
async execute() {
|
|
8206
8345
|
const status = healthMonitor.check();
|
|
8207
8346
|
const zombies = zombieHunter.scan();
|
|
8208
|
-
const
|
|
8347
|
+
const lines = [];
|
|
8348
|
+
lines.push(`Health: ${status.overall.toUpperCase()}`);
|
|
8349
|
+
lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
|
|
8350
|
+
if (status.ports.inUse.length > 0) {
|
|
8351
|
+
lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
|
|
8352
|
+
}
|
|
8353
|
+
if (status.ports.conflicts.length > 0) {
|
|
8354
|
+
lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
|
|
8355
|
+
}
|
|
8356
|
+
if (status.recommendations.length > 0) {
|
|
8357
|
+
lines.push(`Recommendations:`);
|
|
8358
|
+
for (const r of status.recommendations) {
|
|
8359
|
+
lines.push(` - ${r}`);
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
const output = lines.join("\n");
|
|
8209
8363
|
if (zombies.length > 0) {
|
|
8210
8364
|
return result(false, output + "\n\n" + zombieHunter.generateReport(zombies));
|
|
8211
8365
|
}
|
|
@@ -8295,258 +8449,183 @@ var SMB_PORTS = [
|
|
|
8295
8449
|
];
|
|
8296
8450
|
|
|
8297
8451
|
// src/domains/network/tools.ts
|
|
8298
|
-
var NETWORK_TOOLS =
|
|
8299
|
-
|
|
8300
|
-
name: TOOL_NAMES.NMAP_QUICK,
|
|
8301
|
-
description: "Quick nmap scan - fast discovery",
|
|
8302
|
-
parameters: {
|
|
8303
|
-
target: { type: "string", description: "Target IP/CIDR" }
|
|
8304
|
-
},
|
|
8305
|
-
required: ["target"],
|
|
8306
|
-
execute: async (params) => {
|
|
8307
|
-
const target = params.target;
|
|
8308
|
-
return await runCommand("nmap", ["-Pn", "-T4", "-F", target]);
|
|
8309
|
-
}
|
|
8310
|
-
},
|
|
8311
|
-
{
|
|
8312
|
-
name: TOOL_NAMES.NMAP_FULL,
|
|
8313
|
-
description: "Full nmap scan - comprehensive",
|
|
8314
|
-
parameters: {
|
|
8315
|
-
target: { type: "string", description: "Target IP/CIDR" }
|
|
8316
|
-
},
|
|
8317
|
-
required: ["target"],
|
|
8318
|
-
execute: async (params) => {
|
|
8319
|
-
const target = params.target;
|
|
8320
|
-
return await runCommand("nmap", ["-Pn", "-T4", "-sV", "-O", "-p-", target]);
|
|
8321
|
-
}
|
|
8322
|
-
},
|
|
8323
|
-
{
|
|
8324
|
-
name: TOOL_NAMES.RUSTSCAN,
|
|
8325
|
-
description: "RustScan - very fast port discovery",
|
|
8326
|
-
parameters: {
|
|
8327
|
-
target: { type: "string", description: "Target IP" }
|
|
8328
|
-
},
|
|
8329
|
-
required: ["target"],
|
|
8330
|
-
execute: async (params) => {
|
|
8331
|
-
const target = params.target;
|
|
8332
|
-
return await runCommand("rustscan", ["-a", target, "--range", "1-65535"]);
|
|
8333
|
-
}
|
|
8334
|
-
}
|
|
8335
|
-
];
|
|
8336
|
-
var NETWORK_CONFIG2 = {
|
|
8337
|
-
name: SERVICE_CATEGORIES.NETWORK,
|
|
8452
|
+
var { tools: NETWORK_TOOLS, config: NETWORK_CONFIG2 } = createDomain({
|
|
8453
|
+
category: SERVICE_CATEGORIES.NETWORK,
|
|
8338
8454
|
description: "Network reconnaissance - scanning, OS fingerprinting",
|
|
8339
|
-
tools: NETWORK_TOOLS,
|
|
8340
8455
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8341
8456
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8342
8457
|
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
|
|
8343
|
-
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
parameters: {
|
|
8352
|
-
target: { type: "string", description: "Target URL" }
|
|
8353
|
-
},
|
|
8354
|
-
required: ["target"],
|
|
8355
|
-
execute: async (params) => {
|
|
8356
|
-
const target = params.target;
|
|
8357
|
-
return await runCommand("curl", ["-sI", target]);
|
|
8358
|
-
}
|
|
8359
|
-
},
|
|
8360
|
-
{
|
|
8361
|
-
name: TOOL_NAMES.WAF_DETECT,
|
|
8362
|
-
description: "WAF detection using wafw00f",
|
|
8363
|
-
parameters: {
|
|
8364
|
-
target: { type: "string", description: "Target URL" }
|
|
8365
|
-
},
|
|
8366
|
-
required: ["target"],
|
|
8367
|
-
execute: async (params) => {
|
|
8368
|
-
const target = params.target;
|
|
8369
|
-
return await runCommand("wafw00f", [target]);
|
|
8370
|
-
}
|
|
8371
|
-
},
|
|
8372
|
-
{
|
|
8373
|
-
name: TOOL_NAMES.DIRSEARCH,
|
|
8374
|
-
description: "Directory bruteforcing",
|
|
8375
|
-
parameters: {
|
|
8376
|
-
target: { type: "string", description: "Target URL" }
|
|
8458
|
+
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB],
|
|
8459
|
+
commands: [
|
|
8460
|
+
{
|
|
8461
|
+
name: TOOL_NAMES.NMAP_QUICK,
|
|
8462
|
+
description: "Quick nmap scan - fast discovery",
|
|
8463
|
+
cmd: "nmap",
|
|
8464
|
+
args: ["-Pn", "-T4", "-F", "{target}"],
|
|
8465
|
+
params: ["target"]
|
|
8377
8466
|
},
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
{
|
|
8385
|
-
name: TOOL_NAMES.NUCLEI_WEB,
|
|
8386
|
-
description: "Nuclei web vulnerability scanner",
|
|
8387
|
-
parameters: {
|
|
8388
|
-
target: { type: "string", description: "Target URL" },
|
|
8389
|
-
severity: { type: "string", description: "Severities to check" }
|
|
8467
|
+
{
|
|
8468
|
+
name: TOOL_NAMES.NMAP_FULL,
|
|
8469
|
+
description: "Full nmap scan - comprehensive",
|
|
8470
|
+
cmd: "nmap",
|
|
8471
|
+
args: ["-Pn", "-T4", "-sV", "-O", "-p-", "{target}"],
|
|
8472
|
+
params: ["target"]
|
|
8390
8473
|
},
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8474
|
+
{
|
|
8475
|
+
name: TOOL_NAMES.RUSTSCAN,
|
|
8476
|
+
description: "RustScan - very fast port discovery",
|
|
8477
|
+
cmd: "rustscan",
|
|
8478
|
+
args: ["-a", "{target}", "--range", "1-65535"],
|
|
8479
|
+
params: ["target"]
|
|
8396
8480
|
}
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8481
|
+
]
|
|
8482
|
+
});
|
|
8483
|
+
|
|
8484
|
+
// src/domains/web/tools.ts
|
|
8485
|
+
var { tools: WEB_TOOLS, config: WEB_CONFIG } = createDomain({
|
|
8486
|
+
category: SERVICE_CATEGORIES.WEB,
|
|
8401
8487
|
description: "Web application testing - HTTP/HTTPS, APIs",
|
|
8402
|
-
tools: WEB_TOOLS,
|
|
8403
8488
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8404
8489
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8405
8490
|
commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
|
|
8406
|
-
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8407
|
-
|
|
8491
|
+
commonServices: [SERVICES.HTTP, SERVICES.HTTPS],
|
|
8492
|
+
commands: [
|
|
8493
|
+
{
|
|
8494
|
+
name: TOOL_NAMES.HTTP_FINGERPRINT,
|
|
8495
|
+
description: "HTTP fingerprinting - detect WAF, server type",
|
|
8496
|
+
cmd: "curl",
|
|
8497
|
+
args: ["-sI", "{target}"],
|
|
8498
|
+
params: ["target"],
|
|
8499
|
+
requiresUrl: true
|
|
8500
|
+
},
|
|
8501
|
+
{
|
|
8502
|
+
name: TOOL_NAMES.WAF_DETECT,
|
|
8503
|
+
description: "WAF detection using wafw00f",
|
|
8504
|
+
cmd: "wafw00f",
|
|
8505
|
+
args: ["{target}"],
|
|
8506
|
+
params: ["target"],
|
|
8507
|
+
requiresUrl: true
|
|
8508
|
+
},
|
|
8509
|
+
{
|
|
8510
|
+
name: TOOL_NAMES.DIRSEARCH,
|
|
8511
|
+
description: "Directory bruteforcing",
|
|
8512
|
+
cmd: "dirsearch",
|
|
8513
|
+
args: ["-u", "{target}", "--quiet"],
|
|
8514
|
+
params: ["target"],
|
|
8515
|
+
requiresUrl: true
|
|
8516
|
+
},
|
|
8517
|
+
{
|
|
8518
|
+
name: TOOL_NAMES.NUCLEI_WEB,
|
|
8519
|
+
description: "Nuclei web vulnerability scanner",
|
|
8520
|
+
cmd: "nuclei",
|
|
8521
|
+
args: ["-u", "{target}", "-s", "{severity}", "-silent"],
|
|
8522
|
+
params: ["target"],
|
|
8523
|
+
optionalParams: { severity: "medium,critical,high" },
|
|
8524
|
+
requiresUrl: true
|
|
8525
|
+
}
|
|
8526
|
+
]
|
|
8527
|
+
});
|
|
8408
8528
|
|
|
8409
8529
|
// src/domains/database/tools.ts
|
|
8410
|
-
var
|
|
8530
|
+
var SQLMAP_BASIC = createTool(
|
|
8531
|
+
TOOL_NAMES.SQLMAP_BASIC,
|
|
8532
|
+
"SQL injection testing with sqlmap - basic scan",
|
|
8533
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8534
|
+
["target"],
|
|
8535
|
+
async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=1", "--level=1"])
|
|
8536
|
+
);
|
|
8537
|
+
var SQLMAP_ADVANCED = createTool(
|
|
8538
|
+
TOOL_NAMES.SQLMAP_ADVANCED,
|
|
8539
|
+
"SQL injection with sqlmap - full enumeration",
|
|
8540
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8541
|
+
["target"],
|
|
8542
|
+
async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=3", "--level=5", "--dbs", "--tables"])
|
|
8543
|
+
);
|
|
8544
|
+
var MYSQL_ENUM = createTool(
|
|
8545
|
+
TOOL_NAMES.MYSQL_ENUM,
|
|
8546
|
+
"MySQL enumeration - version, users, databases",
|
|
8411
8547
|
{
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
parameters: {
|
|
8415
|
-
target: { type: "string", description: "Target URL" }
|
|
8416
|
-
},
|
|
8417
|
-
required: ["target"],
|
|
8418
|
-
execute: async (params) => {
|
|
8419
|
-
const target = params.target;
|
|
8420
|
-
return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=1", "--level=1"]);
|
|
8421
|
-
}
|
|
8548
|
+
target: { type: "string", description: "Target IP/hostname" },
|
|
8549
|
+
port: { type: "number", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8422
8550
|
},
|
|
8551
|
+
["target"],
|
|
8552
|
+
async (params) => runCommand("mysql", ["-h", param(params, "target"), "-P", String(paramNumber(params, "port", SERVICE_PORTS.MYSQL)), "-e", "SELECT VERSION(), USER(), DATABASE();"])
|
|
8553
|
+
);
|
|
8554
|
+
var POSTGRES_ENUM = createTool(
|
|
8555
|
+
TOOL_NAMES.POSTGRES_ENUM,
|
|
8556
|
+
"PostgreSQL enumeration",
|
|
8557
|
+
{ target: { type: "string", description: "Target IP" } },
|
|
8558
|
+
["target"],
|
|
8559
|
+
async (params) => runCommand("psql", ["-h", param(params, "target"), "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"])
|
|
8560
|
+
);
|
|
8561
|
+
var REDIS_ENUM = createTool(
|
|
8562
|
+
TOOL_NAMES.REDIS_ENUM,
|
|
8563
|
+
"Redis enumeration",
|
|
8423
8564
|
{
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
parameters: {
|
|
8427
|
-
target: { type: "string", description: "Target URL" }
|
|
8428
|
-
},
|
|
8429
|
-
required: ["target"],
|
|
8430
|
-
execute: async (params) => {
|
|
8431
|
-
const target = params.target;
|
|
8432
|
-
return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=3", "--level=5", "--dbs", "--tables"]);
|
|
8433
|
-
}
|
|
8565
|
+
target: { type: "string", description: "Target IP" },
|
|
8566
|
+
port: { type: "number", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8434
8567
|
},
|
|
8568
|
+
["target"],
|
|
8569
|
+
async (params) => runCommand("redis-cli", ["-h", param(params, "target"), "-p", String(paramNumber(params, "port", SERVICE_PORTS.REDIS)), "INFO"])
|
|
8570
|
+
);
|
|
8571
|
+
var DB_BRUTE = createTool(
|
|
8572
|
+
TOOL_NAMES.DB_BRUTE,
|
|
8573
|
+
"Brute force database credentials",
|
|
8435
8574
|
{
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
parameters: {
|
|
8439
|
-
target: { type: "string", description: "Target IP/hostname" },
|
|
8440
|
-
port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8441
|
-
},
|
|
8442
|
-
required: ["target"],
|
|
8443
|
-
execute: async (params) => {
|
|
8444
|
-
const target = params.target;
|
|
8445
|
-
const port = params.port || String(SERVICE_PORTS.MYSQL);
|
|
8446
|
-
return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
|
|
8447
|
-
}
|
|
8575
|
+
target: { type: "string", description: "Target IP" },
|
|
8576
|
+
service: { type: "string", description: "Service (mysql, postgres, etc.)" }
|
|
8448
8577
|
},
|
|
8578
|
+
["target", "service"],
|
|
8579
|
+
async (params) => runCommand("hydra", [
|
|
8580
|
+
"-L",
|
|
8581
|
+
WORDLISTS.USERNAMES,
|
|
8582
|
+
"-P",
|
|
8583
|
+
WORDLISTS.COMMON_PASSWORDS,
|
|
8584
|
+
param(params, "target"),
|
|
8585
|
+
param(params, "service")
|
|
8586
|
+
])
|
|
8587
|
+
);
|
|
8588
|
+
var DATABASE_TOOLS = [SQLMAP_BASIC, SQLMAP_ADVANCED, MYSQL_ENUM, POSTGRES_ENUM, REDIS_ENUM, DB_BRUTE];
|
|
8589
|
+
var DATABASE_CONFIG = {
|
|
8590
|
+
name: SERVICE_CATEGORIES.DATABASE,
|
|
8591
|
+
description: "Database exploitation - SQL injection, credential extraction",
|
|
8592
|
+
tools: DATABASE_TOOLS,
|
|
8593
|
+
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8594
|
+
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8595
|
+
commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
|
|
8596
|
+
commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
|
|
8597
|
+
};
|
|
8598
|
+
|
|
8599
|
+
// src/domains/ad/tools.ts
|
|
8600
|
+
var BLOODHOUND_COLLECT = createTool(
|
|
8601
|
+
TOOL_NAMES.BLOODHOUND_COLLECT,
|
|
8602
|
+
"BloodHound data collection",
|
|
8449
8603
|
{
|
|
8450
|
-
|
|
8451
|
-
description: "
|
|
8452
|
-
parameters: {
|
|
8453
|
-
target: { type: "string", description: "Target IP" }
|
|
8454
|
-
},
|
|
8455
|
-
required: ["target"],
|
|
8456
|
-
execute: async (params) => {
|
|
8457
|
-
const target = params.target;
|
|
8458
|
-
return await runCommand("psql", ["-h", target, "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"]);
|
|
8459
|
-
}
|
|
8604
|
+
target: { type: "string", description: "Target DC IP" },
|
|
8605
|
+
domain: { type: "string", description: "Domain name" }
|
|
8460
8606
|
},
|
|
8607
|
+
["target"],
|
|
8608
|
+
async (params) => runCommand("bloodhound-python", ["-c", "All", "-d", param(params, "domain", "DOMAIN"), param(params, "target"), "--zip"])
|
|
8609
|
+
);
|
|
8610
|
+
var KERBEROAST = createTool(
|
|
8611
|
+
TOOL_NAMES.KERBEROAST,
|
|
8612
|
+
"Kerberoasting attack",
|
|
8461
8613
|
{
|
|
8462
|
-
|
|
8463
|
-
description: "
|
|
8464
|
-
|
|
8465
|
-
target: { type: "string", description: "Target IP" },
|
|
8466
|
-
port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8467
|
-
},
|
|
8468
|
-
required: ["target"],
|
|
8469
|
-
execute: async (params) => {
|
|
8470
|
-
const target = params.target;
|
|
8471
|
-
const port = params.port || String(SERVICE_PORTS.REDIS);
|
|
8472
|
-
return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
|
|
8473
|
-
}
|
|
8474
|
-
},
|
|
8475
|
-
{
|
|
8476
|
-
name: TOOL_NAMES.DB_BRUTE,
|
|
8477
|
-
description: "Brute force database credentials",
|
|
8478
|
-
parameters: {
|
|
8479
|
-
target: { type: "string", description: "Target IP" },
|
|
8480
|
-
service: { type: "string", description: "Service (mysql, postgres, etc.)" }
|
|
8481
|
-
},
|
|
8482
|
-
required: ["target", "service"],
|
|
8483
|
-
execute: async (params) => {
|
|
8484
|
-
const target = params.target;
|
|
8485
|
-
const service = params.service || SERVICES.MYSQL;
|
|
8486
|
-
return await runCommand("hydra", [
|
|
8487
|
-
"-L",
|
|
8488
|
-
WORDLISTS.USERNAMES,
|
|
8489
|
-
"-P",
|
|
8490
|
-
WORDLISTS.COMMON_PASSWORDS,
|
|
8491
|
-
target,
|
|
8492
|
-
service
|
|
8493
|
-
]);
|
|
8494
|
-
}
|
|
8495
|
-
}
|
|
8496
|
-
];
|
|
8497
|
-
var DATABASE_CONFIG = {
|
|
8498
|
-
name: SERVICE_CATEGORIES.DATABASE,
|
|
8499
|
-
description: "Database exploitation - SQL injection, credential extraction",
|
|
8500
|
-
tools: DATABASE_TOOLS,
|
|
8501
|
-
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8502
|
-
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8503
|
-
commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
|
|
8504
|
-
commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
|
|
8505
|
-
};
|
|
8506
|
-
|
|
8507
|
-
// src/domains/ad/tools.ts
|
|
8508
|
-
var AD_TOOLS = [
|
|
8509
|
-
{
|
|
8510
|
-
name: TOOL_NAMES.BLOODHOUND_COLLECT,
|
|
8511
|
-
description: "BloodHound data collection",
|
|
8512
|
-
parameters: {
|
|
8513
|
-
target: { type: "string", description: "Target DC IP" },
|
|
8514
|
-
domain: { type: "string", description: "Domain name" }
|
|
8515
|
-
},
|
|
8516
|
-
required: ["target"],
|
|
8517
|
-
execute: async (params) => {
|
|
8518
|
-
const target = params.target;
|
|
8519
|
-
const domain = params.domain || "DOMAIN";
|
|
8520
|
-
return await runCommand("bloodhound-python", ["-c", "All", "-d", domain, target, "--zip"]);
|
|
8521
|
-
}
|
|
8614
|
+
target: { type: "string", description: "DC IP" },
|
|
8615
|
+
user: { type: "string", description: "Domain user" },
|
|
8616
|
+
password: { type: "string", description: "Password" }
|
|
8522
8617
|
},
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
return await runCommand("GetUserSPNs.py", ["-dc-ip", target, `${params.user}:${params.password}`]);
|
|
8535
|
-
}
|
|
8536
|
-
},
|
|
8537
|
-
{
|
|
8538
|
-
name: TOOL_NAMES.LDAP_ENUM,
|
|
8539
|
-
description: "LDAP enumeration",
|
|
8540
|
-
parameters: {
|
|
8541
|
-
target: { type: "string", description: "LDAP Server" }
|
|
8542
|
-
},
|
|
8543
|
-
required: ["target"],
|
|
8544
|
-
execute: async (params) => {
|
|
8545
|
-
const target = params.target;
|
|
8546
|
-
return await runCommand("ldapsearch", ["-x", "-H", `ldap://${target}`, "-b", ""]);
|
|
8547
|
-
}
|
|
8548
|
-
}
|
|
8549
|
-
];
|
|
8618
|
+
["target", "user", "password"],
|
|
8619
|
+
async (params) => runCommand("GetUserSPNs.py", ["-dc-ip", param(params, "target"), `${param(params, "user")}:${param(params, "password")}`])
|
|
8620
|
+
);
|
|
8621
|
+
var LDAP_ENUM = createTool(
|
|
8622
|
+
TOOL_NAMES.LDAP_ENUM,
|
|
8623
|
+
"LDAP enumeration",
|
|
8624
|
+
{ target: { type: "string", description: "LDAP Server" } },
|
|
8625
|
+
["target"],
|
|
8626
|
+
async (params) => runCommand("ldapsearch", ["-x", "-H", `ldap://${param(params, "target")}`, "-b", ""])
|
|
8627
|
+
);
|
|
8628
|
+
var AD_TOOLS = [BLOODHOUND_COLLECT, KERBEROAST, LDAP_ENUM];
|
|
8550
8629
|
var AD_CONFIG = {
|
|
8551
8630
|
name: SERVICE_CATEGORIES.AD,
|
|
8552
8631
|
description: "Active Directory and Windows domain",
|
|
@@ -8558,19 +8637,14 @@ var AD_CONFIG = {
|
|
|
8558
8637
|
};
|
|
8559
8638
|
|
|
8560
8639
|
// src/domains/email/tools.ts
|
|
8561
|
-
var
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
execute: async (params) => {
|
|
8570
|
-
return await runCommand("smtp-user-enum", ["-M", "VRFY", "-t", params.target]);
|
|
8571
|
-
}
|
|
8572
|
-
}
|
|
8573
|
-
];
|
|
8640
|
+
var SMTP_ENUM = createTool(
|
|
8641
|
+
TOOL_NAMES.SMTP_ENUM,
|
|
8642
|
+
"SMTP user enumeration",
|
|
8643
|
+
{ target: { type: "string", description: "SMTP server" } },
|
|
8644
|
+
["target"],
|
|
8645
|
+
async (params) => runCommand("smtp-user-enum", ["-M", "VRFY", "-t", param(params, "target")])
|
|
8646
|
+
);
|
|
8647
|
+
var EMAIL_TOOLS = [SMTP_ENUM];
|
|
8574
8648
|
var EMAIL_CONFIG = {
|
|
8575
8649
|
name: SERVICE_CATEGORIES.EMAIL,
|
|
8576
8650
|
description: "Email services - SMTP, IMAP, POP3",
|
|
@@ -8582,30 +8656,21 @@ var EMAIL_CONFIG = {
|
|
|
8582
8656
|
};
|
|
8583
8657
|
|
|
8584
8658
|
// src/domains/remote-access/tools.ts
|
|
8585
|
-
var
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
parameters: {
|
|
8601
|
-
target: { type: "string", description: "RDP Server" }
|
|
8602
|
-
},
|
|
8603
|
-
required: ["target"],
|
|
8604
|
-
execute: async (params) => {
|
|
8605
|
-
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
|
|
8606
|
-
}
|
|
8607
|
-
}
|
|
8608
|
-
];
|
|
8659
|
+
var SSH_ENUM = createTool(
|
|
8660
|
+
TOOL_NAMES.SSH_ENUM,
|
|
8661
|
+
"SSH enumeration",
|
|
8662
|
+
{ target: { type: "string", description: "SSH Server" } },
|
|
8663
|
+
["target"],
|
|
8664
|
+
async (params) => runCommand("ssh-audit", [param(params, "target")])
|
|
8665
|
+
);
|
|
8666
|
+
var RDP_ENUM = createTool(
|
|
8667
|
+
TOOL_NAMES.RDP_ENUM,
|
|
8668
|
+
"RDP enumeration",
|
|
8669
|
+
{ target: { type: "string", description: "RDP Server" } },
|
|
8670
|
+
["target"],
|
|
8671
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", param(params, "target")])
|
|
8672
|
+
);
|
|
8673
|
+
var REMOTE_ACCESS_TOOLS = [SSH_ENUM, RDP_ENUM];
|
|
8609
8674
|
var REMOTE_ACCESS_CONFIG = {
|
|
8610
8675
|
name: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
8611
8676
|
description: "Remote access services - SSH, RDP, VNC",
|
|
@@ -8617,30 +8682,21 @@ var REMOTE_ACCESS_CONFIG = {
|
|
|
8617
8682
|
};
|
|
8618
8683
|
|
|
8619
8684
|
// src/domains/file-sharing/tools.ts
|
|
8620
|
-
var
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
parameters: {
|
|
8636
|
-
target: { type: "string", description: "SMB Server" }
|
|
8637
|
-
},
|
|
8638
|
-
required: ["target"],
|
|
8639
|
-
execute: async (params) => {
|
|
8640
|
-
return await runCommand("enum4linux", ["-a", params.target]);
|
|
8641
|
-
}
|
|
8642
|
-
}
|
|
8643
|
-
];
|
|
8685
|
+
var FTP_ENUM = createTool(
|
|
8686
|
+
TOOL_NAMES.FTP_ENUM,
|
|
8687
|
+
"FTP enumeration",
|
|
8688
|
+
{ target: { type: "string", description: "FTP Server" } },
|
|
8689
|
+
["target"],
|
|
8690
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", param(params, "target")])
|
|
8691
|
+
);
|
|
8692
|
+
var SMB_ENUM = createTool(
|
|
8693
|
+
TOOL_NAMES.SMB_ENUM,
|
|
8694
|
+
"SMB enumeration",
|
|
8695
|
+
{ target: { type: "string", description: "SMB Server" } },
|
|
8696
|
+
["target"],
|
|
8697
|
+
async (params) => runCommand("enum4linux", ["-a", param(params, "target")])
|
|
8698
|
+
);
|
|
8699
|
+
var FILE_SHARING_TOOLS = [FTP_ENUM, SMB_ENUM];
|
|
8644
8700
|
var FILE_SHARING_CONFIG = {
|
|
8645
8701
|
name: SERVICE_CATEGORIES.FILE_SHARING,
|
|
8646
8702
|
description: "File sharing protocols - SMB, FTP, NFS",
|
|
@@ -8657,34 +8713,26 @@ var CLOUD_PROVIDER = {
|
|
|
8657
8713
|
AZURE: "azure",
|
|
8658
8714
|
GCP: "gcp"
|
|
8659
8715
|
};
|
|
8660
|
-
var
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
execute: async (params) => {
|
|
8681
|
-
const provider = params.provider;
|
|
8682
|
-
if (provider === CLOUD_PROVIDER.AWS) return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
|
|
8683
|
-
if (provider === CLOUD_PROVIDER.AZURE) return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
|
|
8684
|
-
return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
|
|
8685
|
-
}
|
|
8686
|
-
}
|
|
8687
|
-
];
|
|
8716
|
+
var AWS_S3_CHECK = createTool(
|
|
8717
|
+
TOOL_NAMES.AWS_S3_CHECK,
|
|
8718
|
+
"S3 bucket security check",
|
|
8719
|
+
{ bucket: { type: "string", description: "Bucket name" } },
|
|
8720
|
+
["bucket"],
|
|
8721
|
+
async (params) => runCommand("aws", ["s3", "ls", `s3://${param(params, "bucket")}`, "--no-sign-request"])
|
|
8722
|
+
);
|
|
8723
|
+
var CLOUD_META_CHECK = createTool(
|
|
8724
|
+
TOOL_NAMES.CLOUD_META_CHECK,
|
|
8725
|
+
"Check cloud metadata service access",
|
|
8726
|
+
{ provider: { type: "string", description: "aws, azure, or gcp" } },
|
|
8727
|
+
["provider"],
|
|
8728
|
+
async (params) => {
|
|
8729
|
+
const provider = param(params, "provider");
|
|
8730
|
+
if (provider === CLOUD_PROVIDER.AWS) return runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
|
|
8731
|
+
if (provider === CLOUD_PROVIDER.AZURE) return runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
|
|
8732
|
+
return runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
|
|
8733
|
+
}
|
|
8734
|
+
);
|
|
8735
|
+
var CLOUD_TOOLS = [AWS_S3_CHECK, CLOUD_META_CHECK];
|
|
8688
8736
|
var CLOUD_CONFIG = {
|
|
8689
8737
|
name: SERVICE_CATEGORIES.CLOUD,
|
|
8690
8738
|
description: "Cloud infrastructure - AWS, Azure, GCP",
|
|
@@ -8696,30 +8744,21 @@ var CLOUD_CONFIG = {
|
|
|
8696
8744
|
};
|
|
8697
8745
|
|
|
8698
8746
|
// src/domains/container/tools.ts
|
|
8699
|
-
var
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
parameters: {
|
|
8715
|
-
context: { type: "string", description: "Kube context" }
|
|
8716
|
-
},
|
|
8717
|
-
required: ["context"],
|
|
8718
|
-
execute: async (params) => {
|
|
8719
|
-
return await runCommand("kubectl", ["--context", params.context, "get", "pods"]);
|
|
8720
|
-
}
|
|
8721
|
-
}
|
|
8722
|
-
];
|
|
8747
|
+
var DOCKER_PS = createTool(
|
|
8748
|
+
TOOL_NAMES.DOCKER_PS,
|
|
8749
|
+
"List running Docker containers",
|
|
8750
|
+
{ host: { type: "string", description: "Docker host" } },
|
|
8751
|
+
["host"],
|
|
8752
|
+
async (params) => runCommand("docker", ["-H", param(params, "host"), "ps"])
|
|
8753
|
+
);
|
|
8754
|
+
var KUBE_GET_PODS = createTool(
|
|
8755
|
+
TOOL_NAMES.KUBE_GET_PODS,
|
|
8756
|
+
"List Kubernetes pods",
|
|
8757
|
+
{ context: { type: "string", description: "Kube context" } },
|
|
8758
|
+
["context"],
|
|
8759
|
+
async (params) => runCommand("kubectl", ["--context", param(params, "context"), "get", "pods"])
|
|
8760
|
+
);
|
|
8761
|
+
var CONTAINER_TOOLS = [DOCKER_PS, KUBE_GET_PODS];
|
|
8723
8762
|
var CONTAINER_CONFIG = {
|
|
8724
8763
|
name: SERVICE_CATEGORIES.CONTAINER,
|
|
8725
8764
|
description: "Container platforms - Docker, Kubernetes",
|
|
@@ -8731,58 +8770,38 @@ var CONTAINER_CONFIG = {
|
|
|
8731
8770
|
};
|
|
8732
8771
|
|
|
8733
8772
|
// src/domains/api/tools.ts
|
|
8734
|
-
var
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
}
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
},
|
|
8773
|
+
var API_DISCOVER = createTool(
|
|
8774
|
+
TOOL_NAMES.API_DISCOVER,
|
|
8775
|
+
"API endpoint discovery - using Arjun",
|
|
8776
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8777
|
+
["target"],
|
|
8778
|
+
async (params) => runCommand("arjun", ["-u", param(params, "target"), "-t", "20"])
|
|
8779
|
+
);
|
|
8780
|
+
var GRAPHQL_INTROSPECT = createTool(
|
|
8781
|
+
TOOL_NAMES.GRAPHQL_INTROSPECT,
|
|
8782
|
+
"GraphQL introspection - schema discovery",
|
|
8783
|
+
{ target: { type: "string", description: "GQL Endpoint" } },
|
|
8784
|
+
["target"],
|
|
8785
|
+
async (params) => runCommand("curl", ["-X", "POST", param(params, "target"), "-H", "Content-Type: application/json", "-d", '{"query":"{__schema {queryType {name}}}"}'])
|
|
8786
|
+
);
|
|
8787
|
+
var SWAGGER_PARSE = createTool(
|
|
8788
|
+
TOOL_NAMES.SWAGGER_PARSE,
|
|
8789
|
+
"Parse Swagger/OpenAPI specification",
|
|
8790
|
+
{ spec: { type: "string", description: "URL to swagger.json/yaml" } },
|
|
8791
|
+
["spec"],
|
|
8792
|
+
async (params) => runCommand("swagger-codegen", ["generate", "-i", param(params, "spec"), "-l", "html2"])
|
|
8793
|
+
);
|
|
8794
|
+
var API_FUZZ = createTool(
|
|
8795
|
+
TOOL_NAMES.API_FUZZ,
|
|
8796
|
+
"General API fuzzing",
|
|
8759
8797
|
{
|
|
8760
|
-
|
|
8761
|
-
description: "
|
|
8762
|
-
parameters: {
|
|
8763
|
-
spec: { type: "string", description: "URL to swagger.json/yaml" }
|
|
8764
|
-
},
|
|
8765
|
-
required: ["spec"],
|
|
8766
|
-
execute: async (params) => {
|
|
8767
|
-
const spec = params.spec;
|
|
8768
|
-
return await runCommand("swagger-codegen", ["generate", "-i", spec, "-l", "html2"]);
|
|
8769
|
-
}
|
|
8798
|
+
target: { type: "string", description: "Base API URL" },
|
|
8799
|
+
wordlist: { type: "string", description: "Wordlist path" }
|
|
8770
8800
|
},
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
target: { type: "string", description: "Base API URL" },
|
|
8776
|
-
wordlist: { type: "string", description: "Wordlist path" }
|
|
8777
|
-
},
|
|
8778
|
-
required: ["target"],
|
|
8779
|
-
execute: async (params) => {
|
|
8780
|
-
const target = params.target;
|
|
8781
|
-
const wordlist = params.wordlist || WORDLISTS.API_FUZZ;
|
|
8782
|
-
return await runCommand("ffuf", ["-u", `${target}/FUZZ`, "-w", wordlist, "-mc", "all"]);
|
|
8783
|
-
}
|
|
8784
|
-
}
|
|
8785
|
-
];
|
|
8801
|
+
["target"],
|
|
8802
|
+
async (params) => runCommand("ffuf", ["-u", `${param(params, "target")}/FUZZ`, "-w", param(params, "wordlist", WORDLISTS.API_FUZZ), "-mc", "all"])
|
|
8803
|
+
);
|
|
8804
|
+
var API_TOOLS = [API_DISCOVER, GRAPHQL_INTROSPECT, SWAGGER_PARSE, API_FUZZ];
|
|
8786
8805
|
var API_CONFIG = {
|
|
8787
8806
|
name: SERVICE_CATEGORIES.API,
|
|
8788
8807
|
description: "API testing - REST, GraphQL, SOAP",
|
|
@@ -8794,19 +8813,14 @@ var API_CONFIG = {
|
|
|
8794
8813
|
};
|
|
8795
8814
|
|
|
8796
8815
|
// src/domains/wireless/tools.ts
|
|
8797
|
-
var
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
execute: async (params) => {
|
|
8806
|
-
return await runCommand("iwlist", [params.interface, "scanning"]);
|
|
8807
|
-
}
|
|
8808
|
-
}
|
|
8809
|
-
];
|
|
8816
|
+
var WIFI_SCAN = createTool(
|
|
8817
|
+
TOOL_NAMES.WIFI_SCAN,
|
|
8818
|
+
"WiFi network scanning",
|
|
8819
|
+
{ interface: { type: "string", description: "Wireless interface" } },
|
|
8820
|
+
["interface"],
|
|
8821
|
+
async (params) => runCommand("iwlist", [param(params, "interface"), "scanning"])
|
|
8822
|
+
);
|
|
8823
|
+
var WIRELESS_TOOLS = [WIFI_SCAN];
|
|
8810
8824
|
var WIRELESS_CONFIG = {
|
|
8811
8825
|
name: SERVICE_CATEGORIES.WIRELESS,
|
|
8812
8826
|
description: "Wireless security - WiFi, Bluetooth",
|
|
@@ -8818,19 +8832,14 @@ var WIRELESS_CONFIG = {
|
|
|
8818
8832
|
};
|
|
8819
8833
|
|
|
8820
8834
|
// src/domains/ics/tools.ts
|
|
8821
|
-
var
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
execute: async (params) => {
|
|
8830
|
-
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
|
|
8831
|
-
}
|
|
8832
|
-
}
|
|
8833
|
-
];
|
|
8835
|
+
var MODBUS_ENUM = createTool(
|
|
8836
|
+
TOOL_NAMES.MODBUS_ENUM,
|
|
8837
|
+
"Modbus enumeration",
|
|
8838
|
+
{ target: { type: "string", description: "ICS Device" } },
|
|
8839
|
+
["target"],
|
|
8840
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", param(params, "target")])
|
|
8841
|
+
);
|
|
8842
|
+
var ICS_TOOLS = [MODBUS_ENUM];
|
|
8834
8843
|
var ICS_CONFIG = {
|
|
8835
8844
|
name: SERVICE_CATEGORIES.ICS,
|
|
8836
8845
|
description: "Industrial control systems - Modbus, DNP3, EtherNet/IP",
|
|
@@ -9083,7 +9092,7 @@ var ServiceParser = class {
|
|
|
9083
9092
|
};
|
|
9084
9093
|
|
|
9085
9094
|
// src/domains/registry.ts
|
|
9086
|
-
import { join as
|
|
9095
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
9087
9096
|
import { fileURLToPath } from "url";
|
|
9088
9097
|
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
9089
9098
|
var DOMAINS = {
|
|
@@ -9091,73 +9100,73 @@ var DOMAINS = {
|
|
|
9091
9100
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
9092
9101
|
name: "Network Infrastructure",
|
|
9093
9102
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
9094
|
-
promptPath:
|
|
9103
|
+
promptPath: join8(__dirname, "network/prompt.md")
|
|
9095
9104
|
},
|
|
9096
9105
|
[SERVICE_CATEGORIES.WEB]: {
|
|
9097
9106
|
id: SERVICE_CATEGORIES.WEB,
|
|
9098
9107
|
name: "Web Application",
|
|
9099
9108
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
9100
|
-
promptPath:
|
|
9109
|
+
promptPath: join8(__dirname, "web/prompt.md")
|
|
9101
9110
|
},
|
|
9102
9111
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
9103
9112
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
9104
9113
|
name: "Database Security",
|
|
9105
9114
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
9106
|
-
promptPath:
|
|
9115
|
+
promptPath: join8(__dirname, "database/prompt.md")
|
|
9107
9116
|
},
|
|
9108
9117
|
[SERVICE_CATEGORIES.AD]: {
|
|
9109
9118
|
id: SERVICE_CATEGORIES.AD,
|
|
9110
9119
|
name: "Active Directory",
|
|
9111
9120
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
9112
|
-
promptPath:
|
|
9121
|
+
promptPath: join8(__dirname, "ad/prompt.md")
|
|
9113
9122
|
},
|
|
9114
9123
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
9115
9124
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
9116
9125
|
name: "Email Services",
|
|
9117
9126
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
9118
|
-
promptPath:
|
|
9127
|
+
promptPath: join8(__dirname, "email/prompt.md")
|
|
9119
9128
|
},
|
|
9120
9129
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
9121
9130
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
9122
9131
|
name: "Remote Access",
|
|
9123
9132
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
9124
|
-
promptPath:
|
|
9133
|
+
promptPath: join8(__dirname, "remote-access/prompt.md")
|
|
9125
9134
|
},
|
|
9126
9135
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
9127
9136
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
9128
9137
|
name: "File Sharing",
|
|
9129
9138
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
9130
|
-
promptPath:
|
|
9139
|
+
promptPath: join8(__dirname, "file-sharing/prompt.md")
|
|
9131
9140
|
},
|
|
9132
9141
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
9133
9142
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
9134
9143
|
name: "Cloud Infrastructure",
|
|
9135
9144
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
9136
|
-
promptPath:
|
|
9145
|
+
promptPath: join8(__dirname, "cloud/prompt.md")
|
|
9137
9146
|
},
|
|
9138
9147
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
9139
9148
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
9140
9149
|
name: "Container Systems",
|
|
9141
9150
|
description: "Docker and Kubernetes security testing.",
|
|
9142
|
-
promptPath:
|
|
9151
|
+
promptPath: join8(__dirname, "container/prompt.md")
|
|
9143
9152
|
},
|
|
9144
9153
|
[SERVICE_CATEGORIES.API]: {
|
|
9145
9154
|
id: SERVICE_CATEGORIES.API,
|
|
9146
9155
|
name: "API Security",
|
|
9147
9156
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
9148
|
-
promptPath:
|
|
9157
|
+
promptPath: join8(__dirname, "api/prompt.md")
|
|
9149
9158
|
},
|
|
9150
9159
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
9151
9160
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
9152
9161
|
name: "Wireless Networks",
|
|
9153
9162
|
description: "WiFi and Bluetooth security testing.",
|
|
9154
|
-
promptPath:
|
|
9163
|
+
promptPath: join8(__dirname, "wireless/prompt.md")
|
|
9155
9164
|
},
|
|
9156
9165
|
[SERVICE_CATEGORIES.ICS]: {
|
|
9157
9166
|
id: SERVICE_CATEGORIES.ICS,
|
|
9158
9167
|
name: "Industrial Systems",
|
|
9159
9168
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
9160
|
-
promptPath:
|
|
9169
|
+
promptPath: join8(__dirname, "ics/prompt.md")
|
|
9161
9170
|
}
|
|
9162
9171
|
};
|
|
9163
9172
|
|
|
@@ -9234,22 +9243,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
9234
9243
|
}
|
|
9235
9244
|
return results;
|
|
9236
9245
|
}
|
|
9237
|
-
suggestSubAgent(target) {
|
|
9238
|
-
const suggestions = this.suggestTools(target);
|
|
9239
|
-
const priority = [
|
|
9240
|
-
SERVICE_CATEGORIES.ICS,
|
|
9241
|
-
SERVICE_CATEGORIES.AD,
|
|
9242
|
-
SERVICE_CATEGORIES.DATABASE,
|
|
9243
|
-
SERVICE_CATEGORIES.CLOUD,
|
|
9244
|
-
SERVICE_CATEGORIES.CONTAINER,
|
|
9245
|
-
SERVICE_CATEGORIES.WEB,
|
|
9246
|
-
SERVICE_CATEGORIES.NETWORK
|
|
9247
|
-
];
|
|
9248
|
-
for (const cat of priority) {
|
|
9249
|
-
if (suggestions.some((s) => s.category === cat)) return cat;
|
|
9250
|
-
}
|
|
9251
|
-
return AGENT_ROLES.RECON;
|
|
9252
|
-
}
|
|
9253
9246
|
fingerprintService(port) {
|
|
9254
9247
|
const category = ServiceParser.detectCategory(port.port, port.service);
|
|
9255
9248
|
if (!category) return null;
|
|
@@ -9578,14 +9571,6 @@ var LLMClient = class {
|
|
|
9578
9571
|
this.processStreamEvent(event, requestId, {
|
|
9579
9572
|
toolCallsMap,
|
|
9580
9573
|
callbacks,
|
|
9581
|
-
onTextStart: () => {
|
|
9582
|
-
},
|
|
9583
|
-
onReasoningStart: () => {
|
|
9584
|
-
},
|
|
9585
|
-
onTextEnd: () => {
|
|
9586
|
-
},
|
|
9587
|
-
onReasoningEnd: () => {
|
|
9588
|
-
},
|
|
9589
9574
|
onContent: (text) => {
|
|
9590
9575
|
fullContent += text;
|
|
9591
9576
|
totalChars += text.length;
|
|
@@ -9660,18 +9645,18 @@ var LLMClient = class {
|
|
|
9660
9645
|
return { cleanText, extractedReasoning };
|
|
9661
9646
|
}
|
|
9662
9647
|
processStreamEvent(event, requestId, context) {
|
|
9663
|
-
const { toolCallsMap, callbacks,
|
|
9648
|
+
const { toolCallsMap, callbacks, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
|
|
9664
9649
|
switch (event.type) {
|
|
9665
9650
|
case LLM_SSE_EVENT.CONTENT_BLOCK_START:
|
|
9666
9651
|
if (event.content_block) {
|
|
9667
9652
|
const blockType = event.content_block.type;
|
|
9668
9653
|
if (blockType === LLM_BLOCK_TYPE.TEXT) {
|
|
9669
9654
|
currentBlockRef.value = "text";
|
|
9670
|
-
onTextStart();
|
|
9655
|
+
context.onTextStart?.();
|
|
9671
9656
|
callbacks?.onOutputStart?.();
|
|
9672
9657
|
} else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
|
|
9673
9658
|
currentBlockRef.value = "reasoning";
|
|
9674
|
-
onReasoningStart();
|
|
9659
|
+
context.onReasoningStart?.();
|
|
9675
9660
|
callbacks?.onReasoningStart?.();
|
|
9676
9661
|
} else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
|
|
9677
9662
|
currentBlockRef.value = "tool_use";
|
|
@@ -9717,10 +9702,10 @@ var LLMClient = class {
|
|
|
9717
9702
|
const stoppedType = currentBlockRef.value;
|
|
9718
9703
|
currentBlockRef.value = null;
|
|
9719
9704
|
if (stoppedType === "text") {
|
|
9720
|
-
onTextEnd();
|
|
9705
|
+
context.onTextEnd?.();
|
|
9721
9706
|
callbacks?.onOutputEnd?.();
|
|
9722
9707
|
} else if (stoppedType === "reasoning") {
|
|
9723
|
-
onReasoningEnd();
|
|
9708
|
+
context.onReasoningEnd?.();
|
|
9724
9709
|
callbacks?.onReasoningEnd?.();
|
|
9725
9710
|
}
|
|
9726
9711
|
break;
|
|
@@ -9792,13 +9777,10 @@ function getLLMClient() {
|
|
|
9792
9777
|
}
|
|
9793
9778
|
return llmInstance;
|
|
9794
9779
|
}
|
|
9795
|
-
function logLLM(message, data) {
|
|
9796
|
-
debugLog("llm", message, data);
|
|
9797
|
-
}
|
|
9798
9780
|
|
|
9799
9781
|
// src/engine/state-persistence.ts
|
|
9800
|
-
import { writeFileSync as
|
|
9801
|
-
import { join as
|
|
9782
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync4, existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync4, rmSync } from "fs";
|
|
9783
|
+
import { join as join9 } from "path";
|
|
9802
9784
|
function saveState(state) {
|
|
9803
9785
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
9804
9786
|
ensureDirExists(sessionsDir);
|
|
@@ -9815,20 +9797,24 @@ function saveState(state) {
|
|
|
9815
9797
|
missionSummary: state.getMissionSummary(),
|
|
9816
9798
|
missionChecklist: state.getMissionChecklist()
|
|
9817
9799
|
};
|
|
9818
|
-
const sessionFile =
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9800
|
+
const sessionFile = join9(sessionsDir, FILE_PATTERNS.session());
|
|
9801
|
+
const json = JSON.stringify(snapshot, null, 2);
|
|
9802
|
+
writeFileSync7(sessionFile, json, "utf-8");
|
|
9803
|
+
const latestFile = join9(sessionsDir, "latest.json");
|
|
9804
|
+
writeFileSync7(latestFile, json, "utf-8");
|
|
9822
9805
|
pruneOldSessions(sessionsDir);
|
|
9823
9806
|
return sessionFile;
|
|
9824
9807
|
}
|
|
9825
9808
|
function pruneOldSessions(sessionsDir) {
|
|
9826
9809
|
try {
|
|
9827
|
-
const sessionFiles =
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9810
|
+
const sessionFiles = readdirSync2(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => {
|
|
9811
|
+
const filePath = join9(sessionsDir, f);
|
|
9812
|
+
return {
|
|
9813
|
+
name: f,
|
|
9814
|
+
path: filePath,
|
|
9815
|
+
mtime: statSync2(filePath).mtimeMs
|
|
9816
|
+
};
|
|
9817
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
9832
9818
|
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
9833
9819
|
for (const file of toDelete) {
|
|
9834
9820
|
unlinkSync4(file.path);
|
|
@@ -9837,8 +9823,8 @@ function pruneOldSessions(sessionsDir) {
|
|
|
9837
9823
|
}
|
|
9838
9824
|
}
|
|
9839
9825
|
function loadState(state) {
|
|
9840
|
-
const latestFile =
|
|
9841
|
-
if (!
|
|
9826
|
+
const latestFile = join9(WORKSPACE.SESSIONS, "latest.json");
|
|
9827
|
+
if (!existsSync7(latestFile)) {
|
|
9842
9828
|
return false;
|
|
9843
9829
|
}
|
|
9844
9830
|
try {
|
|
@@ -9865,10 +9851,7 @@ function loadState(state) {
|
|
|
9865
9851
|
state.addLoot(loot);
|
|
9866
9852
|
}
|
|
9867
9853
|
for (const item of snapshot.todo) {
|
|
9868
|
-
|
|
9869
|
-
if (item.status && item.status !== "pending") {
|
|
9870
|
-
state.updateTodo(id, { status: item.status });
|
|
9871
|
-
}
|
|
9854
|
+
state.restoreTodoItem(item);
|
|
9872
9855
|
}
|
|
9873
9856
|
const validPhases = new Set(Object.values(PHASES));
|
|
9874
9857
|
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
@@ -9877,21 +9860,7 @@ function loadState(state) {
|
|
|
9877
9860
|
state.setMissionSummary(snapshot.missionSummary);
|
|
9878
9861
|
}
|
|
9879
9862
|
if (snapshot.missionChecklist?.length > 0) {
|
|
9880
|
-
state.
|
|
9881
|
-
const restoredChecklist = state.getMissionChecklist();
|
|
9882
|
-
const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
|
|
9883
|
-
const completedUpdates = [];
|
|
9884
|
-
for (let i = 0; i < snapshot.missionChecklist.length; i++) {
|
|
9885
|
-
if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
|
|
9886
|
-
completedUpdates.push({
|
|
9887
|
-
id: restoredChecklist[baseIndex + i].id,
|
|
9888
|
-
isCompleted: true
|
|
9889
|
-
});
|
|
9890
|
-
}
|
|
9891
|
-
}
|
|
9892
|
-
if (completedUpdates.length > 0) {
|
|
9893
|
-
state.updateMissionChecklist(completedUpdates);
|
|
9894
|
-
}
|
|
9863
|
+
state.restoreMissionChecklist(snapshot.missionChecklist);
|
|
9895
9864
|
}
|
|
9896
9865
|
return true;
|
|
9897
9866
|
} catch (err) {
|
|
@@ -9914,7 +9883,7 @@ function clearWorkspace() {
|
|
|
9914
9883
|
];
|
|
9915
9884
|
for (const dir of dirsToClean) {
|
|
9916
9885
|
try {
|
|
9917
|
-
if (
|
|
9886
|
+
if (existsSync7(dir.path)) {
|
|
9918
9887
|
rmSync(dir.path, { recursive: true, force: true });
|
|
9919
9888
|
ensureDirExists(dir.path);
|
|
9920
9889
|
cleared.push(dir.label);
|
|
@@ -10004,7 +9973,7 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
10004
9973
|
}
|
|
10005
9974
|
|
|
10006
9975
|
// src/shared/utils/context-digest.ts
|
|
10007
|
-
import { writeFileSync as
|
|
9976
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
10008
9977
|
|
|
10009
9978
|
// src/shared/constants/document-schema.ts
|
|
10010
9979
|
var MEMO_SECTIONS = {
|
|
@@ -10336,7 +10305,7 @@ function saveFullOutput(output, toolName) {
|
|
|
10336
10305
|
const toolsDir = WORKSPACE.turnToolsPath(_currentTurn);
|
|
10337
10306
|
ensureDirExists(toolsDir);
|
|
10338
10307
|
const filePath = `${toolsDir}/${FILE_PATTERNS.toolOutput(toolName)}`;
|
|
10339
|
-
|
|
10308
|
+
writeFileSync8(filePath, output, "utf-8");
|
|
10340
10309
|
return filePath;
|
|
10341
10310
|
} catch (err) {
|
|
10342
10311
|
debugLog("general", "Failed to save full output to file", { toolName, error: String(err) });
|
|
@@ -10365,8 +10334,306 @@ ${text}
|
|
|
10365
10334
|
};
|
|
10366
10335
|
}
|
|
10367
10336
|
|
|
10337
|
+
// src/agents/tool-executor.ts
|
|
10338
|
+
var ToolExecutor = class _ToolExecutor {
|
|
10339
|
+
state;
|
|
10340
|
+
events;
|
|
10341
|
+
toolRegistry;
|
|
10342
|
+
llm;
|
|
10343
|
+
/** Collected tool execution records for the current turn */
|
|
10344
|
+
turnToolJournal = [];
|
|
10345
|
+
/** Aggregated memo from all tools in the current turn */
|
|
10346
|
+
turnMemo = {
|
|
10347
|
+
keyFindings: [],
|
|
10348
|
+
credentials: [],
|
|
10349
|
+
attackVectors: [],
|
|
10350
|
+
failures: [],
|
|
10351
|
+
suspicions: [],
|
|
10352
|
+
attackValue: "LOW",
|
|
10353
|
+
nextSteps: []
|
|
10354
|
+
};
|
|
10355
|
+
/** Analyst reflections collected during this turn */
|
|
10356
|
+
turnReflections = [];
|
|
10357
|
+
/** Tools safe to run in parallel */
|
|
10358
|
+
static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
10359
|
+
TOOL_NAMES.GET_STATE,
|
|
10360
|
+
TOOL_NAMES.PARSE_NMAP,
|
|
10361
|
+
TOOL_NAMES.SEARCH_CVE,
|
|
10362
|
+
TOOL_NAMES.WEB_SEARCH,
|
|
10363
|
+
TOOL_NAMES.BROWSE_URL,
|
|
10364
|
+
TOOL_NAMES.READ_FILE,
|
|
10365
|
+
TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
10366
|
+
TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
10367
|
+
TOOL_NAMES.GET_CVE_INFO,
|
|
10368
|
+
TOOL_NAMES.FILL_FORM,
|
|
10369
|
+
TOOL_NAMES.ADD_TARGET,
|
|
10370
|
+
TOOL_NAMES.ADD_FINDING,
|
|
10371
|
+
TOOL_NAMES.ADD_LOOT,
|
|
10372
|
+
TOOL_NAMES.UPDATE_MISSION,
|
|
10373
|
+
TOOL_NAMES.UPDATE_TODO
|
|
10374
|
+
]);
|
|
10375
|
+
constructor(deps) {
|
|
10376
|
+
this.state = deps.state;
|
|
10377
|
+
this.events = deps.events;
|
|
10378
|
+
this.toolRegistry = deps.toolRegistry;
|
|
10379
|
+
this.llm = deps.llm;
|
|
10380
|
+
}
|
|
10381
|
+
/** Clear turn-level state at the start of each step */
|
|
10382
|
+
clearTurnState() {
|
|
10383
|
+
this.turnToolJournal = [];
|
|
10384
|
+
this.turnMemo = {
|
|
10385
|
+
keyFindings: [],
|
|
10386
|
+
credentials: [],
|
|
10387
|
+
attackVectors: [],
|
|
10388
|
+
failures: [],
|
|
10389
|
+
suspicions: [],
|
|
10390
|
+
attackValue: "LOW",
|
|
10391
|
+
nextSteps: []
|
|
10392
|
+
};
|
|
10393
|
+
this.turnReflections = [];
|
|
10394
|
+
}
|
|
10395
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10396
|
+
// SUBSECTION: Tool Execution
|
|
10397
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10398
|
+
async processToolCalls(toolCalls, progress) {
|
|
10399
|
+
if (toolCalls.length <= 1) {
|
|
10400
|
+
return this.executeSequentially(toolCalls, progress);
|
|
10401
|
+
}
|
|
10402
|
+
const allParallelSafe = toolCalls.every((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10403
|
+
if (allParallelSafe) {
|
|
10404
|
+
return this.executeInParallel(toolCalls, progress);
|
|
10405
|
+
}
|
|
10406
|
+
const parallelCalls = toolCalls.filter((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10407
|
+
const sequentialCalls = toolCalls.filter((c) => !_ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10408
|
+
const parallelResults = parallelCalls.length > 0 ? await this.executeInParallel(parallelCalls, progress) : [];
|
|
10409
|
+
const sequentialResults = sequentialCalls.length > 0 ? await this.executeSequentially(sequentialCalls, progress) : [];
|
|
10410
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
10411
|
+
for (const r of [...parallelResults, ...sequentialResults]) {
|
|
10412
|
+
resultMap.set(r.toolCallId, r);
|
|
10413
|
+
}
|
|
10414
|
+
return toolCalls.map((c) => resultMap.get(c.id)).filter(Boolean);
|
|
10415
|
+
}
|
|
10416
|
+
async executeInParallel(toolCalls, progress) {
|
|
10417
|
+
for (const call of toolCalls) {
|
|
10418
|
+
this.emitToolCall(call.name, call.input);
|
|
10419
|
+
}
|
|
10420
|
+
const promises = toolCalls.map((call) => this.executeSingle(call, progress));
|
|
10421
|
+
const settled = await Promise.allSettled(promises);
|
|
10422
|
+
return settled.map((s, i) => {
|
|
10423
|
+
if (s.status === "fulfilled") return s.value;
|
|
10424
|
+
const errorMsg = String(s.reason);
|
|
10425
|
+
return { toolCallId: toolCalls[i].id, output: errorMsg, error: errorMsg };
|
|
10426
|
+
});
|
|
10427
|
+
}
|
|
10428
|
+
async executeSequentially(toolCalls, progress) {
|
|
10429
|
+
const results = [];
|
|
10430
|
+
for (const call of toolCalls) {
|
|
10431
|
+
this.emitToolCall(call.name, call.input);
|
|
10432
|
+
const result2 = await this.executeSingle(call, progress);
|
|
10433
|
+
results.push(result2);
|
|
10434
|
+
}
|
|
10435
|
+
return results;
|
|
10436
|
+
}
|
|
10437
|
+
async executeSingle(call, progress) {
|
|
10438
|
+
const toolStartTime = Date.now();
|
|
10439
|
+
try {
|
|
10440
|
+
const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
|
|
10441
|
+
let outputText = result2.output ?? "";
|
|
10442
|
+
this.scanForFlags(outputText);
|
|
10443
|
+
outputText = this.handleToolResult(result2, call, outputText, progress);
|
|
10444
|
+
const { digestedOutputForLLM, digestResult } = await this.digestAndEmit(
|
|
10445
|
+
call,
|
|
10446
|
+
outputText,
|
|
10447
|
+
result2,
|
|
10448
|
+
toolStartTime
|
|
10449
|
+
);
|
|
10450
|
+
this.recordJournalMemo(call, result2, digestedOutputForLLM, digestResult);
|
|
10451
|
+
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10452
|
+
} catch (error) {
|
|
10453
|
+
const errorMsg = String(error);
|
|
10454
|
+
const enrichedError = enrichToolErrorContext({
|
|
10455
|
+
toolName: call.name,
|
|
10456
|
+
input: call.input,
|
|
10457
|
+
error: errorMsg,
|
|
10458
|
+
originalOutput: "",
|
|
10459
|
+
progress
|
|
10460
|
+
});
|
|
10461
|
+
if (progress) progress.toolErrors++;
|
|
10462
|
+
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
10463
|
+
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
10464
|
+
}
|
|
10465
|
+
}
|
|
10466
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10467
|
+
// SUBSECTION: Result Handling
|
|
10468
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10469
|
+
handleToolResult(result2, call, outputText, progress) {
|
|
10470
|
+
if (result2.error) {
|
|
10471
|
+
if (progress) progress.toolErrors++;
|
|
10472
|
+
return enrichToolErrorContext({
|
|
10473
|
+
toolName: call.name,
|
|
10474
|
+
input: call.input,
|
|
10475
|
+
error: result2.error,
|
|
10476
|
+
originalOutput: outputText,
|
|
10477
|
+
progress
|
|
10478
|
+
});
|
|
10479
|
+
}
|
|
10480
|
+
if (progress) {
|
|
10481
|
+
progress.toolSuccesses++;
|
|
10482
|
+
progress.blockedCommandPatterns.clear();
|
|
10483
|
+
}
|
|
10484
|
+
return outputText;
|
|
10485
|
+
}
|
|
10486
|
+
async digestAndEmit(call, outputText, result2, toolStartTime) {
|
|
10487
|
+
let digestedOutputForLLM = outputText;
|
|
10488
|
+
let digestResult = null;
|
|
10489
|
+
try {
|
|
10490
|
+
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10491
|
+
digestResult = await digestToolOutput(
|
|
10492
|
+
outputText,
|
|
10493
|
+
call.name,
|
|
10494
|
+
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10495
|
+
llmDigestFn
|
|
10496
|
+
);
|
|
10497
|
+
digestedOutputForLLM = digestResult.digestedOutput;
|
|
10498
|
+
} catch {
|
|
10499
|
+
if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10500
|
+
const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10501
|
+
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10502
|
+
digestedOutputForLLM = `${truncated}
|
|
10503
|
+
|
|
10504
|
+
... [TRUNCATED ${remaining} chars] ...`;
|
|
10505
|
+
}
|
|
10506
|
+
}
|
|
10507
|
+
const outputFilePath = digestResult?.fullOutputPath ?? null;
|
|
10508
|
+
const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
|
|
10509
|
+
\u{1F4C4} Full output: ${outputFilePath}` : ""}` : outputText.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
|
|
10510
|
+
this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
|
|
10511
|
+
return { digestedOutputForLLM, digestResult };
|
|
10512
|
+
}
|
|
10513
|
+
recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
|
|
10514
|
+
this.turnToolJournal.push({
|
|
10515
|
+
name: call.name,
|
|
10516
|
+
inputSummary: JSON.stringify(call.input),
|
|
10517
|
+
success: result2.success,
|
|
10518
|
+
analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
|
|
10519
|
+
outputFile: digestResult?.fullOutputPath ?? null
|
|
10520
|
+
});
|
|
10521
|
+
if (digestResult?.memo) {
|
|
10522
|
+
const m = digestResult.memo;
|
|
10523
|
+
this.turnMemo.keyFindings.push(...m.keyFindings);
|
|
10524
|
+
this.turnMemo.credentials.push(...m.credentials);
|
|
10525
|
+
this.turnMemo.attackVectors.push(...m.attackVectors);
|
|
10526
|
+
this.turnMemo.failures.push(...m.failures);
|
|
10527
|
+
this.turnMemo.suspicions.push(...m.suspicions);
|
|
10528
|
+
if ((ATTACK_VALUE_RANK[m.attackValue] ?? 0) > (ATTACK_VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
|
|
10529
|
+
this.turnMemo.attackValue = m.attackValue;
|
|
10530
|
+
}
|
|
10531
|
+
this.turnMemo.nextSteps.push(...m.nextSteps);
|
|
10532
|
+
if (m.reflection) this.turnReflections.push(m.reflection);
|
|
10533
|
+
}
|
|
10534
|
+
if (digestResult?.memo?.credentials.length) {
|
|
10535
|
+
for (const cred of digestResult.memo.credentials) {
|
|
10536
|
+
this.state.addLoot({
|
|
10537
|
+
type: LOOT_TYPES.CREDENTIAL,
|
|
10538
|
+
host: "auto-extracted",
|
|
10539
|
+
detail: cred,
|
|
10540
|
+
obtainedAt: Date.now()
|
|
10541
|
+
});
|
|
10542
|
+
}
|
|
10543
|
+
}
|
|
10544
|
+
if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
|
|
10545
|
+
const existingTitles = new Set(this.state.getFindings().map((f) => f.title));
|
|
10546
|
+
for (const vector of digestResult.memo.attackVectors) {
|
|
10547
|
+
const title = `[Auto] ${vector.slice(0, 100)}`;
|
|
10548
|
+
if (!existingTitles.has(title)) {
|
|
10549
|
+
this.state.addFinding({
|
|
10550
|
+
id: generateId(),
|
|
10551
|
+
title,
|
|
10552
|
+
severity: "high",
|
|
10553
|
+
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
10554
|
+
affected: [],
|
|
10555
|
+
description: `Auto-extracted by Analyst LLM: ${vector}`,
|
|
10556
|
+
evidence: digestResult.memo.keyFindings.slice(0, 5),
|
|
10557
|
+
remediation: "",
|
|
10558
|
+
foundAt: Date.now()
|
|
10559
|
+
});
|
|
10560
|
+
this.state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
|
|
10561
|
+
existingTitles.add(title);
|
|
10562
|
+
}
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
if (this.state.getFindings().length > 0 && this.state.getPhase() === PHASES.RECON) {
|
|
10566
|
+
this.state.setPhase(PHASES.VULN_ANALYSIS);
|
|
10567
|
+
}
|
|
10568
|
+
}
|
|
10569
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10570
|
+
// SUBSECTION: CTF Flag Detection
|
|
10571
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10572
|
+
scanForFlags(output) {
|
|
10573
|
+
if (!this.state.isCtfMode()) return;
|
|
10574
|
+
const flags = detectFlags(output);
|
|
10575
|
+
for (const flag of flags) {
|
|
10576
|
+
const isNew = this.state.addFlag(flag);
|
|
10577
|
+
if (isNew) {
|
|
10578
|
+
this.events.emit({
|
|
10579
|
+
type: EVENT_TYPES.FLAG_FOUND,
|
|
10580
|
+
timestamp: Date.now(),
|
|
10581
|
+
data: {
|
|
10582
|
+
flag,
|
|
10583
|
+
totalFlags: this.state.getFlags().length,
|
|
10584
|
+
phase: this.state.getPhase()
|
|
10585
|
+
}
|
|
10586
|
+
});
|
|
10587
|
+
}
|
|
10588
|
+
}
|
|
10589
|
+
}
|
|
10590
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10591
|
+
// SUBSECTION: Event Emitters
|
|
10592
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10593
|
+
emitToolCall(toolName, input) {
|
|
10594
|
+
this.events.emit({
|
|
10595
|
+
type: EVENT_TYPES.TOOL_CALL,
|
|
10596
|
+
timestamp: Date.now(),
|
|
10597
|
+
data: {
|
|
10598
|
+
toolName,
|
|
10599
|
+
input,
|
|
10600
|
+
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
10601
|
+
needsApproval: false
|
|
10602
|
+
}
|
|
10603
|
+
});
|
|
10604
|
+
}
|
|
10605
|
+
emitToolResult(toolName, success, output, error, duration) {
|
|
10606
|
+
this.events.emit({
|
|
10607
|
+
type: EVENT_TYPES.TOOL_RESULT,
|
|
10608
|
+
timestamp: Date.now(),
|
|
10609
|
+
data: {
|
|
10610
|
+
toolName,
|
|
10611
|
+
success,
|
|
10612
|
+
output,
|
|
10613
|
+
outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10614
|
+
error,
|
|
10615
|
+
duration
|
|
10616
|
+
}
|
|
10617
|
+
});
|
|
10618
|
+
}
|
|
10619
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10620
|
+
// SUBSECTION: Schema Helper
|
|
10621
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10622
|
+
getToolSchemas() {
|
|
10623
|
+
return this.toolRegistry.getAll().map((t) => ({
|
|
10624
|
+
name: t.name,
|
|
10625
|
+
description: t.description,
|
|
10626
|
+
input_schema: {
|
|
10627
|
+
type: "object",
|
|
10628
|
+
properties: t.parameters,
|
|
10629
|
+
required: t.required || []
|
|
10630
|
+
}
|
|
10631
|
+
}));
|
|
10632
|
+
}
|
|
10633
|
+
};
|
|
10634
|
+
|
|
10368
10635
|
// src/agents/core-agent.ts
|
|
10369
|
-
var CoreAgent = class
|
|
10636
|
+
var CoreAgent = class {
|
|
10370
10637
|
llm;
|
|
10371
10638
|
state;
|
|
10372
10639
|
events;
|
|
@@ -10374,16 +10641,7 @@ var CoreAgent = class _CoreAgent {
|
|
|
10374
10641
|
agentType;
|
|
10375
10642
|
maxIterations;
|
|
10376
10643
|
abortController = null;
|
|
10377
|
-
|
|
10378
|
-
* Collected tool execution records for the current turn.
|
|
10379
|
-
* MainAgent reads this after each step to write journal entries.
|
|
10380
|
-
* Cleared at the start of each step.
|
|
10381
|
-
*/
|
|
10382
|
-
turnToolJournal = [];
|
|
10383
|
-
/** Aggregated memo from all tools in the current turn */
|
|
10384
|
-
turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10385
|
-
/** Analyst reflections collected during this turn (1-line assessments) */
|
|
10386
|
-
turnReflections = [];
|
|
10644
|
+
toolExecutor = null;
|
|
10387
10645
|
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
10388
10646
|
this.agentType = agentType;
|
|
10389
10647
|
this.state = state;
|
|
@@ -10396,10 +10654,20 @@ var CoreAgent = class _CoreAgent {
|
|
|
10396
10654
|
setToolRegistry(registry) {
|
|
10397
10655
|
this.toolRegistry = registry;
|
|
10398
10656
|
}
|
|
10399
|
-
/** Abort the current execution
|
|
10657
|
+
/** Abort the current execution */
|
|
10400
10658
|
abort() {
|
|
10401
10659
|
this.abortController?.abort();
|
|
10402
10660
|
}
|
|
10661
|
+
/** Get turn tool journal (for MainAgent) */
|
|
10662
|
+
getTurnToolJournal() {
|
|
10663
|
+
return this.toolExecutor?.turnToolJournal ?? [];
|
|
10664
|
+
}
|
|
10665
|
+
getTurnMemo() {
|
|
10666
|
+
return this.toolExecutor?.turnMemo ?? { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10667
|
+
}
|
|
10668
|
+
getTurnReflections() {
|
|
10669
|
+
return this.toolExecutor?.turnReflections ?? [];
|
|
10670
|
+
}
|
|
10403
10671
|
// ─────────────────────────────────────────────────────────────────
|
|
10404
10672
|
// SUBSECTION: Main Run Loop
|
|
10405
10673
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -10445,110 +10713,106 @@ var CoreAgent = class _CoreAgent {
|
|
|
10445
10713
|
messages.push({ role: LLM_ROLES.USER, content: this.buildDeadlockNudge(progress) });
|
|
10446
10714
|
}
|
|
10447
10715
|
} catch (error) {
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
isRecoverable: errorInfo.isRetryable,
|
|
10460
|
-
errorInfo
|
|
10461
|
-
}
|
|
10462
|
-
});
|
|
10463
|
-
if (errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10716
|
+
const action = this.handleLoopError(
|
|
10717
|
+
error,
|
|
10718
|
+
messages,
|
|
10719
|
+
progress,
|
|
10720
|
+
iteration,
|
|
10721
|
+
consecutiveLLMErrors,
|
|
10722
|
+
maxConsecutiveLLMErrors
|
|
10723
|
+
);
|
|
10724
|
+
if (action.action === "return") return action.result;
|
|
10725
|
+
if (action.action === "continue") {
|
|
10726
|
+
if (error instanceof LLMError && error.errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10464
10727
|
consecutiveLLMErrors++;
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
output: `LLM rate limit errors exceeded limit. Last error: ${errorInfo.message}`,
|
|
10468
|
-
iterations: iteration + 1,
|
|
10469
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
10470
|
-
isCompleted: false
|
|
10471
|
-
};
|
|
10472
|
-
}
|
|
10473
|
-
continue;
|
|
10728
|
+
} else {
|
|
10729
|
+
consecutiveLLMErrors = 0;
|
|
10474
10730
|
}
|
|
10475
|
-
consecutiveLLMErrors = 0;
|
|
10476
|
-
const errorMessage = this.formatLLMErrorForAgent(errorInfo);
|
|
10477
|
-
messages.push({
|
|
10478
|
-
role: LLM_ROLES.USER,
|
|
10479
|
-
content: errorMessage
|
|
10480
|
-
});
|
|
10481
10731
|
continue;
|
|
10482
10732
|
}
|
|
10483
|
-
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10484
|
-
this.events.emit({
|
|
10485
|
-
type: EVENT_TYPES.ERROR,
|
|
10486
|
-
timestamp: Date.now(),
|
|
10487
|
-
data: {
|
|
10488
|
-
message: `Unexpected error: ${unexpectedMsg}`,
|
|
10489
|
-
phase: this.state.getPhase(),
|
|
10490
|
-
isRecoverable: true
|
|
10491
|
-
}
|
|
10492
|
-
});
|
|
10493
|
-
messages.push({
|
|
10494
|
-
role: LLM_ROLES.USER,
|
|
10495
|
-
content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10496
|
-
This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
|
|
10497
|
-
});
|
|
10498
|
-
continue;
|
|
10499
10733
|
}
|
|
10500
10734
|
}
|
|
10501
|
-
const summary = `Max iterations (${this.maxIterations}) reached. Progress: ${progress.totalToolsExecuted} tools executed (${progress.toolSuccesses} succeeded, ${progress.toolErrors} failed). Current phase: ${this.state.getPhase()}. Findings: ${this.state.getFindings?.()?.length ?? "unknown"}.`;
|
|
10502
10735
|
return {
|
|
10503
|
-
output:
|
|
10736
|
+
output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
|
|
10504
10737
|
iterations: this.maxIterations,
|
|
10505
10738
|
toolsExecuted: progress.totalToolsExecuted,
|
|
10506
10739
|
isCompleted: false
|
|
10507
10740
|
};
|
|
10508
10741
|
}
|
|
10509
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10510
|
-
// SUBSECTION: Error Formatting
|
|
10511
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10512
10742
|
/**
|
|
10513
|
-
*
|
|
10743
|
+
* §4-1: Handle errors caught in the run loop — extracted to reduce nesting depth.
|
|
10744
|
+
*
|
|
10745
|
+
* Returns a LoopErrorAction so the caller controls `continue` / `return`.
|
|
10746
|
+
* WHY separate: The catch block in run() had nesting depth 8. By moving error
|
|
10747
|
+
* dispatch here, run() stays at depth 3 (for → try → if) throughout.
|
|
10514
10748
|
*/
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10749
|
+
handleLoopError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
|
|
10750
|
+
if (this.isAbortError(error)) {
|
|
10751
|
+
return { action: "return", result: this.buildCancelledResult(iteration, progress.totalToolsExecuted) };
|
|
10752
|
+
}
|
|
10753
|
+
if (error instanceof LLMError) {
|
|
10754
|
+
return this.handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors);
|
|
10755
|
+
}
|
|
10756
|
+
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10757
|
+
this.events.emit({
|
|
10758
|
+
type: EVENT_TYPES.ERROR,
|
|
10759
|
+
timestamp: Date.now(),
|
|
10760
|
+
data: { message: `Unexpected error: ${unexpectedMsg}`, phase: this.state.getPhase(), isRecoverable: true }
|
|
10761
|
+
});
|
|
10762
|
+
messages.push({ role: LLM_ROLES.USER, content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10763
|
+
Continue your task.` });
|
|
10764
|
+
return { action: "continue" };
|
|
10765
|
+
}
|
|
10766
|
+
/** Handle LLMError specifically — rate limits vs other LLM errors. */
|
|
10767
|
+
handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
|
|
10768
|
+
const errorInfo = error.errorInfo;
|
|
10769
|
+
this.events.emit({
|
|
10770
|
+
type: EVENT_TYPES.ERROR,
|
|
10771
|
+
timestamp: Date.now(),
|
|
10772
|
+
data: {
|
|
10773
|
+
message: `LLM Error: ${errorInfo.message}`,
|
|
10774
|
+
phase: this.state.getPhase(),
|
|
10775
|
+
isRecoverable: errorInfo.isRetryable,
|
|
10776
|
+
errorInfo
|
|
10777
|
+
}
|
|
10778
|
+
});
|
|
10779
|
+
if (errorInfo.type !== LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10780
|
+
messages.push({ role: LLM_ROLES.USER, content: this.formatLLMErrorForAgent(errorInfo) });
|
|
10781
|
+
return { action: "continue" };
|
|
10782
|
+
}
|
|
10783
|
+
const newCount = consecutiveLLMErrors + 1;
|
|
10784
|
+
if (maxConsecutiveLLMErrors !== Infinity && newCount >= maxConsecutiveLLMErrors) {
|
|
10785
|
+
return {
|
|
10786
|
+
action: "return",
|
|
10787
|
+
result: {
|
|
10788
|
+
output: `LLM rate limit errors exceeded. Last error: ${errorInfo.message}`,
|
|
10789
|
+
iterations: iteration + 1,
|
|
10790
|
+
toolsExecuted: progress.totalToolsExecuted,
|
|
10791
|
+
isCompleted: false
|
|
10792
|
+
}
|
|
10793
|
+
};
|
|
10794
|
+
}
|
|
10795
|
+
return { action: "continue" };
|
|
10539
10796
|
}
|
|
10540
10797
|
// ─────────────────────────────────────────────────────────────────
|
|
10541
10798
|
// SUBSECTION: Step Execution
|
|
10542
10799
|
// ─────────────────────────────────────────────────────────────────
|
|
10543
|
-
/** Execute a single Think → Act → Observe iteration */
|
|
10544
10800
|
async step(iteration, messages, systemPrompt, progress) {
|
|
10545
10801
|
const phase = this.state.getPhase();
|
|
10546
|
-
const stepStartTime = Date.now();
|
|
10547
10802
|
this.emitThink(iteration, progress);
|
|
10803
|
+
if (this.toolRegistry && !this.toolExecutor) {
|
|
10804
|
+
this.toolExecutor = new ToolExecutor({
|
|
10805
|
+
state: this.state,
|
|
10806
|
+
events: this.events,
|
|
10807
|
+
toolRegistry: this.toolRegistry,
|
|
10808
|
+
llm: this.llm
|
|
10809
|
+
});
|
|
10810
|
+
}
|
|
10811
|
+
this.toolExecutor?.clearTurnState();
|
|
10548
10812
|
const callbacks = this.buildStreamCallbacks(phase);
|
|
10549
10813
|
const response = await this.llm.generateResponseStream(
|
|
10550
10814
|
messages,
|
|
10551
|
-
this.getToolSchemas(),
|
|
10815
|
+
this.toolExecutor?.getToolSchemas() ?? [],
|
|
10552
10816
|
systemPrompt,
|
|
10553
10817
|
callbacks
|
|
10554
10818
|
);
|
|
@@ -10571,12 +10835,10 @@ Please decide how to handle this error and continue.`;
|
|
|
10571
10835
|
}
|
|
10572
10836
|
}
|
|
10573
10837
|
messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
|
|
10574
|
-
const stepDuration = Date.now() - stepStartTime;
|
|
10575
|
-
const tokens = response.usage ? { input: response.usage.input_tokens, output: response.usage.output_tokens } : void 0;
|
|
10576
10838
|
if (!response.toolCalls?.length) {
|
|
10577
10839
|
return { output: response.content, toolsExecuted: 0, isCompleted: false };
|
|
10578
10840
|
}
|
|
10579
|
-
const results = await this.processToolCalls(response.toolCalls, progress);
|
|
10841
|
+
const results = await this.toolExecutor.processToolCalls(response.toolCalls, progress);
|
|
10580
10842
|
this.addToolResultsToMessages(messages, results);
|
|
10581
10843
|
return { output: "", toolsExecuted: results.length, isCompleted: false };
|
|
10582
10844
|
}
|
|
@@ -10586,15 +10848,13 @@ Please decide how to handle this error and continue.`;
|
|
|
10586
10848
|
buildStreamCallbacks(phase) {
|
|
10587
10849
|
let _reasoningEndFired = false;
|
|
10588
10850
|
let _outputBuffer = "";
|
|
10589
|
-
|
|
10851
|
+
return {
|
|
10590
10852
|
onReasoningStart: () => this.emitReasoningStart(phase),
|
|
10591
10853
|
onReasoningDelta: (content) => this.emitReasoningDelta(content, phase),
|
|
10592
10854
|
onReasoningEnd: () => {
|
|
10593
10855
|
_reasoningEndFired = true;
|
|
10594
10856
|
this.emitReasoningEnd(phase);
|
|
10595
10857
|
},
|
|
10596
|
-
// WHY: Show AI text as it streams, not after completion.
|
|
10597
|
-
// The user sees what the AI is writing in real-time via the status bar.
|
|
10598
10858
|
onOutputDelta: (text) => {
|
|
10599
10859
|
_outputBuffer += text;
|
|
10600
10860
|
const firstLine = _outputBuffer.split("\n")[0]?.slice(0, 120) || "";
|
|
@@ -10620,57 +10880,63 @@ ${firstLine}`, phase }
|
|
|
10620
10880
|
});
|
|
10621
10881
|
},
|
|
10622
10882
|
abortSignal: this.abortController?.signal,
|
|
10623
|
-
// WHY: Used by step() to detect if SSE reasoning blocks fired.
|
|
10624
|
-
// If not, we know it was an inline <think> model and must emit post-stream.
|
|
10625
10883
|
hadReasoningEnd: () => _reasoningEndFired
|
|
10626
10884
|
};
|
|
10627
|
-
return callbacks;
|
|
10628
10885
|
}
|
|
10629
10886
|
// ─────────────────────────────────────────────────────────────────
|
|
10630
|
-
// SUBSECTION: Deadlock Nudge
|
|
10887
|
+
// SUBSECTION: Deadlock Nudge
|
|
10631
10888
|
// ─────────────────────────────────────────────────────────────────
|
|
10632
|
-
/**
|
|
10633
|
-
* Build a deadlock nudge message for the agent.
|
|
10634
|
-
*
|
|
10635
|
-
* WHY separated: The nudge template is ~30 lines of prompt engineering.
|
|
10636
|
-
* Keeping it in run() obscures the iteration control logic.
|
|
10637
|
-
* Philosophy §12: Nudge is a safety net, not a driver —
|
|
10638
|
-
* it reminds the agent to ACT, but never prescribes HOW.
|
|
10639
|
-
*/
|
|
10640
10889
|
buildDeadlockNudge(progress) {
|
|
10641
10890
|
const phase = this.state.getPhase();
|
|
10642
10891
|
const targets = this.state.getTargets().size;
|
|
10643
10892
|
const findings = this.state.getFindings().length;
|
|
10644
10893
|
const phaseDirection = {
|
|
10645
|
-
[PHASES.RECON]: `RECON: Scan targets. Enumerate services
|
|
10646
|
-
[PHASES.VULN_ANALYSIS]: `VULN
|
|
10647
|
-
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s)
|
|
10648
|
-
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges
|
|
10649
|
-
[PHASES.PRIV_ESC]: `PRIVESC: Find
|
|
10650
|
-
[PHASES.LATERAL]: `LATERAL: Reuse
|
|
10651
|
-
[PHASES.WEB]: `WEB: Enumerate
|
|
10894
|
+
[PHASES.RECON]: `RECON: Scan targets. Enumerate services.`,
|
|
10895
|
+
[PHASES.VULN_ANALYSIS]: `VULN: ${targets} target(s). Search for CVEs.`,
|
|
10896
|
+
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s). Attack the highest-severity one.`,
|
|
10897
|
+
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges.`,
|
|
10898
|
+
[PHASES.PRIV_ESC]: `PRIVESC: Find privilege escalation vectors.`,
|
|
10899
|
+
[PHASES.LATERAL]: `LATERAL: Reuse credentials on other hosts.`,
|
|
10900
|
+
[PHASES.WEB]: `WEB: Enumerate attack surface. Test every input.`
|
|
10652
10901
|
};
|
|
10653
10902
|
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
10654
10903
|
return `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
10655
|
-
Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
|
|
10904
|
+
Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
|
|
10656
10905
|
|
|
10657
10906
|
${direction}
|
|
10658
10907
|
|
|
10659
|
-
ESCALATION
|
|
10660
|
-
1. web_search
|
|
10661
|
-
2.
|
|
10662
|
-
3.
|
|
10663
|
-
4.
|
|
10664
|
-
5. ask_user
|
|
10908
|
+
ESCALATION:
|
|
10909
|
+
1. web_search for techniques
|
|
10910
|
+
2. Try alternative approaches
|
|
10911
|
+
3. Probe for unknown vulns
|
|
10912
|
+
4. Brute-force with wordlists
|
|
10913
|
+
5. ask_user for hints
|
|
10914
|
+
|
|
10915
|
+
ACT NOW \u2014 EXECUTE.`;
|
|
10916
|
+
}
|
|
10917
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10918
|
+
// SUBSECTION: Error Formatting
|
|
10919
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10920
|
+
formatLLMErrorForAgent(errorInfo) {
|
|
10921
|
+
const actionHints = {
|
|
10922
|
+
[LLM_ERROR_TYPES.RATE_LIMIT]: "Wait and retry.",
|
|
10923
|
+
[LLM_ERROR_TYPES.AUTH_ERROR]: "Use ask_user to request API key.",
|
|
10924
|
+
[LLM_ERROR_TYPES.INVALID_REQUEST]: "Simplify your request.",
|
|
10925
|
+
[LLM_ERROR_TYPES.NETWORK_ERROR]: "Check network and retry.",
|
|
10926
|
+
[LLM_ERROR_TYPES.TIMEOUT]: "Retry with simpler request.",
|
|
10927
|
+
[LLM_ERROR_TYPES.UNKNOWN]: "Analyze and decide."
|
|
10928
|
+
};
|
|
10929
|
+
return `[SYSTEM ERROR - LLM API Issue]
|
|
10930
|
+
Error Type: ${errorInfo.type}
|
|
10931
|
+
Message: ${errorInfo.message}
|
|
10932
|
+
Status Code: ${errorInfo.statusCode || "N/A"}
|
|
10933
|
+
Retryable: ${errorInfo.isRetryable ? "Yes" : "No"}
|
|
10665
10934
|
|
|
10666
|
-
|
|
10667
|
-
- Every turn MUST have tool calls
|
|
10668
|
-
- NEVER silently give up \u2014 exhaust ALL 5 steps above first
|
|
10669
|
-
- ACT NOW \u2014 do not plan, do not explain, do not summarize. EXECUTE.`;
|
|
10935
|
+
Suggested Action: ${errorInfo.suggestedAction || actionHints[errorInfo.type] || "Decide appropriate action"}`;
|
|
10670
10936
|
}
|
|
10671
10937
|
// ─────────────────────────────────────────────────────────────────
|
|
10672
10938
|
// SUBSECTION: Event Emitters
|
|
10673
|
-
//
|
|
10939
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10674
10940
|
emitThink(iteration, progress) {
|
|
10675
10941
|
const phase = this.state.getPhase();
|
|
10676
10942
|
const targets = this.state.getTargets().size;
|
|
@@ -10679,26 +10945,22 @@ RULES:
|
|
|
10679
10945
|
const hasErrors = (progress?.toolErrors ?? 0) > 0;
|
|
10680
10946
|
let thought;
|
|
10681
10947
|
if (iteration === 0) {
|
|
10682
|
-
thought = targets > 0 ? `Analyzing ${targets} target
|
|
10948
|
+
thought = targets > 0 ? `Analyzing ${targets} target(s) \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
|
|
10683
10949
|
} else if (toolsUsed === 0) {
|
|
10684
|
-
thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering
|
|
10950
|
+
thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering`;
|
|
10685
10951
|
} else if (hasErrors) {
|
|
10686
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error
|
|
10952
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error(s)`;
|
|
10687
10953
|
} else if (findings > 0) {
|
|
10688
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding
|
|
10954
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding(s) \xB7 ${toolsUsed} tools`;
|
|
10689
10955
|
} else {
|
|
10690
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool
|
|
10956
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool(s) executed`;
|
|
10691
10957
|
}
|
|
10692
10958
|
this.events.emit({
|
|
10693
10959
|
type: EVENT_TYPES.THINK,
|
|
10694
10960
|
timestamp: Date.now(),
|
|
10695
|
-
data: {
|
|
10696
|
-
thought,
|
|
10697
|
-
phase
|
|
10698
|
-
}
|
|
10961
|
+
data: { thought, phase }
|
|
10699
10962
|
});
|
|
10700
10963
|
}
|
|
10701
|
-
/** Emit reasoning lifecycle events for extended thinking */
|
|
10702
10964
|
emitReasoningStart(phase) {
|
|
10703
10965
|
this.events.emit({ type: EVENT_TYPES.REASONING_START, timestamp: Date.now(), data: { phase } });
|
|
10704
10966
|
}
|
|
@@ -10712,308 +10974,25 @@ RULES:
|
|
|
10712
10974
|
this.events.emit({
|
|
10713
10975
|
type: EVENT_TYPES.COMPLETE,
|
|
10714
10976
|
timestamp: Date.now(),
|
|
10715
|
-
data: {
|
|
10716
|
-
finalOutput: output,
|
|
10717
|
-
iterations: iteration + 1,
|
|
10718
|
-
toolsExecuted,
|
|
10719
|
-
durationMs,
|
|
10720
|
-
tokens
|
|
10721
|
-
}
|
|
10722
|
-
});
|
|
10723
|
-
}
|
|
10724
|
-
/** Emit tool call event for TUI tracking */
|
|
10725
|
-
emitToolCall(toolName, input) {
|
|
10726
|
-
this.events.emit({
|
|
10727
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
10728
|
-
timestamp: Date.now(),
|
|
10729
|
-
data: {
|
|
10730
|
-
toolName,
|
|
10731
|
-
input,
|
|
10732
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
10733
|
-
needsApproval: false
|
|
10734
|
-
}
|
|
10735
|
-
});
|
|
10736
|
-
}
|
|
10737
|
-
/** Emit tool result event for TUI tracking */
|
|
10738
|
-
emitToolResult(toolName, success, output, error, duration) {
|
|
10739
|
-
this.events.emit({
|
|
10740
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
10741
|
-
timestamp: Date.now(),
|
|
10742
|
-
data: {
|
|
10743
|
-
toolName,
|
|
10744
|
-
success,
|
|
10745
|
-
output,
|
|
10746
|
-
outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10747
|
-
error,
|
|
10748
|
-
duration
|
|
10749
|
-
}
|
|
10977
|
+
data: { finalOutput: output, iterations: iteration + 1, toolsExecuted, durationMs, tokens }
|
|
10750
10978
|
});
|
|
10751
10979
|
}
|
|
10752
10980
|
// ─────────────────────────────────────────────────────────────────
|
|
10753
|
-
// SUBSECTION:
|
|
10981
|
+
// SUBSECTION: Message Helpers
|
|
10754
10982
|
// ─────────────────────────────────────────────────────────────────
|
|
10755
10983
|
addToolResultsToMessages(messages, results) {
|
|
10756
10984
|
for (const res of results) {
|
|
10757
10985
|
messages.push({
|
|
10758
10986
|
role: LLM_ROLES.USER,
|
|
10759
|
-
content: [
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
}
|
|
10766
|
-
]
|
|
10987
|
+
content: [{
|
|
10988
|
+
type: LLM_BLOCK_TYPE.TOOL_RESULT,
|
|
10989
|
+
tool_use_id: res.toolCallId,
|
|
10990
|
+
content: res.output,
|
|
10991
|
+
is_error: !!res.error
|
|
10992
|
+
}]
|
|
10767
10993
|
});
|
|
10768
10994
|
}
|
|
10769
10995
|
}
|
|
10770
|
-
/** Tools that are safe to run in parallel (read-only or per-key state mutations) */
|
|
10771
|
-
static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
10772
|
-
// Read-only intelligence tools
|
|
10773
|
-
TOOL_NAMES.GET_STATE,
|
|
10774
|
-
TOOL_NAMES.PARSE_NMAP,
|
|
10775
|
-
TOOL_NAMES.SEARCH_CVE,
|
|
10776
|
-
TOOL_NAMES.WEB_SEARCH,
|
|
10777
|
-
TOOL_NAMES.BROWSE_URL,
|
|
10778
|
-
TOOL_NAMES.READ_FILE,
|
|
10779
|
-
TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
10780
|
-
TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
10781
|
-
TOOL_NAMES.GET_CVE_INFO,
|
|
10782
|
-
TOOL_NAMES.FILL_FORM,
|
|
10783
|
-
// State recording (per-key mutations, no external side effects)
|
|
10784
|
-
TOOL_NAMES.ADD_TARGET,
|
|
10785
|
-
TOOL_NAMES.ADD_FINDING,
|
|
10786
|
-
TOOL_NAMES.ADD_LOOT,
|
|
10787
|
-
TOOL_NAMES.UPDATE_MISSION,
|
|
10788
|
-
TOOL_NAMES.UPDATE_TODO
|
|
10789
|
-
]);
|
|
10790
|
-
async processToolCalls(toolCalls, progress) {
|
|
10791
|
-
if (toolCalls.length <= 1) {
|
|
10792
|
-
return this.executeToolCallsSequentially(toolCalls, progress);
|
|
10793
|
-
}
|
|
10794
|
-
const allParallelSafe = toolCalls.every((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10795
|
-
if (allParallelSafe) {
|
|
10796
|
-
return this.executeToolCallsInParallel(toolCalls, progress);
|
|
10797
|
-
}
|
|
10798
|
-
const parallelCalls = toolCalls.filter((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10799
|
-
const sequentialCalls = toolCalls.filter((c) => !_CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10800
|
-
const parallelResults = parallelCalls.length > 0 ? await this.executeToolCallsInParallel(parallelCalls, progress) : [];
|
|
10801
|
-
const sequentialResults = sequentialCalls.length > 0 ? await this.executeToolCallsSequentially(sequentialCalls, progress) : [];
|
|
10802
|
-
const resultMap = /* @__PURE__ */ new Map();
|
|
10803
|
-
for (const r of [...parallelResults, ...sequentialResults]) {
|
|
10804
|
-
resultMap.set(r.toolCallId, r);
|
|
10805
|
-
}
|
|
10806
|
-
return toolCalls.map((c) => resultMap.get(c.id)).filter(Boolean);
|
|
10807
|
-
}
|
|
10808
|
-
/** Execute tool calls in parallel via Promise.allSettled */
|
|
10809
|
-
async executeToolCallsInParallel(toolCalls, progress) {
|
|
10810
|
-
for (const call of toolCalls) {
|
|
10811
|
-
this.emitToolCall(call.name, call.input);
|
|
10812
|
-
}
|
|
10813
|
-
const promises = toolCalls.map((call) => this.executeSingleTool(call, progress));
|
|
10814
|
-
const settled = await Promise.allSettled(promises);
|
|
10815
|
-
return settled.map((s, i) => {
|
|
10816
|
-
if (s.status === "fulfilled") return s.value;
|
|
10817
|
-
const errorMsg = String(s.reason);
|
|
10818
|
-
return { toolCallId: toolCalls[i].id, output: errorMsg, error: errorMsg };
|
|
10819
|
-
});
|
|
10820
|
-
}
|
|
10821
|
-
/** Execute tool calls sequentially */
|
|
10822
|
-
async executeToolCallsSequentially(toolCalls, progress) {
|
|
10823
|
-
const results = [];
|
|
10824
|
-
for (const call of toolCalls) {
|
|
10825
|
-
this.emitToolCall(call.name, call.input);
|
|
10826
|
-
const result2 = await this.executeSingleTool(call, progress);
|
|
10827
|
-
results.push(result2);
|
|
10828
|
-
}
|
|
10829
|
-
return results;
|
|
10830
|
-
}
|
|
10831
|
-
/** Execute a single tool call with error enrichment and event emission */
|
|
10832
|
-
async executeSingleTool(call, progress) {
|
|
10833
|
-
const toolStartTime = Date.now();
|
|
10834
|
-
logLLM("CoreAgent executing tool", { id: call.id, name: call.name, input: call.input });
|
|
10835
|
-
if (!this.toolRegistry) {
|
|
10836
|
-
return { toolCallId: call.id, output: "", error: "Tool registry not initialized. Call setToolRegistry() first." };
|
|
10837
|
-
}
|
|
10838
|
-
try {
|
|
10839
|
-
const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
|
|
10840
|
-
let outputText = result2.output ?? "";
|
|
10841
|
-
this.scanForFlags(outputText);
|
|
10842
|
-
outputText = this.handleToolResult(result2, call, outputText, progress);
|
|
10843
|
-
const { digestedOutputForLLM, digestResult } = await this.digestAndEmit(
|
|
10844
|
-
call,
|
|
10845
|
-
outputText,
|
|
10846
|
-
result2,
|
|
10847
|
-
toolStartTime
|
|
10848
|
-
);
|
|
10849
|
-
this.recordJournalMemo(call, result2, digestedOutputForLLM, digestResult);
|
|
10850
|
-
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10851
|
-
} catch (error) {
|
|
10852
|
-
const errorMsg = String(error);
|
|
10853
|
-
const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
10854
|
-
if (progress) progress.toolErrors++;
|
|
10855
|
-
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
10856
|
-
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
10857
|
-
}
|
|
10858
|
-
}
|
|
10859
|
-
/**
|
|
10860
|
-
* Handle tool result: enrich errors or track success.
|
|
10861
|
-
* @returns Possibly enriched output text.
|
|
10862
|
-
*/
|
|
10863
|
-
handleToolResult(result2, call, outputText, progress) {
|
|
10864
|
-
if (result2.error) {
|
|
10865
|
-
if (progress) progress.toolErrors++;
|
|
10866
|
-
return this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
|
|
10867
|
-
}
|
|
10868
|
-
if (progress) {
|
|
10869
|
-
progress.toolSuccesses++;
|
|
10870
|
-
progress.blockedCommandPatterns.clear();
|
|
10871
|
-
}
|
|
10872
|
-
return outputText;
|
|
10873
|
-
}
|
|
10874
|
-
/**
|
|
10875
|
-
* Digest tool output via Analyst LLM (§13 ③) and emit TUI event.
|
|
10876
|
-
*
|
|
10877
|
-
* WHY separated: Digest + emit is a self-contained pipeline:
|
|
10878
|
-
* raw output → Analyst → digest + file → TUI event.
|
|
10879
|
-
* Isolating it makes the pipeline testable without running actual tools.
|
|
10880
|
-
*/
|
|
10881
|
-
async digestAndEmit(call, outputText, result2, toolStartTime) {
|
|
10882
|
-
const digestFallbackOutput = outputText;
|
|
10883
|
-
let digestedOutputForLLM = outputText;
|
|
10884
|
-
let digestResult = null;
|
|
10885
|
-
try {
|
|
10886
|
-
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10887
|
-
digestResult = await digestToolOutput(
|
|
10888
|
-
outputText,
|
|
10889
|
-
call.name,
|
|
10890
|
-
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10891
|
-
llmDigestFn
|
|
10892
|
-
);
|
|
10893
|
-
digestedOutputForLLM = digestResult.digestedOutput;
|
|
10894
|
-
} catch {
|
|
10895
|
-
if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10896
|
-
const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10897
|
-
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10898
|
-
digestedOutputForLLM = `${truncated}
|
|
10899
|
-
|
|
10900
|
-
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
10901
|
-
\u{1F4A1} TIP: If you need to see the full output, use a tool to read the file directly or run the command with | head, | tail, or | grep.`;
|
|
10902
|
-
}
|
|
10903
|
-
}
|
|
10904
|
-
const outputFilePath = digestResult?.fullOutputPath ?? null;
|
|
10905
|
-
const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
|
|
10906
|
-
\u{1F4C4} Full output: ${outputFilePath}` : ""}` : digestFallbackOutput.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
|
|
10907
|
-
this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
|
|
10908
|
-
return { digestedOutputForLLM, digestResult };
|
|
10909
|
-
}
|
|
10910
|
-
/**
|
|
10911
|
-
* Record tool execution results to Journal and aggregate memos.
|
|
10912
|
-
*
|
|
10913
|
-
* WHY no truncation on inputSummary: Strategist needs full context —
|
|
10914
|
-
* "hydra -l admin -P rockyou.txt ssh://10.0.0.1" must survive intact.
|
|
10915
|
-
*/
|
|
10916
|
-
recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
|
|
10917
|
-
this.turnToolJournal.push({
|
|
10918
|
-
name: call.name,
|
|
10919
|
-
inputSummary: JSON.stringify(call.input),
|
|
10920
|
-
success: result2.success,
|
|
10921
|
-
analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
|
|
10922
|
-
outputFile: digestResult?.fullOutputPath ?? null
|
|
10923
|
-
});
|
|
10924
|
-
if (digestResult?.memo) {
|
|
10925
|
-
const m = digestResult.memo;
|
|
10926
|
-
this.turnMemo.keyFindings.push(...m.keyFindings);
|
|
10927
|
-
this.turnMemo.credentials.push(...m.credentials);
|
|
10928
|
-
this.turnMemo.attackVectors.push(...m.attackVectors);
|
|
10929
|
-
this.turnMemo.failures.push(...m.failures);
|
|
10930
|
-
this.turnMemo.suspicions.push(...m.suspicions);
|
|
10931
|
-
if ((ATTACK_VALUE_RANK[m.attackValue] ?? 0) > (ATTACK_VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
|
|
10932
|
-
this.turnMemo.attackValue = m.attackValue;
|
|
10933
|
-
}
|
|
10934
|
-
this.turnMemo.nextSteps.push(...m.nextSteps);
|
|
10935
|
-
if (m.reflection) this.turnReflections.push(m.reflection);
|
|
10936
|
-
}
|
|
10937
|
-
if (digestResult?.memo?.credentials.length) {
|
|
10938
|
-
for (const cred of digestResult.memo.credentials) {
|
|
10939
|
-
this.state.addLoot({ type: LOOT_TYPES.CREDENTIAL, host: "auto-extracted", detail: cred, obtainedAt: Date.now() });
|
|
10940
|
-
}
|
|
10941
|
-
}
|
|
10942
|
-
if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
|
|
10943
|
-
const existingTitles = new Set(this.state.getFindings().map((f) => f.title));
|
|
10944
|
-
for (const vector of digestResult.memo.attackVectors) {
|
|
10945
|
-
const title = `[Auto] ${vector.slice(0, 100)}`;
|
|
10946
|
-
if (!existingTitles.has(title)) {
|
|
10947
|
-
this.state.addFinding({
|
|
10948
|
-
id: generateId(),
|
|
10949
|
-
title,
|
|
10950
|
-
severity: "high",
|
|
10951
|
-
// Auto-extracted findings are unverified signals — score POSSIBLE (25)
|
|
10952
|
-
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
10953
|
-
affected: [],
|
|
10954
|
-
description: `Auto-extracted by Analyst LLM: ${vector}`,
|
|
10955
|
-
evidence: digestResult.memo.keyFindings.slice(0, 5),
|
|
10956
|
-
remediation: "",
|
|
10957
|
-
foundAt: Date.now()
|
|
10958
|
-
});
|
|
10959
|
-
this.state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
|
|
10960
|
-
existingTitles.add(title);
|
|
10961
|
-
}
|
|
10962
|
-
}
|
|
10963
|
-
}
|
|
10964
|
-
if (this.state.getFindings().length > 0 && this.state.getPhase() === PHASES.RECON) {
|
|
10965
|
-
this.state.setPhase(PHASES.VULN_ANALYSIS);
|
|
10966
|
-
}
|
|
10967
|
-
}
|
|
10968
|
-
/**
|
|
10969
|
-
* Enrich tool error — delegates to extracted module (§3-1)
|
|
10970
|
-
*/
|
|
10971
|
-
enrichToolError(ctx) {
|
|
10972
|
-
return enrichToolErrorContext(ctx);
|
|
10973
|
-
}
|
|
10974
|
-
getToolSchemas() {
|
|
10975
|
-
if (!this.toolRegistry) {
|
|
10976
|
-
return [];
|
|
10977
|
-
}
|
|
10978
|
-
return this.toolRegistry.getAll().map((t) => ({
|
|
10979
|
-
name: t.name,
|
|
10980
|
-
description: t.description,
|
|
10981
|
-
input_schema: {
|
|
10982
|
-
type: "object",
|
|
10983
|
-
properties: t.parameters,
|
|
10984
|
-
required: t.required || []
|
|
10985
|
-
}
|
|
10986
|
-
}));
|
|
10987
|
-
}
|
|
10988
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10989
|
-
// SUBSECTION: CTF Flag Detection
|
|
10990
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10991
|
-
/**
|
|
10992
|
-
* Scan tool output for CTF flag patterns.
|
|
10993
|
-
* When a new flag is detected, store it in state and emit event.
|
|
10994
|
-
*
|
|
10995
|
-
* @remarks
|
|
10996
|
-
* WHY: Automatic flag detection eliminates manual flag hunting in CTF competitions.
|
|
10997
|
-
* Called after every tool execution — zero overhead when CTF mode is OFF.
|
|
10998
|
-
*/
|
|
10999
|
-
scanForFlags(output) {
|
|
11000
|
-
if (!this.state.isCtfMode()) return;
|
|
11001
|
-
const flags = detectFlags(output);
|
|
11002
|
-
for (const flag of flags) {
|
|
11003
|
-
const isNew = this.state.addFlag(flag);
|
|
11004
|
-
if (isNew) {
|
|
11005
|
-
this.events.emit({
|
|
11006
|
-
type: EVENT_TYPES.FLAG_FOUND,
|
|
11007
|
-
timestamp: Date.now(),
|
|
11008
|
-
data: {
|
|
11009
|
-
flag,
|
|
11010
|
-
totalFlags: this.state.getFlags().length,
|
|
11011
|
-
phase: this.state.getPhase()
|
|
11012
|
-
}
|
|
11013
|
-
});
|
|
11014
|
-
}
|
|
11015
|
-
}
|
|
11016
|
-
}
|
|
11017
10996
|
// ─────────────────────────────────────────────────────────────────
|
|
11018
10997
|
// SUBSECTION: Abort Helpers
|
|
11019
10998
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -11031,8 +11010,8 @@ RULES:
|
|
|
11031
11010
|
};
|
|
11032
11011
|
|
|
11033
11012
|
// src/agents/prompt-builder.ts
|
|
11034
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
11035
|
-
import { join as
|
|
11013
|
+
import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
11014
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
11036
11015
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11037
11016
|
|
|
11038
11017
|
// src/shared/constants/prompts.ts
|
|
@@ -11306,19 +11285,19 @@ function getAttacksForService(service, port) {
|
|
|
11306
11285
|
}
|
|
11307
11286
|
|
|
11308
11287
|
// src/shared/utils/journal.ts
|
|
11309
|
-
import { writeFileSync as
|
|
11310
|
-
import { join as
|
|
11288
|
+
import { writeFileSync as writeFileSync9, readFileSync as readFileSync5, existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync3, rmSync as rmSync2 } from "fs";
|
|
11289
|
+
import { join as join10 } from "path";
|
|
11311
11290
|
function parseTurnNumbers(turnsDir) {
|
|
11312
|
-
if (!
|
|
11313
|
-
return
|
|
11291
|
+
if (!existsSync9(turnsDir)) return [];
|
|
11292
|
+
return readdirSync3(turnsDir).filter((e) => e.startsWith(TURN_FOLDER_PREFIX) && /^\d+$/.test(e.slice(TURN_FOLDER_PREFIX.length))).map((e) => Number(e.slice(TURN_FOLDER_PREFIX.length)));
|
|
11314
11293
|
}
|
|
11315
11294
|
function readJournalSummary() {
|
|
11316
11295
|
try {
|
|
11317
11296
|
const turnsDir = WORKSPACE.TURNS;
|
|
11318
11297
|
const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => b - a);
|
|
11319
11298
|
for (const turn of turnDirs) {
|
|
11320
|
-
const summaryPath =
|
|
11321
|
-
if (
|
|
11299
|
+
const summaryPath = join10(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
|
|
11300
|
+
if (existsSync9(summaryPath)) {
|
|
11322
11301
|
return readFileSync5(summaryPath, "utf-8");
|
|
11323
11302
|
}
|
|
11324
11303
|
}
|
|
@@ -11334,8 +11313,8 @@ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
|
11334
11313
|
const entries = [];
|
|
11335
11314
|
for (const turn of turnDirs) {
|
|
11336
11315
|
try {
|
|
11337
|
-
const filePath =
|
|
11338
|
-
if (
|
|
11316
|
+
const filePath = join10(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
|
|
11317
|
+
if (existsSync9(filePath)) {
|
|
11339
11318
|
const raw = readFileSync5(filePath, "utf-8");
|
|
11340
11319
|
entries.push(JSON.parse(raw));
|
|
11341
11320
|
}
|
|
@@ -11366,8 +11345,8 @@ function regenerateJournalSummary() {
|
|
|
11366
11345
|
const turnDir = WORKSPACE.turnPath(latestTurn);
|
|
11367
11346
|
ensureDirExists(turnDir);
|
|
11368
11347
|
const summary = buildSummaryFromEntries(entries);
|
|
11369
|
-
const summaryPath =
|
|
11370
|
-
|
|
11348
|
+
const summaryPath = join10(turnDir, TURN_FILES.SUMMARY);
|
|
11349
|
+
writeFileSync9(summaryPath, summary, "utf-8");
|
|
11371
11350
|
debugLog("general", "Journal summary regenerated", {
|
|
11372
11351
|
entries: entries.length,
|
|
11373
11352
|
summaryLength: summary.length
|
|
@@ -11472,13 +11451,13 @@ function formatSummaryMarkdown(buckets, entries) {
|
|
|
11472
11451
|
function rotateTurnRecords() {
|
|
11473
11452
|
try {
|
|
11474
11453
|
const turnsDir = WORKSPACE.TURNS;
|
|
11475
|
-
if (!
|
|
11476
|
-
const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) =>
|
|
11454
|
+
if (!existsSync9(turnsDir)) return;
|
|
11455
|
+
const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync3(join10(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
|
|
11477
11456
|
if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
11478
11457
|
const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
|
|
11479
11458
|
for (const dir of dirsToDel) {
|
|
11480
11459
|
try {
|
|
11481
|
-
rmSync2(
|
|
11460
|
+
rmSync2(join10(turnsDir, dir), { recursive: true, force: true });
|
|
11482
11461
|
} catch {
|
|
11483
11462
|
}
|
|
11484
11463
|
}
|
|
@@ -11493,8 +11472,8 @@ function rotateTurnRecords() {
|
|
|
11493
11472
|
|
|
11494
11473
|
// src/agents/prompt-builder.ts
|
|
11495
11474
|
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
11496
|
-
var PROMPTS_DIR =
|
|
11497
|
-
var TECHNIQUES_DIR =
|
|
11475
|
+
var PROMPTS_DIR = join11(__dirname2, "prompts");
|
|
11476
|
+
var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
11498
11477
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
11499
11478
|
var PHASE_PROMPT_MAP = {
|
|
11500
11479
|
// Direct mappings — phase has its own prompt file
|
|
@@ -11627,8 +11606,8 @@ ${content}
|
|
|
11627
11606
|
* Load a prompt file from src/agents/prompts/
|
|
11628
11607
|
*/
|
|
11629
11608
|
loadPromptFile(filename) {
|
|
11630
|
-
const path2 =
|
|
11631
|
-
return
|
|
11609
|
+
const path2 = join11(PROMPTS_DIR, filename);
|
|
11610
|
+
return existsSync10(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
11632
11611
|
}
|
|
11633
11612
|
/**
|
|
11634
11613
|
* Load phase-specific prompt.
|
|
@@ -11674,14 +11653,14 @@ ${content}
|
|
|
11674
11653
|
* "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
|
|
11675
11654
|
*/
|
|
11676
11655
|
loadPhaseRelevantTechniques(phase) {
|
|
11677
|
-
if (!
|
|
11656
|
+
if (!existsSync10(TECHNIQUES_DIR)) return "";
|
|
11678
11657
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
11679
11658
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11680
11659
|
const fragments = [];
|
|
11681
11660
|
for (const technique of priorityTechniques) {
|
|
11682
|
-
const filePath =
|
|
11661
|
+
const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
|
|
11683
11662
|
try {
|
|
11684
|
-
if (!
|
|
11663
|
+
if (!existsSync10(filePath)) continue;
|
|
11685
11664
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11686
11665
|
if (content) {
|
|
11687
11666
|
fragments.push(`<technique-reference category="${technique}">
|
|
@@ -11693,9 +11672,9 @@ ${content}
|
|
|
11693
11672
|
}
|
|
11694
11673
|
}
|
|
11695
11674
|
try {
|
|
11696
|
-
const allFiles =
|
|
11675
|
+
const allFiles = readdirSync4(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11697
11676
|
for (const file of allFiles) {
|
|
11698
|
-
const filePath =
|
|
11677
|
+
const filePath = join11(TECHNIQUES_DIR, file);
|
|
11699
11678
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11700
11679
|
if (content) {
|
|
11701
11680
|
const category = file.replace(".md", "");
|
|
@@ -11826,11 +11805,11 @@ ${summary}
|
|
|
11826
11805
|
};
|
|
11827
11806
|
|
|
11828
11807
|
// src/agents/strategist.ts
|
|
11829
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
11830
|
-
import { join as
|
|
11808
|
+
import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
|
|
11809
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
11831
11810
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11832
11811
|
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
11833
|
-
var STRATEGIST_PROMPT_PATH =
|
|
11812
|
+
var STRATEGIST_PROMPT_PATH = join12(__dirname3, "prompts", "strategist-system.md");
|
|
11834
11813
|
var Strategist = class {
|
|
11835
11814
|
llm;
|
|
11836
11815
|
state;
|
|
@@ -11969,7 +11948,7 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
|
|
|
11969
11948
|
// ─── System Prompt Loading ──────────────────────────────────
|
|
11970
11949
|
loadSystemPrompt() {
|
|
11971
11950
|
try {
|
|
11972
|
-
if (
|
|
11951
|
+
if (existsSync11(STRATEGIST_PROMPT_PATH)) {
|
|
11973
11952
|
return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11974
11953
|
}
|
|
11975
11954
|
} catch {
|
|
@@ -12283,8 +12262,8 @@ function formatReflectionInput(input) {
|
|
|
12283
12262
|
}
|
|
12284
12263
|
|
|
12285
12264
|
// src/agents/main-agent.ts
|
|
12286
|
-
import { writeFileSync as
|
|
12287
|
-
import { join as
|
|
12265
|
+
import { writeFileSync as writeFileSync10, existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
12266
|
+
import { join as join13 } from "path";
|
|
12288
12267
|
var MainAgent = class extends CoreAgent {
|
|
12289
12268
|
promptBuilder;
|
|
12290
12269
|
strategist;
|
|
@@ -12348,11 +12327,11 @@ var MainAgent = class extends CoreAgent {
|
|
|
12348
12327
|
});
|
|
12349
12328
|
}
|
|
12350
12329
|
}
|
|
12351
|
-
this.turnToolJournal = [];
|
|
12352
|
-
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
12353
|
-
this.turnReflections = [];
|
|
12354
12330
|
const dynamicPrompt = await this.getCurrentPrompt();
|
|
12355
12331
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
12332
|
+
const turnToolJournal = this.getTurnToolJournal();
|
|
12333
|
+
const turnMemo = this.getTurnMemo();
|
|
12334
|
+
const turnReflections = this.getTurnReflections();
|
|
12356
12335
|
try {
|
|
12357
12336
|
if (messages.length > 2) {
|
|
12358
12337
|
const extraction = await this.llm.generateResponse(
|
|
@@ -12373,13 +12352,13 @@ ${extraction.content.trim()}
|
|
|
12373
12352
|
} catch {
|
|
12374
12353
|
}
|
|
12375
12354
|
try {
|
|
12376
|
-
if (
|
|
12355
|
+
if (turnToolJournal.length > 0) {
|
|
12377
12356
|
const reflection = await this.llm.generateResponse(
|
|
12378
12357
|
[{
|
|
12379
12358
|
role: "user",
|
|
12380
12359
|
content: formatReflectionInput({
|
|
12381
|
-
tools:
|
|
12382
|
-
memo:
|
|
12360
|
+
tools: turnToolJournal,
|
|
12361
|
+
memo: turnMemo,
|
|
12383
12362
|
phase: this.state.getPhase()
|
|
12384
12363
|
})
|
|
12385
12364
|
}],
|
|
@@ -12387,20 +12366,20 @@ ${extraction.content.trim()}
|
|
|
12387
12366
|
REFLECTION_PROMPT
|
|
12388
12367
|
);
|
|
12389
12368
|
if (reflection.content?.trim()) {
|
|
12390
|
-
|
|
12369
|
+
turnReflections.push(reflection.content.trim());
|
|
12391
12370
|
}
|
|
12392
12371
|
}
|
|
12393
12372
|
} catch {
|
|
12394
12373
|
}
|
|
12395
|
-
if (
|
|
12374
|
+
if (turnToolJournal.length > 0) {
|
|
12396
12375
|
try {
|
|
12397
12376
|
const entry = {
|
|
12398
12377
|
turn: this.turnCounter,
|
|
12399
12378
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12400
12379
|
phase: this.state.getPhase(),
|
|
12401
|
-
tools:
|
|
12402
|
-
memo:
|
|
12403
|
-
reflection:
|
|
12380
|
+
tools: turnToolJournal,
|
|
12381
|
+
memo: turnMemo,
|
|
12382
|
+
reflection: turnReflections.length > 0 ? turnReflections.join(" | ") : turnMemo.nextSteps.join("; ")
|
|
12404
12383
|
};
|
|
12405
12384
|
try {
|
|
12406
12385
|
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
@@ -12411,32 +12390,32 @@ ${extraction.content.trim()}
|
|
|
12411
12390
|
turn: this.turnCounter,
|
|
12412
12391
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12413
12392
|
phase: this.state.getPhase(),
|
|
12414
|
-
tools:
|
|
12415
|
-
memo:
|
|
12393
|
+
tools: turnToolJournal,
|
|
12394
|
+
memo: turnMemo,
|
|
12416
12395
|
reflection: entry.reflection
|
|
12417
12396
|
});
|
|
12418
|
-
|
|
12419
|
-
|
|
12397
|
+
writeFileSync10(join13(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
|
|
12398
|
+
writeFileSync10(join13(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
|
|
12420
12399
|
const memoLines = [];
|
|
12421
|
-
if (
|
|
12422
|
-
if (
|
|
12423
|
-
if (
|
|
12424
|
-
if (
|
|
12425
|
-
if (
|
|
12426
|
-
if (
|
|
12400
|
+
if (turnMemo.keyFindings.length > 0) memoLines.push("## Key Findings", ...turnMemo.keyFindings.map((f) => `- ${f}`), "");
|
|
12401
|
+
if (turnMemo.credentials.length > 0) memoLines.push("## Credentials", ...turnMemo.credentials.map((c) => `- ${c}`), "");
|
|
12402
|
+
if (turnMemo.attackVectors.length > 0) memoLines.push("## Attack Vectors", ...turnMemo.attackVectors.map((v) => `- ${v}`), "");
|
|
12403
|
+
if (turnMemo.failures.length > 0) memoLines.push("## Failures", ...turnMemo.failures.map((f) => `- ${f}`), "");
|
|
12404
|
+
if (turnMemo.suspicions.length > 0) memoLines.push("## Suspicious", ...turnMemo.suspicions.map((s) => `- ${s}`), "");
|
|
12405
|
+
if (turnMemo.nextSteps.length > 0) memoLines.push("## Next Steps", ...turnMemo.nextSteps.map((n) => `- ${n}`), "");
|
|
12427
12406
|
if (memoLines.length > 0) {
|
|
12428
|
-
|
|
12407
|
+
writeFileSync10(join13(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
|
|
12429
12408
|
}
|
|
12430
12409
|
} catch {
|
|
12431
12410
|
}
|
|
12432
12411
|
try {
|
|
12433
12412
|
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
12434
|
-
const summaryPath =
|
|
12413
|
+
const summaryPath = join13(turnDir, TURN_FILES.SUMMARY);
|
|
12435
12414
|
const prevTurn = this.turnCounter - 1;
|
|
12436
12415
|
let existingSummary = "";
|
|
12437
12416
|
if (prevTurn >= 1) {
|
|
12438
|
-
const prevSummaryPath =
|
|
12439
|
-
if (
|
|
12417
|
+
const prevSummaryPath = join13(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
|
|
12418
|
+
if (existsSync12(prevSummaryPath)) {
|
|
12440
12419
|
existingSummary = readFileSync8(prevSummaryPath, "utf-8");
|
|
12441
12420
|
}
|
|
12442
12421
|
}
|
|
@@ -12444,8 +12423,8 @@ ${extraction.content.trim()}
|
|
|
12444
12423
|
turn: this.turnCounter,
|
|
12445
12424
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12446
12425
|
phase: this.state.getPhase(),
|
|
12447
|
-
tools:
|
|
12448
|
-
memo:
|
|
12426
|
+
tools: turnToolJournal,
|
|
12427
|
+
memo: turnMemo,
|
|
12449
12428
|
reflection: entry.reflection
|
|
12450
12429
|
});
|
|
12451
12430
|
const summaryResponse = await this.llm.generateResponse(
|
|
@@ -12463,7 +12442,7 @@ ${turnData}`
|
|
|
12463
12442
|
);
|
|
12464
12443
|
if (summaryResponse.content?.trim()) {
|
|
12465
12444
|
ensureDirExists(turnDir);
|
|
12466
|
-
|
|
12445
|
+
writeFileSync10(summaryPath, summaryResponse.content.trim(), "utf-8");
|
|
12467
12446
|
}
|
|
12468
12447
|
} catch {
|
|
12469
12448
|
regenerateJournalSummary();
|
|
@@ -12598,27 +12577,19 @@ ${turnData}`
|
|
|
12598
12577
|
};
|
|
12599
12578
|
|
|
12600
12579
|
// src/agents/factory.ts
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
state,
|
|
12615
|
-
scopeGuard,
|
|
12616
|
-
approvalGate,
|
|
12617
|
-
events
|
|
12618
|
-
);
|
|
12619
|
-
return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
|
|
12620
|
-
}
|
|
12621
|
-
};
|
|
12580
|
+
function createMainAgent(shouldAutoApprove = false) {
|
|
12581
|
+
const state = new SharedState();
|
|
12582
|
+
const events = new AgentEventEmitter();
|
|
12583
|
+
const approvalGate = new ApprovalGate(shouldAutoApprove);
|
|
12584
|
+
const scopeGuard = new ScopeGuard(state);
|
|
12585
|
+
const toolRegistry = new CategorizedToolRegistry(
|
|
12586
|
+
state,
|
|
12587
|
+
scopeGuard,
|
|
12588
|
+
approvalGate,
|
|
12589
|
+
events
|
|
12590
|
+
);
|
|
12591
|
+
return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
|
|
12592
|
+
}
|
|
12622
12593
|
|
|
12623
12594
|
// src/platform/tui/utils/format.ts
|
|
12624
12595
|
var formatDuration2 = (ms) => {
|
|
@@ -13242,7 +13213,7 @@ function getCommandEventIcon(eventType) {
|
|
|
13242
13213
|
|
|
13243
13214
|
// src/platform/tui/hooks/useAgent.ts
|
|
13244
13215
|
var useAgent = (shouldAutoApprove, target) => {
|
|
13245
|
-
const [agent] = useState2(() =>
|
|
13216
|
+
const [agent] = useState2(() => createMainAgent(shouldAutoApprove));
|
|
13246
13217
|
const eventsRef = useRef3(agent.getEventEmitter());
|
|
13247
13218
|
const state = useAgentState();
|
|
13248
13219
|
const {
|
|
@@ -13616,7 +13587,6 @@ var MusicSpinner = memo2(({ color }) => {
|
|
|
13616
13587
|
|
|
13617
13588
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
13618
13589
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
13619
|
-
var MAX_THINKING_LINES = 15;
|
|
13620
13590
|
var StatusDisplay = memo3(({
|
|
13621
13591
|
retryState,
|
|
13622
13592
|
isProcessing,
|
|
@@ -13649,26 +13619,17 @@ var StatusDisplay = memo3(({
|
|
|
13649
13619
|
] });
|
|
13650
13620
|
}
|
|
13651
13621
|
if (isProcessing) {
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
] }),
|
|
13659
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13660
|
-
" ",
|
|
13661
|
-
meta
|
|
13662
|
-
] })
|
|
13622
|
+
const previewText = isThinkingStatus && statusLines.length > 1 ? `${statusMain} \u2014 ${statusLines[statusLines.length - 1]}` : statusMain;
|
|
13623
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13624
|
+
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13625
|
+
/* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
|
|
13626
|
+
" ",
|
|
13627
|
+
previewText
|
|
13663
13628
|
] }),
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
] }
|
|
13668
|
-
!isThinkingStatus && statusLines.length > 1 && /* @__PURE__ */ jsx4(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13669
|
-
"\u2502 ",
|
|
13670
|
-
statusLines.slice(1).join(" ")
|
|
13671
|
-
] }) })
|
|
13629
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13630
|
+
" ",
|
|
13631
|
+
meta
|
|
13632
|
+
] })
|
|
13672
13633
|
] });
|
|
13673
13634
|
}
|
|
13674
13635
|
return /* @__PURE__ */ jsx4(Box3, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) });
|
|
@@ -13758,10 +13719,10 @@ var ChatInput = memo4(({
|
|
|
13758
13719
|
paddingX: 1,
|
|
13759
13720
|
overflowX: "hidden",
|
|
13760
13721
|
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
13761
|
-
/* @__PURE__ */
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13722
|
+
/* @__PURE__ */ jsxs4(Text5, { color: THEME.yellow, children: [
|
|
13723
|
+
"\u25B8 ",
|
|
13724
|
+
inputRequest.prompt,
|
|
13725
|
+
" "
|
|
13765
13726
|
] }),
|
|
13766
13727
|
/* @__PURE__ */ jsx5(
|
|
13767
13728
|
TextInput,
|
|
@@ -13769,7 +13730,7 @@ var ChatInput = memo4(({
|
|
|
13769
13730
|
value: secretInput,
|
|
13770
13731
|
onChange: setSecretInput,
|
|
13771
13732
|
onSubmit: onSecretSubmit,
|
|
13772
|
-
placeholder: "...",
|
|
13733
|
+
placeholder: inputRequest.isPassword ? "(hidden)" : "...",
|
|
13773
13734
|
mask: inputRequest.isPassword ? "\u2022" : void 0
|
|
13774
13735
|
}
|
|
13775
13736
|
)
|
|
@@ -14104,7 +14065,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14104
14065
|
}, [handleCtrlC]);
|
|
14105
14066
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
14106
14067
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
14107
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
14068
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: 6, children: [
|
|
14108
14069
|
/* @__PURE__ */ jsx7(
|
|
14109
14070
|
StatusDisplay,
|
|
14110
14071
|
{
|
|
@@ -14170,6 +14131,11 @@ var CLI_SCAN_TYPES = Object.freeze([
|
|
|
14170
14131
|
import gradient from "gradient-string";
|
|
14171
14132
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
14172
14133
|
initDebugLogger();
|
|
14134
|
+
var _configErrors = validateRequiredConfig();
|
|
14135
|
+
if (_configErrors.length > 0) {
|
|
14136
|
+
_configErrors.forEach((e) => console.error(chalk.hex(HEX.red)(e)));
|
|
14137
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
14138
|
+
}
|
|
14173
14139
|
var program = new Command();
|
|
14174
14140
|
program.name("pentesting").version(APP_VERSION).description(APP_DESCRIPTION).option("--dangerously-skip-permissions", "Skip all permission prompts (dangerous!)").option("-t, --target <target>", "Set initial target");
|
|
14175
14141
|
program.command("interactive", { isDefault: true }).alias("i").description("Start interactive TUI mode").action(async () => {
|
|
@@ -14204,7 +14170,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
14204
14170
|
}
|
|
14205
14171
|
console.log(chalk.hex(HEX.primary)(`[target] Objective: ${objective}
|
|
14206
14172
|
`));
|
|
14207
|
-
const agent =
|
|
14173
|
+
const agent = createMainAgent(skipPermissions);
|
|
14208
14174
|
if (skipPermissions) {
|
|
14209
14175
|
agent.setAutoApprove(true);
|
|
14210
14176
|
}
|
|
@@ -14242,7 +14208,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
14242
14208
|
console.log(chalk.hex(HEX.primary)(`
|
|
14243
14209
|
[scan] Target: ${target} (${options.scanType})
|
|
14244
14210
|
`));
|
|
14245
|
-
const agent =
|
|
14211
|
+
const agent = createMainAgent(skipPermissions);
|
|
14246
14212
|
agent.addTarget(target);
|
|
14247
14213
|
agent.setScope([target]);
|
|
14248
14214
|
const shutdown = async (exitCode = 0) => {
|