pentesting 0.47.3 → 0.48.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 +0 -45
- package/dist/main.js +1280 -786
- package/dist/prompts/base.md +21 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -331,7 +331,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
331
331
|
|
|
332
332
|
// src/shared/constants/agent.ts
|
|
333
333
|
var APP_NAME = "Pentest AI";
|
|
334
|
-
var APP_VERSION = "0.
|
|
334
|
+
var APP_VERSION = "0.48.0";
|
|
335
335
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
336
|
var LLM_ROLES = {
|
|
337
337
|
SYSTEM: "system",
|
|
@@ -698,6 +698,12 @@ var ATTACK_TACTICS = {
|
|
|
698
698
|
C2: "command_and_control",
|
|
699
699
|
IMPACT: "impact"
|
|
700
700
|
};
|
|
701
|
+
var ATTACK_VALUE_RANK = {
|
|
702
|
+
HIGH: 3,
|
|
703
|
+
MED: 2,
|
|
704
|
+
LOW: 1,
|
|
705
|
+
NONE: 0
|
|
706
|
+
};
|
|
701
707
|
var APPROVAL_STATUSES = {
|
|
702
708
|
AUTO: "auto",
|
|
703
709
|
USER_CONFIRMED: "user_confirmed",
|
|
@@ -816,10 +822,6 @@ var SECONDS_PER_HOUR = 3600;
|
|
|
816
822
|
|
|
817
823
|
// src/shared/constants/paths.ts
|
|
818
824
|
import path from "path";
|
|
819
|
-
import { fileURLToPath } from "url";
|
|
820
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
821
|
-
var __dirname = path.dirname(__filename);
|
|
822
|
-
var PROJECT_ROOT = path.resolve(__dirname, "../../../");
|
|
823
825
|
var PENTESTING_ROOT = ".pentesting";
|
|
824
826
|
var WORK_DIR = `${PENTESTING_ROOT}/tmp`;
|
|
825
827
|
var MEMORY_DIR = `${PENTESTING_ROOT}/memory`;
|
|
@@ -828,6 +830,8 @@ var SESSIONS_DIR = `${PENTESTING_ROOT}/sessions`;
|
|
|
828
830
|
var LOOT_DIR = `${PENTESTING_ROOT}/loot`;
|
|
829
831
|
var OUTPUTS_DIR = `${PENTESTING_ROOT}/outputs`;
|
|
830
832
|
var DEBUG_DIR = `${PENTESTING_ROOT}/debug`;
|
|
833
|
+
var JOURNAL_DIR = `${PENTESTING_ROOT}/journal`;
|
|
834
|
+
var TURNS_DIR = `${PENTESTING_ROOT}/memory/turns`;
|
|
831
835
|
var WORKSPACE = {
|
|
832
836
|
/** Root directory */
|
|
833
837
|
get ROOT() {
|
|
@@ -860,6 +864,14 @@ var WORKSPACE = {
|
|
|
860
864
|
/** Debug logs */
|
|
861
865
|
get DEBUG() {
|
|
862
866
|
return path.resolve(DEBUG_DIR);
|
|
867
|
+
},
|
|
868
|
+
/** Persistent per-turn journal (§13 memo system) */
|
|
869
|
+
get JOURNAL() {
|
|
870
|
+
return path.resolve(JOURNAL_DIR);
|
|
871
|
+
},
|
|
872
|
+
/** Turn record files */
|
|
873
|
+
get TURNS() {
|
|
874
|
+
return path.resolve(TURNS_DIR);
|
|
863
875
|
}
|
|
864
876
|
};
|
|
865
877
|
|
|
@@ -2687,13 +2699,13 @@ var AttackGraph = class {
|
|
|
2687
2699
|
* Record a credential discovery and create spray edges.
|
|
2688
2700
|
*/
|
|
2689
2701
|
addCredential(username, password, source) {
|
|
2690
|
-
const credId = this.addNode(
|
|
2702
|
+
const credId = this.addNode(NODE_TYPE.CREDENTIAL, `${username}:***`, {
|
|
2691
2703
|
username,
|
|
2692
2704
|
password,
|
|
2693
2705
|
source
|
|
2694
2706
|
});
|
|
2695
2707
|
for (const [id, node] of this.nodes) {
|
|
2696
|
-
if (node.type ===
|
|
2708
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
2697
2709
|
const svc = String(node.data.service || "");
|
|
2698
2710
|
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
|
|
2699
2711
|
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
@@ -2706,7 +2718,7 @@ var AttackGraph = class {
|
|
|
2706
2718
|
* Record a vulnerability finding.
|
|
2707
2719
|
*/
|
|
2708
2720
|
addVulnerability(title, target, severity, hasExploit = false) {
|
|
2709
|
-
const vulnId = this.addNode(
|
|
2721
|
+
const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, title, {
|
|
2710
2722
|
target,
|
|
2711
2723
|
severity,
|
|
2712
2724
|
hasExploit
|
|
@@ -2717,7 +2729,7 @@ var AttackGraph = class {
|
|
|
2717
2729
|
}
|
|
2718
2730
|
}
|
|
2719
2731
|
if (hasExploit) {
|
|
2720
|
-
const accessId = this.addNode(
|
|
2732
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `shell via ${title}`, {
|
|
2721
2733
|
via: title,
|
|
2722
2734
|
status: GRAPH_STATUS.POTENTIAL
|
|
2723
2735
|
});
|
|
@@ -2729,14 +2741,14 @@ var AttackGraph = class {
|
|
|
2729
2741
|
* Record gained access.
|
|
2730
2742
|
*/
|
|
2731
2743
|
addAccess(host, level, via) {
|
|
2732
|
-
const accessId = this.addNode(
|
|
2744
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `${level}@${host}`, {
|
|
2733
2745
|
host,
|
|
2734
2746
|
level,
|
|
2735
2747
|
via
|
|
2736
2748
|
});
|
|
2737
2749
|
this.markSucceeded(accessId);
|
|
2738
2750
|
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
2739
|
-
const lootId = this.addNode(
|
|
2751
|
+
const lootId = this.addNode(NODE_TYPE.LOOT, `flags on ${host}`, {
|
|
2740
2752
|
host,
|
|
2741
2753
|
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
2742
2754
|
});
|
|
@@ -2748,7 +2760,7 @@ var AttackGraph = class {
|
|
|
2748
2760
|
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
2749
2761
|
*/
|
|
2750
2762
|
addOSINT(category, detail, data = {}) {
|
|
2751
|
-
const osintId = this.addNode(
|
|
2763
|
+
const osintId = this.addNode(NODE_TYPE.OSINT, `${category}: ${detail}`, {
|
|
2752
2764
|
category,
|
|
2753
2765
|
detail,
|
|
2754
2766
|
...data
|
|
@@ -3981,20 +3993,6 @@ var ScopeGuard = class {
|
|
|
3981
3993
|
};
|
|
3982
3994
|
|
|
3983
3995
|
// src/engine/approval.ts
|
|
3984
|
-
var CATEGORY_APPROVAL = {
|
|
3985
|
-
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
3986
|
-
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
3987
|
-
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
3988
|
-
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
3989
|
-
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
3990
|
-
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
3991
|
-
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
3992
|
-
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
3993
|
-
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
3994
|
-
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
3995
|
-
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
3996
|
-
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
3997
|
-
};
|
|
3998
3996
|
var ApprovalGate = class {
|
|
3999
3997
|
constructor(shouldAutoApprove = false) {
|
|
4000
3998
|
this.shouldAutoApprove = shouldAutoApprove;
|
|
@@ -4298,7 +4296,7 @@ function autoExtractStructured(toolName, output) {
|
|
|
4298
4296
|
data.vulnerabilities = vulns;
|
|
4299
4297
|
hasData = true;
|
|
4300
4298
|
}
|
|
4301
|
-
if (toolName ===
|
|
4299
|
+
if (toolName === TOOL_NAMES.PARSE_NMAP || /nmap scan report/i.test(output)) {
|
|
4302
4300
|
const nmap = extractNmapStructured(output);
|
|
4303
4301
|
if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
|
|
4304
4302
|
data.openPorts = nmap.structured.openPorts;
|
|
@@ -4703,7 +4701,8 @@ Used ports: ${usedPorts.join(", ")}
|
|
|
4703
4701
|
[!] STRATEGY ADAPTATION REQUIRED:
|
|
4704
4702
|
1. Try the next available port (e.g., ${nextPort} or 4445, 9001)
|
|
4705
4703
|
2. If this is for a listener, update your Mission/Checklist with the NEW port so other agents know.
|
|
4706
|
-
3. Check bg_process({ action: "list" }) to see if you can stop the conflicting process
|
|
4704
|
+
3. Check bg_process({ action: "list" }) to see if you can stop the conflicting process.`,
|
|
4705
|
+
error: `Port ${requestedPort} already in use`
|
|
4707
4706
|
};
|
|
4708
4707
|
}
|
|
4709
4708
|
}
|
|
@@ -4850,7 +4849,7 @@ ${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connecti
|
|
|
4850
4849
|
if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
|
|
4851
4850
|
const waitMs = Math.min(params.wait_ms || SYSTEM_LIMITS.DEFAULT_WAIT_MS_INTERACT, SYSTEM_LIMITS.MAX_WAIT_MS_INTERACT);
|
|
4852
4851
|
const result2 = await sendToProcess(processId, cmd, waitMs);
|
|
4853
|
-
if (!result2.success) return { success: false, output: result2.output };
|
|
4852
|
+
if (!result2.success) return { success: false, output: result2.output, error: result2.output };
|
|
4854
4853
|
return {
|
|
4855
4854
|
success: true,
|
|
4856
4855
|
output: `Command sent: ${cmd}
|
|
@@ -4866,7 +4865,7 @@ ${result2.output}`
|
|
|
4866
4865
|
if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
|
|
4867
4866
|
const desc = params.description;
|
|
4868
4867
|
const success = promoteToShell(processId, desc);
|
|
4869
|
-
if (!success) return { success: false, output: `Process ${processId} not found` };
|
|
4868
|
+
if (!success) return { success: false, output: `Process ${processId} not found`, error: `Process ${processId} not found` };
|
|
4870
4869
|
return {
|
|
4871
4870
|
success: true,
|
|
4872
4871
|
output: `[OK] Process ${processId} promoted to ACTIVE SHELL.
|
|
@@ -5016,7 +5015,8 @@ Examples:
|
|
|
5016
5015
|
if (!validPhases.includes(newPhase)) {
|
|
5017
5016
|
return {
|
|
5018
5017
|
success: false,
|
|
5019
|
-
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}
|
|
5018
|
+
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}`,
|
|
5019
|
+
error: `Invalid phase: ${newPhase}`
|
|
5020
5020
|
};
|
|
5021
5021
|
}
|
|
5022
5022
|
state.setPhase(newPhase);
|
|
@@ -5732,50 +5732,60 @@ var DEFAULT_BROWSER_OPTIONS = {
|
|
|
5732
5732
|
|
|
5733
5733
|
// src/engine/tools/web-browser.ts
|
|
5734
5734
|
async function browseUrl(url, options = {}) {
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5735
|
+
try {
|
|
5736
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5737
|
+
const { installed, browserInstalled } = await checkPlaywright();
|
|
5738
|
+
if (!installed || !browserInstalled) {
|
|
5739
|
+
const installResult = await installPlaywright();
|
|
5740
|
+
if (!installResult.success) {
|
|
5741
|
+
return {
|
|
5742
|
+
success: false,
|
|
5743
|
+
output: "",
|
|
5744
|
+
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
5745
|
+
};
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
5749
|
+
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5750
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5751
|
+
if (!result2.success) {
|
|
5740
5752
|
return {
|
|
5741
5753
|
success: false,
|
|
5742
|
-
output:
|
|
5743
|
-
error:
|
|
5754
|
+
output: result2.output,
|
|
5755
|
+
error: result2.error
|
|
5756
|
+
};
|
|
5757
|
+
}
|
|
5758
|
+
if (result2.parsedData) {
|
|
5759
|
+
return {
|
|
5760
|
+
success: true,
|
|
5761
|
+
output: formatBrowserOutput(result2.parsedData, browserOptions),
|
|
5762
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
5763
|
+
extractedData: result2.parsedData
|
|
5744
5764
|
};
|
|
5745
5765
|
}
|
|
5746
|
-
}
|
|
5747
|
-
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
5748
|
-
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5749
|
-
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5750
|
-
if (!result2.success) {
|
|
5751
5766
|
return {
|
|
5752
|
-
success:
|
|
5753
|
-
output: result2.output,
|
|
5754
|
-
|
|
5767
|
+
success: true,
|
|
5768
|
+
output: result2.output || "Navigation completed",
|
|
5769
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5755
5770
|
};
|
|
5756
|
-
}
|
|
5757
|
-
|
|
5771
|
+
} catch (error) {
|
|
5772
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5758
5773
|
return {
|
|
5759
|
-
success:
|
|
5760
|
-
output:
|
|
5761
|
-
|
|
5762
|
-
extractedData: result2.parsedData
|
|
5774
|
+
success: false,
|
|
5775
|
+
output: "",
|
|
5776
|
+
error: `Browser error: ${msg}`
|
|
5763
5777
|
};
|
|
5764
5778
|
}
|
|
5765
|
-
return {
|
|
5766
|
-
success: true,
|
|
5767
|
-
output: result2.output || "Navigation completed",
|
|
5768
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5769
|
-
};
|
|
5770
5779
|
}
|
|
5771
5780
|
async function fillAndSubmitForm(url, formData, options = {}) {
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5781
|
+
try {
|
|
5782
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5783
|
+
const safeUrl = safeJsString(url);
|
|
5784
|
+
const safeFormData = JSON.stringify(formData);
|
|
5785
|
+
const playwrightPath = getPlaywrightPath();
|
|
5786
|
+
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5787
|
+
const headlessMode = process.env.HEADLESS !== "false";
|
|
5788
|
+
const script = `
|
|
5779
5789
|
const { chromium } = require(${safePlaywrightPath});
|
|
5780
5790
|
|
|
5781
5791
|
(async () => {
|
|
@@ -5815,27 +5825,35 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
5815
5825
|
}
|
|
5816
5826
|
})();
|
|
5817
5827
|
`;
|
|
5818
|
-
|
|
5819
|
-
|
|
5828
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "form");
|
|
5829
|
+
if (!result2.success) {
|
|
5830
|
+
return {
|
|
5831
|
+
success: false,
|
|
5832
|
+
output: result2.output,
|
|
5833
|
+
error: result2.error
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
if (result2.parsedData) {
|
|
5837
|
+
const data = result2.parsedData;
|
|
5838
|
+
return {
|
|
5839
|
+
success: true,
|
|
5840
|
+
output: `Form submitted. Current URL: ${data.url}
|
|
5841
|
+
Title: ${data.title}`,
|
|
5842
|
+
extractedData: result2.parsedData
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5820
5845
|
return {
|
|
5821
|
-
success:
|
|
5822
|
-
output: result2.output
|
|
5823
|
-
error: result2.error
|
|
5846
|
+
success: true,
|
|
5847
|
+
output: result2.output || "Form submitted"
|
|
5824
5848
|
};
|
|
5825
|
-
}
|
|
5826
|
-
|
|
5827
|
-
const data = result2.parsedData;
|
|
5849
|
+
} catch (error) {
|
|
5850
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5828
5851
|
return {
|
|
5829
|
-
success:
|
|
5830
|
-
output:
|
|
5831
|
-
|
|
5832
|
-
extractedData: result2.parsedData
|
|
5852
|
+
success: false,
|
|
5853
|
+
output: "",
|
|
5854
|
+
error: `Form submission error: ${msg}`
|
|
5833
5855
|
};
|
|
5834
5856
|
}
|
|
5835
|
-
return {
|
|
5836
|
-
success: true,
|
|
5837
|
-
output: result2.output || "Form submitted"
|
|
5838
|
-
};
|
|
5839
5857
|
}
|
|
5840
5858
|
async function webSearchWithBrowser(query, engine = "google") {
|
|
5841
5859
|
const searchUrls = {
|
|
@@ -5852,6 +5870,10 @@ async function webSearchWithBrowser(query, engine = "google") {
|
|
|
5852
5870
|
}
|
|
5853
5871
|
|
|
5854
5872
|
// src/engine/web-search-providers.ts
|
|
5873
|
+
var SEARCH_TIMEOUT_MS = 15e3;
|
|
5874
|
+
function getErrorMessage(error) {
|
|
5875
|
+
return error instanceof Error ? error.message : String(error);
|
|
5876
|
+
}
|
|
5855
5877
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
5856
5878
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5857
5879
|
const requestBody = {
|
|
@@ -5860,21 +5882,39 @@ async function searchWithGLM(query, apiKey, apiUrl) {
|
|
|
5860
5882
|
stream: false
|
|
5861
5883
|
};
|
|
5862
5884
|
debugLog("search", "GLM request body", requestBody);
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5885
|
+
let response;
|
|
5886
|
+
try {
|
|
5887
|
+
response = await fetch(apiUrl, {
|
|
5888
|
+
method: "POST",
|
|
5889
|
+
headers: {
|
|
5890
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5891
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`
|
|
5892
|
+
},
|
|
5893
|
+
body: JSON.stringify(requestBody),
|
|
5894
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5895
|
+
});
|
|
5896
|
+
} catch (err) {
|
|
5897
|
+
const msg = getErrorMessage(err);
|
|
5898
|
+
debugLog("search", "GLM fetch FAILED (network)", { error: msg });
|
|
5899
|
+
return { success: false, output: "", error: `GLM search network error: ${msg}. Check internet connection or API endpoint.` };
|
|
5900
|
+
}
|
|
5871
5901
|
debugLog("search", "GLM response status", { status: response.status, ok: response.ok });
|
|
5872
5902
|
if (!response.ok) {
|
|
5873
|
-
|
|
5903
|
+
let errorText = "";
|
|
5904
|
+
try {
|
|
5905
|
+
errorText = await response.text();
|
|
5906
|
+
} catch {
|
|
5907
|
+
}
|
|
5874
5908
|
debugLog("search", "GLM response ERROR", { status: response.status, error: errorText });
|
|
5875
|
-
|
|
5909
|
+
return { success: false, output: "", error: `GLM Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5910
|
+
}
|
|
5911
|
+
let data;
|
|
5912
|
+
try {
|
|
5913
|
+
data = await response.json();
|
|
5914
|
+
} catch (err) {
|
|
5915
|
+
debugLog("search", "GLM JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5916
|
+
return { success: false, output: "", error: `GLM search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5876
5917
|
}
|
|
5877
|
-
const data = await response.json();
|
|
5878
5918
|
debugLog("search", "GLM response data", { hasChoices: !!data.choices, choicesCount: data.choices?.length });
|
|
5879
5919
|
let results = "";
|
|
5880
5920
|
if (data.choices?.[0]?.message?.content) {
|
|
@@ -5902,19 +5942,37 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5902
5942
|
debugLog("search", "Brave request START", { apiUrl, query });
|
|
5903
5943
|
const url = `${apiUrl}?q=${encodeURIComponent(query)}&count=${SEARCH_LIMIT.DEFAULT_RESULT_COUNT}`;
|
|
5904
5944
|
debugLog("search", "Brave request URL", { url });
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5945
|
+
let response;
|
|
5946
|
+
try {
|
|
5947
|
+
response = await fetch(url, {
|
|
5948
|
+
headers: {
|
|
5949
|
+
[SEARCH_HEADER.ACCEPT]: "application/json",
|
|
5950
|
+
[SEARCH_HEADER.X_SUBSCRIPTION_TOKEN]: apiKey
|
|
5951
|
+
},
|
|
5952
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5953
|
+
});
|
|
5954
|
+
} catch (err) {
|
|
5955
|
+
const msg = getErrorMessage(err);
|
|
5956
|
+
debugLog("search", "Brave fetch FAILED (network)", { error: msg });
|
|
5957
|
+
return { success: false, output: "", error: `Brave search network error: ${msg}. Check internet connection.` };
|
|
5958
|
+
}
|
|
5911
5959
|
debugLog("search", "Brave response status", { status: response.status, ok: response.ok });
|
|
5912
5960
|
if (!response.ok) {
|
|
5913
|
-
|
|
5961
|
+
let errorText = "";
|
|
5962
|
+
try {
|
|
5963
|
+
errorText = await response.text();
|
|
5964
|
+
} catch {
|
|
5965
|
+
}
|
|
5914
5966
|
debugLog("search", "Brave response ERROR", { status: response.status, error: errorText });
|
|
5915
|
-
|
|
5967
|
+
return { success: false, output: "", error: `Brave API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5968
|
+
}
|
|
5969
|
+
let data;
|
|
5970
|
+
try {
|
|
5971
|
+
data = await response.json();
|
|
5972
|
+
} catch (err) {
|
|
5973
|
+
debugLog("search", "Brave JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5974
|
+
return { success: false, output: "", error: `Brave search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5916
5975
|
}
|
|
5917
|
-
const data = await response.json();
|
|
5918
5976
|
const results = data.web?.results || [];
|
|
5919
5977
|
debugLog("search", "Brave results count", { count: results.length });
|
|
5920
5978
|
if (results.length === 0) {
|
|
@@ -5930,21 +5988,39 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5930
5988
|
}
|
|
5931
5989
|
async function searchWithSerper(query, apiKey, apiUrl) {
|
|
5932
5990
|
debugLog("search", "Serper request START", { apiUrl, query });
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5991
|
+
let response;
|
|
5992
|
+
try {
|
|
5993
|
+
response = await fetch(apiUrl, {
|
|
5994
|
+
method: "POST",
|
|
5995
|
+
headers: {
|
|
5996
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5997
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
5998
|
+
},
|
|
5999
|
+
body: JSON.stringify({ q: query }),
|
|
6000
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
6001
|
+
});
|
|
6002
|
+
} catch (err) {
|
|
6003
|
+
const msg = getErrorMessage(err);
|
|
6004
|
+
debugLog("search", "Serper fetch FAILED (network)", { error: msg });
|
|
6005
|
+
return { success: false, output: "", error: `Serper search network error: ${msg}. Check internet connection.` };
|
|
6006
|
+
}
|
|
5941
6007
|
debugLog("search", "Serper response status", { status: response.status, ok: response.ok });
|
|
5942
6008
|
if (!response.ok) {
|
|
5943
|
-
|
|
6009
|
+
let errorText = "";
|
|
6010
|
+
try {
|
|
6011
|
+
errorText = await response.text();
|
|
6012
|
+
} catch {
|
|
6013
|
+
}
|
|
5944
6014
|
debugLog("search", "Serper response ERROR", { status: response.status, error: errorText });
|
|
5945
|
-
|
|
6015
|
+
return { success: false, output: "", error: `Serper API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6016
|
+
}
|
|
6017
|
+
let data;
|
|
6018
|
+
try {
|
|
6019
|
+
data = await response.json();
|
|
6020
|
+
} catch (err) {
|
|
6021
|
+
debugLog("search", "Serper JSON parse FAILED", { error: getErrorMessage(err) });
|
|
6022
|
+
return { success: false, output: "", error: `Serper search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5946
6023
|
}
|
|
5947
|
-
const data = await response.json();
|
|
5948
6024
|
const results = data.organic || [];
|
|
5949
6025
|
debugLog("search", "Serper results count", { count: results.length });
|
|
5950
6026
|
if (results.length === 0) {
|
|
@@ -5959,20 +6035,36 @@ async function searchWithSerper(query, apiKey, apiUrl) {
|
|
|
5959
6035
|
return { success: true, output: formatted };
|
|
5960
6036
|
}
|
|
5961
6037
|
async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
6038
|
+
let response;
|
|
6039
|
+
try {
|
|
6040
|
+
response = await fetch(apiUrl, {
|
|
6041
|
+
method: "POST",
|
|
6042
|
+
headers: {
|
|
6043
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
6044
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
|
|
6045
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
6046
|
+
},
|
|
6047
|
+
body: JSON.stringify({ query, q: query }),
|
|
6048
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
6049
|
+
});
|
|
6050
|
+
} catch (err) {
|
|
6051
|
+
const msg = getErrorMessage(err);
|
|
6052
|
+
return { success: false, output: "", error: `Search API network error: ${msg}. Check internet connection or API endpoint.` };
|
|
6053
|
+
}
|
|
5971
6054
|
if (!response.ok) {
|
|
5972
|
-
|
|
5973
|
-
|
|
6055
|
+
let errorText = "";
|
|
6056
|
+
try {
|
|
6057
|
+
errorText = await response.text();
|
|
6058
|
+
} catch {
|
|
6059
|
+
}
|
|
6060
|
+
return { success: false, output: "", error: `Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6061
|
+
}
|
|
6062
|
+
let data;
|
|
6063
|
+
try {
|
|
6064
|
+
data = await response.json();
|
|
6065
|
+
} catch (err) {
|
|
6066
|
+
return { success: false, output: "", error: `Search API returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5974
6067
|
}
|
|
5975
|
-
const data = await response.json();
|
|
5976
6068
|
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
5977
6069
|
}
|
|
5978
6070
|
|
|
@@ -5982,7 +6074,7 @@ var PORT_STATE2 = {
|
|
|
5982
6074
|
CLOSED: "closed",
|
|
5983
6075
|
FILTERED: "filtered"
|
|
5984
6076
|
};
|
|
5985
|
-
function
|
|
6077
|
+
function getErrorMessage2(error) {
|
|
5986
6078
|
return error instanceof Error ? error.message : String(error);
|
|
5987
6079
|
}
|
|
5988
6080
|
async function parseNmap(xmlPath) {
|
|
@@ -6033,7 +6125,7 @@ async function parseNmap(xmlPath) {
|
|
|
6033
6125
|
return {
|
|
6034
6126
|
success: false,
|
|
6035
6127
|
output: "",
|
|
6036
|
-
error:
|
|
6128
|
+
error: getErrorMessage2(error)
|
|
6037
6129
|
};
|
|
6038
6130
|
}
|
|
6039
6131
|
}
|
|
@@ -6044,7 +6136,7 @@ async function searchCVE(service, version) {
|
|
|
6044
6136
|
return {
|
|
6045
6137
|
success: false,
|
|
6046
6138
|
output: "",
|
|
6047
|
-
error:
|
|
6139
|
+
error: getErrorMessage2(error)
|
|
6048
6140
|
};
|
|
6049
6141
|
}
|
|
6050
6142
|
}
|
|
@@ -6081,7 +6173,7 @@ async function searchExploitDB(service, version) {
|
|
|
6081
6173
|
return {
|
|
6082
6174
|
success: false,
|
|
6083
6175
|
output: "",
|
|
6084
|
-
error:
|
|
6176
|
+
error: getErrorMessage2(error)
|
|
6085
6177
|
};
|
|
6086
6178
|
}
|
|
6087
6179
|
}
|
|
@@ -6128,11 +6220,11 @@ async function webSearch(query, _engine) {
|
|
|
6128
6220
|
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
6129
6221
|
}
|
|
6130
6222
|
} catch (error) {
|
|
6131
|
-
debugLog("search", "webSearch ERROR", { error:
|
|
6223
|
+
debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
|
|
6132
6224
|
return {
|
|
6133
6225
|
success: false,
|
|
6134
6226
|
output: "",
|
|
6135
|
-
error:
|
|
6227
|
+
error: getErrorMessage2(error)
|
|
6136
6228
|
};
|
|
6137
6229
|
}
|
|
6138
6230
|
}
|
|
@@ -6509,7 +6601,8 @@ ${results.join("\n\n")}`
|
|
|
6509
6601
|
}
|
|
6510
6602
|
return {
|
|
6511
6603
|
success: false,
|
|
6512
|
-
output: `Category ${category} not found in any edition
|
|
6604
|
+
output: `Category ${category} not found in any edition.`,
|
|
6605
|
+
error: `Category ${category} not found`
|
|
6513
6606
|
};
|
|
6514
6607
|
}
|
|
6515
6608
|
if (edition === "all") {
|
|
@@ -6520,7 +6613,7 @@ ${results.join("\n\n")}`
|
|
|
6520
6613
|
}
|
|
6521
6614
|
const data = OWASP_FULL_HISTORY[edition];
|
|
6522
6615
|
if (!data) {
|
|
6523
|
-
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025
|
|
6616
|
+
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025.`, error: `Edition ${edition} not found` };
|
|
6524
6617
|
}
|
|
6525
6618
|
return {
|
|
6526
6619
|
success: true,
|
|
@@ -7065,8 +7158,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7065
7158
|
}
|
|
7066
7159
|
},
|
|
7067
7160
|
execute: async (p) => {
|
|
7068
|
-
const { existsSync:
|
|
7069
|
-
const { join:
|
|
7161
|
+
const { existsSync: existsSync12, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7162
|
+
const { join: join14 } = await import("path");
|
|
7070
7163
|
const category = p.category || "";
|
|
7071
7164
|
const search = p.search || "";
|
|
7072
7165
|
const minSize = p.min_size || 0;
|
|
@@ -7102,7 +7195,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7102
7195
|
const processFile = (fullPath, fileName) => {
|
|
7103
7196
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
7104
7197
|
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
7105
|
-
const stats =
|
|
7198
|
+
const stats = statSync3(fullPath);
|
|
7106
7199
|
if (stats.size < minSize) return;
|
|
7107
7200
|
if (!matchesCategory(fullPath)) return;
|
|
7108
7201
|
if (!matchesSearch(fullPath, fileName)) return;
|
|
@@ -7112,16 +7205,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7112
7205
|
results.push("");
|
|
7113
7206
|
};
|
|
7114
7207
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
7115
|
-
if (depth > maxDepth || !
|
|
7208
|
+
if (depth > maxDepth || !existsSync12(dirPath)) return;
|
|
7116
7209
|
let entries;
|
|
7117
7210
|
try {
|
|
7118
|
-
entries =
|
|
7211
|
+
entries = readdirSync4(dirPath, { withFileTypes: true });
|
|
7119
7212
|
} catch {
|
|
7120
7213
|
return;
|
|
7121
7214
|
}
|
|
7122
7215
|
for (const entry of entries) {
|
|
7123
7216
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
7124
|
-
const fullPath =
|
|
7217
|
+
const fullPath = join14(dirPath, entry.name);
|
|
7125
7218
|
if (entry.isDirectory()) {
|
|
7126
7219
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
7127
7220
|
continue;
|
|
@@ -7498,8 +7591,8 @@ Requires root/sudo privileges.`,
|
|
|
7498
7591
|
const iface = p.interface || "";
|
|
7499
7592
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7500
7593
|
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7501
|
-
const { writeFileSync:
|
|
7502
|
-
|
|
7594
|
+
const { writeFileSync: writeFileSync10 } = await import("fs");
|
|
7595
|
+
writeFileSync10(hostsFile, `${spoofIp} ${domain}
|
|
7503
7596
|
${spoofIp} *.${domain}
|
|
7504
7597
|
`);
|
|
7505
7598
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -8006,6 +8099,86 @@ Returns recommendations on process status, port conflicts, long-running tasks, e
|
|
|
8006
8099
|
}
|
|
8007
8100
|
];
|
|
8008
8101
|
|
|
8102
|
+
// src/shared/constants/service-ports.ts
|
|
8103
|
+
var SERVICE_PORTS = {
|
|
8104
|
+
SSH: 22,
|
|
8105
|
+
FTP: 21,
|
|
8106
|
+
TELNET: 23,
|
|
8107
|
+
SMTP: 25,
|
|
8108
|
+
DNS: 53,
|
|
8109
|
+
HTTP: 80,
|
|
8110
|
+
POP3: 110,
|
|
8111
|
+
IMAP: 143,
|
|
8112
|
+
SMB_NETBIOS: 139,
|
|
8113
|
+
KERBEROS: 88,
|
|
8114
|
+
LDAP: 389,
|
|
8115
|
+
SMB: 445,
|
|
8116
|
+
HTTPS: 443,
|
|
8117
|
+
SMTPS: 465,
|
|
8118
|
+
SMTP_TLS: 587,
|
|
8119
|
+
MODBUS: 502,
|
|
8120
|
+
IMAPS: 993,
|
|
8121
|
+
POP3S: 995,
|
|
8122
|
+
MSSQL: 1433,
|
|
8123
|
+
MYSQL: 3306,
|
|
8124
|
+
RDP: 3389,
|
|
8125
|
+
POSTGRESQL: 5432,
|
|
8126
|
+
VNC: 5900,
|
|
8127
|
+
REDIS: 6379,
|
|
8128
|
+
DOCKER_HTTP: 2375,
|
|
8129
|
+
DOCKER_HTTPS: 2376,
|
|
8130
|
+
KUBERNETES_API: 6443,
|
|
8131
|
+
HTTP_ALT: 8080,
|
|
8132
|
+
HTTPS_ALT: 8443,
|
|
8133
|
+
NFS: 2049,
|
|
8134
|
+
DNP3: 2e4,
|
|
8135
|
+
MONGODB: 27017,
|
|
8136
|
+
ELASTICSEARCH: 9200,
|
|
8137
|
+
MEMCACHED: 11211,
|
|
8138
|
+
NODE_DEFAULT: 3e3,
|
|
8139
|
+
FLASK_DEFAULT: 5e3,
|
|
8140
|
+
DJANGO_DEFAULT: 8e3
|
|
8141
|
+
};
|
|
8142
|
+
var CRITICAL_SERVICE_PORTS = [
|
|
8143
|
+
SERVICE_PORTS.SSH,
|
|
8144
|
+
SERVICE_PORTS.RDP,
|
|
8145
|
+
SERVICE_PORTS.MYSQL,
|
|
8146
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8147
|
+
SERVICE_PORTS.REDIS,
|
|
8148
|
+
SERVICE_PORTS.MONGODB
|
|
8149
|
+
];
|
|
8150
|
+
var NO_AUTH_CRITICAL_PORTS = [
|
|
8151
|
+
SERVICE_PORTS.REDIS,
|
|
8152
|
+
SERVICE_PORTS.MONGODB,
|
|
8153
|
+
SERVICE_PORTS.ELASTICSEARCH,
|
|
8154
|
+
SERVICE_PORTS.MEMCACHED
|
|
8155
|
+
];
|
|
8156
|
+
var WEB_SERVICE_PORTS = [
|
|
8157
|
+
SERVICE_PORTS.HTTP,
|
|
8158
|
+
SERVICE_PORTS.HTTPS,
|
|
8159
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8160
|
+
SERVICE_PORTS.HTTPS_ALT,
|
|
8161
|
+
SERVICE_PORTS.NODE_DEFAULT,
|
|
8162
|
+
SERVICE_PORTS.FLASK_DEFAULT,
|
|
8163
|
+
SERVICE_PORTS.DJANGO_DEFAULT
|
|
8164
|
+
];
|
|
8165
|
+
var PLAINTEXT_HTTP_PORTS = [
|
|
8166
|
+
SERVICE_PORTS.HTTP,
|
|
8167
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8168
|
+
SERVICE_PORTS.NODE_DEFAULT
|
|
8169
|
+
];
|
|
8170
|
+
var DATABASE_PORTS = [
|
|
8171
|
+
SERVICE_PORTS.MYSQL,
|
|
8172
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8173
|
+
SERVICE_PORTS.MSSQL,
|
|
8174
|
+
SERVICE_PORTS.MONGODB,
|
|
8175
|
+
SERVICE_PORTS.REDIS
|
|
8176
|
+
];
|
|
8177
|
+
var SMB_PORTS = [
|
|
8178
|
+
SERVICE_PORTS.SMB,
|
|
8179
|
+
SERVICE_PORTS.SMB_NETBIOS
|
|
8180
|
+
];
|
|
8181
|
+
|
|
8009
8182
|
// src/domains/network/tools.ts
|
|
8010
8183
|
var NETWORK_TOOLS = [
|
|
8011
8184
|
{
|
|
@@ -8051,7 +8224,7 @@ var NETWORK_CONFIG2 = {
|
|
|
8051
8224
|
tools: NETWORK_TOOLS,
|
|
8052
8225
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8053
8226
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8054
|
-
commonPorts: [
|
|
8227
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
|
|
8055
8228
|
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
|
|
8056
8229
|
};
|
|
8057
8230
|
|
|
@@ -8114,7 +8287,7 @@ var WEB_CONFIG = {
|
|
|
8114
8287
|
tools: WEB_TOOLS,
|
|
8115
8288
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8116
8289
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8117
|
-
commonPorts: [
|
|
8290
|
+
commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
|
|
8118
8291
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8119
8292
|
};
|
|
8120
8293
|
|
|
@@ -8149,12 +8322,12 @@ var DATABASE_TOOLS = [
|
|
|
8149
8322
|
description: "MySQL enumeration - version, users, databases",
|
|
8150
8323
|
parameters: {
|
|
8151
8324
|
target: { type: "string", description: "Target IP/hostname" },
|
|
8152
|
-
port: { type: "string", description:
|
|
8325
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8153
8326
|
},
|
|
8154
8327
|
required: ["target"],
|
|
8155
8328
|
execute: async (params) => {
|
|
8156
8329
|
const target = params.target;
|
|
8157
|
-
const port = params.port ||
|
|
8330
|
+
const port = params.port || String(SERVICE_PORTS.MYSQL);
|
|
8158
8331
|
return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
|
|
8159
8332
|
}
|
|
8160
8333
|
},
|
|
@@ -8175,12 +8348,12 @@ var DATABASE_TOOLS = [
|
|
|
8175
8348
|
description: "Redis enumeration",
|
|
8176
8349
|
parameters: {
|
|
8177
8350
|
target: { type: "string", description: "Target IP" },
|
|
8178
|
-
port: { type: "string", description:
|
|
8351
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8179
8352
|
},
|
|
8180
8353
|
required: ["target"],
|
|
8181
8354
|
execute: async (params) => {
|
|
8182
8355
|
const target = params.target;
|
|
8183
|
-
const port = params.port ||
|
|
8356
|
+
const port = params.port || String(SERVICE_PORTS.REDIS);
|
|
8184
8357
|
return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
|
|
8185
8358
|
}
|
|
8186
8359
|
},
|
|
@@ -8212,7 +8385,7 @@ var DATABASE_CONFIG = {
|
|
|
8212
8385
|
tools: DATABASE_TOOLS,
|
|
8213
8386
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8214
8387
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8215
|
-
commonPorts: [
|
|
8388
|
+
commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
|
|
8216
8389
|
commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
|
|
8217
8390
|
};
|
|
8218
8391
|
|
|
@@ -8265,7 +8438,7 @@ var AD_CONFIG = {
|
|
|
8265
8438
|
tools: AD_TOOLS,
|
|
8266
8439
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8267
8440
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8268
|
-
commonPorts: [
|
|
8441
|
+
commonPorts: [SERVICE_PORTS.KERBEROS, SERVICE_PORTS.LDAP, SERVICE_PORTS.SMB],
|
|
8269
8442
|
commonServices: [SERVICES.AD, SERVICES.SMB]
|
|
8270
8443
|
};
|
|
8271
8444
|
|
|
@@ -8289,7 +8462,7 @@ var EMAIL_CONFIG = {
|
|
|
8289
8462
|
tools: EMAIL_TOOLS,
|
|
8290
8463
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8291
8464
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8292
|
-
commonPorts: [
|
|
8465
|
+
commonPorts: [SERVICE_PORTS.SMTP, SERVICE_PORTS.POP3, SERVICE_PORTS.IMAP, SERVICE_PORTS.SMTPS, SERVICE_PORTS.SMTP_TLS, SERVICE_PORTS.IMAPS, SERVICE_PORTS.POP3S],
|
|
8293
8466
|
commonServices: [SERVICES.SMTP, SERVICES.POP3, SERVICES.IMAP]
|
|
8294
8467
|
};
|
|
8295
8468
|
|
|
@@ -8314,7 +8487,7 @@ var REMOTE_ACCESS_TOOLS = [
|
|
|
8314
8487
|
},
|
|
8315
8488
|
required: ["target"],
|
|
8316
8489
|
execute: async (params) => {
|
|
8317
|
-
return await runCommand("nmap", ["-p",
|
|
8490
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
|
|
8318
8491
|
}
|
|
8319
8492
|
}
|
|
8320
8493
|
];
|
|
@@ -8324,7 +8497,7 @@ var REMOTE_ACCESS_CONFIG = {
|
|
|
8324
8497
|
tools: REMOTE_ACCESS_TOOLS,
|
|
8325
8498
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8326
8499
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8327
|
-
commonPorts: [
|
|
8500
|
+
commonPorts: [SERVICE_PORTS.SSH, SERVICE_PORTS.RDP, SERVICE_PORTS.VNC],
|
|
8328
8501
|
commonServices: [SERVICES.SSH, SERVICES.RDP, SERVICES.VNC]
|
|
8329
8502
|
};
|
|
8330
8503
|
|
|
@@ -8338,7 +8511,7 @@ var FILE_SHARING_TOOLS = [
|
|
|
8338
8511
|
},
|
|
8339
8512
|
required: ["target"],
|
|
8340
8513
|
execute: async (params) => {
|
|
8341
|
-
return await runCommand("nmap", ["-p",
|
|
8514
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", params.target]);
|
|
8342
8515
|
}
|
|
8343
8516
|
},
|
|
8344
8517
|
{
|
|
@@ -8359,7 +8532,7 @@ var FILE_SHARING_CONFIG = {
|
|
|
8359
8532
|
tools: FILE_SHARING_TOOLS,
|
|
8360
8533
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8361
8534
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8362
|
-
commonPorts: [
|
|
8535
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SMB_NETBIOS, SERVICE_PORTS.SMB, SERVICE_PORTS.NFS],
|
|
8363
8536
|
commonServices: [SERVICES.FTP, SERVICES.SMB, SERVICES.NFS]
|
|
8364
8537
|
};
|
|
8365
8538
|
|
|
@@ -8403,7 +8576,7 @@ var CLOUD_CONFIG = {
|
|
|
8403
8576
|
tools: CLOUD_TOOLS,
|
|
8404
8577
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8405
8578
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8406
|
-
commonPorts: [
|
|
8579
|
+
commonPorts: [SERVICE_PORTS.HTTPS],
|
|
8407
8580
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8408
8581
|
};
|
|
8409
8582
|
|
|
@@ -8438,7 +8611,7 @@ var CONTAINER_CONFIG = {
|
|
|
8438
8611
|
tools: CONTAINER_TOOLS,
|
|
8439
8612
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8440
8613
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8441
|
-
commonPorts: [
|
|
8614
|
+
commonPorts: [SERVICE_PORTS.DOCKER_HTTP, SERVICE_PORTS.DOCKER_HTTPS, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.KUBERNETES_API],
|
|
8442
8615
|
commonServices: [SERVICES.DOCKER, SERVICES.KUBERNETES]
|
|
8443
8616
|
};
|
|
8444
8617
|
|
|
@@ -8501,7 +8674,7 @@ var API_CONFIG = {
|
|
|
8501
8674
|
tools: API_TOOLS,
|
|
8502
8675
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8503
8676
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8504
|
-
commonPorts: [
|
|
8677
|
+
commonPorts: [SERVICE_PORTS.NODE_DEFAULT, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.DJANGO_DEFAULT, SERVICE_PORTS.HTTP_ALT],
|
|
8505
8678
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8506
8679
|
};
|
|
8507
8680
|
|
|
@@ -8539,7 +8712,7 @@ var ICS_TOOLS = [
|
|
|
8539
8712
|
},
|
|
8540
8713
|
required: ["target"],
|
|
8541
8714
|
execute: async (params) => {
|
|
8542
|
-
return await runCommand("nmap", ["-p",
|
|
8715
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
|
|
8543
8716
|
}
|
|
8544
8717
|
}
|
|
8545
8718
|
];
|
|
@@ -8549,7 +8722,7 @@ var ICS_CONFIG = {
|
|
|
8549
8722
|
tools: ICS_TOOLS,
|
|
8550
8723
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8551
8724
|
defaultApproval: APPROVAL_LEVELS.BLOCK,
|
|
8552
|
-
commonPorts: [
|
|
8725
|
+
commonPorts: [SERVICE_PORTS.MODBUS, SERVICE_PORTS.DNP3],
|
|
8553
8726
|
commonServices: [SERVICES.MODBUS, SERVICES.DNP3]
|
|
8554
8727
|
};
|
|
8555
8728
|
|
|
@@ -8611,8 +8784,18 @@ var ToolRegistry = class {
|
|
|
8611
8784
|
this.logDeniedAction(toolCall, approval.reason || "Execution denied");
|
|
8612
8785
|
return { success: false, output: "", error: approval.reason || "Denied by policy" };
|
|
8613
8786
|
}
|
|
8614
|
-
|
|
8615
|
-
|
|
8787
|
+
let result2;
|
|
8788
|
+
try {
|
|
8789
|
+
result2 = await tool.execute(toolCall.input);
|
|
8790
|
+
} catch (execError) {
|
|
8791
|
+
const errMsg = execError instanceof Error ? execError.message : String(execError);
|
|
8792
|
+
result2 = {
|
|
8793
|
+
success: false,
|
|
8794
|
+
output: "",
|
|
8795
|
+
error: `Tool execution error: ${errMsg}`
|
|
8796
|
+
};
|
|
8797
|
+
}
|
|
8798
|
+
const command = String(toolCall.input.command || toolCall.input.url || toolCall.input.query || JSON.stringify(toolCall.input));
|
|
8616
8799
|
if (result2.success) {
|
|
8617
8800
|
this.state.workingMemory.recordSuccess(toolCall.name, command, result2.output || "");
|
|
8618
8801
|
} else {
|
|
@@ -8715,7 +8898,7 @@ var SERVICE_CATEGORY_MAP = {
|
|
|
8715
8898
|
"docker": SERVICE_CATEGORIES.CONTAINER,
|
|
8716
8899
|
"modbus": SERVICE_CATEGORIES.ICS
|
|
8717
8900
|
};
|
|
8718
|
-
var
|
|
8901
|
+
var CATEGORY_APPROVAL = {
|
|
8719
8902
|
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
8720
8903
|
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
8721
8904
|
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
@@ -8786,80 +8969,80 @@ var ServiceParser = class {
|
|
|
8786
8969
|
|
|
8787
8970
|
// src/domains/registry.ts
|
|
8788
8971
|
import { join as join7, dirname as dirname3 } from "path";
|
|
8789
|
-
import { fileURLToPath
|
|
8790
|
-
var
|
|
8972
|
+
import { fileURLToPath } from "url";
|
|
8973
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
8791
8974
|
var DOMAINS = {
|
|
8792
8975
|
[SERVICE_CATEGORIES.NETWORK]: {
|
|
8793
8976
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
8794
8977
|
name: "Network Infrastructure",
|
|
8795
8978
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
8796
|
-
promptPath: join7(
|
|
8979
|
+
promptPath: join7(__dirname, "network/prompt.md")
|
|
8797
8980
|
},
|
|
8798
8981
|
[SERVICE_CATEGORIES.WEB]: {
|
|
8799
8982
|
id: SERVICE_CATEGORIES.WEB,
|
|
8800
8983
|
name: "Web Application",
|
|
8801
8984
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
8802
|
-
promptPath: join7(
|
|
8985
|
+
promptPath: join7(__dirname, "web/prompt.md")
|
|
8803
8986
|
},
|
|
8804
8987
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
8805
8988
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
8806
8989
|
name: "Database Security",
|
|
8807
8990
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
8808
|
-
promptPath: join7(
|
|
8991
|
+
promptPath: join7(__dirname, "database/prompt.md")
|
|
8809
8992
|
},
|
|
8810
8993
|
[SERVICE_CATEGORIES.AD]: {
|
|
8811
8994
|
id: SERVICE_CATEGORIES.AD,
|
|
8812
8995
|
name: "Active Directory",
|
|
8813
8996
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
8814
|
-
promptPath: join7(
|
|
8997
|
+
promptPath: join7(__dirname, "ad/prompt.md")
|
|
8815
8998
|
},
|
|
8816
8999
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
8817
9000
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
8818
9001
|
name: "Email Services",
|
|
8819
9002
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
8820
|
-
promptPath: join7(
|
|
9003
|
+
promptPath: join7(__dirname, "email/prompt.md")
|
|
8821
9004
|
},
|
|
8822
9005
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
8823
9006
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
8824
9007
|
name: "Remote Access",
|
|
8825
9008
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
8826
|
-
promptPath: join7(
|
|
9009
|
+
promptPath: join7(__dirname, "remote-access/prompt.md")
|
|
8827
9010
|
},
|
|
8828
9011
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
8829
9012
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
8830
9013
|
name: "File Sharing",
|
|
8831
9014
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
8832
|
-
promptPath: join7(
|
|
9015
|
+
promptPath: join7(__dirname, "file-sharing/prompt.md")
|
|
8833
9016
|
},
|
|
8834
9017
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
8835
9018
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
8836
9019
|
name: "Cloud Infrastructure",
|
|
8837
9020
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
8838
|
-
promptPath: join7(
|
|
9021
|
+
promptPath: join7(__dirname, "cloud/prompt.md")
|
|
8839
9022
|
},
|
|
8840
9023
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
8841
9024
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
8842
9025
|
name: "Container Systems",
|
|
8843
9026
|
description: "Docker and Kubernetes security testing.",
|
|
8844
|
-
promptPath: join7(
|
|
9027
|
+
promptPath: join7(__dirname, "container/prompt.md")
|
|
8845
9028
|
},
|
|
8846
9029
|
[SERVICE_CATEGORIES.API]: {
|
|
8847
9030
|
id: SERVICE_CATEGORIES.API,
|
|
8848
9031
|
name: "API Security",
|
|
8849
9032
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
8850
|
-
promptPath: join7(
|
|
9033
|
+
promptPath: join7(__dirname, "api/prompt.md")
|
|
8851
9034
|
},
|
|
8852
9035
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
8853
9036
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
8854
9037
|
name: "Wireless Networks",
|
|
8855
9038
|
description: "WiFi and Bluetooth security testing.",
|
|
8856
|
-
promptPath: join7(
|
|
9039
|
+
promptPath: join7(__dirname, "wireless/prompt.md")
|
|
8857
9040
|
},
|
|
8858
9041
|
[SERVICE_CATEGORIES.ICS]: {
|
|
8859
9042
|
id: SERVICE_CATEGORIES.ICS,
|
|
8860
9043
|
name: "Industrial Systems",
|
|
8861
9044
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
8862
|
-
promptPath: join7(
|
|
9045
|
+
promptPath: join7(__dirname, "ics/prompt.md")
|
|
8863
9046
|
}
|
|
8864
9047
|
};
|
|
8865
9048
|
|
|
@@ -8905,7 +9088,7 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8905
9088
|
description: DOMAINS[id]?.description || "",
|
|
8906
9089
|
tools: [...coreTools],
|
|
8907
9090
|
dangerLevel: this.calculateDanger(id),
|
|
8908
|
-
defaultApproval:
|
|
9091
|
+
defaultApproval: CATEGORY_APPROVAL[id] || "confirm"
|
|
8909
9092
|
});
|
|
8910
9093
|
});
|
|
8911
9094
|
}
|
|
@@ -8971,7 +9154,7 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8971
9154
|
return Array.from(this.categories.values());
|
|
8972
9155
|
}
|
|
8973
9156
|
getApprovalForCategory(cat) {
|
|
8974
|
-
return
|
|
9157
|
+
return CATEGORY_APPROVAL[cat] || "confirm";
|
|
8975
9158
|
}
|
|
8976
9159
|
};
|
|
8977
9160
|
|
|
@@ -9056,14 +9239,28 @@ var LLM_ERROR_TYPES = {
|
|
|
9056
9239
|
var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
|
|
9057
9240
|
var NETWORK_ERROR_CODES = {
|
|
9058
9241
|
ECONNRESET: "ECONNRESET",
|
|
9242
|
+
ECONNREFUSED: "ECONNREFUSED",
|
|
9059
9243
|
ETIMEDOUT: "ETIMEDOUT",
|
|
9060
9244
|
ENOTFOUND: "ENOTFOUND",
|
|
9061
|
-
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
|
|
9245
|
+
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT",
|
|
9246
|
+
SOCKET_TIMEOUT: "UND_ERR_SOCKET"
|
|
9062
9247
|
};
|
|
9063
9248
|
var TRANSIENT_NETWORK_ERRORS = [
|
|
9064
9249
|
NETWORK_ERROR_CODES.ECONNRESET,
|
|
9250
|
+
NETWORK_ERROR_CODES.ECONNREFUSED,
|
|
9065
9251
|
NETWORK_ERROR_CODES.ETIMEDOUT,
|
|
9066
|
-
NETWORK_ERROR_CODES.ENOTFOUND
|
|
9252
|
+
NETWORK_ERROR_CODES.ENOTFOUND,
|
|
9253
|
+
NETWORK_ERROR_CODES.SOCKET_TIMEOUT
|
|
9254
|
+
];
|
|
9255
|
+
var NETWORK_ERROR_PATTERNS = [
|
|
9256
|
+
"fetch failed",
|
|
9257
|
+
"network error",
|
|
9258
|
+
"econnrefused",
|
|
9259
|
+
"econnreset",
|
|
9260
|
+
"enotfound",
|
|
9261
|
+
"etimedout",
|
|
9262
|
+
"socket hang up",
|
|
9263
|
+
"dns lookup failed"
|
|
9067
9264
|
];
|
|
9068
9265
|
var LLMError = class extends Error {
|
|
9069
9266
|
/** Structured error information */
|
|
@@ -9090,12 +9287,17 @@ function classifyError(error) {
|
|
|
9090
9287
|
if (statusCode === HTTP_STATUS.BAD_REQUEST) {
|
|
9091
9288
|
return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
|
|
9092
9289
|
}
|
|
9093
|
-
|
|
9290
|
+
const errorCode = e.code || e.cause?.code;
|
|
9291
|
+
if (errorCode && TRANSIENT_NETWORK_ERRORS.includes(errorCode)) {
|
|
9094
9292
|
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
9095
9293
|
}
|
|
9096
|
-
if (errorMessage.toLowerCase().includes("timeout") ||
|
|
9294
|
+
if (errorMessage.toLowerCase().includes("timeout") || errorCode === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
|
|
9097
9295
|
return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
|
|
9098
9296
|
}
|
|
9297
|
+
const lowerMsg = errorMessage.toLowerCase();
|
|
9298
|
+
if (NETWORK_ERROR_PATTERNS.some((pattern) => lowerMsg.includes(pattern))) {
|
|
9299
|
+
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
9300
|
+
}
|
|
9099
9301
|
return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
|
|
9100
9302
|
}
|
|
9101
9303
|
|
|
@@ -9174,7 +9376,11 @@ var LLMClient = class {
|
|
|
9174
9376
|
signal
|
|
9175
9377
|
});
|
|
9176
9378
|
if (!response.ok) {
|
|
9177
|
-
|
|
9379
|
+
let errorBody = `HTTP ${response.status}`;
|
|
9380
|
+
try {
|
|
9381
|
+
errorBody = await response.text();
|
|
9382
|
+
} catch {
|
|
9383
|
+
}
|
|
9178
9384
|
const error = new Error(errorBody);
|
|
9179
9385
|
error.status = response.status;
|
|
9180
9386
|
throw error;
|
|
@@ -9476,10 +9682,10 @@ function logLLM(message, data) {
|
|
|
9476
9682
|
}
|
|
9477
9683
|
|
|
9478
9684
|
// src/engine/orchestrator/orchestrator.ts
|
|
9479
|
-
import { fileURLToPath as
|
|
9685
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9480
9686
|
import { dirname as dirname4, join as join8 } from "path";
|
|
9481
|
-
var
|
|
9482
|
-
var
|
|
9687
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
9688
|
+
var __dirname2 = dirname4(__filename);
|
|
9483
9689
|
|
|
9484
9690
|
// src/engine/state-persistence.ts
|
|
9485
9691
|
import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
|
|
@@ -9547,7 +9753,10 @@ function loadState(state) {
|
|
|
9547
9753
|
state.addLoot(loot);
|
|
9548
9754
|
}
|
|
9549
9755
|
for (const item of snapshot.todo) {
|
|
9550
|
-
state.addTodo(item.content, item.priority);
|
|
9756
|
+
const id = state.addTodo(item.content, item.priority);
|
|
9757
|
+
if (item.status && item.status !== "pending") {
|
|
9758
|
+
state.updateTodo(id, { status: item.status });
|
|
9759
|
+
}
|
|
9551
9760
|
}
|
|
9552
9761
|
const validPhases = new Set(Object.values(PHASES));
|
|
9553
9762
|
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
@@ -9557,6 +9766,20 @@ function loadState(state) {
|
|
|
9557
9766
|
}
|
|
9558
9767
|
if (snapshot.missionChecklist?.length > 0) {
|
|
9559
9768
|
state.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
|
|
9769
|
+
const restoredChecklist = state.getMissionChecklist();
|
|
9770
|
+
const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
|
|
9771
|
+
const completedUpdates = [];
|
|
9772
|
+
for (let i = 0; i < snapshot.missionChecklist.length; i++) {
|
|
9773
|
+
if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
|
|
9774
|
+
completedUpdates.push({
|
|
9775
|
+
id: restoredChecklist[baseIndex + i].id,
|
|
9776
|
+
isCompleted: true
|
|
9777
|
+
});
|
|
9778
|
+
}
|
|
9779
|
+
}
|
|
9780
|
+
if (completedUpdates.length > 0) {
|
|
9781
|
+
state.updateMissionChecklist(completedUpdates);
|
|
9782
|
+
}
|
|
9560
9783
|
}
|
|
9561
9784
|
return true;
|
|
9562
9785
|
} catch (err) {
|
|
@@ -9570,8 +9793,9 @@ function clearWorkspace() {
|
|
|
9570
9793
|
const dirsToClean = [
|
|
9571
9794
|
{ path: WORKSPACE.SESSIONS, label: "sessions" },
|
|
9572
9795
|
{ path: WORKSPACE.DEBUG, label: "debug logs" },
|
|
9573
|
-
{ path: WORKSPACE.
|
|
9574
|
-
{ path: WORKSPACE.OUTPUTS, label: "outputs" }
|
|
9796
|
+
{ path: WORKSPACE.TMP, label: "temp files" },
|
|
9797
|
+
{ path: WORKSPACE.OUTPUTS, label: "outputs" },
|
|
9798
|
+
{ path: WORKSPACE.JOURNAL, label: "journal" }
|
|
9575
9799
|
];
|
|
9576
9800
|
for (const dir of dirsToClean) {
|
|
9577
9801
|
try {
|
|
@@ -9664,306 +9888,66 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9664
9888
|
}
|
|
9665
9889
|
}
|
|
9666
9890
|
|
|
9667
|
-
// src/shared/utils/
|
|
9668
|
-
|
|
9669
|
-
var
|
|
9670
|
-
var
|
|
9671
|
-
var
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
name: "nmap",
|
|
9689
|
-
signatures: [/Nmap scan report/i, /PORT\s+STATE\s+SERVICE/i, /Nmap done:/i],
|
|
9690
|
-
extract: extractNmapIntel
|
|
9691
|
-
},
|
|
9692
|
-
{
|
|
9693
|
-
name: "linpeas",
|
|
9694
|
-
signatures: [/linpeas/i, /╔══.*╗/, /Linux Privilege Escalation/i],
|
|
9695
|
-
extract: extractLinpeasIntel
|
|
9696
|
-
},
|
|
9697
|
-
{
|
|
9698
|
-
name: "enum4linux",
|
|
9699
|
-
signatures: [/enum4linux/i, /Starting enum4linux/i, /\|\s+Target\s+Information/i],
|
|
9700
|
-
extract: extractEnum4linuxIntel
|
|
9701
|
-
},
|
|
9702
|
-
{
|
|
9703
|
-
name: "gobuster/ffuf/feroxbuster",
|
|
9704
|
-
signatures: [/Gobuster/i, /FFUF/i, /feroxbuster/i, /Status:\s*\d{3}/],
|
|
9705
|
-
extract: extractDirBustIntel
|
|
9706
|
-
},
|
|
9707
|
-
{
|
|
9708
|
-
name: "sqlmap",
|
|
9709
|
-
signatures: [/sqlmap/i, /\[INFO\]\s+testing/i, /Parameter:\s+/i],
|
|
9710
|
-
extract: extractSqlmapIntel
|
|
9711
|
-
},
|
|
9712
|
-
{
|
|
9713
|
-
name: "hash_dump",
|
|
9714
|
-
signatures: [/\$[0-9]\$/, /\$2[aby]\$/, /:[0-9]+:[a-f0-9]{32}:/i],
|
|
9715
|
-
extract: extractHashIntel
|
|
9716
|
-
}
|
|
9717
|
-
];
|
|
9718
|
-
function compressToolOutput(output, toolName) {
|
|
9719
|
-
if (!output || output.length < MIN_COMPRESS_LENGTH) {
|
|
9720
|
-
return output;
|
|
9721
|
-
}
|
|
9722
|
-
let intel = [];
|
|
9723
|
-
let detectedTool = "";
|
|
9724
|
-
for (const sig of TOOL_SIGNATURES) {
|
|
9725
|
-
const matched = sig.signatures.some((s) => s.test(output));
|
|
9726
|
-
if (matched) {
|
|
9727
|
-
detectedTool = sig.name;
|
|
9728
|
-
intel = sig.extract(output);
|
|
9729
|
-
break;
|
|
9730
|
-
}
|
|
9891
|
+
// src/shared/utils/context-digest.ts
|
|
9892
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
9893
|
+
var PASSTHROUGH_THRESHOLD = 500;
|
|
9894
|
+
var PREPROCESS_THRESHOLD = 3e3;
|
|
9895
|
+
var MAX_PREPROCESSED_LINES = 800;
|
|
9896
|
+
var getOutputDir = () => WORKSPACE.OUTPUTS;
|
|
9897
|
+
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9898
|
+
var ANALYST_MAX_INPUT_CHARS = 8e4;
|
|
9899
|
+
var FALLBACK_MAX_CHARS = 3e4;
|
|
9900
|
+
async function digestToolOutput(output, toolName, toolInput, analystFn) {
|
|
9901
|
+
const originalLength = output.length;
|
|
9902
|
+
if (originalLength < PASSTHROUGH_THRESHOLD) {
|
|
9903
|
+
return {
|
|
9904
|
+
digestedOutput: output,
|
|
9905
|
+
fullOutputPath: null,
|
|
9906
|
+
analystUsed: false,
|
|
9907
|
+
memo: null,
|
|
9908
|
+
originalLength,
|
|
9909
|
+
digestedLength: originalLength,
|
|
9910
|
+
compressionRatio: 1
|
|
9911
|
+
};
|
|
9731
9912
|
}
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9913
|
+
const savedOutputPath = saveFullOutput(output, toolName);
|
|
9914
|
+
let preprocessed = output;
|
|
9915
|
+
if (originalLength > PREPROCESS_THRESHOLD) {
|
|
9916
|
+
preprocessed = structuralPreprocess(output);
|
|
9735
9917
|
}
|
|
9736
|
-
if (
|
|
9737
|
-
|
|
9918
|
+
if (analystFn) {
|
|
9919
|
+
try {
|
|
9920
|
+
const context = `Tool: ${toolName}${toolInput ? ` | Input: ${toolInput}` : ""}`;
|
|
9921
|
+
const rawAnalystResponse = await analystFn(preprocessed, context);
|
|
9922
|
+
const memo6 = parseAnalystMemo(rawAnalystResponse);
|
|
9923
|
+
const formatted = formatAnalystDigest(rawAnalystResponse, savedOutputPath, originalLength);
|
|
9924
|
+
return {
|
|
9925
|
+
digestedOutput: formatted,
|
|
9926
|
+
fullOutputPath: savedOutputPath,
|
|
9927
|
+
analystUsed: true,
|
|
9928
|
+
memo: memo6,
|
|
9929
|
+
originalLength,
|
|
9930
|
+
digestedLength: formatted.length,
|
|
9931
|
+
compressionRatio: formatted.length / originalLength
|
|
9932
|
+
};
|
|
9933
|
+
} catch (err) {
|
|
9934
|
+
debugLog("general", "Analyst LLM failed, falling back to truncation", {
|
|
9935
|
+
toolName,
|
|
9936
|
+
error: String(err)
|
|
9937
|
+
});
|
|
9938
|
+
}
|
|
9738
9939
|
}
|
|
9739
|
-
const
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
}
|
|
9750
|
-
function extractNmapIntel(output) {
|
|
9751
|
-
const intel = [];
|
|
9752
|
-
const lines = output.split("\n");
|
|
9753
|
-
const hostMatches = output.match(/Nmap scan report for\s+(\S+)/gi);
|
|
9754
|
-
const openPorts = output.match(/^\d+\/\w+\s+open\s+/gm);
|
|
9755
|
-
if (hostMatches) intel.push(`Hosts scanned: ${hostMatches.length}`);
|
|
9756
|
-
if (openPorts) intel.push(`Open ports found: ${openPorts.length}`);
|
|
9757
|
-
const portLines = lines.filter((l) => /^\d+\/\w+\s+open\s+/.test(l.trim()));
|
|
9758
|
-
if (portLines.length > 0) {
|
|
9759
|
-
intel.push("Open ports:");
|
|
9760
|
-
for (const pl of portLines.slice(0, EXTRACT_LIMITS.NMAP_PORTS)) {
|
|
9761
|
-
intel.push(` ${pl.trim()}`);
|
|
9762
|
-
}
|
|
9763
|
-
}
|
|
9764
|
-
const vulnLines = lines.filter(
|
|
9765
|
-
(l) => /VULNERABLE|CVE-\d{4}-\d+|exploit|CRITICAL/i.test(l)
|
|
9766
|
-
);
|
|
9767
|
-
if (vulnLines.length > 0) {
|
|
9768
|
-
intel.push("\u26A0\uFE0F Vulnerability indicators:");
|
|
9769
|
-
for (const vl of vulnLines.slice(0, EXTRACT_LIMITS.NMAP_VULNS)) {
|
|
9770
|
-
intel.push(` ${vl.trim()}`);
|
|
9771
|
-
}
|
|
9772
|
-
}
|
|
9773
|
-
const osMatch = output.match(/OS details:\s*(.+)/i) || output.match(/Running:\s*(.+)/i);
|
|
9774
|
-
if (osMatch) intel.push(`OS: ${osMatch[1].trim()}`);
|
|
9775
|
-
return intel;
|
|
9776
|
-
}
|
|
9777
|
-
function extractLinpeasIntel(output) {
|
|
9778
|
-
const intel = [];
|
|
9779
|
-
const suidSection = output.match(/SUID[\s\S]*?(?=\n[═╔╗━]+|\n\n\n)/i);
|
|
9780
|
-
if (suidSection) {
|
|
9781
|
-
const suidBins = suidSection[0].match(/\/\S+/g);
|
|
9782
|
-
if (suidBins) {
|
|
9783
|
-
const interestingSuid = suidBins.filter(
|
|
9784
|
-
(b) => /python|perl|ruby|node|bash|vim|less|more|nano|find|nmap|awk|env|php|gcc|gdb|docker|strace|ltrace/i.test(b)
|
|
9785
|
-
);
|
|
9786
|
-
if (interestingSuid.length > 0) {
|
|
9787
|
-
intel.push(`\u{1F534} Exploitable SUID binaries: ${interestingSuid.join(", ")}`);
|
|
9788
|
-
}
|
|
9789
|
-
}
|
|
9790
|
-
}
|
|
9791
|
-
const sudoMatch = output.match(/User \S+ may run[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9792
|
-
if (sudoMatch) {
|
|
9793
|
-
intel.push(`\u{1F534} sudo -l: ${sudoMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_SUDO)}`);
|
|
9794
|
-
}
|
|
9795
|
-
const writableMatch = output.match(/Interesting writable[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9796
|
-
if (writableMatch) {
|
|
9797
|
-
intel.push(`\u{1F4DD} Writable: ${writableMatch[0].trim().slice(0, EXTRACT_LIMITS.LINPEAS_WRITABLE)}`);
|
|
9798
|
-
}
|
|
9799
|
-
const cronMatch = output.match(/Cron[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
9800
|
-
if (cronMatch) {
|
|
9801
|
-
const cronLines = cronMatch[0].split("\n").filter((l) => l.includes("*") || /\/(root|cron)/i.test(l));
|
|
9802
|
-
if (cronLines.length > 0) {
|
|
9803
|
-
intel.push("\u23F0 Cron entries:");
|
|
9804
|
-
cronLines.slice(0, EXTRACT_LIMITS.LINPEAS_CRON).forEach((c) => intel.push(` ${c.trim()}`));
|
|
9805
|
-
}
|
|
9806
|
-
}
|
|
9807
|
-
const kernelMatch = output.match(/Linux version\s+(\S+)/i) || output.match(/Kernel:\s*(\S+)/i);
|
|
9808
|
-
if (kernelMatch) intel.push(`\u{1F427} Kernel: ${kernelMatch[1]}`);
|
|
9809
|
-
const passLines = output.split("\n").filter(
|
|
9810
|
-
(l) => /password\s*[=:]\s*\S+/i.test(l) && !/\*\*\*|example|sample/i.test(l)
|
|
9811
|
-
);
|
|
9812
|
-
if (passLines.length > 0) {
|
|
9813
|
-
intel.push("\u{1F511} Potential credentials found:");
|
|
9814
|
-
passLines.slice(0, EXTRACT_LIMITS.LINPEAS_PASSWORDS).forEach((p) => intel.push(` ${p.trim()}`));
|
|
9815
|
-
}
|
|
9816
|
-
const cveMatches = output.match(/CVE-\d{4}-\d+/gi);
|
|
9817
|
-
if (cveMatches) {
|
|
9818
|
-
const uniqueCves = [...new Set(cveMatches)];
|
|
9819
|
-
intel.push(`\u26A0\uFE0F CVEs mentioned: ${uniqueCves.join(", ")}`);
|
|
9820
|
-
}
|
|
9821
|
-
return intel;
|
|
9822
|
-
}
|
|
9823
|
-
function extractEnum4linuxIntel(output) {
|
|
9824
|
-
const intel = [];
|
|
9825
|
-
const userMatches = output.match(/user:\[(\S+?)\]/gi);
|
|
9826
|
-
if (userMatches) {
|
|
9827
|
-
const users = userMatches.map((u) => u.replace(/user:\[|\]/gi, ""));
|
|
9828
|
-
intel.push(`\u{1F464} Users found: ${users.join(", ")}`);
|
|
9829
|
-
}
|
|
9830
|
-
const shareMatches = output.match(/Mapping: (\S+),\s*Listing: (\S+)/gi) || output.match(/\\\\\S+\\\\\S+/g);
|
|
9831
|
-
if (shareMatches) {
|
|
9832
|
-
intel.push(`\u{1F4C2} Shares: ${shareMatches.slice(0, EXTRACT_LIMITS.ENUM4LINUX_SHARES).join(", ")}`);
|
|
9833
|
-
}
|
|
9834
|
-
const domainMatch = output.match(/Domain:\s*\[(\S+?)\]/i) || output.match(/Workgroup:\s*\[(\S+?)\]/i);
|
|
9835
|
-
if (domainMatch) intel.push(`\u{1F3E2} Domain: ${domainMatch[1]}`);
|
|
9836
|
-
const policyMatch = output.match(/Password Complexity|Account Lockout/i);
|
|
9837
|
-
if (policyMatch) intel.push("\u{1F510} Password policy information found");
|
|
9838
|
-
return intel;
|
|
9839
|
-
}
|
|
9840
|
-
function extractDirBustIntel(output) {
|
|
9841
|
-
const intel = [];
|
|
9842
|
-
const lines = output.split("\n");
|
|
9843
|
-
const interestingPaths = [];
|
|
9844
|
-
for (const line of lines) {
|
|
9845
|
-
const match = line.match(/(?:Status:\s*(\d{3})|(\d{3})\s+\d+\s+\d+).*?(\/\S+)/);
|
|
9846
|
-
if (match) {
|
|
9847
|
-
const status = match[1] || match[2];
|
|
9848
|
-
const path2 = match[3];
|
|
9849
|
-
if (["200", "301", "302", "403"].includes(status)) {
|
|
9850
|
-
interestingPaths.push(`[${status}] ${path2}`);
|
|
9851
|
-
}
|
|
9852
|
-
}
|
|
9853
|
-
const ffufMatch = line.match(/(\S+)\s+\[Status:\s*(\d+),?\s*Size:\s*(\d+)/);
|
|
9854
|
-
if (ffufMatch && ["200", "301", "302", "403"].includes(ffufMatch[2])) {
|
|
9855
|
-
interestingPaths.push(`[${ffufMatch[2]}] ${ffufMatch[1]} (${ffufMatch[3]}b)`);
|
|
9856
|
-
}
|
|
9857
|
-
}
|
|
9858
|
-
if (interestingPaths.length > 0) {
|
|
9859
|
-
intel.push(`\u{1F4C1} Discovered paths (${interestingPaths.length}):`);
|
|
9860
|
-
interestingPaths.slice(0, EXTRACT_LIMITS.DIRBUST_PATHS).forEach((p) => intel.push(` ${p}`));
|
|
9861
|
-
if (interestingPaths.length > EXTRACT_LIMITS.DIRBUST_PATHS) {
|
|
9862
|
-
intel.push(` ... and ${interestingPaths.length - EXTRACT_LIMITS.DIRBUST_PATHS} more`);
|
|
9863
|
-
}
|
|
9864
|
-
}
|
|
9865
|
-
return intel;
|
|
9866
|
-
}
|
|
9867
|
-
function extractSqlmapIntel(output) {
|
|
9868
|
-
const intel = [];
|
|
9869
|
-
const injectionTypes = output.match(/Type:\s*(\S.*?)(?:\n|$)/gi);
|
|
9870
|
-
if (injectionTypes) {
|
|
9871
|
-
intel.push("\u{1F489} SQL injection found:");
|
|
9872
|
-
injectionTypes.slice(0, EXTRACT_LIMITS.SQLMAP_INJECTIONS).forEach((t) => intel.push(` ${t.trim()}`));
|
|
9873
|
-
}
|
|
9874
|
-
const dbMatch = output.match(/back-end DBMS:\s*(.+)/i);
|
|
9875
|
-
if (dbMatch) intel.push(`\u{1F5C4}\uFE0F DBMS: ${dbMatch[1].trim()}`);
|
|
9876
|
-
const dbListMatch = output.match(/available databases.*?:\s*([\s\S]*?)(?=\n\[|\n$)/i);
|
|
9877
|
-
if (dbListMatch) {
|
|
9878
|
-
intel.push(`\u{1F4CA} Databases: ${dbListMatch[1].trim().replace(/\n/g, ", ")}`);
|
|
9879
|
-
}
|
|
9880
|
-
const tableMatches = output.match(/Database:\s*\S+\s+Table:\s*\S+/gi);
|
|
9881
|
-
if (tableMatches) {
|
|
9882
|
-
intel.push(`\u{1F4CB} Tables dumped: ${tableMatches.length}`);
|
|
9883
|
-
}
|
|
9884
|
-
return intel;
|
|
9885
|
-
}
|
|
9886
|
-
function extractHashIntel(output) {
|
|
9887
|
-
const intel = [];
|
|
9888
|
-
const lines = output.split("\n");
|
|
9889
|
-
const md5Hashes = lines.filter((l) => /\b[a-f0-9]{32}\b/i.test(l) && !l.includes("{"));
|
|
9890
|
-
const sha256Hashes = lines.filter((l) => /\b[a-f0-9]{64}\b/i.test(l));
|
|
9891
|
-
const unixHashes = lines.filter((l) => /\$[0-9]\$|\$2[aby]\$|\$6\$|\$5\$/i.test(l));
|
|
9892
|
-
const ntlmHashes = lines.filter((l) => /:[0-9]+:[a-f0-9]{32}:[a-f0-9]{32}:::/i.test(l));
|
|
9893
|
-
if (md5Hashes.length > 0) intel.push(`#\uFE0F\u20E3 MD5 hashes: ${md5Hashes.length}`);
|
|
9894
|
-
if (sha256Hashes.length > 0) intel.push(`#\uFE0F\u20E3 SHA256 hashes: ${sha256Hashes.length}`);
|
|
9895
|
-
if (unixHashes.length > 0) intel.push(`#\uFE0F\u20E3 Unix crypt hashes: ${unixHashes.length}`);
|
|
9896
|
-
if (ntlmHashes.length > 0) {
|
|
9897
|
-
intel.push(`#\uFE0F\u20E3 NTLM hashes: ${ntlmHashes.length}`);
|
|
9898
|
-
ntlmHashes.slice(0, EXTRACT_LIMITS.HASH_NTLM).forEach((h) => intel.push(` ${h.trim().slice(0, EXTRACT_LIMITS.HASH_PREVIEW_LEN)}`));
|
|
9899
|
-
}
|
|
9900
|
-
return intel;
|
|
9901
|
-
}
|
|
9902
|
-
function extractGenericIntel(output) {
|
|
9903
|
-
const intel = [];
|
|
9904
|
-
const credPatterns = output.match(/(?:password|passwd|pwd|credentials?)\s*[=:]\s*\S+/gi);
|
|
9905
|
-
if (credPatterns) {
|
|
9906
|
-
intel.push("\u{1F511} Potential credentials detected:");
|
|
9907
|
-
credPatterns.slice(0, EXTRACT_LIMITS.GENERIC_CREDS).forEach((c) => intel.push(` ${c.trim()}`));
|
|
9908
|
-
}
|
|
9909
|
-
const cves = output.match(/CVE-\d{4}-\d+/gi);
|
|
9910
|
-
if (cves) {
|
|
9911
|
-
const unique = [...new Set(cves)];
|
|
9912
|
-
intel.push(`\u26A0\uFE0F CVEs mentioned: ${unique.join(", ")}`);
|
|
9913
|
-
}
|
|
9914
|
-
const ips = output.match(/\b(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\b/g);
|
|
9915
|
-
if (ips) {
|
|
9916
|
-
const uniqueIps = [...new Set(ips)].filter(
|
|
9917
|
-
(ip) => !ip.startsWith("0.") && !ip.startsWith("255.") && ip !== "127.0.0.1"
|
|
9918
|
-
);
|
|
9919
|
-
if (uniqueIps.length > 0 && uniqueIps.length <= 20) {
|
|
9920
|
-
intel.push(`\u{1F310} IP addresses found: ${uniqueIps.join(", ")}`);
|
|
9921
|
-
}
|
|
9922
|
-
}
|
|
9923
|
-
const paths = output.match(/\/(?:etc\/(?:shadow|passwd|sudoers)|root\/|home\/\S+|var\/www\/\S+|opt\/\S+)\S*/g);
|
|
9924
|
-
if (paths) {
|
|
9925
|
-
const uniquePaths = [...new Set(paths)].slice(0, EXTRACT_LIMITS.GENERIC_PATHS);
|
|
9926
|
-
intel.push(`\u{1F4C2} Interesting paths: ${uniquePaths.join(", ")}`);
|
|
9927
|
-
}
|
|
9928
|
-
const flagPatterns = output.match(/(?:flag|secret|key|token)\{[^}]+\}/gi);
|
|
9929
|
-
if (flagPatterns) {
|
|
9930
|
-
intel.push("\u{1F3F4} FLAG/SECRET patterns detected:");
|
|
9931
|
-
flagPatterns.forEach((f) => intel.push(` ${f}`));
|
|
9932
|
-
}
|
|
9933
|
-
return intel;
|
|
9940
|
+
const fallback = formatFallbackDigest(preprocessed, savedOutputPath, originalLength);
|
|
9941
|
+
return {
|
|
9942
|
+
digestedOutput: fallback,
|
|
9943
|
+
fullOutputPath: savedOutputPath,
|
|
9944
|
+
analystUsed: false,
|
|
9945
|
+
memo: null,
|
|
9946
|
+
originalLength,
|
|
9947
|
+
digestedLength: fallback.length,
|
|
9948
|
+
compressionRatio: fallback.length / originalLength
|
|
9949
|
+
};
|
|
9934
9950
|
}
|
|
9935
|
-
|
|
9936
|
-
// src/shared/utils/context-digest.ts
|
|
9937
|
-
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
9938
|
-
var PASSTHROUGH_THRESHOLD = 3e3;
|
|
9939
|
-
var LAYER2_THRESHOLD = 8e3;
|
|
9940
|
-
var LAYER3_THRESHOLD = 5e4;
|
|
9941
|
-
var MAX_REDUCED_LINES = 500;
|
|
9942
|
-
var getOutputDir = () => WORKSPACE.OUTPUTS;
|
|
9943
|
-
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9944
|
-
var LAYER3_MAX_INPUT_CHARS = 8e4;
|
|
9945
|
-
var FALLBACK_MAX_CHARS = 3e4;
|
|
9946
|
-
var SIGNAL_PATTERNS = [
|
|
9947
|
-
/error|fail|denied|refused|timeout|exception/i,
|
|
9948
|
-
/warning|warn|deprecated|insecure/i,
|
|
9949
|
-
/success|found|detected|discovered|vulnerable|VULNERABLE/i,
|
|
9950
|
-
/password|passwd|credential|secret|key|token|hash/i,
|
|
9951
|
-
/CVE-\d{4}-\d+/i,
|
|
9952
|
-
/\d+\/\w+\s+open\s+/,
|
|
9953
|
-
// nmap open port
|
|
9954
|
-
/flag\{|ctf\{|HTB\{|THM\{/i,
|
|
9955
|
-
// CTF flags
|
|
9956
|
-
/root:|admin:|www-data:|nobody:/,
|
|
9957
|
-
// /etc/passwd entries
|
|
9958
|
-
/\$[0-9]\$|\$2[aby]\$/,
|
|
9959
|
-
// password hashes
|
|
9960
|
-
/\b(?:192\.168|10\.|172\.(?:1[6-9]|2\d|3[01]))\.\d+\.\d+\b/,
|
|
9961
|
-
// internal IPs
|
|
9962
|
-
/BEGIN\s+(?:RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY/i,
|
|
9963
|
-
// private keys
|
|
9964
|
-
/Authorization:|Bearer\s+|Basic\s+/i
|
|
9965
|
-
// auth tokens
|
|
9966
|
-
];
|
|
9967
9951
|
var NOISE_PATTERNS = [
|
|
9968
9952
|
/^\s*$/,
|
|
9969
9953
|
// blank lines
|
|
@@ -9971,67 +9955,40 @@ var NOISE_PATTERNS = [
|
|
|
9971
9955
|
// timestamp-only lines
|
|
9972
9956
|
/^#+\s*$/,
|
|
9973
9957
|
// separator lines
|
|
9974
|
-
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d
|
|
9958
|
+
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d/i,
|
|
9975
9959
|
// progress bars
|
|
9976
9960
|
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
9977
9961
|
// request counters
|
|
9978
9962
|
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
9979
9963
|
// decoration lines
|
|
9980
9964
|
];
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9965
|
+
function structuralPreprocess(output) {
|
|
9966
|
+
let cleaned = stripAnsi(output);
|
|
9967
|
+
const filteredLines = filterAndDedup(cleaned.split("\n"));
|
|
9968
|
+
if (filteredLines.length > MAX_PREPROCESSED_LINES) {
|
|
9969
|
+
const headSize = Math.floor(MAX_PREPROCESSED_LINES * 0.5);
|
|
9970
|
+
const tailSize = Math.floor(MAX_PREPROCESSED_LINES * 0.3);
|
|
9971
|
+
const head = filteredLines.slice(0, headSize);
|
|
9972
|
+
const tail = filteredLines.slice(-tailSize);
|
|
9973
|
+
const skipped = filteredLines.length - headSize - tailSize;
|
|
9974
|
+
cleaned = [
|
|
9975
|
+
...head,
|
|
9976
|
+
"",
|
|
9977
|
+
`... [${skipped} lines skipped for Analyst LLM context \u2014 full output saved to file] ...`,
|
|
9978
|
+
"",
|
|
9979
|
+
...tail
|
|
9980
|
+
].join("\n");
|
|
9981
|
+
} else {
|
|
9982
|
+
cleaned = filteredLines.join("\n");
|
|
9992
9983
|
}
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
processed = compressToolOutput(processed, toolName);
|
|
9997
|
-
layersApplied.push(1);
|
|
9998
|
-
if (processed.length > LAYER2_THRESHOLD) {
|
|
9999
|
-
processed = structuralReduce(processed);
|
|
10000
|
-
layersApplied.push(2);
|
|
10001
|
-
}
|
|
10002
|
-
if (processed.length > LAYER3_THRESHOLD) {
|
|
10003
|
-
savedOutputPath = saveFullOutput(output, toolName);
|
|
10004
|
-
if (llmDigestFn) {
|
|
10005
|
-
try {
|
|
10006
|
-
const context = `Tool: ${toolName}${toolInput ? ` | Input: ${toolInput}` : ""}`;
|
|
10007
|
-
const digest = await llmDigestFn(processed, context);
|
|
10008
|
-
processed = formatLLMDigest(digest, savedOutputPath, originalLength);
|
|
10009
|
-
layersApplied.push(3);
|
|
10010
|
-
} catch (err) {
|
|
10011
|
-
debugLog("general", "Context Digest Layer 3 failed, falling back", { toolName, error: String(err) });
|
|
10012
|
-
processed = formatFallbackDigest(processed, savedOutputPath, originalLength);
|
|
10013
|
-
layersApplied.push(3);
|
|
10014
|
-
}
|
|
10015
|
-
} else {
|
|
10016
|
-
processed = formatFallbackDigest(processed, savedOutputPath, originalLength);
|
|
10017
|
-
}
|
|
10018
|
-
} else if (layersApplied.includes(2)) {
|
|
10019
|
-
savedOutputPath = saveFullOutput(output, toolName);
|
|
9984
|
+
if (cleaned.length > ANALYST_MAX_INPUT_CHARS) {
|
|
9985
|
+
cleaned = cleaned.slice(0, ANALYST_MAX_INPUT_CHARS) + `
|
|
9986
|
+
... [truncated at ${ANALYST_MAX_INPUT_CHARS} chars for Analyst LLM \u2014 full output saved to file]`;
|
|
10020
9987
|
}
|
|
10021
|
-
return
|
|
10022
|
-
digestedOutput: processed,
|
|
10023
|
-
fullOutputPath: savedOutputPath,
|
|
10024
|
-
layersApplied,
|
|
10025
|
-
originalLength,
|
|
10026
|
-
digestedLength: processed.length,
|
|
10027
|
-
compressionRatio: processed.length / originalLength
|
|
10028
|
-
};
|
|
9988
|
+
return cleaned;
|
|
10029
9989
|
}
|
|
10030
|
-
function
|
|
10031
|
-
let cleaned = stripAnsi(output);
|
|
10032
|
-
const lines = cleaned.split("\n");
|
|
9990
|
+
function filterAndDedup(lines) {
|
|
10033
9991
|
const result2 = [];
|
|
10034
|
-
const duplicateCounts = /* @__PURE__ */ new Map();
|
|
10035
9992
|
let lastLine = "";
|
|
10036
9993
|
let consecutiveDupes = 0;
|
|
10037
9994
|
for (const line of lines) {
|
|
@@ -10039,11 +9996,9 @@ function structuralReduce(output) {
|
|
|
10039
9996
|
if (NOISE_PATTERNS.some((p) => p.test(trimmed))) {
|
|
10040
9997
|
continue;
|
|
10041
9998
|
}
|
|
10042
|
-
const isSignal = SIGNAL_PATTERNS.some((p) => p.test(trimmed));
|
|
10043
9999
|
const normalized = normalizeLine(trimmed);
|
|
10044
|
-
if (normalized === normalizeLine(lastLine)
|
|
10000
|
+
if (normalized === normalizeLine(lastLine)) {
|
|
10045
10001
|
consecutiveDupes++;
|
|
10046
|
-
duplicateCounts.set(normalized, (duplicateCounts.get(normalized) || 1) + 1);
|
|
10047
10002
|
continue;
|
|
10048
10003
|
}
|
|
10049
10004
|
if (consecutiveDupes > 0) {
|
|
@@ -10066,57 +10021,104 @@ function structuralReduce(output) {
|
|
|
10066
10021
|
result2.push(lastLine);
|
|
10067
10022
|
}
|
|
10068
10023
|
}
|
|
10069
|
-
|
|
10070
|
-
const headSize = Math.floor(MAX_REDUCED_LINES * 0.4);
|
|
10071
|
-
const tailSize = Math.floor(MAX_REDUCED_LINES * 0.3);
|
|
10072
|
-
const signalBudget = MAX_REDUCED_LINES - headSize - tailSize;
|
|
10073
|
-
const head = result2.slice(0, headSize);
|
|
10074
|
-
const tail = result2.slice(-tailSize);
|
|
10075
|
-
const middle = result2.slice(headSize, -tailSize);
|
|
10076
|
-
const middleSignals = middle.filter((line) => SIGNAL_PATTERNS.some((p) => p.test(line))).slice(0, signalBudget);
|
|
10077
|
-
const skipped = middle.length - middleSignals.length;
|
|
10078
|
-
cleaned = [
|
|
10079
|
-
...head,
|
|
10080
|
-
"",
|
|
10081
|
-
`... [${skipped} routine lines skipped \u2014 ${middleSignals.length} important lines preserved] ...`,
|
|
10082
|
-
"",
|
|
10083
|
-
...middleSignals,
|
|
10084
|
-
"",
|
|
10085
|
-
`... [resuming last ${tailSize} lines] ...`,
|
|
10086
|
-
"",
|
|
10087
|
-
...tail
|
|
10088
|
-
].join("\n");
|
|
10089
|
-
} else {
|
|
10090
|
-
cleaned = result2.join("\n");
|
|
10091
|
-
}
|
|
10092
|
-
return cleaned;
|
|
10024
|
+
return result2;
|
|
10093
10025
|
}
|
|
10094
|
-
var
|
|
10026
|
+
var ANALYST_SYSTEM_PROMPT = `You are an independent pentesting output analyst. You receive raw tool output and must extract ONLY actionable intelligence for the main attack agent.
|
|
10095
10027
|
|
|
10096
10028
|
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
10029
|
+
|
|
10097
10030
|
## Key Findings
|
|
10098
|
-
- [finding 1]
|
|
10031
|
+
- [finding 1 with exact values: ports, versions, paths]
|
|
10099
10032
|
- [finding 2]
|
|
10100
10033
|
|
|
10101
10034
|
## Credentials/Secrets
|
|
10102
|
-
- [any discovered credentials, hashes, tokens, keys]
|
|
10035
|
+
- [any discovered credentials, hashes, tokens, keys, certificates]
|
|
10036
|
+
- (write "None found" if none)
|
|
10103
10037
|
|
|
10104
10038
|
## Attack Vectors
|
|
10105
|
-
- [exploitable services, vulnerabilities, misconfigurations]
|
|
10039
|
+
- [exploitable services, vulnerabilities, misconfigurations, CVEs]
|
|
10040
|
+
- (write "None identified" if none)
|
|
10041
|
+
|
|
10042
|
+
## Failures/Errors
|
|
10043
|
+
- [what was attempted and FAILED \u2014 include the FULL command, wordlist, target, and the reason WHY it failed]
|
|
10044
|
+
- [e.g.: "SSH brute force: hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://10.0.0.1 \u2014 connection refused (port filtered)"]
|
|
10045
|
+
- [e.g.: "SQLi on /login with sqlmap --tamper=space2comment \u2014 input sanitized, WAF detected (ModSecurity)"]
|
|
10046
|
+
- (write "No failures" if everything succeeded)
|
|
10047
|
+
|
|
10048
|
+
## Suspicious Signals
|
|
10049
|
+
- [anomalies that are NOT confirmed vulnerabilities but suggest exploitable surface]
|
|
10050
|
+
- [e.g.: "Response time 3x slower on /admin path \u2014 possible auth check or backend processing"]
|
|
10051
|
+
- [e.g.: "X-Debug-Token header present \u2014 debug mode may be enabled"]
|
|
10052
|
+
- [e.g.: "Verbose error message reveals stack trace / internal path / DB schema"]
|
|
10053
|
+
- [e.g.: "Unexpected 302 redirect with session param leaked in URL"]
|
|
10054
|
+
- (write "No suspicious signals" if nothing anomalous)
|
|
10055
|
+
|
|
10056
|
+
## Attack Value
|
|
10057
|
+
- [ONE word: HIGH / MED / LOW / NONE]
|
|
10058
|
+
- Reasoning: [1 sentence why \u2014 what makes this worth pursuing or abandoning]
|
|
10106
10059
|
|
|
10107
10060
|
## Next Steps
|
|
10108
10061
|
- [recommended immediate actions based on findings]
|
|
10109
10062
|
|
|
10110
10063
|
RULES:
|
|
10111
|
-
-
|
|
10112
|
-
-
|
|
10064
|
+
- Include EXACT values: port numbers, versions, usernames, file paths, IPs, full commands used
|
|
10065
|
+
- For failures: include the COMPLETE command with all flags, wordlists, and targets \u2014 "brute force failed" alone is USELESS
|
|
10066
|
+
- Look for the UNEXPECTED \u2014 non-standard ports, unusual banners, timing anomalies, error leaks
|
|
10067
|
+
- Credentials include: passwords, hashes, API keys, tokens, private keys, cookies, session IDs
|
|
10068
|
+
- Flag any information disclosure: server versions, internal paths, stack traces, debug output
|
|
10113
10069
|
- If nothing interesting found, say "No actionable findings in this output"
|
|
10114
|
-
-
|
|
10115
|
-
-
|
|
10116
|
-
|
|
10070
|
+
- Never include decorative output, banners, or progress information
|
|
10071
|
+
- Do NOT miss subtle signals: unusual HTTP headers, non-standard responses, timing differences
|
|
10072
|
+
- Write as much detail as needed \u2014 do NOT artificially shorten. Every detail matters for strategy.
|
|
10073
|
+
|
|
10074
|
+
## Reflection
|
|
10075
|
+
- What this output tells us: [1-line assessment]
|
|
10076
|
+
- Recommended next action: [1-2 specific follow-up actions]`;
|
|
10077
|
+
function parseAnalystMemo(response) {
|
|
10078
|
+
const sections = {};
|
|
10079
|
+
let currentSection = "";
|
|
10080
|
+
let reflectionLines = [];
|
|
10081
|
+
let attackValueLine = "NONE";
|
|
10082
|
+
let attackValueReasoning = "";
|
|
10083
|
+
for (const line of response.split("\n")) {
|
|
10084
|
+
if (line.startsWith("## ")) {
|
|
10085
|
+
currentSection = line.replace("## ", "").trim().toLowerCase();
|
|
10086
|
+
sections[currentSection] = [];
|
|
10087
|
+
} else if (currentSection === "reflection") {
|
|
10088
|
+
if (line.trim()) reflectionLines.push(line.trim());
|
|
10089
|
+
} else if (currentSection === "attack value") {
|
|
10090
|
+
const match = line.match(/\b(HIGH|MED|LOW|NONE)\b/);
|
|
10091
|
+
if (match) attackValueLine = match[1];
|
|
10092
|
+
const reasonMatch = line.match(/[Rr]easoning:\s*(.+)/);
|
|
10093
|
+
if (reasonMatch) attackValueReasoning = reasonMatch[1].trim();
|
|
10094
|
+
} else if (currentSection) {
|
|
10095
|
+
const trimmed = line.trim();
|
|
10096
|
+
if (!trimmed) continue;
|
|
10097
|
+
const content = trimmed.replace(/^(?:-|\*|\d+[.)]\s*)\s*/, "").trim();
|
|
10098
|
+
if (content) sections[currentSection].push(content);
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
const filterNone = (items) => items.filter((i) => !/(^none|^no )/i.test(i.trim()));
|
|
10102
|
+
const rawValue = attackValueLine.toUpperCase();
|
|
10103
|
+
const attackValue = ["HIGH", "MED", "LOW", "NONE"].includes(rawValue) ? rawValue : "LOW";
|
|
10104
|
+
return {
|
|
10105
|
+
keyFindings: filterNone(sections["key findings"] || []),
|
|
10106
|
+
credentials: filterNone(sections["credentials/secrets"] || []),
|
|
10107
|
+
attackVectors: filterNone(sections["attack vectors"] || []),
|
|
10108
|
+
failures: filterNone(sections["failures/errors"] || []),
|
|
10109
|
+
suspicions: filterNone(sections["suspicious signals"] || []),
|
|
10110
|
+
attackValue,
|
|
10111
|
+
nextSteps: filterNone(sections["next steps"] || []),
|
|
10112
|
+
reflection: [
|
|
10113
|
+
attackValueReasoning ? `[${attackValue}] ${attackValueReasoning}` : "",
|
|
10114
|
+
...reflectionLines
|
|
10115
|
+
].filter(Boolean).join(" | ")
|
|
10116
|
+
};
|
|
10117
|
+
}
|
|
10118
|
+
function formatAnalystDigest(digest, filePath, originalChars) {
|
|
10117
10119
|
return [
|
|
10118
10120
|
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
10119
|
-
"\u2551
|
|
10121
|
+
"\u2551 ANALYST DIGEST (Independent LLM analysis) \u2551",
|
|
10120
10122
|
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
10121
10123
|
"",
|
|
10122
10124
|
digest,
|
|
@@ -10126,29 +10128,26 @@ function formatLLMDigest(digest, filePath, originalChars) {
|
|
|
10126
10128
|
].join("\n");
|
|
10127
10129
|
}
|
|
10128
10130
|
function formatFallbackDigest(processed, filePath, originalChars) {
|
|
10129
|
-
const lines = processed.split("\n");
|
|
10130
|
-
const summaryEndIdx = lines.findIndex((l) => l.includes("END SUMMARY"));
|
|
10131
|
-
const summaryBlock = summaryEndIdx > 0 ? lines.slice(0, summaryEndIdx + 1).join("\n") : "";
|
|
10132
|
-
const remaining = summaryEndIdx > 0 ? lines.slice(summaryEndIdx + 1).join("\n") : processed;
|
|
10133
10131
|
const maxChars = FALLBACK_MAX_CHARS;
|
|
10134
|
-
let
|
|
10135
|
-
if (
|
|
10132
|
+
let truncated = processed;
|
|
10133
|
+
if (processed.length > maxChars) {
|
|
10136
10134
|
const headChars = Math.floor(maxChars * 0.6);
|
|
10137
10135
|
const tailChars = Math.floor(maxChars * 0.4);
|
|
10138
|
-
const skipped =
|
|
10139
|
-
|
|
10136
|
+
const skipped = processed.length - headChars - tailChars;
|
|
10137
|
+
truncated = processed.slice(0, headChars) + `
|
|
10140
10138
|
|
|
10141
|
-
... [${skipped} chars omitted \u2014 read full output from file] ...
|
|
10139
|
+
... [${skipped} chars omitted \u2014 Analyst LLM unavailable, read full output from file] ...
|
|
10142
10140
|
|
|
10143
|
-
` +
|
|
10141
|
+
` + processed.slice(-tailChars);
|
|
10144
10142
|
}
|
|
10145
10143
|
return [
|
|
10146
|
-
|
|
10147
|
-
|
|
10144
|
+
"\u26A0\uFE0F ANALYST UNAVAILABLE \u2014 showing truncated raw output:",
|
|
10145
|
+
"",
|
|
10146
|
+
truncated,
|
|
10148
10147
|
"",
|
|
10149
10148
|
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
10150
10149
|
`\u{1F4A1} Use read_file("${filePath}") to see the complete raw output.`
|
|
10151
|
-
].
|
|
10150
|
+
].join("\n");
|
|
10152
10151
|
}
|
|
10153
10152
|
function stripAnsi(text) {
|
|
10154
10153
|
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\[[\d;]*m/g, "");
|
|
@@ -10174,20 +10173,21 @@ function saveFullOutput(output, toolName) {
|
|
|
10174
10173
|
}
|
|
10175
10174
|
function createLLMDigestFn(llmClient) {
|
|
10176
10175
|
return async (text, context) => {
|
|
10177
|
-
const
|
|
10178
|
-
|
|
10179
|
-
|
|
10176
|
+
const messages = [{
|
|
10177
|
+
role: LLM_ROLES.USER,
|
|
10178
|
+
content: `Analyze this pentesting tool output and extract actionable intelligence.
|
|
10180
10179
|
|
|
10181
10180
|
Context: ${context}
|
|
10182
10181
|
|
|
10183
10182
|
--- OUTPUT START ---
|
|
10184
|
-
${
|
|
10185
|
-
--- OUTPUT END ---`
|
|
10183
|
+
${text}
|
|
10184
|
+
--- OUTPUT END ---`
|
|
10185
|
+
}];
|
|
10186
10186
|
const response = await llmClient.generateResponse(
|
|
10187
10187
|
messages,
|
|
10188
10188
|
void 0,
|
|
10189
|
-
// no tools —
|
|
10190
|
-
|
|
10189
|
+
// no tools — analysis only
|
|
10190
|
+
ANALYST_SYSTEM_PROMPT
|
|
10191
10191
|
);
|
|
10192
10192
|
return response.content || "No actionable findings extracted.";
|
|
10193
10193
|
};
|
|
@@ -10202,6 +10202,16 @@ var CoreAgent = class _CoreAgent {
|
|
|
10202
10202
|
agentType;
|
|
10203
10203
|
maxIterations;
|
|
10204
10204
|
abortController = null;
|
|
10205
|
+
/**
|
|
10206
|
+
* Collected tool execution records for the current turn.
|
|
10207
|
+
* MainAgent reads this after each step to write journal entries.
|
|
10208
|
+
* Cleared at the start of each step.
|
|
10209
|
+
*/
|
|
10210
|
+
turnToolJournal = [];
|
|
10211
|
+
/** Aggregated memo from all tools in the current turn */
|
|
10212
|
+
turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10213
|
+
/** Analyst reflections collected during this turn (1-line assessments) */
|
|
10214
|
+
turnReflections = [];
|
|
10205
10215
|
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
10206
10216
|
this.agentType = agentType;
|
|
10207
10217
|
this.state = state;
|
|
@@ -10260,38 +10270,7 @@ var CoreAgent = class _CoreAgent {
|
|
|
10260
10270
|
}
|
|
10261
10271
|
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
10262
10272
|
progress.consecutiveIdleIterations = 0;
|
|
10263
|
-
|
|
10264
|
-
const targets = this.state.getTargets().size;
|
|
10265
|
-
const findings = this.state.getFindings().length;
|
|
10266
|
-
const phaseDirection = {
|
|
10267
|
-
[PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
|
|
10268
|
-
[PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
|
|
10269
|
-
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
|
|
10270
|
-
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
|
|
10271
|
-
[PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
|
|
10272
|
-
[PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
|
|
10273
|
-
[PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
|
|
10274
|
-
};
|
|
10275
|
-
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
10276
|
-
messages.push({
|
|
10277
|
-
role: LLM_ROLES.USER,
|
|
10278
|
-
content: `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
10279
|
-
Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
|
|
10280
|
-
|
|
10281
|
-
${direction}
|
|
10282
|
-
|
|
10283
|
-
ESCALATION CHAIN \u2014 follow this order:
|
|
10284
|
-
1. web_search: Search for techniques, bypasses, default creds, CVEs, HackTricks
|
|
10285
|
-
2. BYPASS: Try alternative approaches \u2014 different protocols, ports, encodings, methods
|
|
10286
|
-
3. ZERO-DAY EXPLORATION: Probe for unknown vulns \u2014 fuzz parameters, test edge cases, analyze error responses for leaks
|
|
10287
|
-
4. BRUTE-FORCE: Wordlists, credential stuffing, common passwords, custom password lists from context
|
|
10288
|
-
5. ask_user: ONLY as last resort \u2014 ask the user for hints, wordlists, or guidance
|
|
10289
|
-
|
|
10290
|
-
RULES:
|
|
10291
|
-
- Every turn MUST have tool calls
|
|
10292
|
-
- NEVER silently give up \u2014 exhaust ALL 5 steps above first
|
|
10293
|
-
- ACT NOW \u2014 do not plan, do not explain, do not summarize. EXECUTE.`
|
|
10294
|
-
});
|
|
10273
|
+
messages.push({ role: LLM_ROLES.USER, content: this.buildDeadlockNudge(progress) });
|
|
10295
10274
|
}
|
|
10296
10275
|
} catch (error) {
|
|
10297
10276
|
if (this.isAbortError(error)) {
|
|
@@ -10329,7 +10308,22 @@ RULES:
|
|
|
10329
10308
|
});
|
|
10330
10309
|
continue;
|
|
10331
10310
|
}
|
|
10332
|
-
|
|
10311
|
+
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10312
|
+
this.events.emit({
|
|
10313
|
+
type: EVENT_TYPES.ERROR,
|
|
10314
|
+
timestamp: Date.now(),
|
|
10315
|
+
data: {
|
|
10316
|
+
message: `Unexpected error: ${unexpectedMsg}`,
|
|
10317
|
+
phase: this.state.getPhase(),
|
|
10318
|
+
isRecoverable: true
|
|
10319
|
+
}
|
|
10320
|
+
});
|
|
10321
|
+
messages.push({
|
|
10322
|
+
role: LLM_ROLES.USER,
|
|
10323
|
+
content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10324
|
+
This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
|
|
10325
|
+
});
|
|
10326
|
+
continue;
|
|
10333
10327
|
}
|
|
10334
10328
|
}
|
|
10335
10329
|
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"}.`;
|
|
@@ -10461,6 +10455,48 @@ ${firstLine}`, phase }
|
|
|
10461
10455
|
return callbacks;
|
|
10462
10456
|
}
|
|
10463
10457
|
// ─────────────────────────────────────────────────────────────────
|
|
10458
|
+
// SUBSECTION: Deadlock Nudge Builder
|
|
10459
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10460
|
+
/**
|
|
10461
|
+
* Build a deadlock nudge message for the agent.
|
|
10462
|
+
*
|
|
10463
|
+
* WHY separated: The nudge template is ~30 lines of prompt engineering.
|
|
10464
|
+
* Keeping it in run() obscures the iteration control logic.
|
|
10465
|
+
* Philosophy §12: Nudge is a safety net, not a driver —
|
|
10466
|
+
* it reminds the agent to ACT, but never prescribes HOW.
|
|
10467
|
+
*/
|
|
10468
|
+
buildDeadlockNudge(progress) {
|
|
10469
|
+
const phase = this.state.getPhase();
|
|
10470
|
+
const targets = this.state.getTargets().size;
|
|
10471
|
+
const findings = this.state.getFindings().length;
|
|
10472
|
+
const phaseDirection = {
|
|
10473
|
+
[PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
|
|
10474
|
+
[PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
|
|
10475
|
+
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
|
|
10476
|
+
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
|
|
10477
|
+
[PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
|
|
10478
|
+
[PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
|
|
10479
|
+
[PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
|
|
10480
|
+
};
|
|
10481
|
+
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
10482
|
+
return `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
10483
|
+
Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
|
|
10484
|
+
|
|
10485
|
+
${direction}
|
|
10486
|
+
|
|
10487
|
+
ESCALATION CHAIN \u2014 follow this order:
|
|
10488
|
+
1. web_search: Search for techniques, bypasses, default creds, CVEs, HackTricks
|
|
10489
|
+
2. BYPASS: Try alternative approaches \u2014 different protocols, ports, encodings, methods
|
|
10490
|
+
3. ZERO-DAY EXPLORATION: Probe for unknown vulns \u2014 fuzz parameters, test edge cases, analyze error responses for leaks
|
|
10491
|
+
4. BRUTE-FORCE: Wordlists, credential stuffing, common passwords, custom password lists from context
|
|
10492
|
+
5. ask_user: ONLY as last resort \u2014 ask the user for hints, wordlists, or guidance
|
|
10493
|
+
|
|
10494
|
+
RULES:
|
|
10495
|
+
- Every turn MUST have tool calls
|
|
10496
|
+
- NEVER silently give up \u2014 exhaust ALL 5 steps above first
|
|
10497
|
+
- ACT NOW \u2014 do not plan, do not explain, do not summarize. EXECUTE.`;
|
|
10498
|
+
}
|
|
10499
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10464
10500
|
// SUBSECTION: Event Emitters
|
|
10465
10501
|
// ─────────────────════════════════════════════════════════════
|
|
10466
10502
|
emitThink(iteration, progress) {
|
|
@@ -10625,49 +10661,21 @@ ${firstLine}`, phase }
|
|
|
10625
10661
|
const toolStartTime = Date.now();
|
|
10626
10662
|
logLLM("CoreAgent executing tool", { id: call.id, name: call.name, input: call.input });
|
|
10627
10663
|
if (!this.toolRegistry) {
|
|
10628
|
-
return {
|
|
10629
|
-
toolCallId: call.id,
|
|
10630
|
-
output: "",
|
|
10631
|
-
error: "Tool registry not initialized. Call setToolRegistry() first."
|
|
10632
|
-
};
|
|
10664
|
+
return { toolCallId: call.id, output: "", error: "Tool registry not initialized. Call setToolRegistry() first." };
|
|
10633
10665
|
}
|
|
10634
10666
|
try {
|
|
10635
|
-
const result2 = await this.toolRegistry.execute({
|
|
10636
|
-
name: call.name,
|
|
10637
|
-
input: call.input
|
|
10638
|
-
});
|
|
10667
|
+
const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
|
|
10639
10668
|
let outputText = result2.output ?? "";
|
|
10640
10669
|
this.scanForFlags(outputText);
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
}
|
|
10650
|
-
try {
|
|
10651
|
-
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10652
|
-
const digestResult = await digestToolOutput(
|
|
10653
|
-
outputText,
|
|
10654
|
-
call.name,
|
|
10655
|
-
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10656
|
-
llmDigestFn
|
|
10657
|
-
);
|
|
10658
|
-
outputText = digestResult.digestedOutput;
|
|
10659
|
-
} catch {
|
|
10660
|
-
if (outputText.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10661
|
-
const truncated = outputText.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10662
|
-
const remaining = outputText.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10663
|
-
outputText = `${truncated}
|
|
10664
|
-
|
|
10665
|
-
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
10666
|
-
\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.`;
|
|
10667
|
-
}
|
|
10668
|
-
}
|
|
10669
|
-
this.emitToolResult(call.name, result2.success, outputText, result2.error, Date.now() - toolStartTime);
|
|
10670
|
-
return { toolCallId: call.id, output: outputText, error: result2.error };
|
|
10670
|
+
outputText = this.handleToolResult(result2, call, outputText, progress);
|
|
10671
|
+
const { digestedOutputForLLM, digestResult } = await this.digestAndEmit(
|
|
10672
|
+
call,
|
|
10673
|
+
outputText,
|
|
10674
|
+
result2,
|
|
10675
|
+
toolStartTime
|
|
10676
|
+
);
|
|
10677
|
+
this.recordJournalMemo(call, result2, digestedOutputForLLM, digestResult);
|
|
10678
|
+
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10671
10679
|
} catch (error) {
|
|
10672
10680
|
const errorMsg = String(error);
|
|
10673
10681
|
const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
@@ -10676,6 +10684,90 @@ ${firstLine}`, phase }
|
|
|
10676
10684
|
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
10677
10685
|
}
|
|
10678
10686
|
}
|
|
10687
|
+
/**
|
|
10688
|
+
* Handle tool result: enrich errors or track success.
|
|
10689
|
+
* @returns Possibly enriched output text.
|
|
10690
|
+
*/
|
|
10691
|
+
handleToolResult(result2, call, outputText, progress) {
|
|
10692
|
+
if (result2.error) {
|
|
10693
|
+
if (progress) progress.toolErrors++;
|
|
10694
|
+
return this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
|
|
10695
|
+
}
|
|
10696
|
+
if (progress) {
|
|
10697
|
+
progress.toolSuccesses++;
|
|
10698
|
+
progress.blockedCommandPatterns.clear();
|
|
10699
|
+
}
|
|
10700
|
+
return outputText;
|
|
10701
|
+
}
|
|
10702
|
+
/**
|
|
10703
|
+
* Digest tool output via Analyst LLM (§13 ③) and emit TUI event.
|
|
10704
|
+
*
|
|
10705
|
+
* WHY separated: Digest + emit is a self-contained pipeline:
|
|
10706
|
+
* raw output → Analyst → digest + file → TUI event.
|
|
10707
|
+
* Isolating it makes the pipeline testable without running actual tools.
|
|
10708
|
+
*/
|
|
10709
|
+
async digestAndEmit(call, outputText, result2, toolStartTime) {
|
|
10710
|
+
const digestFallbackOutput = outputText;
|
|
10711
|
+
let digestedOutputForLLM = outputText;
|
|
10712
|
+
let digestResult = null;
|
|
10713
|
+
try {
|
|
10714
|
+
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10715
|
+
digestResult = await digestToolOutput(
|
|
10716
|
+
outputText,
|
|
10717
|
+
call.name,
|
|
10718
|
+
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10719
|
+
llmDigestFn
|
|
10720
|
+
);
|
|
10721
|
+
digestedOutputForLLM = digestResult.digestedOutput;
|
|
10722
|
+
} catch {
|
|
10723
|
+
if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10724
|
+
const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10725
|
+
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10726
|
+
digestedOutputForLLM = `${truncated}
|
|
10727
|
+
|
|
10728
|
+
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
10729
|
+
\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.`;
|
|
10730
|
+
}
|
|
10731
|
+
}
|
|
10732
|
+
const outputFilePath = digestResult?.fullOutputPath ?? null;
|
|
10733
|
+
const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
|
|
10734
|
+
\u{1F4C4} Full output: ${outputFilePath}` : ""}` : digestFallbackOutput.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
|
|
10735
|
+
this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
|
|
10736
|
+
return { digestedOutputForLLM, digestResult };
|
|
10737
|
+
}
|
|
10738
|
+
/**
|
|
10739
|
+
* Record tool execution results to Journal and aggregate memos.
|
|
10740
|
+
*
|
|
10741
|
+
* WHY no truncation on inputSummary: Strategist needs full context —
|
|
10742
|
+
* "hydra -l admin -P rockyou.txt ssh://10.0.0.1" must survive intact.
|
|
10743
|
+
*/
|
|
10744
|
+
recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
|
|
10745
|
+
this.turnToolJournal.push({
|
|
10746
|
+
name: call.name,
|
|
10747
|
+
inputSummary: JSON.stringify(call.input),
|
|
10748
|
+
success: result2.success,
|
|
10749
|
+
analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
|
|
10750
|
+
outputFile: digestResult?.fullOutputPath ?? null
|
|
10751
|
+
});
|
|
10752
|
+
if (digestResult?.memo) {
|
|
10753
|
+
const m = digestResult.memo;
|
|
10754
|
+
this.turnMemo.keyFindings.push(...m.keyFindings);
|
|
10755
|
+
this.turnMemo.credentials.push(...m.credentials);
|
|
10756
|
+
this.turnMemo.attackVectors.push(...m.attackVectors);
|
|
10757
|
+
this.turnMemo.failures.push(...m.failures);
|
|
10758
|
+
this.turnMemo.suspicions.push(...m.suspicions);
|
|
10759
|
+
if ((ATTACK_VALUE_RANK[m.attackValue] ?? 0) > (ATTACK_VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
|
|
10760
|
+
this.turnMemo.attackValue = m.attackValue;
|
|
10761
|
+
}
|
|
10762
|
+
this.turnMemo.nextSteps.push(...m.nextSteps);
|
|
10763
|
+
if (m.reflection) this.turnReflections.push(m.reflection);
|
|
10764
|
+
}
|
|
10765
|
+
if (digestResult?.memo?.credentials.length) {
|
|
10766
|
+
for (const cred of digestResult.memo.credentials) {
|
|
10767
|
+
this.state.addLoot({ type: LOOT_TYPES.CREDENTIAL, host: "auto-extracted", detail: cred, obtainedAt: Date.now() });
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10679
10771
|
/**
|
|
10680
10772
|
* Enrich tool error — delegates to extracted module (§3-1)
|
|
10681
10773
|
*/
|
|
@@ -10742,9 +10834,9 @@ ${firstLine}`, phase }
|
|
|
10742
10834
|
};
|
|
10743
10835
|
|
|
10744
10836
|
// src/agents/prompt-builder.ts
|
|
10745
|
-
import { readFileSync as
|
|
10746
|
-
import { join as
|
|
10747
|
-
import { fileURLToPath as
|
|
10837
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
10838
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
10839
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10748
10840
|
|
|
10749
10841
|
// src/shared/constants/prompts.ts
|
|
10750
10842
|
var PROMPT_PATHS = {
|
|
@@ -10803,73 +10895,44 @@ var PROMPT_CONFIG = {
|
|
|
10803
10895
|
var INITIAL_TASKS = {
|
|
10804
10896
|
RECON: "Initial reconnaissance and target discovery"
|
|
10805
10897
|
};
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
SERVICE_PORTS.MONGODB,
|
|
10845
|
-
SERVICE_PORTS.ELASTICSEARCH,
|
|
10846
|
-
SERVICE_PORTS.MEMCACHED
|
|
10847
|
-
];
|
|
10848
|
-
var WEB_SERVICE_PORTS = [
|
|
10849
|
-
SERVICE_PORTS.HTTP,
|
|
10850
|
-
SERVICE_PORTS.HTTPS,
|
|
10851
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
10852
|
-
SERVICE_PORTS.HTTPS_ALT,
|
|
10853
|
-
SERVICE_PORTS.NODE_DEFAULT,
|
|
10854
|
-
SERVICE_PORTS.FLASK_DEFAULT,
|
|
10855
|
-
SERVICE_PORTS.DJANGO_DEFAULT
|
|
10856
|
-
];
|
|
10857
|
-
var PLAINTEXT_HTTP_PORTS = [
|
|
10858
|
-
SERVICE_PORTS.HTTP,
|
|
10859
|
-
SERVICE_PORTS.HTTP_ALT,
|
|
10860
|
-
SERVICE_PORTS.NODE_DEFAULT
|
|
10861
|
-
];
|
|
10862
|
-
var DATABASE_PORTS = [
|
|
10863
|
-
SERVICE_PORTS.MYSQL,
|
|
10864
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
10865
|
-
SERVICE_PORTS.MSSQL,
|
|
10866
|
-
SERVICE_PORTS.MONGODB,
|
|
10867
|
-
SERVICE_PORTS.REDIS
|
|
10868
|
-
];
|
|
10869
|
-
var SMB_PORTS = [
|
|
10870
|
-
SERVICE_PORTS.SMB,
|
|
10871
|
-
SERVICE_PORTS.SMB_NETBIOS
|
|
10872
|
-
];
|
|
10898
|
+
var CONTEXT_EXTRACTOR_PROMPT = `You are extracting actionable intelligence from a penetration testing session.
|
|
10899
|
+
DO NOT simply summarize or shorten. EXTRACT critical facts:
|
|
10900
|
+
|
|
10901
|
+
1. DISCOVERED: Services, versions, paths, parameters (exact IPs, ports, versions)
|
|
10902
|
+
2. CONFIRMED: Vulnerabilities or access confirmed
|
|
10903
|
+
3. CREDENTIALS: Usernames, passwords, tokens, keys
|
|
10904
|
+
4. DEAD ENDS: What failed \u2014 include EXACT command, tool, arguments, wordlist/file used.
|
|
10905
|
+
Distinguish between:
|
|
10906
|
+
- "This approach itself is impossible" (e.g., SSH key-only \u2192 no password brute force works)
|
|
10907
|
+
- "This specific attempt failed" (e.g., sqlmap with default tamper \u2192 try different tamper)
|
|
10908
|
+
5. OPEN LEADS: Unexplored paths worth pursuing
|
|
10909
|
+
|
|
10910
|
+
Every line must include exact commands/tools/files used.
|
|
10911
|
+
The reader must be able to judge whether a retry with different parameters is worthwhile.`;
|
|
10912
|
+
var REFLECTION_PROMPT = `You are a tactical reviewer for a penetration testing agent.
|
|
10913
|
+
Review ALL actions from this turn \u2014 successes AND failures.
|
|
10914
|
+
|
|
10915
|
+
1. ASSESSMENT: What did this turn accomplish? Rate: HIGH / MED / LOW / NONE.
|
|
10916
|
+
2. SUCCESSES: What worked? Can this pattern be replicated elsewhere?
|
|
10917
|
+
3. FAILURES: What failed? Is this a repeated pattern? If so \u2192 STOP this approach.
|
|
10918
|
+
4. BLIND SPOTS: What was missed or overlooked?
|
|
10919
|
+
5. NEXT PRIORITY: Single most valuable next action.
|
|
10920
|
+
|
|
10921
|
+
3-5 lines. Every word must be actionable.`;
|
|
10922
|
+
var SUMMARY_REGENERATOR_PROMPT = `Update this penetration testing session summary with the new turn data.
|
|
10923
|
+
|
|
10924
|
+
Must include:
|
|
10925
|
+
- All discovered hosts, services, versions (exact IPs, ports, software versions)
|
|
10926
|
+
- All confirmed vulnerabilities
|
|
10927
|
+
- All obtained credentials
|
|
10928
|
+
- Failed attempts with EXACT commands/tools/arguments/files used.
|
|
10929
|
+
For each failure, state:
|
|
10930
|
+
- The root cause (auth method? WAF? patched? wrong params?)
|
|
10931
|
+
- Whether retrying with different parameters could work
|
|
10932
|
+
- Top unexplored leads
|
|
10933
|
+
|
|
10934
|
+
Remove outdated/superseded info. Keep concise but COMPLETE.
|
|
10935
|
+
The reader must be able to decide what to retry and what to never attempt again.`;
|
|
10873
10936
|
|
|
10874
10937
|
// src/shared/constants/scoring.ts
|
|
10875
10938
|
var ATTACK_SCORING = {
|
|
@@ -11031,10 +11094,225 @@ function getAttacksForService(service, port) {
|
|
|
11031
11094
|
return attacks;
|
|
11032
11095
|
}
|
|
11033
11096
|
|
|
11097
|
+
// src/shared/utils/journal.ts
|
|
11098
|
+
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
|
|
11099
|
+
import { join as join10 } from "path";
|
|
11100
|
+
var MAX_JOURNAL_ENTRIES = 50;
|
|
11101
|
+
var MAX_OUTPUT_FILES = 30;
|
|
11102
|
+
var TURN_PREFIX = "turn-";
|
|
11103
|
+
var SUMMARY_FILE = "summary.md";
|
|
11104
|
+
function writeJournalEntry(entry) {
|
|
11105
|
+
try {
|
|
11106
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11107
|
+
ensureDirExists(journalDir);
|
|
11108
|
+
const padded = String(entry.turn).padStart(4, "0");
|
|
11109
|
+
const filePath = join10(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11110
|
+
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11111
|
+
return filePath;
|
|
11112
|
+
} catch (err) {
|
|
11113
|
+
debugLog("general", "Failed to write journal entry", { turn: entry.turn, error: String(err) });
|
|
11114
|
+
return null;
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
11117
|
+
function readJournalSummary() {
|
|
11118
|
+
try {
|
|
11119
|
+
const summaryPath = join10(WORKSPACE.JOURNAL, SUMMARY_FILE);
|
|
11120
|
+
if (!existsSync8(summaryPath)) return "";
|
|
11121
|
+
return readFileSync5(summaryPath, "utf-8");
|
|
11122
|
+
} catch {
|
|
11123
|
+
return "";
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
11127
|
+
try {
|
|
11128
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11129
|
+
if (!existsSync8(journalDir)) return [];
|
|
11130
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort().slice(-count);
|
|
11131
|
+
const entries = [];
|
|
11132
|
+
for (const file of files) {
|
|
11133
|
+
try {
|
|
11134
|
+
const raw = readFileSync5(join10(journalDir, file), "utf-8");
|
|
11135
|
+
entries.push(JSON.parse(raw));
|
|
11136
|
+
} catch {
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
return entries;
|
|
11140
|
+
} catch {
|
|
11141
|
+
return [];
|
|
11142
|
+
}
|
|
11143
|
+
}
|
|
11144
|
+
function getNextTurnNumber() {
|
|
11145
|
+
try {
|
|
11146
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11147
|
+
if (!existsSync8(journalDir)) return 1;
|
|
11148
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11149
|
+
if (files.length === 0) return 1;
|
|
11150
|
+
const lastFile = files[files.length - 1];
|
|
11151
|
+
const match = lastFile.match(/turn-(\d+)\.json/);
|
|
11152
|
+
return match ? parseInt(match[1], 10) + 1 : 1;
|
|
11153
|
+
} catch {
|
|
11154
|
+
return 1;
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
function regenerateJournalSummary() {
|
|
11158
|
+
try {
|
|
11159
|
+
const entries = getRecentEntries();
|
|
11160
|
+
if (entries.length === 0) return;
|
|
11161
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11162
|
+
ensureDirExists(journalDir);
|
|
11163
|
+
const summary = buildSummaryFromEntries(entries);
|
|
11164
|
+
const summaryPath = join10(journalDir, SUMMARY_FILE);
|
|
11165
|
+
writeFileSync8(summaryPath, summary, "utf-8");
|
|
11166
|
+
debugLog("general", "Journal summary regenerated", {
|
|
11167
|
+
entries: entries.length,
|
|
11168
|
+
summaryLength: summary.length
|
|
11169
|
+
});
|
|
11170
|
+
} catch (err) {
|
|
11171
|
+
debugLog("general", "Failed to regenerate journal summary", { error: String(err) });
|
|
11172
|
+
}
|
|
11173
|
+
}
|
|
11174
|
+
function buildSummaryFromEntries(entries) {
|
|
11175
|
+
const buckets = collectSummaryBuckets(entries);
|
|
11176
|
+
return formatSummaryMarkdown(buckets, entries);
|
|
11177
|
+
}
|
|
11178
|
+
function collectSummaryBuckets(entries) {
|
|
11179
|
+
const attempts = [];
|
|
11180
|
+
const findings = [];
|
|
11181
|
+
const credentials = [];
|
|
11182
|
+
const successes = [];
|
|
11183
|
+
const failures = [];
|
|
11184
|
+
const suspicions = [];
|
|
11185
|
+
const nextSteps = [];
|
|
11186
|
+
const reflections = [];
|
|
11187
|
+
const reversed = [...entries].reverse();
|
|
11188
|
+
for (const entry of reversed) {
|
|
11189
|
+
const value = entry.memo.attackValue || "LOW";
|
|
11190
|
+
for (const tool of entry.tools) {
|
|
11191
|
+
attempts.push({ turn: entry.turn, phase: entry.phase, ok: tool.success, name: tool.name, input: tool.inputSummary, value });
|
|
11192
|
+
}
|
|
11193
|
+
for (const finding of entry.memo.keyFindings) {
|
|
11194
|
+
const line = `- [T${entry.turn}|\u26A1${value}] ${finding}`;
|
|
11195
|
+
if (!findings.includes(line)) findings.push(line);
|
|
11196
|
+
}
|
|
11197
|
+
for (const cred of entry.memo.credentials) {
|
|
11198
|
+
const line = `- [T${entry.turn}] ${cred}`;
|
|
11199
|
+
if (!credentials.includes(line)) credentials.push(line);
|
|
11200
|
+
}
|
|
11201
|
+
for (const vector of entry.memo.attackVectors) {
|
|
11202
|
+
const line = `- [T${entry.turn}] ${vector}`;
|
|
11203
|
+
if (!successes.includes(line)) successes.push(line);
|
|
11204
|
+
}
|
|
11205
|
+
for (const fail of entry.memo.failures) {
|
|
11206
|
+
const line = `- [T${entry.turn}] ${fail}`;
|
|
11207
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11208
|
+
}
|
|
11209
|
+
for (const tool of entry.tools) {
|
|
11210
|
+
if (!tool.success) {
|
|
11211
|
+
const detail = `${tool.name}(${tool.inputSummary}): ${tool.analystSummary}`;
|
|
11212
|
+
const line = `- [T${entry.turn}] ${detail}`;
|
|
11213
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11216
|
+
for (const s of entry.memo.suspicions || []) {
|
|
11217
|
+
const line = `- [T${entry.turn}] ${s}`;
|
|
11218
|
+
if (!suspicions.includes(line)) suspicions.push(line);
|
|
11219
|
+
}
|
|
11220
|
+
if (nextSteps.length < 5) {
|
|
11221
|
+
for (const step of entry.memo.nextSteps) {
|
|
11222
|
+
if (!nextSteps.includes(`- ${step}`)) nextSteps.push(`- ${step}`);
|
|
11223
|
+
}
|
|
11224
|
+
}
|
|
11225
|
+
if (entry.reflection) {
|
|
11226
|
+
reflections.push(`- [T${entry.turn}|\u26A1${value}] ${entry.reflection}`);
|
|
11227
|
+
}
|
|
11228
|
+
}
|
|
11229
|
+
attempts.sort((a, b) => {
|
|
11230
|
+
const vd = (ATTACK_VALUE_RANK[b.value] ?? 0) - (ATTACK_VALUE_RANK[a.value] ?? 0);
|
|
11231
|
+
return vd !== 0 ? vd : b.turn - a.turn;
|
|
11232
|
+
});
|
|
11233
|
+
return { attempts, findings, credentials, successes, failures, suspicions, nextSteps, reflections };
|
|
11234
|
+
}
|
|
11235
|
+
function formatSummaryMarkdown(buckets, entries) {
|
|
11236
|
+
const { attempts, findings, credentials, successes, failures, suspicions, nextSteps, reflections } = buckets;
|
|
11237
|
+
const attemptLines = attempts.map(
|
|
11238
|
+
(a) => `- [T${a.turn}|${a.phase}|\u26A1${a.value}] ${a.ok ? "\u2705" : "\u274C"} ${a.name}: ${a.input}`
|
|
11239
|
+
);
|
|
11240
|
+
const lastTurn = entries[entries.length - 1]?.turn || 0;
|
|
11241
|
+
const sections = [
|
|
11242
|
+
`# Session Journal Summary`,
|
|
11243
|
+
`> Turn ${lastTurn} / ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}`,
|
|
11244
|
+
""
|
|
11245
|
+
];
|
|
11246
|
+
const addSection = (title, items) => {
|
|
11247
|
+
if (items.length === 0) return;
|
|
11248
|
+
sections.push(`## ${title}`);
|
|
11249
|
+
sections.push(...items);
|
|
11250
|
+
sections.push("");
|
|
11251
|
+
};
|
|
11252
|
+
if (attemptLines.length > 0) {
|
|
11253
|
+
sections.push("## Techniques Tried (by attack value)");
|
|
11254
|
+
sections.push("> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon");
|
|
11255
|
+
sections.push(...attemptLines);
|
|
11256
|
+
sections.push("");
|
|
11257
|
+
}
|
|
11258
|
+
addSection("\u{1F9E0} Analyst Analysis (attack value rationale)", reflections);
|
|
11259
|
+
addSection("\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)", suspicions);
|
|
11260
|
+
addSection("\u{1F4CB} Key Findings", findings);
|
|
11261
|
+
addSection("\u{1F511} Credentials Obtained", credentials);
|
|
11262
|
+
addSection("\u2705 Successful Attack Vectors", successes);
|
|
11263
|
+
addSection("\u274C Failure Causes (do not repeat)", failures);
|
|
11264
|
+
addSection("\u27A1\uFE0F Next Recommendations", nextSteps);
|
|
11265
|
+
return sections.join("\n");
|
|
11266
|
+
}
|
|
11267
|
+
function rotateJournalEntries() {
|
|
11268
|
+
try {
|
|
11269
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11270
|
+
if (!existsSync8(journalDir)) return;
|
|
11271
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11272
|
+
if (files.length <= MAX_JOURNAL_ENTRIES) return;
|
|
11273
|
+
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11274
|
+
for (const file of toDelete) {
|
|
11275
|
+
try {
|
|
11276
|
+
unlinkSync5(join10(journalDir, file));
|
|
11277
|
+
} catch {
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11280
|
+
debugLog("general", "Journal entries rotated", {
|
|
11281
|
+
deleted: toDelete.length,
|
|
11282
|
+
remaining: MAX_JOURNAL_ENTRIES
|
|
11283
|
+
});
|
|
11284
|
+
} catch {
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
11287
|
+
function rotateOutputFiles() {
|
|
11288
|
+
try {
|
|
11289
|
+
const outputDir = WORKSPACE.OUTPUTS;
|
|
11290
|
+
if (!existsSync8(outputDir)) return;
|
|
11291
|
+
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11292
|
+
name: f,
|
|
11293
|
+
path: join10(outputDir, f),
|
|
11294
|
+
mtime: statSync2(join10(outputDir, f)).mtimeMs
|
|
11295
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
11296
|
+
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11297
|
+
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
11298
|
+
for (const file of toDelete) {
|
|
11299
|
+
try {
|
|
11300
|
+
unlinkSync5(file.path);
|
|
11301
|
+
} catch {
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11304
|
+
debugLog("general", "Output files rotated", {
|
|
11305
|
+
deleted: toDelete.length,
|
|
11306
|
+
remaining: MAX_OUTPUT_FILES
|
|
11307
|
+
});
|
|
11308
|
+
} catch {
|
|
11309
|
+
}
|
|
11310
|
+
}
|
|
11311
|
+
|
|
11034
11312
|
// src/agents/prompt-builder.ts
|
|
11035
|
-
var
|
|
11036
|
-
var PROMPTS_DIR =
|
|
11037
|
-
var TECHNIQUES_DIR =
|
|
11313
|
+
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
11314
|
+
var PROMPTS_DIR = join11(__dirname3, "prompts");
|
|
11315
|
+
var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
11038
11316
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
11039
11317
|
var PHASE_PROMPT_MAP = {
|
|
11040
11318
|
// Direct mappings — phase has its own prompt file
|
|
@@ -11108,6 +11386,7 @@ var PromptBuilder = class {
|
|
|
11108
11386
|
* 13. Learned techniques (#7: dynamic technique library)
|
|
11109
11387
|
* 14. Persistent memory (#12: cross-session knowledge)
|
|
11110
11388
|
* ★ 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
|
|
11389
|
+
* ★ 15b. SESSION JOURNAL — compressed history of past turns (§13 memo system)
|
|
11111
11390
|
* 16. User context
|
|
11112
11391
|
*/
|
|
11113
11392
|
async build(userInput, phase) {
|
|
@@ -11133,8 +11412,10 @@ var PromptBuilder = class {
|
|
|
11133
11412
|
// #12
|
|
11134
11413
|
this.getDynamicTechniquesFragment(),
|
|
11135
11414
|
// #7
|
|
11136
|
-
this.getPersistentMemoryFragment()
|
|
11415
|
+
this.getPersistentMemoryFragment(),
|
|
11137
11416
|
// #12
|
|
11417
|
+
this.getJournalFragment()
|
|
11418
|
+
// §13 session journal
|
|
11138
11419
|
];
|
|
11139
11420
|
const strategistDirective = await this.getStrategistFragment();
|
|
11140
11421
|
if (strategistDirective) {
|
|
@@ -11158,8 +11439,8 @@ ${content}
|
|
|
11158
11439
|
* Load a prompt file from src/agents/prompts/
|
|
11159
11440
|
*/
|
|
11160
11441
|
loadPromptFile(filename) {
|
|
11161
|
-
const path2 =
|
|
11162
|
-
return
|
|
11442
|
+
const path2 = join11(PROMPTS_DIR, filename);
|
|
11443
|
+
return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
11163
11444
|
}
|
|
11164
11445
|
/**
|
|
11165
11446
|
* Load phase-specific prompt.
|
|
@@ -11202,18 +11483,18 @@ ${content}
|
|
|
11202
11483
|
* as general reference — NO code change needed to add new techniques.
|
|
11203
11484
|
*
|
|
11204
11485
|
* The map is an optimization (priority ordering), not a gate.
|
|
11205
|
-
* "
|
|
11486
|
+
* "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
|
|
11206
11487
|
*/
|
|
11207
11488
|
loadPhaseRelevantTechniques(phase) {
|
|
11208
|
-
if (!
|
|
11489
|
+
if (!existsSync9(TECHNIQUES_DIR)) return "";
|
|
11209
11490
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
11210
11491
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11211
11492
|
const fragments = [];
|
|
11212
11493
|
for (const technique of priorityTechniques) {
|
|
11213
|
-
const filePath =
|
|
11494
|
+
const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
|
|
11214
11495
|
try {
|
|
11215
|
-
if (!
|
|
11216
|
-
const content =
|
|
11496
|
+
if (!existsSync9(filePath)) continue;
|
|
11497
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11217
11498
|
if (content) {
|
|
11218
11499
|
fragments.push(`<technique-reference category="${technique}">
|
|
11219
11500
|
${content}
|
|
@@ -11224,10 +11505,10 @@ ${content}
|
|
|
11224
11505
|
}
|
|
11225
11506
|
}
|
|
11226
11507
|
try {
|
|
11227
|
-
const allFiles =
|
|
11508
|
+
const allFiles = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11228
11509
|
for (const file of allFiles) {
|
|
11229
|
-
const filePath =
|
|
11230
|
-
const content =
|
|
11510
|
+
const filePath = join11(TECHNIQUES_DIR, file);
|
|
11511
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11231
11512
|
if (content) {
|
|
11232
11513
|
const category = file.replace(".md", "");
|
|
11233
11514
|
fragments.push(`<technique-reference category="${category}">
|
|
@@ -11330,6 +11611,31 @@ ${lines.join("\n")}
|
|
|
11330
11611
|
}
|
|
11331
11612
|
return this.state.persistentMemory.toPrompt(services);
|
|
11332
11613
|
}
|
|
11614
|
+
// --- §13: Session Journal Summary ---
|
|
11615
|
+
/**
|
|
11616
|
+
* Load journal summary — prefers Summary Regenerator (⑥) output,
|
|
11617
|
+
* falls back to deterministic journal summary.
|
|
11618
|
+
*/
|
|
11619
|
+
getJournalFragment() {
|
|
11620
|
+
try {
|
|
11621
|
+
const summaryPath = join11(WORKSPACE.TURNS, "summary.md");
|
|
11622
|
+
if (existsSync9(summaryPath)) {
|
|
11623
|
+
const summary2 = readFileSync6(summaryPath, "utf-8");
|
|
11624
|
+
if (summary2.trim()) {
|
|
11625
|
+
return `<session-journal>
|
|
11626
|
+
${summary2}
|
|
11627
|
+
</session-journal>`;
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
const summary = readJournalSummary();
|
|
11631
|
+
if (!summary) return "";
|
|
11632
|
+
return `<session-journal>
|
|
11633
|
+
${summary}
|
|
11634
|
+
</session-journal>`;
|
|
11635
|
+
} catch {
|
|
11636
|
+
return "";
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11333
11639
|
// --- D-CIPHER: Strategist Meta-Prompting ---
|
|
11334
11640
|
/**
|
|
11335
11641
|
* Generate strategic directive via Strategist LLM.
|
|
@@ -11342,29 +11648,11 @@ ${lines.join("\n")}
|
|
|
11342
11648
|
};
|
|
11343
11649
|
|
|
11344
11650
|
// src/agents/strategist.ts
|
|
11345
|
-
import { readFileSync as
|
|
11346
|
-
import { join as
|
|
11347
|
-
import { fileURLToPath as
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
var STRATEGIST_LIMITS = {
|
|
11351
|
-
/** Maximum characters of state context sent to Strategist LLM.
|
|
11352
|
-
* WHY: Keeps Strategist input focused and affordable (~3-5K tokens).
|
|
11353
|
-
* Full state can be 20K+; Strategist needs summary, not everything. */
|
|
11354
|
-
MAX_INPUT_CHARS: 15e3,
|
|
11355
|
-
/** Maximum characters for the Strategist's response.
|
|
11356
|
-
* WHY: Directives should be terse and actionable (~800-1500 tokens).
|
|
11357
|
-
* Enhanced format includes SITUATION, priorities, EXHAUSTED, and SEARCH ORDERS. */
|
|
11358
|
-
MAX_OUTPUT_CHARS: 5e3,
|
|
11359
|
-
/** Maximum lines in the directive output.
|
|
11360
|
-
* WHY: Forces concise, prioritized directives while allowing
|
|
11361
|
-
* structured format (priorities + exhausted + search orders). */
|
|
11362
|
-
MAX_DIRECTIVE_LINES: 60
|
|
11363
|
-
};
|
|
11364
|
-
|
|
11365
|
-
// src/agents/strategist.ts
|
|
11366
|
-
var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
|
|
11367
|
-
var STRATEGIST_PROMPT_PATH = join11(__dirname5, "prompts", "strategist-system.md");
|
|
11651
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
11652
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
11653
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11654
|
+
var __dirname4 = dirname6(fileURLToPath4(import.meta.url));
|
|
11655
|
+
var STRATEGIST_PROMPT_PATH = join12(__dirname4, "prompts", "strategist-system.md");
|
|
11368
11656
|
var Strategist = class {
|
|
11369
11657
|
llm;
|
|
11370
11658
|
state;
|
|
@@ -11415,24 +11703,40 @@ var Strategist = class {
|
|
|
11415
11703
|
const sections = [];
|
|
11416
11704
|
sections.push("## Engagement State");
|
|
11417
11705
|
sections.push(this.state.toPrompt());
|
|
11418
|
-
const timeline = this.state.episodicMemory.toPrompt();
|
|
11419
|
-
if (timeline) {
|
|
11420
|
-
sections.push("");
|
|
11421
|
-
sections.push("## Recent Actions");
|
|
11422
|
-
sections.push(timeline);
|
|
11423
|
-
}
|
|
11424
11706
|
const failures = this.state.workingMemory.toPrompt();
|
|
11425
11707
|
if (failures) {
|
|
11426
11708
|
sections.push("");
|
|
11427
11709
|
sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
|
|
11428
11710
|
sections.push(failures);
|
|
11429
11711
|
}
|
|
11712
|
+
try {
|
|
11713
|
+
let journalSummary = "";
|
|
11714
|
+
const summaryPath = join12(WORKSPACE.TURNS, "summary.md");
|
|
11715
|
+
if (existsSync10(summaryPath)) {
|
|
11716
|
+
journalSummary = readFileSync7(summaryPath, "utf-8").trim();
|
|
11717
|
+
}
|
|
11718
|
+
if (!journalSummary) {
|
|
11719
|
+
journalSummary = readJournalSummary();
|
|
11720
|
+
}
|
|
11721
|
+
if (journalSummary) {
|
|
11722
|
+
sections.push("");
|
|
11723
|
+
sections.push("## Session Journal (past turns summary)");
|
|
11724
|
+
sections.push(journalSummary);
|
|
11725
|
+
}
|
|
11726
|
+
} catch {
|
|
11727
|
+
}
|
|
11430
11728
|
const graph = this.state.attackGraph.toPrompt();
|
|
11431
11729
|
if (graph) {
|
|
11432
11730
|
sections.push("");
|
|
11433
11731
|
sections.push("## Attack Graph");
|
|
11434
11732
|
sections.push(graph);
|
|
11435
11733
|
}
|
|
11734
|
+
const timeline = this.state.episodicMemory.toPrompt();
|
|
11735
|
+
if (timeline) {
|
|
11736
|
+
sections.push("");
|
|
11737
|
+
sections.push("## Recent Actions");
|
|
11738
|
+
sections.push(timeline);
|
|
11739
|
+
}
|
|
11436
11740
|
const techniques = this.state.dynamicTechniques.toPrompt();
|
|
11437
11741
|
if (techniques) {
|
|
11438
11742
|
sections.push("");
|
|
@@ -11448,11 +11752,7 @@ var Strategist = class {
|
|
|
11448
11752
|
sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
|
|
11449
11753
|
sections.push(analysis.strategySuggestion);
|
|
11450
11754
|
}
|
|
11451
|
-
|
|
11452
|
-
if (input.length > STRATEGIST_LIMITS.MAX_INPUT_CHARS) {
|
|
11453
|
-
input = input.slice(0, STRATEGIST_LIMITS.MAX_INPUT_CHARS) + "\n\n... [state truncated for Strategist context]";
|
|
11454
|
-
}
|
|
11455
|
-
return input;
|
|
11755
|
+
return sections.join("\n");
|
|
11456
11756
|
}
|
|
11457
11757
|
// ─── LLM Call ───────────────────────────────────────────────
|
|
11458
11758
|
async callLLM(input) {
|
|
@@ -11469,9 +11769,6 @@ ${input}`
|
|
|
11469
11769
|
this.systemPrompt
|
|
11470
11770
|
);
|
|
11471
11771
|
let content = response.content || "";
|
|
11472
|
-
if (content.length > STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) {
|
|
11473
|
-
content = content.slice(0, STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) + "\n... [directive truncated]";
|
|
11474
|
-
}
|
|
11475
11772
|
const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
|
|
11476
11773
|
this.totalTokenCost += cost;
|
|
11477
11774
|
return {
|
|
@@ -11501,8 +11798,8 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
|
|
|
11501
11798
|
// ─── System Prompt Loading ──────────────────────────────────
|
|
11502
11799
|
loadSystemPrompt() {
|
|
11503
11800
|
try {
|
|
11504
|
-
if (
|
|
11505
|
-
return
|
|
11801
|
+
if (existsSync10(STRATEGIST_PROMPT_PATH)) {
|
|
11802
|
+
return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11506
11803
|
}
|
|
11507
11804
|
} catch {
|
|
11508
11805
|
}
|
|
@@ -11537,13 +11834,102 @@ Detect stalls (repeated failures, no progress) and force completely different at
|
|
|
11537
11834
|
Chain every finding: "If X works \u2192 immediately do Y \u2192 which enables Z."
|
|
11538
11835
|
Maximum 50 lines. Zero preamble. Direct imperatives only. Never repeat failed approaches.`;
|
|
11539
11836
|
|
|
11837
|
+
// src/shared/utils/turn-record.ts
|
|
11838
|
+
function formatTurnRecord(input) {
|
|
11839
|
+
const { turn, timestamp, phase, tools, memo: memo6, reflection } = input;
|
|
11840
|
+
const time = timestamp.slice(0, 19).replace("T", " ");
|
|
11841
|
+
const sections = [];
|
|
11842
|
+
sections.push(`# Turn ${turn} | ${time} | Phase: ${phase}`);
|
|
11843
|
+
sections.push("");
|
|
11844
|
+
sections.push("## \uC2E4\uD589 \uB3C4\uAD6C");
|
|
11845
|
+
if (tools.length === 0) {
|
|
11846
|
+
sections.push("- (\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)");
|
|
11847
|
+
} else {
|
|
11848
|
+
for (const tool of tools) {
|
|
11849
|
+
const status = tool.success ? "\u2705" : "\u274C";
|
|
11850
|
+
const line = `- ${tool.name}(${tool.inputSummary}) \u2192 ${status} ${tool.analystSummary}`;
|
|
11851
|
+
sections.push(line);
|
|
11852
|
+
}
|
|
11853
|
+
}
|
|
11854
|
+
sections.push("");
|
|
11855
|
+
sections.push("## \uD575\uC2EC \uC778\uC0AC\uC774\uD2B8");
|
|
11856
|
+
if (memo6.keyFindings.length > 0) {
|
|
11857
|
+
for (const f of memo6.keyFindings) sections.push(`- DISCOVERED: ${f}`);
|
|
11858
|
+
}
|
|
11859
|
+
if (memo6.credentials.length > 0) {
|
|
11860
|
+
for (const c of memo6.credentials) sections.push(`- CREDENTIAL: ${c}`);
|
|
11861
|
+
}
|
|
11862
|
+
if (memo6.attackVectors.length > 0) {
|
|
11863
|
+
for (const v of memo6.attackVectors) sections.push(`- CONFIRMED: ${v}`);
|
|
11864
|
+
}
|
|
11865
|
+
if (memo6.failures.length > 0) {
|
|
11866
|
+
for (const f of memo6.failures) sections.push(`- DEAD END: ${f}`);
|
|
11867
|
+
}
|
|
11868
|
+
if (memo6.suspicions.length > 0) {
|
|
11869
|
+
for (const s of memo6.suspicions) sections.push(`- SUSPICIOUS: ${s}`);
|
|
11870
|
+
}
|
|
11871
|
+
if (memo6.nextSteps.length > 0) {
|
|
11872
|
+
for (const n of memo6.nextSteps) sections.push(`- NEXT: ${n}`);
|
|
11873
|
+
}
|
|
11874
|
+
if (memo6.keyFindings.length === 0 && memo6.failures.length === 0 && memo6.credentials.length === 0) {
|
|
11875
|
+
sections.push("- (\uD2B9\uC774\uC0AC\uD56D \uC5C6\uC74C)");
|
|
11876
|
+
}
|
|
11877
|
+
sections.push("");
|
|
11878
|
+
sections.push("## \uC790\uAE30\uBC18\uC131");
|
|
11879
|
+
sections.push(reflection || "- (\uBC18\uC131 \uC5C6\uC74C)");
|
|
11880
|
+
sections.push("");
|
|
11881
|
+
return sections.join("\n");
|
|
11882
|
+
}
|
|
11883
|
+
function formatForExtraction(messages) {
|
|
11884
|
+
const parts = ["\uB2E4\uC74C\uC740 \uD39C\uD14C\uC2A4\uD305 \uC138\uC158\uC758 \uB300\uD654 \uAE30\uB85D\uC785\uB2C8\uB2E4. \uD575\uC2EC \uC778\uC0AC\uC774\uD2B8\uB97C \uCD94\uCD9C\uD558\uC138\uC694:\n"];
|
|
11885
|
+
for (const msg of messages) {
|
|
11886
|
+
const role = msg.role === "assistant" ? "AGENT" : msg.role === "user" ? "RESULT" : msg.role.toUpperCase();
|
|
11887
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
11888
|
+
const truncated = content.length > 3e3 ? content.slice(0, 1500) + "\n...(truncated)...\n" + content.slice(-1500) : content;
|
|
11889
|
+
parts.push(`[${role}]
|
|
11890
|
+
${truncated}
|
|
11891
|
+
`);
|
|
11892
|
+
}
|
|
11893
|
+
return parts.join("\n");
|
|
11894
|
+
}
|
|
11895
|
+
function formatReflectionInput(input) {
|
|
11896
|
+
const { tools, memo: memo6, phase } = input;
|
|
11897
|
+
const parts = [
|
|
11898
|
+
`\uD604\uC7AC Phase: ${phase}`,
|
|
11899
|
+
"",
|
|
11900
|
+
"\uC774\uBC88 \uD134 \uC2E4\uD589 \uACB0\uACFC:"
|
|
11901
|
+
];
|
|
11902
|
+
for (const tool of tools) {
|
|
11903
|
+
const status = tool.success ? "\u2705 \uC131\uACF5" : "\u274C \uC2E4\uD328";
|
|
11904
|
+
parts.push(`- ${tool.name}(${tool.inputSummary}) \u2192 ${status}`);
|
|
11905
|
+
if (tool.analystSummary) {
|
|
11906
|
+
parts.push(` \uC694\uC57D: ${tool.analystSummary}`);
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
if (tools.length === 0) {
|
|
11910
|
+
parts.push("- (\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)");
|
|
11911
|
+
}
|
|
11912
|
+
parts.push("");
|
|
11913
|
+
parts.push("Analyst \uCD94\uCD9C \uBA54\uBAA8:");
|
|
11914
|
+
if (memo6.keyFindings.length > 0) parts.push(` \uBC1C\uACAC: ${memo6.keyFindings.join(", ")}`);
|
|
11915
|
+
if (memo6.credentials.length > 0) parts.push(` \uD06C\uB808\uB374\uC15C: ${memo6.credentials.join(", ")}`);
|
|
11916
|
+
if (memo6.failures.length > 0) parts.push(` \uC2E4\uD328: ${memo6.failures.join(", ")}`);
|
|
11917
|
+
if (memo6.suspicions.length > 0) parts.push(` \uC758\uC2EC: ${memo6.suspicions.join(", ")}`);
|
|
11918
|
+
parts.push(` \uACF5\uACA9 \uAC00\uCE58: ${memo6.attackValue}`);
|
|
11919
|
+
return parts.join("\n");
|
|
11920
|
+
}
|
|
11921
|
+
|
|
11540
11922
|
// src/agents/main-agent.ts
|
|
11923
|
+
import { writeFileSync as writeFileSync9, existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
11924
|
+
import { join as join13 } from "path";
|
|
11541
11925
|
var MainAgent = class extends CoreAgent {
|
|
11542
11926
|
promptBuilder;
|
|
11543
11927
|
strategist;
|
|
11544
11928
|
approvalGate;
|
|
11545
11929
|
scopeGuard;
|
|
11546
11930
|
userInput = "";
|
|
11931
|
+
/** Monotonic turn counter for journal entries */
|
|
11932
|
+
turnCounter = 0;
|
|
11547
11933
|
constructor(state, events, toolRegistry, approvalGate, scopeGuard) {
|
|
11548
11934
|
super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
|
|
11549
11935
|
this.approvalGate = approvalGate;
|
|
@@ -11581,8 +11967,116 @@ var MainAgent = class extends CoreAgent {
|
|
|
11581
11967
|
* The Strategist LLM generates a fresh tactical directive every turn.
|
|
11582
11968
|
*/
|
|
11583
11969
|
async step(iteration, messages, _unusedPrompt, progress) {
|
|
11970
|
+
if (this.turnCounter === 0) {
|
|
11971
|
+
this.turnCounter = getNextTurnNumber();
|
|
11972
|
+
}
|
|
11973
|
+
this.turnToolJournal = [];
|
|
11974
|
+
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
11975
|
+
this.turnReflections = [];
|
|
11584
11976
|
const dynamicPrompt = await this.getCurrentPrompt();
|
|
11585
11977
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
11978
|
+
try {
|
|
11979
|
+
if (messages.length > 2) {
|
|
11980
|
+
const extraction = await this.llm.generateResponse(
|
|
11981
|
+
[{ role: "user", content: formatForExtraction(messages) }],
|
|
11982
|
+
void 0,
|
|
11983
|
+
CONTEXT_EXTRACTOR_PROMPT
|
|
11984
|
+
);
|
|
11985
|
+
if (extraction.content?.trim()) {
|
|
11986
|
+
messages.length = 0;
|
|
11987
|
+
messages.push({
|
|
11988
|
+
role: "user",
|
|
11989
|
+
content: `<session-context>
|
|
11990
|
+
${extraction.content.trim()}
|
|
11991
|
+
</session-context>`
|
|
11992
|
+
});
|
|
11993
|
+
}
|
|
11994
|
+
}
|
|
11995
|
+
} catch {
|
|
11996
|
+
}
|
|
11997
|
+
try {
|
|
11998
|
+
if (this.turnToolJournal.length > 0) {
|
|
11999
|
+
const reflection = await this.llm.generateResponse(
|
|
12000
|
+
[{
|
|
12001
|
+
role: "user",
|
|
12002
|
+
content: formatReflectionInput({
|
|
12003
|
+
tools: this.turnToolJournal,
|
|
12004
|
+
memo: this.turnMemo,
|
|
12005
|
+
phase: this.state.getPhase()
|
|
12006
|
+
})
|
|
12007
|
+
}],
|
|
12008
|
+
void 0,
|
|
12009
|
+
REFLECTION_PROMPT
|
|
12010
|
+
);
|
|
12011
|
+
if (reflection.content?.trim()) {
|
|
12012
|
+
this.turnReflections.push(reflection.content.trim());
|
|
12013
|
+
}
|
|
12014
|
+
}
|
|
12015
|
+
} catch {
|
|
12016
|
+
}
|
|
12017
|
+
if (this.turnToolJournal.length > 0) {
|
|
12018
|
+
try {
|
|
12019
|
+
const entry = {
|
|
12020
|
+
turn: this.turnCounter,
|
|
12021
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12022
|
+
phase: this.state.getPhase(),
|
|
12023
|
+
tools: this.turnToolJournal,
|
|
12024
|
+
memo: this.turnMemo,
|
|
12025
|
+
reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
|
|
12026
|
+
};
|
|
12027
|
+
writeJournalEntry(entry);
|
|
12028
|
+
try {
|
|
12029
|
+
ensureDirExists(WORKSPACE.TURNS);
|
|
12030
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
12031
|
+
const turnFileName = `turn-${String(this.turnCounter).padStart(3, "0")}_${ts}.md`;
|
|
12032
|
+
const turnPath = join13(WORKSPACE.TURNS, turnFileName);
|
|
12033
|
+
const turnContent = formatTurnRecord({
|
|
12034
|
+
turn: this.turnCounter,
|
|
12035
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12036
|
+
phase: this.state.getPhase(),
|
|
12037
|
+
tools: this.turnToolJournal,
|
|
12038
|
+
memo: this.turnMemo,
|
|
12039
|
+
reflection: entry.reflection
|
|
12040
|
+
});
|
|
12041
|
+
writeFileSync9(turnPath, turnContent, "utf-8");
|
|
12042
|
+
} catch {
|
|
12043
|
+
}
|
|
12044
|
+
try {
|
|
12045
|
+
const summaryPath = join13(WORKSPACE.TURNS, "summary.md");
|
|
12046
|
+
const existingSummary = existsSync11(summaryPath) ? readFileSync8(summaryPath, "utf-8") : "";
|
|
12047
|
+
const turnData = formatTurnRecord({
|
|
12048
|
+
turn: this.turnCounter,
|
|
12049
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12050
|
+
phase: this.state.getPhase(),
|
|
12051
|
+
tools: this.turnToolJournal,
|
|
12052
|
+
memo: this.turnMemo,
|
|
12053
|
+
reflection: entry.reflection
|
|
12054
|
+
});
|
|
12055
|
+
const summaryResponse = await this.llm.generateResponse(
|
|
12056
|
+
[{
|
|
12057
|
+
role: "user",
|
|
12058
|
+
content: existingSummary ? `\uAE30\uC874 \uC694\uC57D:
|
|
12059
|
+
${existingSummary}
|
|
12060
|
+
|
|
12061
|
+
\uC774\uBC88 \uD134:
|
|
12062
|
+
${turnData}` : `\uCCAB \uD134 \uB370\uC774\uD130:
|
|
12063
|
+
${turnData}`
|
|
12064
|
+
}],
|
|
12065
|
+
void 0,
|
|
12066
|
+
SUMMARY_REGENERATOR_PROMPT
|
|
12067
|
+
);
|
|
12068
|
+
if (summaryResponse.content?.trim()) {
|
|
12069
|
+
writeFileSync9(summaryPath, summaryResponse.content.trim(), "utf-8");
|
|
12070
|
+
}
|
|
12071
|
+
} catch {
|
|
12072
|
+
regenerateJournalSummary();
|
|
12073
|
+
}
|
|
12074
|
+
rotateJournalEntries();
|
|
12075
|
+
rotateOutputFiles();
|
|
12076
|
+
} catch {
|
|
12077
|
+
}
|
|
12078
|
+
this.turnCounter++;
|
|
12079
|
+
}
|
|
11586
12080
|
this.emitStateChange();
|
|
11587
12081
|
return result2;
|
|
11588
12082
|
}
|