pentesting 0.47.3 → 0.47.4
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 -4
- package/dist/main.js +916 -682
- package/dist/prompts/base.md +1 -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.47.
|
|
334
|
+
var APP_VERSION = "0.47.4";
|
|
335
335
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
336
|
var LLM_ROLES = {
|
|
337
337
|
SYSTEM: "system",
|
|
@@ -828,6 +828,7 @@ var SESSIONS_DIR = `${PENTESTING_ROOT}/sessions`;
|
|
|
828
828
|
var LOOT_DIR = `${PENTESTING_ROOT}/loot`;
|
|
829
829
|
var OUTPUTS_DIR = `${PENTESTING_ROOT}/outputs`;
|
|
830
830
|
var DEBUG_DIR = `${PENTESTING_ROOT}/debug`;
|
|
831
|
+
var JOURNAL_DIR = `${PENTESTING_ROOT}/journal`;
|
|
831
832
|
var WORKSPACE = {
|
|
832
833
|
/** Root directory */
|
|
833
834
|
get ROOT() {
|
|
@@ -860,6 +861,10 @@ var WORKSPACE = {
|
|
|
860
861
|
/** Debug logs */
|
|
861
862
|
get DEBUG() {
|
|
862
863
|
return path.resolve(DEBUG_DIR);
|
|
864
|
+
},
|
|
865
|
+
/** Persistent per-turn journal (§13 memo system) */
|
|
866
|
+
get JOURNAL() {
|
|
867
|
+
return path.resolve(JOURNAL_DIR);
|
|
863
868
|
}
|
|
864
869
|
};
|
|
865
870
|
|
|
@@ -2687,13 +2692,13 @@ var AttackGraph = class {
|
|
|
2687
2692
|
* Record a credential discovery and create spray edges.
|
|
2688
2693
|
*/
|
|
2689
2694
|
addCredential(username, password, source) {
|
|
2690
|
-
const credId = this.addNode(
|
|
2695
|
+
const credId = this.addNode(NODE_TYPE.CREDENTIAL, `${username}:***`, {
|
|
2691
2696
|
username,
|
|
2692
2697
|
password,
|
|
2693
2698
|
source
|
|
2694
2699
|
});
|
|
2695
2700
|
for (const [id, node] of this.nodes) {
|
|
2696
|
-
if (node.type ===
|
|
2701
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
2697
2702
|
const svc = String(node.data.service || "");
|
|
2698
2703
|
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
|
|
2699
2704
|
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
@@ -2706,7 +2711,7 @@ var AttackGraph = class {
|
|
|
2706
2711
|
* Record a vulnerability finding.
|
|
2707
2712
|
*/
|
|
2708
2713
|
addVulnerability(title, target, severity, hasExploit = false) {
|
|
2709
|
-
const vulnId = this.addNode(
|
|
2714
|
+
const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, title, {
|
|
2710
2715
|
target,
|
|
2711
2716
|
severity,
|
|
2712
2717
|
hasExploit
|
|
@@ -2717,7 +2722,7 @@ var AttackGraph = class {
|
|
|
2717
2722
|
}
|
|
2718
2723
|
}
|
|
2719
2724
|
if (hasExploit) {
|
|
2720
|
-
const accessId = this.addNode(
|
|
2725
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `shell via ${title}`, {
|
|
2721
2726
|
via: title,
|
|
2722
2727
|
status: GRAPH_STATUS.POTENTIAL
|
|
2723
2728
|
});
|
|
@@ -2729,14 +2734,14 @@ var AttackGraph = class {
|
|
|
2729
2734
|
* Record gained access.
|
|
2730
2735
|
*/
|
|
2731
2736
|
addAccess(host, level, via) {
|
|
2732
|
-
const accessId = this.addNode(
|
|
2737
|
+
const accessId = this.addNode(NODE_TYPE.ACCESS, `${level}@${host}`, {
|
|
2733
2738
|
host,
|
|
2734
2739
|
level,
|
|
2735
2740
|
via
|
|
2736
2741
|
});
|
|
2737
2742
|
this.markSucceeded(accessId);
|
|
2738
2743
|
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
2739
|
-
const lootId = this.addNode(
|
|
2744
|
+
const lootId = this.addNode(NODE_TYPE.LOOT, `flags on ${host}`, {
|
|
2740
2745
|
host,
|
|
2741
2746
|
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
2742
2747
|
});
|
|
@@ -2748,7 +2753,7 @@ var AttackGraph = class {
|
|
|
2748
2753
|
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
2749
2754
|
*/
|
|
2750
2755
|
addOSINT(category, detail, data = {}) {
|
|
2751
|
-
const osintId = this.addNode(
|
|
2756
|
+
const osintId = this.addNode(NODE_TYPE.OSINT, `${category}: ${detail}`, {
|
|
2752
2757
|
category,
|
|
2753
2758
|
detail,
|
|
2754
2759
|
...data
|
|
@@ -3981,20 +3986,6 @@ var ScopeGuard = class {
|
|
|
3981
3986
|
};
|
|
3982
3987
|
|
|
3983
3988
|
// 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
3989
|
var ApprovalGate = class {
|
|
3999
3990
|
constructor(shouldAutoApprove = false) {
|
|
4000
3991
|
this.shouldAutoApprove = shouldAutoApprove;
|
|
@@ -4298,7 +4289,7 @@ function autoExtractStructured(toolName, output) {
|
|
|
4298
4289
|
data.vulnerabilities = vulns;
|
|
4299
4290
|
hasData = true;
|
|
4300
4291
|
}
|
|
4301
|
-
if (toolName ===
|
|
4292
|
+
if (toolName === TOOL_NAMES.PARSE_NMAP || /nmap scan report/i.test(output)) {
|
|
4302
4293
|
const nmap = extractNmapStructured(output);
|
|
4303
4294
|
if (nmap.structured.openPorts && nmap.structured.openPorts.length > 0) {
|
|
4304
4295
|
data.openPorts = nmap.structured.openPorts;
|
|
@@ -4703,7 +4694,8 @@ Used ports: ${usedPorts.join(", ")}
|
|
|
4703
4694
|
[!] STRATEGY ADAPTATION REQUIRED:
|
|
4704
4695
|
1. Try the next available port (e.g., ${nextPort} or 4445, 9001)
|
|
4705
4696
|
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
|
|
4697
|
+
3. Check bg_process({ action: "list" }) to see if you can stop the conflicting process.`,
|
|
4698
|
+
error: `Port ${requestedPort} already in use`
|
|
4707
4699
|
};
|
|
4708
4700
|
}
|
|
4709
4701
|
}
|
|
@@ -4850,7 +4842,7 @@ ${output.stderr.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) || "(empty)"}` + connecti
|
|
|
4850
4842
|
if (!cmd) return { success: false, output: "", error: "Missing command for interact. Provide the command to execute on the target." };
|
|
4851
4843
|
const waitMs = Math.min(params.wait_ms || SYSTEM_LIMITS.DEFAULT_WAIT_MS_INTERACT, SYSTEM_LIMITS.MAX_WAIT_MS_INTERACT);
|
|
4852
4844
|
const result2 = await sendToProcess(processId, cmd, waitMs);
|
|
4853
|
-
if (!result2.success) return { success: false, output: result2.output };
|
|
4845
|
+
if (!result2.success) return { success: false, output: result2.output, error: result2.output };
|
|
4854
4846
|
return {
|
|
4855
4847
|
success: true,
|
|
4856
4848
|
output: `Command sent: ${cmd}
|
|
@@ -4866,7 +4858,7 @@ ${result2.output}`
|
|
|
4866
4858
|
if (!processId) return { success: false, output: "", error: "Missing process_id for promote" };
|
|
4867
4859
|
const desc = params.description;
|
|
4868
4860
|
const success = promoteToShell(processId, desc);
|
|
4869
|
-
if (!success) return { success: false, output: `Process ${processId} not found` };
|
|
4861
|
+
if (!success) return { success: false, output: `Process ${processId} not found`, error: `Process ${processId} not found` };
|
|
4870
4862
|
return {
|
|
4871
4863
|
success: true,
|
|
4872
4864
|
output: `[OK] Process ${processId} promoted to ACTIVE SHELL.
|
|
@@ -5016,7 +5008,8 @@ Examples:
|
|
|
5016
5008
|
if (!validPhases.includes(newPhase)) {
|
|
5017
5009
|
return {
|
|
5018
5010
|
success: false,
|
|
5019
|
-
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}
|
|
5011
|
+
output: `Invalid phase. Valid phases: ${validPhases.join(", ")}`,
|
|
5012
|
+
error: `Invalid phase: ${newPhase}`
|
|
5020
5013
|
};
|
|
5021
5014
|
}
|
|
5022
5015
|
state.setPhase(newPhase);
|
|
@@ -5732,50 +5725,60 @@ var DEFAULT_BROWSER_OPTIONS = {
|
|
|
5732
5725
|
|
|
5733
5726
|
// src/engine/tools/web-browser.ts
|
|
5734
5727
|
async function browseUrl(url, options = {}) {
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5728
|
+
try {
|
|
5729
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5730
|
+
const { installed, browserInstalled } = await checkPlaywright();
|
|
5731
|
+
if (!installed || !browserInstalled) {
|
|
5732
|
+
const installResult = await installPlaywright();
|
|
5733
|
+
if (!installResult.success) {
|
|
5734
|
+
return {
|
|
5735
|
+
success: false,
|
|
5736
|
+
output: "",
|
|
5737
|
+
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
5738
|
+
};
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
5742
|
+
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5743
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5744
|
+
if (!result2.success) {
|
|
5740
5745
|
return {
|
|
5741
5746
|
success: false,
|
|
5742
|
-
output:
|
|
5743
|
-
error:
|
|
5747
|
+
output: result2.output,
|
|
5748
|
+
error: result2.error
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
if (result2.parsedData) {
|
|
5752
|
+
return {
|
|
5753
|
+
success: true,
|
|
5754
|
+
output: formatBrowserOutput(result2.parsedData, browserOptions),
|
|
5755
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
5756
|
+
extractedData: result2.parsedData
|
|
5744
5757
|
};
|
|
5745
5758
|
}
|
|
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
5759
|
return {
|
|
5752
|
-
success:
|
|
5753
|
-
output: result2.output,
|
|
5754
|
-
|
|
5760
|
+
success: true,
|
|
5761
|
+
output: result2.output || "Navigation completed",
|
|
5762
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5755
5763
|
};
|
|
5756
|
-
}
|
|
5757
|
-
|
|
5764
|
+
} catch (error) {
|
|
5765
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5758
5766
|
return {
|
|
5759
|
-
success:
|
|
5760
|
-
output:
|
|
5761
|
-
|
|
5762
|
-
extractedData: result2.parsedData
|
|
5767
|
+
success: false,
|
|
5768
|
+
output: "",
|
|
5769
|
+
error: `Browser error: ${msg}`
|
|
5763
5770
|
};
|
|
5764
5771
|
}
|
|
5765
|
-
return {
|
|
5766
|
-
success: true,
|
|
5767
|
-
output: result2.output || "Navigation completed",
|
|
5768
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
5769
|
-
};
|
|
5770
5772
|
}
|
|
5771
5773
|
async function fillAndSubmitForm(url, formData, options = {}) {
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5774
|
+
try {
|
|
5775
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
5776
|
+
const safeUrl = safeJsString(url);
|
|
5777
|
+
const safeFormData = JSON.stringify(formData);
|
|
5778
|
+
const playwrightPath = getPlaywrightPath();
|
|
5779
|
+
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5780
|
+
const headlessMode = process.env.HEADLESS !== "false";
|
|
5781
|
+
const script = `
|
|
5779
5782
|
const { chromium } = require(${safePlaywrightPath});
|
|
5780
5783
|
|
|
5781
5784
|
(async () => {
|
|
@@ -5815,27 +5818,35 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
5815
5818
|
}
|
|
5816
5819
|
})();
|
|
5817
5820
|
`;
|
|
5818
|
-
|
|
5819
|
-
|
|
5821
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "form");
|
|
5822
|
+
if (!result2.success) {
|
|
5823
|
+
return {
|
|
5824
|
+
success: false,
|
|
5825
|
+
output: result2.output,
|
|
5826
|
+
error: result2.error
|
|
5827
|
+
};
|
|
5828
|
+
}
|
|
5829
|
+
if (result2.parsedData) {
|
|
5830
|
+
const data = result2.parsedData;
|
|
5831
|
+
return {
|
|
5832
|
+
success: true,
|
|
5833
|
+
output: `Form submitted. Current URL: ${data.url}
|
|
5834
|
+
Title: ${data.title}`,
|
|
5835
|
+
extractedData: result2.parsedData
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5820
5838
|
return {
|
|
5821
|
-
success:
|
|
5822
|
-
output: result2.output
|
|
5823
|
-
error: result2.error
|
|
5839
|
+
success: true,
|
|
5840
|
+
output: result2.output || "Form submitted"
|
|
5824
5841
|
};
|
|
5825
|
-
}
|
|
5826
|
-
|
|
5827
|
-
const data = result2.parsedData;
|
|
5842
|
+
} catch (error) {
|
|
5843
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5828
5844
|
return {
|
|
5829
|
-
success:
|
|
5830
|
-
output:
|
|
5831
|
-
|
|
5832
|
-
extractedData: result2.parsedData
|
|
5845
|
+
success: false,
|
|
5846
|
+
output: "",
|
|
5847
|
+
error: `Form submission error: ${msg}`
|
|
5833
5848
|
};
|
|
5834
5849
|
}
|
|
5835
|
-
return {
|
|
5836
|
-
success: true,
|
|
5837
|
-
output: result2.output || "Form submitted"
|
|
5838
|
-
};
|
|
5839
5850
|
}
|
|
5840
5851
|
async function webSearchWithBrowser(query, engine = "google") {
|
|
5841
5852
|
const searchUrls = {
|
|
@@ -5852,6 +5863,10 @@ async function webSearchWithBrowser(query, engine = "google") {
|
|
|
5852
5863
|
}
|
|
5853
5864
|
|
|
5854
5865
|
// src/engine/web-search-providers.ts
|
|
5866
|
+
var SEARCH_TIMEOUT_MS = 15e3;
|
|
5867
|
+
function getErrorMessage(error) {
|
|
5868
|
+
return error instanceof Error ? error.message : String(error);
|
|
5869
|
+
}
|
|
5855
5870
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
5856
5871
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5857
5872
|
const requestBody = {
|
|
@@ -5860,21 +5875,39 @@ async function searchWithGLM(query, apiKey, apiUrl) {
|
|
|
5860
5875
|
stream: false
|
|
5861
5876
|
};
|
|
5862
5877
|
debugLog("search", "GLM request body", requestBody);
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5878
|
+
let response;
|
|
5879
|
+
try {
|
|
5880
|
+
response = await fetch(apiUrl, {
|
|
5881
|
+
method: "POST",
|
|
5882
|
+
headers: {
|
|
5883
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5884
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`
|
|
5885
|
+
},
|
|
5886
|
+
body: JSON.stringify(requestBody),
|
|
5887
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5888
|
+
});
|
|
5889
|
+
} catch (err) {
|
|
5890
|
+
const msg = getErrorMessage(err);
|
|
5891
|
+
debugLog("search", "GLM fetch FAILED (network)", { error: msg });
|
|
5892
|
+
return { success: false, output: "", error: `GLM search network error: ${msg}. Check internet connection or API endpoint.` };
|
|
5893
|
+
}
|
|
5871
5894
|
debugLog("search", "GLM response status", { status: response.status, ok: response.ok });
|
|
5872
5895
|
if (!response.ok) {
|
|
5873
|
-
|
|
5896
|
+
let errorText = "";
|
|
5897
|
+
try {
|
|
5898
|
+
errorText = await response.text();
|
|
5899
|
+
} catch {
|
|
5900
|
+
}
|
|
5874
5901
|
debugLog("search", "GLM response ERROR", { status: response.status, error: errorText });
|
|
5875
|
-
|
|
5902
|
+
return { success: false, output: "", error: `GLM Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5903
|
+
}
|
|
5904
|
+
let data;
|
|
5905
|
+
try {
|
|
5906
|
+
data = await response.json();
|
|
5907
|
+
} catch (err) {
|
|
5908
|
+
debugLog("search", "GLM JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5909
|
+
return { success: false, output: "", error: `GLM search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5876
5910
|
}
|
|
5877
|
-
const data = await response.json();
|
|
5878
5911
|
debugLog("search", "GLM response data", { hasChoices: !!data.choices, choicesCount: data.choices?.length });
|
|
5879
5912
|
let results = "";
|
|
5880
5913
|
if (data.choices?.[0]?.message?.content) {
|
|
@@ -5902,19 +5935,37 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5902
5935
|
debugLog("search", "Brave request START", { apiUrl, query });
|
|
5903
5936
|
const url = `${apiUrl}?q=${encodeURIComponent(query)}&count=${SEARCH_LIMIT.DEFAULT_RESULT_COUNT}`;
|
|
5904
5937
|
debugLog("search", "Brave request URL", { url });
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5938
|
+
let response;
|
|
5939
|
+
try {
|
|
5940
|
+
response = await fetch(url, {
|
|
5941
|
+
headers: {
|
|
5942
|
+
[SEARCH_HEADER.ACCEPT]: "application/json",
|
|
5943
|
+
[SEARCH_HEADER.X_SUBSCRIPTION_TOKEN]: apiKey
|
|
5944
|
+
},
|
|
5945
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5946
|
+
});
|
|
5947
|
+
} catch (err) {
|
|
5948
|
+
const msg = getErrorMessage(err);
|
|
5949
|
+
debugLog("search", "Brave fetch FAILED (network)", { error: msg });
|
|
5950
|
+
return { success: false, output: "", error: `Brave search network error: ${msg}. Check internet connection.` };
|
|
5951
|
+
}
|
|
5911
5952
|
debugLog("search", "Brave response status", { status: response.status, ok: response.ok });
|
|
5912
5953
|
if (!response.ok) {
|
|
5913
|
-
|
|
5954
|
+
let errorText = "";
|
|
5955
|
+
try {
|
|
5956
|
+
errorText = await response.text();
|
|
5957
|
+
} catch {
|
|
5958
|
+
}
|
|
5914
5959
|
debugLog("search", "Brave response ERROR", { status: response.status, error: errorText });
|
|
5915
|
-
|
|
5960
|
+
return { success: false, output: "", error: `Brave API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
5961
|
+
}
|
|
5962
|
+
let data;
|
|
5963
|
+
try {
|
|
5964
|
+
data = await response.json();
|
|
5965
|
+
} catch (err) {
|
|
5966
|
+
debugLog("search", "Brave JSON parse FAILED", { error: getErrorMessage(err) });
|
|
5967
|
+
return { success: false, output: "", error: `Brave search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5916
5968
|
}
|
|
5917
|
-
const data = await response.json();
|
|
5918
5969
|
const results = data.web?.results || [];
|
|
5919
5970
|
debugLog("search", "Brave results count", { count: results.length });
|
|
5920
5971
|
if (results.length === 0) {
|
|
@@ -5930,21 +5981,39 @@ async function searchWithBrave(query, apiKey, apiUrl) {
|
|
|
5930
5981
|
}
|
|
5931
5982
|
async function searchWithSerper(query, apiKey, apiUrl) {
|
|
5932
5983
|
debugLog("search", "Serper request START", { apiUrl, query });
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5984
|
+
let response;
|
|
5985
|
+
try {
|
|
5986
|
+
response = await fetch(apiUrl, {
|
|
5987
|
+
method: "POST",
|
|
5988
|
+
headers: {
|
|
5989
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
5990
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
5991
|
+
},
|
|
5992
|
+
body: JSON.stringify({ q: query }),
|
|
5993
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
5994
|
+
});
|
|
5995
|
+
} catch (err) {
|
|
5996
|
+
const msg = getErrorMessage(err);
|
|
5997
|
+
debugLog("search", "Serper fetch FAILED (network)", { error: msg });
|
|
5998
|
+
return { success: false, output: "", error: `Serper search network error: ${msg}. Check internet connection.` };
|
|
5999
|
+
}
|
|
5941
6000
|
debugLog("search", "Serper response status", { status: response.status, ok: response.ok });
|
|
5942
6001
|
if (!response.ok) {
|
|
5943
|
-
|
|
6002
|
+
let errorText = "";
|
|
6003
|
+
try {
|
|
6004
|
+
errorText = await response.text();
|
|
6005
|
+
} catch {
|
|
6006
|
+
}
|
|
5944
6007
|
debugLog("search", "Serper response ERROR", { status: response.status, error: errorText });
|
|
5945
|
-
|
|
6008
|
+
return { success: false, output: "", error: `Serper API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6009
|
+
}
|
|
6010
|
+
let data;
|
|
6011
|
+
try {
|
|
6012
|
+
data = await response.json();
|
|
6013
|
+
} catch (err) {
|
|
6014
|
+
debugLog("search", "Serper JSON parse FAILED", { error: getErrorMessage(err) });
|
|
6015
|
+
return { success: false, output: "", error: `Serper search returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5946
6016
|
}
|
|
5947
|
-
const data = await response.json();
|
|
5948
6017
|
const results = data.organic || [];
|
|
5949
6018
|
debugLog("search", "Serper results count", { count: results.length });
|
|
5950
6019
|
if (results.length === 0) {
|
|
@@ -5959,20 +6028,36 @@ async function searchWithSerper(query, apiKey, apiUrl) {
|
|
|
5959
6028
|
return { success: true, output: formatted };
|
|
5960
6029
|
}
|
|
5961
6030
|
async function searchWithGenericApi(query, apiKey, apiUrl) {
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
6031
|
+
let response;
|
|
6032
|
+
try {
|
|
6033
|
+
response = await fetch(apiUrl, {
|
|
6034
|
+
method: "POST",
|
|
6035
|
+
headers: {
|
|
6036
|
+
[SEARCH_HEADER.CONTENT_TYPE]: "application/json",
|
|
6037
|
+
[SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
|
|
6038
|
+
[SEARCH_HEADER.X_API_KEY]: apiKey
|
|
6039
|
+
},
|
|
6040
|
+
body: JSON.stringify({ query, q: query }),
|
|
6041
|
+
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
6042
|
+
});
|
|
6043
|
+
} catch (err) {
|
|
6044
|
+
const msg = getErrorMessage(err);
|
|
6045
|
+
return { success: false, output: "", error: `Search API network error: ${msg}. Check internet connection or API endpoint.` };
|
|
6046
|
+
}
|
|
5971
6047
|
if (!response.ok) {
|
|
5972
|
-
|
|
5973
|
-
|
|
6048
|
+
let errorText = "";
|
|
6049
|
+
try {
|
|
6050
|
+
errorText = await response.text();
|
|
6051
|
+
} catch {
|
|
6052
|
+
}
|
|
6053
|
+
return { success: false, output: "", error: `Search API error ${response.status}: ${errorText.slice(0, 500)}` };
|
|
6054
|
+
}
|
|
6055
|
+
let data;
|
|
6056
|
+
try {
|
|
6057
|
+
data = await response.json();
|
|
6058
|
+
} catch (err) {
|
|
6059
|
+
return { success: false, output: "", error: `Search API returned invalid JSON: ${getErrorMessage(err)}` };
|
|
5974
6060
|
}
|
|
5975
|
-
const data = await response.json();
|
|
5976
6061
|
return { success: true, output: JSON.stringify(data, null, 2) };
|
|
5977
6062
|
}
|
|
5978
6063
|
|
|
@@ -5982,7 +6067,7 @@ var PORT_STATE2 = {
|
|
|
5982
6067
|
CLOSED: "closed",
|
|
5983
6068
|
FILTERED: "filtered"
|
|
5984
6069
|
};
|
|
5985
|
-
function
|
|
6070
|
+
function getErrorMessage2(error) {
|
|
5986
6071
|
return error instanceof Error ? error.message : String(error);
|
|
5987
6072
|
}
|
|
5988
6073
|
async function parseNmap(xmlPath) {
|
|
@@ -6033,7 +6118,7 @@ async function parseNmap(xmlPath) {
|
|
|
6033
6118
|
return {
|
|
6034
6119
|
success: false,
|
|
6035
6120
|
output: "",
|
|
6036
|
-
error:
|
|
6121
|
+
error: getErrorMessage2(error)
|
|
6037
6122
|
};
|
|
6038
6123
|
}
|
|
6039
6124
|
}
|
|
@@ -6044,7 +6129,7 @@ async function searchCVE(service, version) {
|
|
|
6044
6129
|
return {
|
|
6045
6130
|
success: false,
|
|
6046
6131
|
output: "",
|
|
6047
|
-
error:
|
|
6132
|
+
error: getErrorMessage2(error)
|
|
6048
6133
|
};
|
|
6049
6134
|
}
|
|
6050
6135
|
}
|
|
@@ -6081,7 +6166,7 @@ async function searchExploitDB(service, version) {
|
|
|
6081
6166
|
return {
|
|
6082
6167
|
success: false,
|
|
6083
6168
|
output: "",
|
|
6084
|
-
error:
|
|
6169
|
+
error: getErrorMessage2(error)
|
|
6085
6170
|
};
|
|
6086
6171
|
}
|
|
6087
6172
|
}
|
|
@@ -6128,11 +6213,11 @@ async function webSearch(query, _engine) {
|
|
|
6128
6213
|
return await searchWithGenericApi(query, apiKey, apiUrl);
|
|
6129
6214
|
}
|
|
6130
6215
|
} catch (error) {
|
|
6131
|
-
debugLog("search", "webSearch ERROR", { error:
|
|
6216
|
+
debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
|
|
6132
6217
|
return {
|
|
6133
6218
|
success: false,
|
|
6134
6219
|
output: "",
|
|
6135
|
-
error:
|
|
6220
|
+
error: getErrorMessage2(error)
|
|
6136
6221
|
};
|
|
6137
6222
|
}
|
|
6138
6223
|
}
|
|
@@ -6509,7 +6594,8 @@ ${results.join("\n\n")}`
|
|
|
6509
6594
|
}
|
|
6510
6595
|
return {
|
|
6511
6596
|
success: false,
|
|
6512
|
-
output: `Category ${category} not found in any edition
|
|
6597
|
+
output: `Category ${category} not found in any edition.`,
|
|
6598
|
+
error: `Category ${category} not found`
|
|
6513
6599
|
};
|
|
6514
6600
|
}
|
|
6515
6601
|
if (edition === "all") {
|
|
@@ -6520,7 +6606,7 @@ ${results.join("\n\n")}`
|
|
|
6520
6606
|
}
|
|
6521
6607
|
const data = OWASP_FULL_HISTORY[edition];
|
|
6522
6608
|
if (!data) {
|
|
6523
|
-
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025
|
|
6609
|
+
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025.`, error: `Edition ${edition} not found` };
|
|
6524
6610
|
}
|
|
6525
6611
|
return {
|
|
6526
6612
|
success: true,
|
|
@@ -7065,8 +7151,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7065
7151
|
}
|
|
7066
7152
|
},
|
|
7067
7153
|
execute: async (p) => {
|
|
7068
|
-
const { existsSync:
|
|
7069
|
-
const { join:
|
|
7154
|
+
const { existsSync: existsSync11, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7155
|
+
const { join: join13 } = await import("path");
|
|
7070
7156
|
const category = p.category || "";
|
|
7071
7157
|
const search = p.search || "";
|
|
7072
7158
|
const minSize = p.min_size || 0;
|
|
@@ -7102,7 +7188,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7102
7188
|
const processFile = (fullPath, fileName) => {
|
|
7103
7189
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
7104
7190
|
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
7105
|
-
const stats =
|
|
7191
|
+
const stats = statSync3(fullPath);
|
|
7106
7192
|
if (stats.size < minSize) return;
|
|
7107
7193
|
if (!matchesCategory(fullPath)) return;
|
|
7108
7194
|
if (!matchesSearch(fullPath, fileName)) return;
|
|
@@ -7112,16 +7198,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7112
7198
|
results.push("");
|
|
7113
7199
|
};
|
|
7114
7200
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
7115
|
-
if (depth > maxDepth || !
|
|
7201
|
+
if (depth > maxDepth || !existsSync11(dirPath)) return;
|
|
7116
7202
|
let entries;
|
|
7117
7203
|
try {
|
|
7118
|
-
entries =
|
|
7204
|
+
entries = readdirSync4(dirPath, { withFileTypes: true });
|
|
7119
7205
|
} catch {
|
|
7120
7206
|
return;
|
|
7121
7207
|
}
|
|
7122
7208
|
for (const entry of entries) {
|
|
7123
7209
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
7124
|
-
const fullPath =
|
|
7210
|
+
const fullPath = join13(dirPath, entry.name);
|
|
7125
7211
|
if (entry.isDirectory()) {
|
|
7126
7212
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
7127
7213
|
continue;
|
|
@@ -7498,8 +7584,8 @@ Requires root/sudo privileges.`,
|
|
|
7498
7584
|
const iface = p.interface || "";
|
|
7499
7585
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7500
7586
|
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7501
|
-
const { writeFileSync:
|
|
7502
|
-
|
|
7587
|
+
const { writeFileSync: writeFileSync9 } = await import("fs");
|
|
7588
|
+
writeFileSync9(hostsFile, `${spoofIp} ${domain}
|
|
7503
7589
|
${spoofIp} *.${domain}
|
|
7504
7590
|
`);
|
|
7505
7591
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -8006,6 +8092,86 @@ Returns recommendations on process status, port conflicts, long-running tasks, e
|
|
|
8006
8092
|
}
|
|
8007
8093
|
];
|
|
8008
8094
|
|
|
8095
|
+
// src/shared/constants/service-ports.ts
|
|
8096
|
+
var SERVICE_PORTS = {
|
|
8097
|
+
SSH: 22,
|
|
8098
|
+
FTP: 21,
|
|
8099
|
+
TELNET: 23,
|
|
8100
|
+
SMTP: 25,
|
|
8101
|
+
DNS: 53,
|
|
8102
|
+
HTTP: 80,
|
|
8103
|
+
POP3: 110,
|
|
8104
|
+
IMAP: 143,
|
|
8105
|
+
SMB_NETBIOS: 139,
|
|
8106
|
+
KERBEROS: 88,
|
|
8107
|
+
LDAP: 389,
|
|
8108
|
+
SMB: 445,
|
|
8109
|
+
HTTPS: 443,
|
|
8110
|
+
SMTPS: 465,
|
|
8111
|
+
SMTP_TLS: 587,
|
|
8112
|
+
MODBUS: 502,
|
|
8113
|
+
IMAPS: 993,
|
|
8114
|
+
POP3S: 995,
|
|
8115
|
+
MSSQL: 1433,
|
|
8116
|
+
MYSQL: 3306,
|
|
8117
|
+
RDP: 3389,
|
|
8118
|
+
POSTGRESQL: 5432,
|
|
8119
|
+
VNC: 5900,
|
|
8120
|
+
REDIS: 6379,
|
|
8121
|
+
DOCKER_HTTP: 2375,
|
|
8122
|
+
DOCKER_HTTPS: 2376,
|
|
8123
|
+
KUBERNETES_API: 6443,
|
|
8124
|
+
HTTP_ALT: 8080,
|
|
8125
|
+
HTTPS_ALT: 8443,
|
|
8126
|
+
NFS: 2049,
|
|
8127
|
+
DNP3: 2e4,
|
|
8128
|
+
MONGODB: 27017,
|
|
8129
|
+
ELASTICSEARCH: 9200,
|
|
8130
|
+
MEMCACHED: 11211,
|
|
8131
|
+
NODE_DEFAULT: 3e3,
|
|
8132
|
+
FLASK_DEFAULT: 5e3,
|
|
8133
|
+
DJANGO_DEFAULT: 8e3
|
|
8134
|
+
};
|
|
8135
|
+
var CRITICAL_SERVICE_PORTS = [
|
|
8136
|
+
SERVICE_PORTS.SSH,
|
|
8137
|
+
SERVICE_PORTS.RDP,
|
|
8138
|
+
SERVICE_PORTS.MYSQL,
|
|
8139
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8140
|
+
SERVICE_PORTS.REDIS,
|
|
8141
|
+
SERVICE_PORTS.MONGODB
|
|
8142
|
+
];
|
|
8143
|
+
var NO_AUTH_CRITICAL_PORTS = [
|
|
8144
|
+
SERVICE_PORTS.REDIS,
|
|
8145
|
+
SERVICE_PORTS.MONGODB,
|
|
8146
|
+
SERVICE_PORTS.ELASTICSEARCH,
|
|
8147
|
+
SERVICE_PORTS.MEMCACHED
|
|
8148
|
+
];
|
|
8149
|
+
var WEB_SERVICE_PORTS = [
|
|
8150
|
+
SERVICE_PORTS.HTTP,
|
|
8151
|
+
SERVICE_PORTS.HTTPS,
|
|
8152
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8153
|
+
SERVICE_PORTS.HTTPS_ALT,
|
|
8154
|
+
SERVICE_PORTS.NODE_DEFAULT,
|
|
8155
|
+
SERVICE_PORTS.FLASK_DEFAULT,
|
|
8156
|
+
SERVICE_PORTS.DJANGO_DEFAULT
|
|
8157
|
+
];
|
|
8158
|
+
var PLAINTEXT_HTTP_PORTS = [
|
|
8159
|
+
SERVICE_PORTS.HTTP,
|
|
8160
|
+
SERVICE_PORTS.HTTP_ALT,
|
|
8161
|
+
SERVICE_PORTS.NODE_DEFAULT
|
|
8162
|
+
];
|
|
8163
|
+
var DATABASE_PORTS = [
|
|
8164
|
+
SERVICE_PORTS.MYSQL,
|
|
8165
|
+
SERVICE_PORTS.POSTGRESQL,
|
|
8166
|
+
SERVICE_PORTS.MSSQL,
|
|
8167
|
+
SERVICE_PORTS.MONGODB,
|
|
8168
|
+
SERVICE_PORTS.REDIS
|
|
8169
|
+
];
|
|
8170
|
+
var SMB_PORTS = [
|
|
8171
|
+
SERVICE_PORTS.SMB,
|
|
8172
|
+
SERVICE_PORTS.SMB_NETBIOS
|
|
8173
|
+
];
|
|
8174
|
+
|
|
8009
8175
|
// src/domains/network/tools.ts
|
|
8010
8176
|
var NETWORK_TOOLS = [
|
|
8011
8177
|
{
|
|
@@ -8051,7 +8217,7 @@ var NETWORK_CONFIG2 = {
|
|
|
8051
8217
|
tools: NETWORK_TOOLS,
|
|
8052
8218
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8053
8219
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8054
|
-
commonPorts: [
|
|
8220
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
|
|
8055
8221
|
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
|
|
8056
8222
|
};
|
|
8057
8223
|
|
|
@@ -8114,7 +8280,7 @@ var WEB_CONFIG = {
|
|
|
8114
8280
|
tools: WEB_TOOLS,
|
|
8115
8281
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8116
8282
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8117
|
-
commonPorts: [
|
|
8283
|
+
commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
|
|
8118
8284
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8119
8285
|
};
|
|
8120
8286
|
|
|
@@ -8149,12 +8315,12 @@ var DATABASE_TOOLS = [
|
|
|
8149
8315
|
description: "MySQL enumeration - version, users, databases",
|
|
8150
8316
|
parameters: {
|
|
8151
8317
|
target: { type: "string", description: "Target IP/hostname" },
|
|
8152
|
-
port: { type: "string", description:
|
|
8318
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8153
8319
|
},
|
|
8154
8320
|
required: ["target"],
|
|
8155
8321
|
execute: async (params) => {
|
|
8156
8322
|
const target = params.target;
|
|
8157
|
-
const port = params.port ||
|
|
8323
|
+
const port = params.port || String(SERVICE_PORTS.MYSQL);
|
|
8158
8324
|
return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
|
|
8159
8325
|
}
|
|
8160
8326
|
},
|
|
@@ -8175,12 +8341,12 @@ var DATABASE_TOOLS = [
|
|
|
8175
8341
|
description: "Redis enumeration",
|
|
8176
8342
|
parameters: {
|
|
8177
8343
|
target: { type: "string", description: "Target IP" },
|
|
8178
|
-
port: { type: "string", description:
|
|
8344
|
+
port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8179
8345
|
},
|
|
8180
8346
|
required: ["target"],
|
|
8181
8347
|
execute: async (params) => {
|
|
8182
8348
|
const target = params.target;
|
|
8183
|
-
const port = params.port ||
|
|
8349
|
+
const port = params.port || String(SERVICE_PORTS.REDIS);
|
|
8184
8350
|
return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
|
|
8185
8351
|
}
|
|
8186
8352
|
},
|
|
@@ -8212,7 +8378,7 @@ var DATABASE_CONFIG = {
|
|
|
8212
8378
|
tools: DATABASE_TOOLS,
|
|
8213
8379
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8214
8380
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8215
|
-
commonPorts: [
|
|
8381
|
+
commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
|
|
8216
8382
|
commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
|
|
8217
8383
|
};
|
|
8218
8384
|
|
|
@@ -8265,7 +8431,7 @@ var AD_CONFIG = {
|
|
|
8265
8431
|
tools: AD_TOOLS,
|
|
8266
8432
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8267
8433
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8268
|
-
commonPorts: [
|
|
8434
|
+
commonPorts: [SERVICE_PORTS.KERBEROS, SERVICE_PORTS.LDAP, SERVICE_PORTS.SMB],
|
|
8269
8435
|
commonServices: [SERVICES.AD, SERVICES.SMB]
|
|
8270
8436
|
};
|
|
8271
8437
|
|
|
@@ -8289,7 +8455,7 @@ var EMAIL_CONFIG = {
|
|
|
8289
8455
|
tools: EMAIL_TOOLS,
|
|
8290
8456
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8291
8457
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8292
|
-
commonPorts: [
|
|
8458
|
+
commonPorts: [SERVICE_PORTS.SMTP, SERVICE_PORTS.POP3, SERVICE_PORTS.IMAP, SERVICE_PORTS.SMTPS, SERVICE_PORTS.SMTP_TLS, SERVICE_PORTS.IMAPS, SERVICE_PORTS.POP3S],
|
|
8293
8459
|
commonServices: [SERVICES.SMTP, SERVICES.POP3, SERVICES.IMAP]
|
|
8294
8460
|
};
|
|
8295
8461
|
|
|
@@ -8314,7 +8480,7 @@ var REMOTE_ACCESS_TOOLS = [
|
|
|
8314
8480
|
},
|
|
8315
8481
|
required: ["target"],
|
|
8316
8482
|
execute: async (params) => {
|
|
8317
|
-
return await runCommand("nmap", ["-p",
|
|
8483
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
|
|
8318
8484
|
}
|
|
8319
8485
|
}
|
|
8320
8486
|
];
|
|
@@ -8324,7 +8490,7 @@ var REMOTE_ACCESS_CONFIG = {
|
|
|
8324
8490
|
tools: REMOTE_ACCESS_TOOLS,
|
|
8325
8491
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8326
8492
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8327
|
-
commonPorts: [
|
|
8493
|
+
commonPorts: [SERVICE_PORTS.SSH, SERVICE_PORTS.RDP, SERVICE_PORTS.VNC],
|
|
8328
8494
|
commonServices: [SERVICES.SSH, SERVICES.RDP, SERVICES.VNC]
|
|
8329
8495
|
};
|
|
8330
8496
|
|
|
@@ -8338,7 +8504,7 @@ var FILE_SHARING_TOOLS = [
|
|
|
8338
8504
|
},
|
|
8339
8505
|
required: ["target"],
|
|
8340
8506
|
execute: async (params) => {
|
|
8341
|
-
return await runCommand("nmap", ["-p",
|
|
8507
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", params.target]);
|
|
8342
8508
|
}
|
|
8343
8509
|
},
|
|
8344
8510
|
{
|
|
@@ -8359,7 +8525,7 @@ var FILE_SHARING_CONFIG = {
|
|
|
8359
8525
|
tools: FILE_SHARING_TOOLS,
|
|
8360
8526
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8361
8527
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8362
|
-
commonPorts: [
|
|
8528
|
+
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SMB_NETBIOS, SERVICE_PORTS.SMB, SERVICE_PORTS.NFS],
|
|
8363
8529
|
commonServices: [SERVICES.FTP, SERVICES.SMB, SERVICES.NFS]
|
|
8364
8530
|
};
|
|
8365
8531
|
|
|
@@ -8403,7 +8569,7 @@ var CLOUD_CONFIG = {
|
|
|
8403
8569
|
tools: CLOUD_TOOLS,
|
|
8404
8570
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8405
8571
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8406
|
-
commonPorts: [
|
|
8572
|
+
commonPorts: [SERVICE_PORTS.HTTPS],
|
|
8407
8573
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8408
8574
|
};
|
|
8409
8575
|
|
|
@@ -8438,7 +8604,7 @@ var CONTAINER_CONFIG = {
|
|
|
8438
8604
|
tools: CONTAINER_TOOLS,
|
|
8439
8605
|
dangerLevel: DANGER_LEVELS.EXPLOIT,
|
|
8440
8606
|
defaultApproval: APPROVAL_LEVELS.REVIEW,
|
|
8441
|
-
commonPorts: [
|
|
8607
|
+
commonPorts: [SERVICE_PORTS.DOCKER_HTTP, SERVICE_PORTS.DOCKER_HTTPS, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.KUBERNETES_API],
|
|
8442
8608
|
commonServices: [SERVICES.DOCKER, SERVICES.KUBERNETES]
|
|
8443
8609
|
};
|
|
8444
8610
|
|
|
@@ -8501,7 +8667,7 @@ var API_CONFIG = {
|
|
|
8501
8667
|
tools: API_TOOLS,
|
|
8502
8668
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8503
8669
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8504
|
-
commonPorts: [
|
|
8670
|
+
commonPorts: [SERVICE_PORTS.NODE_DEFAULT, SERVICE_PORTS.FLASK_DEFAULT, SERVICE_PORTS.DJANGO_DEFAULT, SERVICE_PORTS.HTTP_ALT],
|
|
8505
8671
|
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8506
8672
|
};
|
|
8507
8673
|
|
|
@@ -8539,7 +8705,7 @@ var ICS_TOOLS = [
|
|
|
8539
8705
|
},
|
|
8540
8706
|
required: ["target"],
|
|
8541
8707
|
execute: async (params) => {
|
|
8542
|
-
return await runCommand("nmap", ["-p",
|
|
8708
|
+
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
|
|
8543
8709
|
}
|
|
8544
8710
|
}
|
|
8545
8711
|
];
|
|
@@ -8549,7 +8715,7 @@ var ICS_CONFIG = {
|
|
|
8549
8715
|
tools: ICS_TOOLS,
|
|
8550
8716
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8551
8717
|
defaultApproval: APPROVAL_LEVELS.BLOCK,
|
|
8552
|
-
commonPorts: [
|
|
8718
|
+
commonPorts: [SERVICE_PORTS.MODBUS, SERVICE_PORTS.DNP3],
|
|
8553
8719
|
commonServices: [SERVICES.MODBUS, SERVICES.DNP3]
|
|
8554
8720
|
};
|
|
8555
8721
|
|
|
@@ -8611,8 +8777,18 @@ var ToolRegistry = class {
|
|
|
8611
8777
|
this.logDeniedAction(toolCall, approval.reason || "Execution denied");
|
|
8612
8778
|
return { success: false, output: "", error: approval.reason || "Denied by policy" };
|
|
8613
8779
|
}
|
|
8614
|
-
|
|
8615
|
-
|
|
8780
|
+
let result2;
|
|
8781
|
+
try {
|
|
8782
|
+
result2 = await tool.execute(toolCall.input);
|
|
8783
|
+
} catch (execError) {
|
|
8784
|
+
const errMsg = execError instanceof Error ? execError.message : String(execError);
|
|
8785
|
+
result2 = {
|
|
8786
|
+
success: false,
|
|
8787
|
+
output: "",
|
|
8788
|
+
error: `Tool execution error: ${errMsg}`
|
|
8789
|
+
};
|
|
8790
|
+
}
|
|
8791
|
+
const command = String(toolCall.input.command || toolCall.input.url || toolCall.input.query || JSON.stringify(toolCall.input));
|
|
8616
8792
|
if (result2.success) {
|
|
8617
8793
|
this.state.workingMemory.recordSuccess(toolCall.name, command, result2.output || "");
|
|
8618
8794
|
} else {
|
|
@@ -8715,7 +8891,7 @@ var SERVICE_CATEGORY_MAP = {
|
|
|
8715
8891
|
"docker": SERVICE_CATEGORIES.CONTAINER,
|
|
8716
8892
|
"modbus": SERVICE_CATEGORIES.ICS
|
|
8717
8893
|
};
|
|
8718
|
-
var
|
|
8894
|
+
var CATEGORY_APPROVAL = {
|
|
8719
8895
|
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
8720
8896
|
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
8721
8897
|
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
@@ -8905,7 +9081,7 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8905
9081
|
description: DOMAINS[id]?.description || "",
|
|
8906
9082
|
tools: [...coreTools],
|
|
8907
9083
|
dangerLevel: this.calculateDanger(id),
|
|
8908
|
-
defaultApproval:
|
|
9084
|
+
defaultApproval: CATEGORY_APPROVAL[id] || "confirm"
|
|
8909
9085
|
});
|
|
8910
9086
|
});
|
|
8911
9087
|
}
|
|
@@ -8971,7 +9147,7 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
8971
9147
|
return Array.from(this.categories.values());
|
|
8972
9148
|
}
|
|
8973
9149
|
getApprovalForCategory(cat) {
|
|
8974
|
-
return
|
|
9150
|
+
return CATEGORY_APPROVAL[cat] || "confirm";
|
|
8975
9151
|
}
|
|
8976
9152
|
};
|
|
8977
9153
|
|
|
@@ -9056,14 +9232,28 @@ var LLM_ERROR_TYPES = {
|
|
|
9056
9232
|
var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
|
|
9057
9233
|
var NETWORK_ERROR_CODES = {
|
|
9058
9234
|
ECONNRESET: "ECONNRESET",
|
|
9235
|
+
ECONNREFUSED: "ECONNREFUSED",
|
|
9059
9236
|
ETIMEDOUT: "ETIMEDOUT",
|
|
9060
9237
|
ENOTFOUND: "ENOTFOUND",
|
|
9061
|
-
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
|
|
9238
|
+
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT",
|
|
9239
|
+
SOCKET_TIMEOUT: "UND_ERR_SOCKET"
|
|
9062
9240
|
};
|
|
9063
9241
|
var TRANSIENT_NETWORK_ERRORS = [
|
|
9064
9242
|
NETWORK_ERROR_CODES.ECONNRESET,
|
|
9243
|
+
NETWORK_ERROR_CODES.ECONNREFUSED,
|
|
9065
9244
|
NETWORK_ERROR_CODES.ETIMEDOUT,
|
|
9066
|
-
NETWORK_ERROR_CODES.ENOTFOUND
|
|
9245
|
+
NETWORK_ERROR_CODES.ENOTFOUND,
|
|
9246
|
+
NETWORK_ERROR_CODES.SOCKET_TIMEOUT
|
|
9247
|
+
];
|
|
9248
|
+
var NETWORK_ERROR_PATTERNS = [
|
|
9249
|
+
"fetch failed",
|
|
9250
|
+
"network error",
|
|
9251
|
+
"econnrefused",
|
|
9252
|
+
"econnreset",
|
|
9253
|
+
"enotfound",
|
|
9254
|
+
"etimedout",
|
|
9255
|
+
"socket hang up",
|
|
9256
|
+
"dns lookup failed"
|
|
9067
9257
|
];
|
|
9068
9258
|
var LLMError = class extends Error {
|
|
9069
9259
|
/** Structured error information */
|
|
@@ -9090,12 +9280,17 @@ function classifyError(error) {
|
|
|
9090
9280
|
if (statusCode === HTTP_STATUS.BAD_REQUEST) {
|
|
9091
9281
|
return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
|
|
9092
9282
|
}
|
|
9093
|
-
|
|
9283
|
+
const errorCode = e.code || e.cause?.code;
|
|
9284
|
+
if (errorCode && TRANSIENT_NETWORK_ERRORS.includes(errorCode)) {
|
|
9094
9285
|
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
9095
9286
|
}
|
|
9096
|
-
if (errorMessage.toLowerCase().includes("timeout") ||
|
|
9287
|
+
if (errorMessage.toLowerCase().includes("timeout") || errorCode === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
|
|
9097
9288
|
return { type: LLM_ERROR_TYPES.TIMEOUT, message: errorMessage, isRetryable: true, suggestedAction: "Retry" };
|
|
9098
9289
|
}
|
|
9290
|
+
const lowerMsg = errorMessage.toLowerCase();
|
|
9291
|
+
if (NETWORK_ERROR_PATTERNS.some((pattern) => lowerMsg.includes(pattern))) {
|
|
9292
|
+
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
9293
|
+
}
|
|
9099
9294
|
return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
|
|
9100
9295
|
}
|
|
9101
9296
|
|
|
@@ -9174,7 +9369,11 @@ var LLMClient = class {
|
|
|
9174
9369
|
signal
|
|
9175
9370
|
});
|
|
9176
9371
|
if (!response.ok) {
|
|
9177
|
-
|
|
9372
|
+
let errorBody = `HTTP ${response.status}`;
|
|
9373
|
+
try {
|
|
9374
|
+
errorBody = await response.text();
|
|
9375
|
+
} catch {
|
|
9376
|
+
}
|
|
9178
9377
|
const error = new Error(errorBody);
|
|
9179
9378
|
error.status = response.status;
|
|
9180
9379
|
throw error;
|
|
@@ -9547,7 +9746,10 @@ function loadState(state) {
|
|
|
9547
9746
|
state.addLoot(loot);
|
|
9548
9747
|
}
|
|
9549
9748
|
for (const item of snapshot.todo) {
|
|
9550
|
-
state.addTodo(item.content, item.priority);
|
|
9749
|
+
const id = state.addTodo(item.content, item.priority);
|
|
9750
|
+
if (item.status && item.status !== "pending") {
|
|
9751
|
+
state.updateTodo(id, { status: item.status });
|
|
9752
|
+
}
|
|
9551
9753
|
}
|
|
9552
9754
|
const validPhases = new Set(Object.values(PHASES));
|
|
9553
9755
|
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
@@ -9557,6 +9759,20 @@ function loadState(state) {
|
|
|
9557
9759
|
}
|
|
9558
9760
|
if (snapshot.missionChecklist?.length > 0) {
|
|
9559
9761
|
state.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
|
|
9762
|
+
const restoredChecklist = state.getMissionChecklist();
|
|
9763
|
+
const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
|
|
9764
|
+
const completedUpdates = [];
|
|
9765
|
+
for (let i = 0; i < snapshot.missionChecklist.length; i++) {
|
|
9766
|
+
if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
|
|
9767
|
+
completedUpdates.push({
|
|
9768
|
+
id: restoredChecklist[baseIndex + i].id,
|
|
9769
|
+
isCompleted: true
|
|
9770
|
+
});
|
|
9771
|
+
}
|
|
9772
|
+
}
|
|
9773
|
+
if (completedUpdates.length > 0) {
|
|
9774
|
+
state.updateMissionChecklist(completedUpdates);
|
|
9775
|
+
}
|
|
9560
9776
|
}
|
|
9561
9777
|
return true;
|
|
9562
9778
|
} catch (err) {
|
|
@@ -9570,8 +9786,9 @@ function clearWorkspace() {
|
|
|
9570
9786
|
const dirsToClean = [
|
|
9571
9787
|
{ path: WORKSPACE.SESSIONS, label: "sessions" },
|
|
9572
9788
|
{ path: WORKSPACE.DEBUG, label: "debug logs" },
|
|
9573
|
-
{ path: WORKSPACE.
|
|
9574
|
-
{ path: WORKSPACE.OUTPUTS, label: "outputs" }
|
|
9789
|
+
{ path: WORKSPACE.TMP, label: "temp files" },
|
|
9790
|
+
{ path: WORKSPACE.OUTPUTS, label: "outputs" },
|
|
9791
|
+
{ path: WORKSPACE.JOURNAL, label: "journal" }
|
|
9575
9792
|
];
|
|
9576
9793
|
for (const dir of dirsToClean) {
|
|
9577
9794
|
try {
|
|
@@ -9664,374 +9881,84 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9664
9881
|
}
|
|
9665
9882
|
}
|
|
9666
9883
|
|
|
9667
|
-
// src/shared/utils/output-compressor.ts
|
|
9668
|
-
var MIN_COMPRESS_LENGTH = 3e3;
|
|
9669
|
-
var SUMMARY_HEADER = "\u2550\u2550\u2550 INTELLIGENCE SUMMARY (auto-extracted) \u2550\u2550\u2550";
|
|
9670
|
-
var SUMMARY_FOOTER = "\u2550\u2550\u2550 END SUMMARY \u2014 Full output follows \u2550\u2550\u2550";
|
|
9671
|
-
var EXTRACT_LIMITS = {
|
|
9672
|
-
NMAP_PORTS: 30,
|
|
9673
|
-
NMAP_VULNS: 10,
|
|
9674
|
-
LINPEAS_SUDO: 500,
|
|
9675
|
-
LINPEAS_WRITABLE: 300,
|
|
9676
|
-
LINPEAS_CRON: 5,
|
|
9677
|
-
LINPEAS_PASSWORDS: 5,
|
|
9678
|
-
ENUM4LINUX_SHARES: 10,
|
|
9679
|
-
DIRBUST_PATHS: 20,
|
|
9680
|
-
SQLMAP_INJECTIONS: 5,
|
|
9681
|
-
HASH_NTLM: 5,
|
|
9682
|
-
HASH_PREVIEW_LEN: 100,
|
|
9683
|
-
GENERIC_CREDS: 5,
|
|
9684
|
-
GENERIC_PATHS: 10
|
|
9685
|
-
};
|
|
9686
|
-
var TOOL_SIGNATURES = [
|
|
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
|
-
}
|
|
9731
|
-
}
|
|
9732
|
-
if (intel.length === 0) {
|
|
9733
|
-
intel = extractGenericIntel(output);
|
|
9734
|
-
detectedTool = toolName || "unknown";
|
|
9735
|
-
}
|
|
9736
|
-
if (intel.length === 0) {
|
|
9737
|
-
return output;
|
|
9738
|
-
}
|
|
9739
|
-
const summary = [
|
|
9740
|
-
SUMMARY_HEADER,
|
|
9741
|
-
`Tool: ${detectedTool} | Output length: ${output.length} chars`,
|
|
9742
|
-
"",
|
|
9743
|
-
...intel,
|
|
9744
|
-
"",
|
|
9745
|
-
SUMMARY_FOOTER,
|
|
9746
|
-
""
|
|
9747
|
-
].join("\n");
|
|
9748
|
-
return summary + output;
|
|
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;
|
|
9934
|
-
}
|
|
9935
|
-
|
|
9936
9884
|
// src/shared/utils/context-digest.ts
|
|
9937
9885
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
|
|
9938
|
-
var PASSTHROUGH_THRESHOLD =
|
|
9939
|
-
var
|
|
9940
|
-
var
|
|
9941
|
-
var MAX_REDUCED_LINES = 500;
|
|
9886
|
+
var PASSTHROUGH_THRESHOLD = 500;
|
|
9887
|
+
var PREPROCESS_THRESHOLD = 3e3;
|
|
9888
|
+
var MAX_PREPROCESSED_LINES = 800;
|
|
9942
9889
|
var getOutputDir = () => WORKSPACE.OUTPUTS;
|
|
9943
9890
|
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9944
|
-
var
|
|
9891
|
+
var ANALYST_MAX_INPUT_CHARS = 8e4;
|
|
9945
9892
|
var FALLBACK_MAX_CHARS = 3e4;
|
|
9946
|
-
|
|
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
|
-
var NOISE_PATTERNS = [
|
|
9968
|
-
/^\s*$/,
|
|
9969
|
-
// blank lines
|
|
9970
|
-
/^\[[\d:]+\]\s*$/,
|
|
9971
|
-
// timestamp-only lines
|
|
9972
|
-
/^#+\s*$/,
|
|
9973
|
-
// separator lines
|
|
9974
|
-
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d+/i,
|
|
9975
|
-
// progress bars
|
|
9976
|
-
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
9977
|
-
// request counters
|
|
9978
|
-
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
9979
|
-
// decoration lines
|
|
9980
|
-
];
|
|
9981
|
-
async function digestToolOutput(output, toolName, toolInput, llmDigestFn) {
|
|
9893
|
+
async function digestToolOutput(output, toolName, toolInput, analystFn) {
|
|
9982
9894
|
const originalLength = output.length;
|
|
9983
9895
|
if (originalLength < PASSTHROUGH_THRESHOLD) {
|
|
9984
9896
|
return {
|
|
9985
9897
|
digestedOutput: output,
|
|
9986
9898
|
fullOutputPath: null,
|
|
9987
|
-
|
|
9899
|
+
analystUsed: false,
|
|
9900
|
+
memo: null,
|
|
9988
9901
|
originalLength,
|
|
9989
9902
|
digestedLength: originalLength,
|
|
9990
9903
|
compressionRatio: 1
|
|
9991
9904
|
};
|
|
9992
9905
|
}
|
|
9993
|
-
const
|
|
9994
|
-
let
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
if (
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
9906
|
+
const savedOutputPath = saveFullOutput(output, toolName);
|
|
9907
|
+
let preprocessed = output;
|
|
9908
|
+
if (originalLength > PREPROCESS_THRESHOLD) {
|
|
9909
|
+
preprocessed = structuralPreprocess(output);
|
|
9910
|
+
}
|
|
9911
|
+
if (analystFn) {
|
|
9912
|
+
try {
|
|
9913
|
+
const context = `Tool: ${toolName}${toolInput ? ` | Input: ${toolInput}` : ""}`;
|
|
9914
|
+
const rawAnalystResponse = await analystFn(preprocessed, context);
|
|
9915
|
+
const memo6 = parseAnalystMemo(rawAnalystResponse);
|
|
9916
|
+
const formatted = formatAnalystDigest(rawAnalystResponse, savedOutputPath, originalLength);
|
|
9917
|
+
return {
|
|
9918
|
+
digestedOutput: formatted,
|
|
9919
|
+
fullOutputPath: savedOutputPath,
|
|
9920
|
+
analystUsed: true,
|
|
9921
|
+
memo: memo6,
|
|
9922
|
+
originalLength,
|
|
9923
|
+
digestedLength: formatted.length,
|
|
9924
|
+
compressionRatio: formatted.length / originalLength
|
|
9925
|
+
};
|
|
9926
|
+
} catch (err) {
|
|
9927
|
+
debugLog("general", "Analyst LLM failed, falling back to truncation", {
|
|
9928
|
+
toolName,
|
|
9929
|
+
error: String(err)
|
|
9930
|
+
});
|
|
10017
9931
|
}
|
|
10018
|
-
} else if (layersApplied.includes(2)) {
|
|
10019
|
-
savedOutputPath = saveFullOutput(output, toolName);
|
|
10020
9932
|
}
|
|
9933
|
+
const fallback = formatFallbackDigest(preprocessed, savedOutputPath, originalLength);
|
|
10021
9934
|
return {
|
|
10022
|
-
digestedOutput:
|
|
9935
|
+
digestedOutput: fallback,
|
|
10023
9936
|
fullOutputPath: savedOutputPath,
|
|
10024
|
-
|
|
9937
|
+
analystUsed: false,
|
|
9938
|
+
memo: null,
|
|
10025
9939
|
originalLength,
|
|
10026
|
-
digestedLength:
|
|
10027
|
-
compressionRatio:
|
|
9940
|
+
digestedLength: fallback.length,
|
|
9941
|
+
compressionRatio: fallback.length / originalLength
|
|
10028
9942
|
};
|
|
10029
9943
|
}
|
|
10030
|
-
|
|
9944
|
+
var NOISE_PATTERNS = [
|
|
9945
|
+
/^\s*$/,
|
|
9946
|
+
// blank lines
|
|
9947
|
+
/^\[[\d:]+\]\s*$/,
|
|
9948
|
+
// timestamp-only lines
|
|
9949
|
+
/^#+\s*$/,
|
|
9950
|
+
// separator lines
|
|
9951
|
+
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d/i,
|
|
9952
|
+
// progress bars
|
|
9953
|
+
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
9954
|
+
// request counters
|
|
9955
|
+
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
9956
|
+
// decoration lines
|
|
9957
|
+
];
|
|
9958
|
+
function structuralPreprocess(output) {
|
|
10031
9959
|
let cleaned = stripAnsi(output);
|
|
10032
9960
|
const lines = cleaned.split("\n");
|
|
10033
9961
|
const result2 = [];
|
|
10034
|
-
const duplicateCounts = /* @__PURE__ */ new Map();
|
|
10035
9962
|
let lastLine = "";
|
|
10036
9963
|
let consecutiveDupes = 0;
|
|
10037
9964
|
for (const line of lines) {
|
|
@@ -10039,11 +9966,9 @@ function structuralReduce(output) {
|
|
|
10039
9966
|
if (NOISE_PATTERNS.some((p) => p.test(trimmed))) {
|
|
10040
9967
|
continue;
|
|
10041
9968
|
}
|
|
10042
|
-
const isSignal = SIGNAL_PATTERNS.some((p) => p.test(trimmed));
|
|
10043
9969
|
const normalized = normalizeLine(trimmed);
|
|
10044
|
-
if (normalized === normalizeLine(lastLine)
|
|
9970
|
+
if (normalized === normalizeLine(lastLine)) {
|
|
10045
9971
|
consecutiveDupes++;
|
|
10046
|
-
duplicateCounts.set(normalized, (duplicateCounts.get(normalized) || 1) + 1);
|
|
10047
9972
|
continue;
|
|
10048
9973
|
}
|
|
10049
9974
|
if (consecutiveDupes > 0) {
|
|
@@ -10066,57 +9991,124 @@ function structuralReduce(output) {
|
|
|
10066
9991
|
result2.push(lastLine);
|
|
10067
9992
|
}
|
|
10068
9993
|
}
|
|
10069
|
-
if (result2.length >
|
|
10070
|
-
const headSize = Math.floor(
|
|
10071
|
-
const tailSize = Math.floor(
|
|
10072
|
-
const signalBudget = MAX_REDUCED_LINES - headSize - tailSize;
|
|
9994
|
+
if (result2.length > MAX_PREPROCESSED_LINES) {
|
|
9995
|
+
const headSize = Math.floor(MAX_PREPROCESSED_LINES * 0.5);
|
|
9996
|
+
const tailSize = Math.floor(MAX_PREPROCESSED_LINES * 0.3);
|
|
10073
9997
|
const head = result2.slice(0, headSize);
|
|
10074
9998
|
const tail = result2.slice(-tailSize);
|
|
10075
|
-
const
|
|
10076
|
-
const middleSignals = middle.filter((line) => SIGNAL_PATTERNS.some((p) => p.test(line))).slice(0, signalBudget);
|
|
10077
|
-
const skipped = middle.length - middleSignals.length;
|
|
9999
|
+
const skipped = result2.length - headSize - tailSize;
|
|
10078
10000
|
cleaned = [
|
|
10079
10001
|
...head,
|
|
10080
10002
|
"",
|
|
10081
|
-
`... [${skipped}
|
|
10082
|
-
"",
|
|
10083
|
-
...middleSignals,
|
|
10084
|
-
"",
|
|
10085
|
-
`... [resuming last ${tailSize} lines] ...`,
|
|
10003
|
+
`... [${skipped} lines skipped for Analyst LLM context \u2014 full output saved to file] ...`,
|
|
10086
10004
|
"",
|
|
10087
10005
|
...tail
|
|
10088
10006
|
].join("\n");
|
|
10089
10007
|
} else {
|
|
10090
10008
|
cleaned = result2.join("\n");
|
|
10091
10009
|
}
|
|
10010
|
+
if (cleaned.length > ANALYST_MAX_INPUT_CHARS) {
|
|
10011
|
+
cleaned = cleaned.slice(0, ANALYST_MAX_INPUT_CHARS) + `
|
|
10012
|
+
... [truncated at ${ANALYST_MAX_INPUT_CHARS} chars for Analyst LLM \u2014 full output saved to file]`;
|
|
10013
|
+
}
|
|
10092
10014
|
return cleaned;
|
|
10093
10015
|
}
|
|
10094
|
-
var
|
|
10016
|
+
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
10017
|
|
|
10096
10018
|
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
10019
|
+
|
|
10097
10020
|
## Key Findings
|
|
10098
|
-
- [finding 1]
|
|
10021
|
+
- [finding 1 with exact values: ports, versions, paths]
|
|
10099
10022
|
- [finding 2]
|
|
10100
10023
|
|
|
10101
10024
|
## Credentials/Secrets
|
|
10102
|
-
- [any discovered credentials, hashes, tokens, keys]
|
|
10025
|
+
- [any discovered credentials, hashes, tokens, keys, certificates]
|
|
10026
|
+
- (write "None found" if none)
|
|
10103
10027
|
|
|
10104
10028
|
## Attack Vectors
|
|
10105
|
-
- [exploitable services, vulnerabilities, misconfigurations]
|
|
10029
|
+
- [exploitable services, vulnerabilities, misconfigurations, CVEs]
|
|
10030
|
+
- (write "None identified" if none)
|
|
10031
|
+
|
|
10032
|
+
## Failures/Errors
|
|
10033
|
+
- [what was attempted and FAILED \u2014 include the FULL command, wordlist, target, and the reason WHY it failed]
|
|
10034
|
+
- [e.g.: "SSH brute force: hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://10.0.0.1 \u2014 connection refused (port filtered)"]
|
|
10035
|
+
- [e.g.: "SQLi on /login with sqlmap --tamper=space2comment \u2014 input sanitized, WAF detected (ModSecurity)"]
|
|
10036
|
+
- (write "No failures" if everything succeeded)
|
|
10037
|
+
|
|
10038
|
+
## Suspicious Signals
|
|
10039
|
+
- [anomalies that are NOT confirmed vulnerabilities but suggest exploitable surface]
|
|
10040
|
+
- [e.g.: "Response time 3x slower on /admin path \u2014 possible auth check or backend processing"]
|
|
10041
|
+
- [e.g.: "X-Debug-Token header present \u2014 debug mode may be enabled"]
|
|
10042
|
+
- [e.g.: "Verbose error message reveals stack trace / internal path / DB schema"]
|
|
10043
|
+
- [e.g.: "Unexpected 302 redirect with session param leaked in URL"]
|
|
10044
|
+
- (write "No suspicious signals" if nothing anomalous)
|
|
10045
|
+
|
|
10046
|
+
## Attack Value
|
|
10047
|
+
- [ONE word: HIGH / MED / LOW / NONE]
|
|
10048
|
+
- Reasoning: [1 sentence why \u2014 what makes this worth pursuing or abandoning]
|
|
10106
10049
|
|
|
10107
10050
|
## Next Steps
|
|
10108
10051
|
- [recommended immediate actions based on findings]
|
|
10109
10052
|
|
|
10110
10053
|
RULES:
|
|
10111
|
-
-
|
|
10112
|
-
-
|
|
10054
|
+
- Include EXACT values: port numbers, versions, usernames, file paths, IPs, full commands used
|
|
10055
|
+
- For failures: include the COMPLETE command with all flags, wordlists, and targets \u2014 "brute force failed" alone is USELESS
|
|
10056
|
+
- Look for the UNEXPECTED \u2014 non-standard ports, unusual banners, timing anomalies, error leaks
|
|
10057
|
+
- Credentials include: passwords, hashes, API keys, tokens, private keys, cookies, session IDs
|
|
10058
|
+
- Flag any information disclosure: server versions, internal paths, stack traces, debug output
|
|
10113
10059
|
- If nothing interesting found, say "No actionable findings in this output"
|
|
10114
|
-
-
|
|
10115
|
-
-
|
|
10116
|
-
|
|
10060
|
+
- Never include decorative output, banners, or progress information
|
|
10061
|
+
- Do NOT miss subtle signals: unusual HTTP headers, non-standard responses, timing differences
|
|
10062
|
+
- Write as much detail as needed \u2014 do NOT artificially shorten. Every detail matters for strategy.
|
|
10063
|
+
|
|
10064
|
+
## Reflection
|
|
10065
|
+
- What this output tells us: [1-line assessment]
|
|
10066
|
+
- Recommended next action: [1-2 specific follow-up actions]`;
|
|
10067
|
+
function parseAnalystMemo(response) {
|
|
10068
|
+
const sections = {};
|
|
10069
|
+
let currentSection = "";
|
|
10070
|
+
let reflectionLines = [];
|
|
10071
|
+
let attackValueLine = "NONE";
|
|
10072
|
+
let attackValueReasoning = "";
|
|
10073
|
+
for (const line of response.split("\n")) {
|
|
10074
|
+
if (line.startsWith("## ")) {
|
|
10075
|
+
currentSection = line.replace("## ", "").trim().toLowerCase();
|
|
10076
|
+
sections[currentSection] = [];
|
|
10077
|
+
} else if (currentSection === "reflection") {
|
|
10078
|
+
if (line.trim()) reflectionLines.push(line.trim());
|
|
10079
|
+
} else if (currentSection === "attack value") {
|
|
10080
|
+
const match = line.match(/\b(HIGH|MED|LOW|NONE)\b/);
|
|
10081
|
+
if (match) attackValueLine = match[1];
|
|
10082
|
+
const reasonMatch = line.match(/[Rr]easoning:\s*(.+)/);
|
|
10083
|
+
if (reasonMatch) attackValueReasoning = reasonMatch[1].trim();
|
|
10084
|
+
} else if (currentSection) {
|
|
10085
|
+
const trimmed = line.trim();
|
|
10086
|
+
if (!trimmed) continue;
|
|
10087
|
+
const content = trimmed.replace(/^(?:-|\*|\d+[.)]\s*)\s*/, "").trim();
|
|
10088
|
+
if (content) sections[currentSection].push(content);
|
|
10089
|
+
}
|
|
10090
|
+
}
|
|
10091
|
+
const filterNone = (items) => items.filter((i) => !/(^none|^no )/i.test(i.trim()));
|
|
10092
|
+
const rawValue = attackValueLine.toUpperCase();
|
|
10093
|
+
const attackValue = ["HIGH", "MED", "LOW", "NONE"].includes(rawValue) ? rawValue : "LOW";
|
|
10094
|
+
return {
|
|
10095
|
+
keyFindings: filterNone(sections["key findings"] || []),
|
|
10096
|
+
credentials: filterNone(sections["credentials/secrets"] || []),
|
|
10097
|
+
attackVectors: filterNone(sections["attack vectors"] || []),
|
|
10098
|
+
failures: filterNone(sections["failures/errors"] || []),
|
|
10099
|
+
suspicions: filterNone(sections["suspicious signals"] || []),
|
|
10100
|
+
attackValue,
|
|
10101
|
+
nextSteps: filterNone(sections["next steps"] || []),
|
|
10102
|
+
reflection: [
|
|
10103
|
+
attackValueReasoning ? `[${attackValue}] ${attackValueReasoning}` : "",
|
|
10104
|
+
...reflectionLines
|
|
10105
|
+
].filter(Boolean).join(" | ")
|
|
10106
|
+
};
|
|
10107
|
+
}
|
|
10108
|
+
function formatAnalystDigest(digest, filePath, originalChars) {
|
|
10117
10109
|
return [
|
|
10118
10110
|
"\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
|
|
10111
|
+
"\u2551 ANALYST DIGEST (Independent LLM analysis) \u2551",
|
|
10120
10112
|
"\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
10113
|
"",
|
|
10122
10114
|
digest,
|
|
@@ -10126,29 +10118,26 @@ function formatLLMDigest(digest, filePath, originalChars) {
|
|
|
10126
10118
|
].join("\n");
|
|
10127
10119
|
}
|
|
10128
10120
|
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
10121
|
const maxChars = FALLBACK_MAX_CHARS;
|
|
10134
|
-
let
|
|
10135
|
-
if (
|
|
10122
|
+
let truncated = processed;
|
|
10123
|
+
if (processed.length > maxChars) {
|
|
10136
10124
|
const headChars = Math.floor(maxChars * 0.6);
|
|
10137
10125
|
const tailChars = Math.floor(maxChars * 0.4);
|
|
10138
|
-
const skipped =
|
|
10139
|
-
|
|
10126
|
+
const skipped = processed.length - headChars - tailChars;
|
|
10127
|
+
truncated = processed.slice(0, headChars) + `
|
|
10140
10128
|
|
|
10141
|
-
... [${skipped} chars omitted \u2014 read full output from file] ...
|
|
10129
|
+
... [${skipped} chars omitted \u2014 Analyst LLM unavailable, read full output from file] ...
|
|
10142
10130
|
|
|
10143
|
-
` +
|
|
10131
|
+
` + processed.slice(-tailChars);
|
|
10144
10132
|
}
|
|
10145
10133
|
return [
|
|
10146
|
-
|
|
10147
|
-
|
|
10134
|
+
"\u26A0\uFE0F ANALYST UNAVAILABLE \u2014 showing truncated raw output:",
|
|
10135
|
+
"",
|
|
10136
|
+
truncated,
|
|
10148
10137
|
"",
|
|
10149
10138
|
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
10150
10139
|
`\u{1F4A1} Use read_file("${filePath}") to see the complete raw output.`
|
|
10151
|
-
].
|
|
10140
|
+
].join("\n");
|
|
10152
10141
|
}
|
|
10153
10142
|
function stripAnsi(text) {
|
|
10154
10143
|
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\[[\d;]*m/g, "");
|
|
@@ -10174,20 +10163,21 @@ function saveFullOutput(output, toolName) {
|
|
|
10174
10163
|
}
|
|
10175
10164
|
function createLLMDigestFn(llmClient) {
|
|
10176
10165
|
return async (text, context) => {
|
|
10177
|
-
const
|
|
10178
|
-
|
|
10179
|
-
|
|
10166
|
+
const messages = [{
|
|
10167
|
+
role: LLM_ROLES.USER,
|
|
10168
|
+
content: `Analyze this pentesting tool output and extract actionable intelligence.
|
|
10180
10169
|
|
|
10181
10170
|
Context: ${context}
|
|
10182
10171
|
|
|
10183
10172
|
--- OUTPUT START ---
|
|
10184
|
-
${
|
|
10185
|
-
--- OUTPUT END ---`
|
|
10173
|
+
${text}
|
|
10174
|
+
--- OUTPUT END ---`
|
|
10175
|
+
}];
|
|
10186
10176
|
const response = await llmClient.generateResponse(
|
|
10187
10177
|
messages,
|
|
10188
10178
|
void 0,
|
|
10189
|
-
// no tools —
|
|
10190
|
-
|
|
10179
|
+
// no tools — analysis only
|
|
10180
|
+
ANALYST_SYSTEM_PROMPT
|
|
10191
10181
|
);
|
|
10192
10182
|
return response.content || "No actionable findings extracted.";
|
|
10193
10183
|
};
|
|
@@ -10202,6 +10192,16 @@ var CoreAgent = class _CoreAgent {
|
|
|
10202
10192
|
agentType;
|
|
10203
10193
|
maxIterations;
|
|
10204
10194
|
abortController = null;
|
|
10195
|
+
/**
|
|
10196
|
+
* Collected tool execution records for the current turn.
|
|
10197
|
+
* MainAgent reads this after each step to write journal entries.
|
|
10198
|
+
* Cleared at the start of each step.
|
|
10199
|
+
*/
|
|
10200
|
+
turnToolJournal = [];
|
|
10201
|
+
/** Aggregated memo from all tools in the current turn */
|
|
10202
|
+
turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10203
|
+
/** Analyst reflections collected during this turn (1-line assessments) */
|
|
10204
|
+
turnReflections = [];
|
|
10205
10205
|
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
10206
10206
|
this.agentType = agentType;
|
|
10207
10207
|
this.state = state;
|
|
@@ -10329,7 +10329,22 @@ RULES:
|
|
|
10329
10329
|
});
|
|
10330
10330
|
continue;
|
|
10331
10331
|
}
|
|
10332
|
-
|
|
10332
|
+
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10333
|
+
this.events.emit({
|
|
10334
|
+
type: EVENT_TYPES.ERROR,
|
|
10335
|
+
timestamp: Date.now(),
|
|
10336
|
+
data: {
|
|
10337
|
+
message: `Unexpected error: ${unexpectedMsg}`,
|
|
10338
|
+
phase: this.state.getPhase(),
|
|
10339
|
+
isRecoverable: true
|
|
10340
|
+
}
|
|
10341
|
+
});
|
|
10342
|
+
messages.push({
|
|
10343
|
+
role: LLM_ROLES.USER,
|
|
10344
|
+
content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10345
|
+
This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
|
|
10346
|
+
});
|
|
10347
|
+
continue;
|
|
10333
10348
|
}
|
|
10334
10349
|
}
|
|
10335
10350
|
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"}.`;
|
|
@@ -10647,27 +10662,62 @@ ${firstLine}`, phase }
|
|
|
10647
10662
|
progress.blockedCommandPatterns.clear();
|
|
10648
10663
|
}
|
|
10649
10664
|
}
|
|
10665
|
+
const rawOutputForTUI = outputText;
|
|
10666
|
+
let digestedOutputForLLM = outputText;
|
|
10667
|
+
let digestResult = null;
|
|
10650
10668
|
try {
|
|
10651
10669
|
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
10652
|
-
|
|
10670
|
+
digestResult = await digestToolOutput(
|
|
10653
10671
|
outputText,
|
|
10654
10672
|
call.name,
|
|
10655
10673
|
JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10656
10674
|
llmDigestFn
|
|
10657
10675
|
);
|
|
10658
|
-
|
|
10676
|
+
digestedOutputForLLM = digestResult.digestedOutput;
|
|
10659
10677
|
} catch {
|
|
10660
|
-
if (
|
|
10661
|
-
const truncated =
|
|
10662
|
-
const remaining =
|
|
10663
|
-
|
|
10678
|
+
if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
10679
|
+
const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
10680
|
+
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10681
|
+
digestedOutputForLLM = `${truncated}
|
|
10664
10682
|
|
|
10665
10683
|
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
10666
10684
|
\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
10685
|
}
|
|
10668
10686
|
}
|
|
10669
|
-
this.emitToolResult(call.name, result2.success,
|
|
10670
|
-
|
|
10687
|
+
this.emitToolResult(call.name, result2.success, rawOutputForTUI, result2.error, Date.now() - toolStartTime);
|
|
10688
|
+
const inputSummary = JSON.stringify(call.input);
|
|
10689
|
+
this.turnToolJournal.push({
|
|
10690
|
+
name: call.name,
|
|
10691
|
+
inputSummary,
|
|
10692
|
+
success: result2.success,
|
|
10693
|
+
analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
|
|
10694
|
+
outputFile: digestResult?.fullOutputPath ?? null
|
|
10695
|
+
});
|
|
10696
|
+
if (digestResult?.memo) {
|
|
10697
|
+
const m = digestResult.memo;
|
|
10698
|
+
this.turnMemo.keyFindings.push(...m.keyFindings);
|
|
10699
|
+
this.turnMemo.credentials.push(...m.credentials);
|
|
10700
|
+
this.turnMemo.attackVectors.push(...m.attackVectors);
|
|
10701
|
+
this.turnMemo.failures.push(...m.failures);
|
|
10702
|
+
this.turnMemo.suspicions.push(...m.suspicions);
|
|
10703
|
+
const VALUE_RANK = { HIGH: 3, MED: 2, LOW: 1, NONE: 0 };
|
|
10704
|
+
if ((VALUE_RANK[m.attackValue] ?? 0) > (VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
|
|
10705
|
+
this.turnMemo.attackValue = m.attackValue;
|
|
10706
|
+
}
|
|
10707
|
+
this.turnMemo.nextSteps.push(...m.nextSteps);
|
|
10708
|
+
if (m.reflection) this.turnReflections.push(m.reflection);
|
|
10709
|
+
}
|
|
10710
|
+
if (digestResult?.memo?.credentials.length) {
|
|
10711
|
+
for (const cred of digestResult.memo.credentials) {
|
|
10712
|
+
this.state.addLoot({
|
|
10713
|
+
type: "credential",
|
|
10714
|
+
host: "auto-extracted",
|
|
10715
|
+
detail: cred,
|
|
10716
|
+
obtainedAt: Date.now()
|
|
10717
|
+
});
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10671
10721
|
} catch (error) {
|
|
10672
10722
|
const errorMsg = String(error);
|
|
10673
10723
|
const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
@@ -10742,8 +10792,8 @@ ${firstLine}`, phase }
|
|
|
10742
10792
|
};
|
|
10743
10793
|
|
|
10744
10794
|
// src/agents/prompt-builder.ts
|
|
10745
|
-
import { readFileSync as
|
|
10746
|
-
import { join as
|
|
10795
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
10796
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
10747
10797
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10748
10798
|
|
|
10749
10799
|
// src/shared/constants/prompts.ts
|
|
@@ -10804,73 +10854,6 @@ var INITIAL_TASKS = {
|
|
|
10804
10854
|
RECON: "Initial reconnaissance and target discovery"
|
|
10805
10855
|
};
|
|
10806
10856
|
|
|
10807
|
-
// src/shared/constants/service-ports.ts
|
|
10808
|
-
var SERVICE_PORTS = {
|
|
10809
|
-
SSH: 22,
|
|
10810
|
-
FTP: 21,
|
|
10811
|
-
TELNET: 23,
|
|
10812
|
-
SMTP: 25,
|
|
10813
|
-
DNS: 53,
|
|
10814
|
-
HTTP: 80,
|
|
10815
|
-
POP3: 110,
|
|
10816
|
-
IMAP: 143,
|
|
10817
|
-
SMB_NETBIOS: 139,
|
|
10818
|
-
SMB: 445,
|
|
10819
|
-
HTTPS: 443,
|
|
10820
|
-
MSSQL: 1433,
|
|
10821
|
-
MYSQL: 3306,
|
|
10822
|
-
RDP: 3389,
|
|
10823
|
-
POSTGRESQL: 5432,
|
|
10824
|
-
REDIS: 6379,
|
|
10825
|
-
HTTP_ALT: 8080,
|
|
10826
|
-
HTTPS_ALT: 8443,
|
|
10827
|
-
MONGODB: 27017,
|
|
10828
|
-
ELASTICSEARCH: 9200,
|
|
10829
|
-
MEMCACHED: 11211,
|
|
10830
|
-
NODE_DEFAULT: 3e3,
|
|
10831
|
-
FLASK_DEFAULT: 5e3,
|
|
10832
|
-
DJANGO_DEFAULT: 8e3
|
|
10833
|
-
};
|
|
10834
|
-
var CRITICAL_SERVICE_PORTS = [
|
|
10835
|
-
SERVICE_PORTS.SSH,
|
|
10836
|
-
SERVICE_PORTS.RDP,
|
|
10837
|
-
SERVICE_PORTS.MYSQL,
|
|
10838
|
-
SERVICE_PORTS.POSTGRESQL,
|
|
10839
|
-
SERVICE_PORTS.REDIS,
|
|
10840
|
-
SERVICE_PORTS.MONGODB
|
|
10841
|
-
];
|
|
10842
|
-
var NO_AUTH_CRITICAL_PORTS = [
|
|
10843
|
-
SERVICE_PORTS.REDIS,
|
|
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
|
-
];
|
|
10873
|
-
|
|
10874
10857
|
// src/shared/constants/scoring.ts
|
|
10875
10858
|
var ATTACK_SCORING = {
|
|
10876
10859
|
/** Base score for all attack prioritization */
|
|
@@ -11031,10 +11014,229 @@ function getAttacksForService(service, port) {
|
|
|
11031
11014
|
return attacks;
|
|
11032
11015
|
}
|
|
11033
11016
|
|
|
11017
|
+
// src/shared/utils/journal.ts
|
|
11018
|
+
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
|
|
11019
|
+
import { join as join10 } from "path";
|
|
11020
|
+
var MAX_JOURNAL_ENTRIES = 50;
|
|
11021
|
+
var SUMMARY_REGEN_INTERVAL = 10;
|
|
11022
|
+
var MAX_OUTPUT_FILES = 30;
|
|
11023
|
+
var TURN_PREFIX = "turn-";
|
|
11024
|
+
var SUMMARY_FILE = "summary.md";
|
|
11025
|
+
function writeJournalEntry(entry) {
|
|
11026
|
+
try {
|
|
11027
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11028
|
+
ensureDirExists(journalDir);
|
|
11029
|
+
const padded = String(entry.turn).padStart(4, "0");
|
|
11030
|
+
const filePath = join10(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11031
|
+
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11032
|
+
return filePath;
|
|
11033
|
+
} catch (err) {
|
|
11034
|
+
debugLog("general", "Failed to write journal entry", { turn: entry.turn, error: String(err) });
|
|
11035
|
+
return null;
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
function readJournalSummary() {
|
|
11039
|
+
try {
|
|
11040
|
+
const summaryPath = join10(WORKSPACE.JOURNAL, SUMMARY_FILE);
|
|
11041
|
+
if (!existsSync8(summaryPath)) return "";
|
|
11042
|
+
return readFileSync5(summaryPath, "utf-8");
|
|
11043
|
+
} catch {
|
|
11044
|
+
return "";
|
|
11045
|
+
}
|
|
11046
|
+
}
|
|
11047
|
+
function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
11048
|
+
try {
|
|
11049
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11050
|
+
if (!existsSync8(journalDir)) return [];
|
|
11051
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort().slice(-count);
|
|
11052
|
+
const entries = [];
|
|
11053
|
+
for (const file of files) {
|
|
11054
|
+
try {
|
|
11055
|
+
const raw = readFileSync5(join10(journalDir, file), "utf-8");
|
|
11056
|
+
entries.push(JSON.parse(raw));
|
|
11057
|
+
} catch {
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
11060
|
+
return entries;
|
|
11061
|
+
} catch {
|
|
11062
|
+
return [];
|
|
11063
|
+
}
|
|
11064
|
+
}
|
|
11065
|
+
function getNextTurnNumber() {
|
|
11066
|
+
try {
|
|
11067
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11068
|
+
if (!existsSync8(journalDir)) return 1;
|
|
11069
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11070
|
+
if (files.length === 0) return 1;
|
|
11071
|
+
const lastFile = files[files.length - 1];
|
|
11072
|
+
const match = lastFile.match(/turn-(\d+)\.json/);
|
|
11073
|
+
return match ? parseInt(match[1], 10) + 1 : 1;
|
|
11074
|
+
} catch {
|
|
11075
|
+
return 1;
|
|
11076
|
+
}
|
|
11077
|
+
}
|
|
11078
|
+
function shouldRegenerateSummary(currentTurn) {
|
|
11079
|
+
return currentTurn > 0 && currentTurn % SUMMARY_REGEN_INTERVAL === 0;
|
|
11080
|
+
}
|
|
11081
|
+
function regenerateJournalSummary() {
|
|
11082
|
+
try {
|
|
11083
|
+
const entries = getRecentEntries();
|
|
11084
|
+
if (entries.length === 0) return;
|
|
11085
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11086
|
+
ensureDirExists(journalDir);
|
|
11087
|
+
const summary = buildSummaryFromEntries(entries);
|
|
11088
|
+
const summaryPath = join10(journalDir, SUMMARY_FILE);
|
|
11089
|
+
writeFileSync8(summaryPath, summary, "utf-8");
|
|
11090
|
+
debugLog("general", "Journal summary regenerated", {
|
|
11091
|
+
entries: entries.length,
|
|
11092
|
+
summaryLength: summary.length
|
|
11093
|
+
});
|
|
11094
|
+
} catch (err) {
|
|
11095
|
+
debugLog("general", "Failed to regenerate journal summary", { error: String(err) });
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
function buildSummaryFromEntries(entries) {
|
|
11099
|
+
const attempts = [];
|
|
11100
|
+
const findings = [];
|
|
11101
|
+
const credentials = [];
|
|
11102
|
+
const successes = [];
|
|
11103
|
+
const failures = [];
|
|
11104
|
+
const suspicions = [];
|
|
11105
|
+
const nextSteps = [];
|
|
11106
|
+
const reflections = [];
|
|
11107
|
+
const VALUE_ORDER = { HIGH: 0, MED: 1, LOW: 2, NONE: 3 };
|
|
11108
|
+
const reversed = [...entries].reverse();
|
|
11109
|
+
for (const entry of reversed) {
|
|
11110
|
+
const value = entry.memo.attackValue || "LOW";
|
|
11111
|
+
for (const tool of entry.tools) {
|
|
11112
|
+
attempts.push({
|
|
11113
|
+
turn: entry.turn,
|
|
11114
|
+
phase: entry.phase,
|
|
11115
|
+
ok: tool.success,
|
|
11116
|
+
name: tool.name,
|
|
11117
|
+
input: tool.inputSummary,
|
|
11118
|
+
value
|
|
11119
|
+
});
|
|
11120
|
+
}
|
|
11121
|
+
for (const finding of entry.memo.keyFindings) {
|
|
11122
|
+
const line = `- [T${entry.turn}|\u26A1${value}] ${finding}`;
|
|
11123
|
+
if (!findings.includes(line)) findings.push(line);
|
|
11124
|
+
}
|
|
11125
|
+
for (const cred of entry.memo.credentials) {
|
|
11126
|
+
const line = `- [T${entry.turn}] ${cred}`;
|
|
11127
|
+
if (!credentials.includes(line)) credentials.push(line);
|
|
11128
|
+
}
|
|
11129
|
+
for (const vector of entry.memo.attackVectors) {
|
|
11130
|
+
const line = `- [T${entry.turn}] ${vector}`;
|
|
11131
|
+
if (!successes.includes(line)) successes.push(line);
|
|
11132
|
+
}
|
|
11133
|
+
for (const fail of entry.memo.failures) {
|
|
11134
|
+
const line = `- [T${entry.turn}] ${fail}`;
|
|
11135
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11136
|
+
}
|
|
11137
|
+
for (const tool of entry.tools) {
|
|
11138
|
+
if (!tool.success) {
|
|
11139
|
+
const detail = `${tool.name}(${tool.inputSummary}): ${tool.analystSummary}`;
|
|
11140
|
+
const line = `- [T${entry.turn}] ${detail}`;
|
|
11141
|
+
if (!failures.includes(line)) failures.push(line);
|
|
11142
|
+
}
|
|
11143
|
+
}
|
|
11144
|
+
for (const s of entry.memo.suspicions || []) {
|
|
11145
|
+
const line = `- [T${entry.turn}] ${s}`;
|
|
11146
|
+
if (!suspicions.includes(line)) suspicions.push(line);
|
|
11147
|
+
}
|
|
11148
|
+
if (nextSteps.length < 5) {
|
|
11149
|
+
for (const step of entry.memo.nextSteps) {
|
|
11150
|
+
if (!nextSteps.includes(`- ${step}`)) nextSteps.push(`- ${step}`);
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
11153
|
+
if (entry.reflection) {
|
|
11154
|
+
reflections.push(`- [T${entry.turn}|\u26A1${value}] ${entry.reflection}`);
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
attempts.sort((a, b) => {
|
|
11158
|
+
const vd = (VALUE_ORDER[a.value] ?? 3) - (VALUE_ORDER[b.value] ?? 3);
|
|
11159
|
+
return vd !== 0 ? vd : b.turn - a.turn;
|
|
11160
|
+
});
|
|
11161
|
+
const attemptLines = attempts.map(
|
|
11162
|
+
(a) => `- [T${a.turn}|${a.phase}|\u26A1${a.value}] ${a.ok ? "\u2705" : "\u274C"} ${a.name}: ${a.input}`
|
|
11163
|
+
);
|
|
11164
|
+
const lastTurn = entries[entries.length - 1]?.turn || 0;
|
|
11165
|
+
const sections = [
|
|
11166
|
+
`# Session Journal Summary`,
|
|
11167
|
+
`> Turn ${lastTurn} / ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}`,
|
|
11168
|
+
""
|
|
11169
|
+
];
|
|
11170
|
+
const addSection = (title, items) => {
|
|
11171
|
+
if (items.length === 0) return;
|
|
11172
|
+
sections.push(`## ${title}`);
|
|
11173
|
+
sections.push(...items);
|
|
11174
|
+
sections.push("");
|
|
11175
|
+
};
|
|
11176
|
+
if (attemptLines.length > 0) {
|
|
11177
|
+
sections.push("## Techniques Tried (by attack value)");
|
|
11178
|
+
sections.push("> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon");
|
|
11179
|
+
sections.push(...attemptLines);
|
|
11180
|
+
sections.push("");
|
|
11181
|
+
}
|
|
11182
|
+
addSection("\u{1F9E0} Analyst Analysis (attack value rationale)", reflections);
|
|
11183
|
+
addSection("\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)", suspicions);
|
|
11184
|
+
addSection("\u{1F4CB} Key Findings", findings);
|
|
11185
|
+
addSection("\u{1F511} Credentials Obtained", credentials);
|
|
11186
|
+
addSection("\u2705 Successful Attack Vectors", successes);
|
|
11187
|
+
addSection("\u274C Failure Causes (do not repeat)", failures);
|
|
11188
|
+
addSection("\u27A1\uFE0F Next Recommendations", nextSteps);
|
|
11189
|
+
return sections.join("\n");
|
|
11190
|
+
}
|
|
11191
|
+
function rotateJournalEntries() {
|
|
11192
|
+
try {
|
|
11193
|
+
const journalDir = WORKSPACE.JOURNAL;
|
|
11194
|
+
if (!existsSync8(journalDir)) return;
|
|
11195
|
+
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11196
|
+
if (files.length <= MAX_JOURNAL_ENTRIES) return;
|
|
11197
|
+
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11198
|
+
for (const file of toDelete) {
|
|
11199
|
+
try {
|
|
11200
|
+
unlinkSync5(join10(journalDir, file));
|
|
11201
|
+
} catch {
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
11204
|
+
debugLog("general", "Journal entries rotated", {
|
|
11205
|
+
deleted: toDelete.length,
|
|
11206
|
+
remaining: MAX_JOURNAL_ENTRIES
|
|
11207
|
+
});
|
|
11208
|
+
} catch {
|
|
11209
|
+
}
|
|
11210
|
+
}
|
|
11211
|
+
function rotateOutputFiles() {
|
|
11212
|
+
try {
|
|
11213
|
+
const outputDir = WORKSPACE.OUTPUTS;
|
|
11214
|
+
if (!existsSync8(outputDir)) return;
|
|
11215
|
+
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11216
|
+
name: f,
|
|
11217
|
+
path: join10(outputDir, f),
|
|
11218
|
+
mtime: statSync2(join10(outputDir, f)).mtimeMs
|
|
11219
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
11220
|
+
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11221
|
+
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
11222
|
+
for (const file of toDelete) {
|
|
11223
|
+
try {
|
|
11224
|
+
unlinkSync5(file.path);
|
|
11225
|
+
} catch {
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11228
|
+
debugLog("general", "Output files rotated", {
|
|
11229
|
+
deleted: toDelete.length,
|
|
11230
|
+
remaining: MAX_OUTPUT_FILES
|
|
11231
|
+
});
|
|
11232
|
+
} catch {
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
|
|
11034
11236
|
// src/agents/prompt-builder.ts
|
|
11035
11237
|
var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
|
|
11036
|
-
var PROMPTS_DIR =
|
|
11037
|
-
var TECHNIQUES_DIR =
|
|
11238
|
+
var PROMPTS_DIR = join11(__dirname4, "prompts");
|
|
11239
|
+
var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
11038
11240
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
11039
11241
|
var PHASE_PROMPT_MAP = {
|
|
11040
11242
|
// Direct mappings — phase has its own prompt file
|
|
@@ -11108,6 +11310,7 @@ var PromptBuilder = class {
|
|
|
11108
11310
|
* 13. Learned techniques (#7: dynamic technique library)
|
|
11109
11311
|
* 14. Persistent memory (#12: cross-session knowledge)
|
|
11110
11312
|
* ★ 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
|
|
11313
|
+
* ★ 15b. SESSION JOURNAL — compressed history of past turns (§13 memo system)
|
|
11111
11314
|
* 16. User context
|
|
11112
11315
|
*/
|
|
11113
11316
|
async build(userInput, phase) {
|
|
@@ -11133,8 +11336,10 @@ var PromptBuilder = class {
|
|
|
11133
11336
|
// #12
|
|
11134
11337
|
this.getDynamicTechniquesFragment(),
|
|
11135
11338
|
// #7
|
|
11136
|
-
this.getPersistentMemoryFragment()
|
|
11339
|
+
this.getPersistentMemoryFragment(),
|
|
11137
11340
|
// #12
|
|
11341
|
+
this.getJournalFragment()
|
|
11342
|
+
// §13 session journal
|
|
11138
11343
|
];
|
|
11139
11344
|
const strategistDirective = await this.getStrategistFragment();
|
|
11140
11345
|
if (strategistDirective) {
|
|
@@ -11158,8 +11363,8 @@ ${content}
|
|
|
11158
11363
|
* Load a prompt file from src/agents/prompts/
|
|
11159
11364
|
*/
|
|
11160
11365
|
loadPromptFile(filename) {
|
|
11161
|
-
const path2 =
|
|
11162
|
-
return
|
|
11366
|
+
const path2 = join11(PROMPTS_DIR, filename);
|
|
11367
|
+
return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
11163
11368
|
}
|
|
11164
11369
|
/**
|
|
11165
11370
|
* Load phase-specific prompt.
|
|
@@ -11202,18 +11407,18 @@ ${content}
|
|
|
11202
11407
|
* as general reference — NO code change needed to add new techniques.
|
|
11203
11408
|
*
|
|
11204
11409
|
* The map is an optimization (priority ordering), not a gate.
|
|
11205
|
-
* "
|
|
11410
|
+
* "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
|
|
11206
11411
|
*/
|
|
11207
11412
|
loadPhaseRelevantTechniques(phase) {
|
|
11208
|
-
if (!
|
|
11413
|
+
if (!existsSync9(TECHNIQUES_DIR)) return "";
|
|
11209
11414
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
11210
11415
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11211
11416
|
const fragments = [];
|
|
11212
11417
|
for (const technique of priorityTechniques) {
|
|
11213
|
-
const filePath =
|
|
11418
|
+
const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
|
|
11214
11419
|
try {
|
|
11215
|
-
if (!
|
|
11216
|
-
const content =
|
|
11420
|
+
if (!existsSync9(filePath)) continue;
|
|
11421
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11217
11422
|
if (content) {
|
|
11218
11423
|
fragments.push(`<technique-reference category="${technique}">
|
|
11219
11424
|
${content}
|
|
@@ -11224,10 +11429,10 @@ ${content}
|
|
|
11224
11429
|
}
|
|
11225
11430
|
}
|
|
11226
11431
|
try {
|
|
11227
|
-
const allFiles =
|
|
11432
|
+
const allFiles = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11228
11433
|
for (const file of allFiles) {
|
|
11229
|
-
const filePath =
|
|
11230
|
-
const content =
|
|
11434
|
+
const filePath = join11(TECHNIQUES_DIR, file);
|
|
11435
|
+
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11231
11436
|
if (content) {
|
|
11232
11437
|
const category = file.replace(".md", "");
|
|
11233
11438
|
fragments.push(`<technique-reference category="${category}">
|
|
@@ -11330,6 +11535,23 @@ ${lines.join("\n")}
|
|
|
11330
11535
|
}
|
|
11331
11536
|
return this.state.persistentMemory.toPrompt(services);
|
|
11332
11537
|
}
|
|
11538
|
+
// --- §13: Session Journal Summary ---
|
|
11539
|
+
/**
|
|
11540
|
+
* Load journal summary from .pentesting/journal/summary.md
|
|
11541
|
+
* Provides compressed history of past turns — what worked, what failed,
|
|
11542
|
+
* what was discovered. Main LLM uses this for continuity across many turns.
|
|
11543
|
+
*/
|
|
11544
|
+
getJournalFragment() {
|
|
11545
|
+
try {
|
|
11546
|
+
const summary = readJournalSummary();
|
|
11547
|
+
if (!summary) return "";
|
|
11548
|
+
return `<session-journal>
|
|
11549
|
+
${summary}
|
|
11550
|
+
</session-journal>`;
|
|
11551
|
+
} catch {
|
|
11552
|
+
return "";
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11333
11555
|
// --- D-CIPHER: Strategist Meta-Prompting ---
|
|
11334
11556
|
/**
|
|
11335
11557
|
* Generate strategic directive via Strategist LLM.
|
|
@@ -11342,29 +11564,11 @@ ${lines.join("\n")}
|
|
|
11342
11564
|
};
|
|
11343
11565
|
|
|
11344
11566
|
// src/agents/strategist.ts
|
|
11345
|
-
import { readFileSync as
|
|
11346
|
-
import { join as
|
|
11567
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
11568
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
11347
11569
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11348
|
-
|
|
11349
|
-
// src/shared/constants/strategist.ts
|
|
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
11570
|
var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
|
|
11367
|
-
var STRATEGIST_PROMPT_PATH =
|
|
11571
|
+
var STRATEGIST_PROMPT_PATH = join12(__dirname5, "prompts", "strategist-system.md");
|
|
11368
11572
|
var Strategist = class {
|
|
11369
11573
|
llm;
|
|
11370
11574
|
state;
|
|
@@ -11415,24 +11619,33 @@ var Strategist = class {
|
|
|
11415
11619
|
const sections = [];
|
|
11416
11620
|
sections.push("## Engagement State");
|
|
11417
11621
|
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
11622
|
const failures = this.state.workingMemory.toPrompt();
|
|
11425
11623
|
if (failures) {
|
|
11426
11624
|
sections.push("");
|
|
11427
11625
|
sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
|
|
11428
11626
|
sections.push(failures);
|
|
11429
11627
|
}
|
|
11628
|
+
try {
|
|
11629
|
+
const journalSummary = readJournalSummary();
|
|
11630
|
+
if (journalSummary) {
|
|
11631
|
+
sections.push("");
|
|
11632
|
+
sections.push("## Session Journal (past turns summary)");
|
|
11633
|
+
sections.push(journalSummary);
|
|
11634
|
+
}
|
|
11635
|
+
} catch {
|
|
11636
|
+
}
|
|
11430
11637
|
const graph = this.state.attackGraph.toPrompt();
|
|
11431
11638
|
if (graph) {
|
|
11432
11639
|
sections.push("");
|
|
11433
11640
|
sections.push("## Attack Graph");
|
|
11434
11641
|
sections.push(graph);
|
|
11435
11642
|
}
|
|
11643
|
+
const timeline = this.state.episodicMemory.toPrompt();
|
|
11644
|
+
if (timeline) {
|
|
11645
|
+
sections.push("");
|
|
11646
|
+
sections.push("## Recent Actions");
|
|
11647
|
+
sections.push(timeline);
|
|
11648
|
+
}
|
|
11436
11649
|
const techniques = this.state.dynamicTechniques.toPrompt();
|
|
11437
11650
|
if (techniques) {
|
|
11438
11651
|
sections.push("");
|
|
@@ -11448,11 +11661,7 @@ var Strategist = class {
|
|
|
11448
11661
|
sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
|
|
11449
11662
|
sections.push(analysis.strategySuggestion);
|
|
11450
11663
|
}
|
|
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;
|
|
11664
|
+
return sections.join("\n");
|
|
11456
11665
|
}
|
|
11457
11666
|
// ─── LLM Call ───────────────────────────────────────────────
|
|
11458
11667
|
async callLLM(input) {
|
|
@@ -11469,9 +11678,6 @@ ${input}`
|
|
|
11469
11678
|
this.systemPrompt
|
|
11470
11679
|
);
|
|
11471
11680
|
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
11681
|
const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
|
|
11476
11682
|
this.totalTokenCost += cost;
|
|
11477
11683
|
return {
|
|
@@ -11501,8 +11707,8 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
|
|
|
11501
11707
|
// ─── System Prompt Loading ──────────────────────────────────
|
|
11502
11708
|
loadSystemPrompt() {
|
|
11503
11709
|
try {
|
|
11504
|
-
if (
|
|
11505
|
-
return
|
|
11710
|
+
if (existsSync10(STRATEGIST_PROMPT_PATH)) {
|
|
11711
|
+
return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11506
11712
|
}
|
|
11507
11713
|
} catch {
|
|
11508
11714
|
}
|
|
@@ -11544,6 +11750,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
11544
11750
|
approvalGate;
|
|
11545
11751
|
scopeGuard;
|
|
11546
11752
|
userInput = "";
|
|
11753
|
+
/** Monotonic turn counter for journal entries */
|
|
11754
|
+
turnCounter = 0;
|
|
11547
11755
|
constructor(state, events, toolRegistry, approvalGate, scopeGuard) {
|
|
11548
11756
|
super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
|
|
11549
11757
|
this.approvalGate = approvalGate;
|
|
@@ -11581,8 +11789,34 @@ var MainAgent = class extends CoreAgent {
|
|
|
11581
11789
|
* The Strategist LLM generates a fresh tactical directive every turn.
|
|
11582
11790
|
*/
|
|
11583
11791
|
async step(iteration, messages, _unusedPrompt, progress) {
|
|
11792
|
+
if (this.turnCounter === 0) {
|
|
11793
|
+
this.turnCounter = getNextTurnNumber();
|
|
11794
|
+
}
|
|
11795
|
+
this.turnToolJournal = [];
|
|
11796
|
+
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
11797
|
+
this.turnReflections = [];
|
|
11584
11798
|
const dynamicPrompt = await this.getCurrentPrompt();
|
|
11585
11799
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
11800
|
+
if (this.turnToolJournal.length > 0) {
|
|
11801
|
+
try {
|
|
11802
|
+
const entry = {
|
|
11803
|
+
turn: this.turnCounter,
|
|
11804
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11805
|
+
phase: this.state.getPhase(),
|
|
11806
|
+
tools: this.turnToolJournal,
|
|
11807
|
+
memo: this.turnMemo,
|
|
11808
|
+
reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
|
|
11809
|
+
};
|
|
11810
|
+
writeJournalEntry(entry);
|
|
11811
|
+
if (shouldRegenerateSummary(this.turnCounter)) {
|
|
11812
|
+
regenerateJournalSummary();
|
|
11813
|
+
}
|
|
11814
|
+
rotateJournalEntries();
|
|
11815
|
+
rotateOutputFiles();
|
|
11816
|
+
} catch {
|
|
11817
|
+
}
|
|
11818
|
+
this.turnCounter++;
|
|
11819
|
+
}
|
|
11586
11820
|
this.emitStateChange();
|
|
11587
11821
|
return result2;
|
|
11588
11822
|
}
|