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