pentesting 0.24.2 → 0.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +689 -69
- package/dist/prompts/ctf-mode.md +212 -13
- package/dist/prompts/strategy.md +51 -0
- package/dist/prompts/techniques/crypto.md +139 -0
- package/dist/prompts/techniques/forensics.md +268 -44
- package/dist/prompts/techniques/pwn.md +336 -51
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -286,7 +286,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
286
286
|
var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
|
|
287
287
|
var ID_RADIX = AGENT_LIMITS.ID_RADIX;
|
|
288
288
|
var APP_NAME = "Pentest AI";
|
|
289
|
-
var APP_VERSION = "0.24.
|
|
289
|
+
var APP_VERSION = "0.24.3";
|
|
290
290
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
291
291
|
var LLM_ROLES = {
|
|
292
292
|
SYSTEM: "system",
|
|
@@ -560,11 +560,6 @@ var UI_COMMANDS = {
|
|
|
560
560
|
};
|
|
561
561
|
var PASSIVE_BINARIES = ["whois", "dig", "curl -i"];
|
|
562
562
|
var EXPLOIT_BINARIES = ["impacket"];
|
|
563
|
-
var MASK_PARTS = {
|
|
564
|
-
C24: "24",
|
|
565
|
-
C16: "16",
|
|
566
|
-
C8: "8"
|
|
567
|
-
};
|
|
568
563
|
var COMMAND_EVENT_TYPES = {
|
|
569
564
|
TOOL_MISSING: "tool_missing",
|
|
570
565
|
TOOL_INSTALL: "tool_install",
|
|
@@ -870,6 +865,10 @@ var WORKSPACE = {
|
|
|
870
865
|
/** Temporary files for active operations */
|
|
871
866
|
get TEMP() {
|
|
872
867
|
return path.join(getWorkspaceRoot(), "temp");
|
|
868
|
+
},
|
|
869
|
+
/** Full tool output files saved by Context Digest */
|
|
870
|
+
get OUTPUTS() {
|
|
871
|
+
return path.join(getWorkspaceRoot(), "outputs");
|
|
873
872
|
}
|
|
874
873
|
};
|
|
875
874
|
|
|
@@ -2164,7 +2163,9 @@ var SharedState = class {
|
|
|
2164
2163
|
missionChecklist: [],
|
|
2165
2164
|
ctfMode: true,
|
|
2166
2165
|
// CTF mode ON by default
|
|
2167
|
-
flags: []
|
|
2166
|
+
flags: [],
|
|
2167
|
+
startedAt: Date.now(),
|
|
2168
|
+
deadlineAt: 0
|
|
2168
2169
|
};
|
|
2169
2170
|
}
|
|
2170
2171
|
// --- Mission & Persistent Context ---
|
|
@@ -2322,6 +2323,42 @@ var SharedState = class {
|
|
|
2322
2323
|
getFlags() {
|
|
2323
2324
|
return this.data.flags;
|
|
2324
2325
|
}
|
|
2326
|
+
// --- Time Management ---
|
|
2327
|
+
/** Set a deadline for time-aware strategy (e.g., CTF competition end time) */
|
|
2328
|
+
setDeadline(deadlineMs) {
|
|
2329
|
+
this.data.deadlineAt = deadlineMs;
|
|
2330
|
+
}
|
|
2331
|
+
/** Get milliseconds since agent started */
|
|
2332
|
+
getElapsedMs() {
|
|
2333
|
+
return Date.now() - this.data.startedAt;
|
|
2334
|
+
}
|
|
2335
|
+
/** Get remaining milliseconds until deadline (0 if no deadline) */
|
|
2336
|
+
getRemainingMs() {
|
|
2337
|
+
if (this.data.deadlineAt === 0) return 0;
|
|
2338
|
+
return Math.max(0, this.data.deadlineAt - Date.now());
|
|
2339
|
+
}
|
|
2340
|
+
/** Get time status string for prompt injection */
|
|
2341
|
+
getTimeStatus() {
|
|
2342
|
+
const elapsed = this.getElapsedMs();
|
|
2343
|
+
const mins = Math.floor(elapsed / 6e4);
|
|
2344
|
+
let status = `\u23F1 Elapsed: ${mins}min`;
|
|
2345
|
+
if (this.data.deadlineAt > 0) {
|
|
2346
|
+
const remainingMs = this.getRemainingMs();
|
|
2347
|
+
const remainingMins = Math.floor(remainingMs / 6e4);
|
|
2348
|
+
if (remainingMins <= 0) {
|
|
2349
|
+
status += " | \u26A0\uFE0F TIME IS UP \u2014 submit any flags you have NOW";
|
|
2350
|
+
} else if (remainingMins <= 15) {
|
|
2351
|
+
status += ` | \u{1F534} ${remainingMins}min LEFT \u2014 FINAL PUSH: submit flags, check obvious locations, env vars, DBs`;
|
|
2352
|
+
} else if (remainingMins <= 30) {
|
|
2353
|
+
status += ` | \u{1F7E1} ${remainingMins}min LEFT \u2014 wrap up current vector, ensure all findings are captured`;
|
|
2354
|
+
} else if (remainingMins <= 60) {
|
|
2355
|
+
status += ` | \u{1F7E0} ${remainingMins}min LEFT \u2014 focus on highest-probability vectors only`;
|
|
2356
|
+
} else {
|
|
2357
|
+
status += ` | \u{1F7E2} ${remainingMins}min remaining`;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return status;
|
|
2361
|
+
}
|
|
2325
2362
|
/**
|
|
2326
2363
|
* @remarks
|
|
2327
2364
|
* WHY: Delegating complex string builder to specialized serializer.
|
|
@@ -2493,30 +2530,43 @@ var ScopeGuard = class {
|
|
|
2493
2530
|
return false;
|
|
2494
2531
|
}
|
|
2495
2532
|
/**
|
|
2496
|
-
*
|
|
2533
|
+
* Accurate CIDR matching using bitwise arithmetic.
|
|
2534
|
+
* Supports any prefix length /0 through /32.
|
|
2535
|
+
* No external dependencies — pure math.
|
|
2497
2536
|
*/
|
|
2498
2537
|
matchesCidr(target, cidr) {
|
|
2499
2538
|
if (target === cidr) return true;
|
|
2500
2539
|
if (cidr.includes("/")) {
|
|
2501
|
-
const [network,
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
return networkPrefix === targetPrefix;
|
|
2540
|
+
const [network, maskStr] = cidr.split("/");
|
|
2541
|
+
const maskBits = parseInt(maskStr, 10);
|
|
2542
|
+
if (isNaN(maskBits) || maskBits < 0 || maskBits > 32) {
|
|
2543
|
+
return target === cidr;
|
|
2506
2544
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
return
|
|
2511
|
-
}
|
|
2512
|
-
if (mask === MASK_PARTS.C16) {
|
|
2513
|
-
const networkPrefix = network.split(".").slice(0, 2).join(".");
|
|
2514
|
-
const targetPrefix = target.split(".").slice(0, 2).join(".");
|
|
2515
|
-
return networkPrefix === targetPrefix;
|
|
2545
|
+
const targetInt = this.ipToUint32(target);
|
|
2546
|
+
const networkInt = this.ipToUint32(network);
|
|
2547
|
+
if (targetInt === null || networkInt === null) {
|
|
2548
|
+
return target === cidr;
|
|
2516
2549
|
}
|
|
2550
|
+
const mask = maskBits === 0 ? 0 : ~0 << 32 - maskBits >>> 0;
|
|
2551
|
+
return (targetInt & mask) >>> 0 === (networkInt & mask) >>> 0;
|
|
2517
2552
|
}
|
|
2518
2553
|
return target === cidr;
|
|
2519
2554
|
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Convert IPv4 dotted-decimal string to unsigned 32-bit integer.
|
|
2557
|
+
* Returns null if the string is not a valid IPv4 address.
|
|
2558
|
+
*/
|
|
2559
|
+
ipToUint32(ip) {
|
|
2560
|
+
const parts = ip.split(".");
|
|
2561
|
+
if (parts.length !== 4) return null;
|
|
2562
|
+
let result2 = 0;
|
|
2563
|
+
for (const part of parts) {
|
|
2564
|
+
const octet = parseInt(part, 10);
|
|
2565
|
+
if (isNaN(octet) || octet < 0 || octet > 255) return null;
|
|
2566
|
+
result2 = (result2 << 8 | octet) >>> 0;
|
|
2567
|
+
}
|
|
2568
|
+
return result2;
|
|
2569
|
+
}
|
|
2520
2570
|
/**
|
|
2521
2571
|
* Extract IPs and Domains from string
|
|
2522
2572
|
*/
|
|
@@ -4803,7 +4853,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4803
4853
|
}
|
|
4804
4854
|
},
|
|
4805
4855
|
execute: async (p) => {
|
|
4806
|
-
const { existsSync:
|
|
4856
|
+
const { existsSync: existsSync8, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
|
|
4807
4857
|
const { join: join10 } = await import("path");
|
|
4808
4858
|
const category = p.category || "";
|
|
4809
4859
|
const search = p.search || "";
|
|
@@ -4850,7 +4900,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4850
4900
|
results.push("");
|
|
4851
4901
|
};
|
|
4852
4902
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
4853
|
-
if (depth > maxDepth || !
|
|
4903
|
+
if (depth > maxDepth || !existsSync8(dirPath)) return;
|
|
4854
4904
|
let entries;
|
|
4855
4905
|
try {
|
|
4856
4906
|
entries = readdirSync3(dirPath, { withFileTypes: true });
|
|
@@ -5233,8 +5283,8 @@ Requires root/sudo privileges.`,
|
|
|
5233
5283
|
const iface = p.interface || "";
|
|
5234
5284
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
5235
5285
|
const hostsFile = createTempFile(".hosts");
|
|
5236
|
-
const { writeFileSync:
|
|
5237
|
-
|
|
5286
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
5287
|
+
writeFileSync7(hostsFile, `${spoofIp} ${domain}
|
|
5238
5288
|
${spoofIp} *.${domain}
|
|
5239
5289
|
`);
|
|
5240
5290
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -6651,7 +6701,7 @@ var FLAG_PATTERNS = {
|
|
|
6651
6701
|
// Generic CTF flag formats
|
|
6652
6702
|
generic: /flag\{[^}]+\}/gi,
|
|
6653
6703
|
curly_upper: /FLAG\{[^}]+\}/g,
|
|
6654
|
-
// Platform-specific
|
|
6704
|
+
// Platform-specific — Major International Competitions
|
|
6655
6705
|
htb: /HTB\{[^}]+\}/g,
|
|
6656
6706
|
thm: /THM\{[^}]+\}/g,
|
|
6657
6707
|
picoCTF: /picoCTF\{[^}]+\}/g,
|
|
@@ -6667,8 +6717,65 @@ var FLAG_PATTERNS = {
|
|
|
6667
6717
|
// hxp CTF
|
|
6668
6718
|
zer0pts: /zer0pts\{[^}]+\}/g,
|
|
6669
6719
|
// zer0pts CTF
|
|
6720
|
+
// Asia-Pacific Major Competitions
|
|
6721
|
+
seccon: /SECCON\{[^}]+\}/g,
|
|
6722
|
+
// SECCON CTF (Japan)
|
|
6723
|
+
hitcon: /hitcon\{[^}]+\}/gi,
|
|
6724
|
+
// HITCON CTF (Taiwan)
|
|
6725
|
+
codegate: /codegate\{[^}]+\}/gi,
|
|
6726
|
+
// Codegate CTF (Korea)
|
|
6727
|
+
wacon: /WACON\{[^}]+\}/g,
|
|
6728
|
+
// WACON CTF (Korea)
|
|
6729
|
+
linectf: /LINECTF\{[^}]+\}/g,
|
|
6730
|
+
// LINE CTF (Korea/Japan)
|
|
6731
|
+
tsgctf: /TSGCTF\{[^}]+\}/g,
|
|
6732
|
+
// TSG CTF (Japan)
|
|
6733
|
+
kosenctf: /KosenCTF\{[^}]+\}/g,
|
|
6734
|
+
// Kosen CTF (Japan)
|
|
6735
|
+
asis: /ASIS\{[^}]+\}/g,
|
|
6736
|
+
// ASIS CTF (Iran)
|
|
6737
|
+
// Americas / Europe Major Competitions
|
|
6738
|
+
plaidctf: /PCTF\{[^}]+\}/g,
|
|
6739
|
+
// PlaidCTF (PPP)
|
|
6740
|
+
dicectf: /dice\{[^}]+\}/gi,
|
|
6741
|
+
// DiceCTF
|
|
6742
|
+
rwctf: /rwctf\{[^}]+\}/gi,
|
|
6743
|
+
// Real World CTF
|
|
6744
|
+
rctf: /RCTF\{[^}]+\}/g,
|
|
6745
|
+
// RCTF
|
|
6746
|
+
n1ctf: /N1CTF\{[^}]+\}/g,
|
|
6747
|
+
// N1CTF (Nu1L)
|
|
6748
|
+
bctf: /BCTF\{[^}]+\}/g,
|
|
6749
|
+
// BCTF
|
|
6750
|
+
_0ctf: /0CTF\{[^}]+\}/g,
|
|
6751
|
+
// 0CTF (Shanghai)
|
|
6752
|
+
defcamp: /CTF\{[^}]+\}/g,
|
|
6753
|
+
// DefCamp
|
|
6754
|
+
insomnihack: /INS\{[^}]+\}/g,
|
|
6755
|
+
// Insomni'hack
|
|
6756
|
+
justctf: /justCTF\{[^}]+\}/g,
|
|
6757
|
+
// justCTF
|
|
6758
|
+
lactf: /lactf\{[^}]+\}/gi,
|
|
6759
|
+
// LA CTF (UCLA)
|
|
6760
|
+
damctf: /dam\{[^}]+\}/gi,
|
|
6761
|
+
// DamCTF
|
|
6762
|
+
tjctf: /tjctf\{[^}]+\}/gi,
|
|
6763
|
+
// TJCTF
|
|
6764
|
+
buckeye: /buckeye\{[^}]+\}/gi,
|
|
6765
|
+
// BuckeyeCTF
|
|
6766
|
+
uiuctf: /uiuctf\{[^}]+\}/gi,
|
|
6767
|
+
// UIUCTF
|
|
6768
|
+
// Platform-specific
|
|
6769
|
+
cyber_apocalypse: /HTB\{[^}]+\}/g,
|
|
6770
|
+
// HTB Cyber Apocalypse (same as HTB)
|
|
6771
|
+
offshift: /oS\{[^}]+\}/g,
|
|
6772
|
+
// Offshift CTF
|
|
6773
|
+
imaginaryctf: /ictf\{[^}]+\}/gi,
|
|
6774
|
+
// ImaginaryCTF
|
|
6775
|
+
corctf: /corctf\{[^}]+\}/gi,
|
|
6776
|
+
// corCTF
|
|
6777
|
+
// Generic CTFd format — catches most custom competitions
|
|
6670
6778
|
ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
|
|
6671
|
-
// Generic CTFd format
|
|
6672
6779
|
// Hash-style flags (HTB user.txt / root.txt — 32-char hex)
|
|
6673
6780
|
hash_flag: /\b[a-f0-9]{32}\b/g,
|
|
6674
6781
|
// Base64-encoded flag detection (common in steganography/forensics)
|
|
@@ -6765,6 +6872,520 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
6765
6872
|
}
|
|
6766
6873
|
}
|
|
6767
6874
|
|
|
6875
|
+
// src/shared/utils/output-compressor.ts
|
|
6876
|
+
var MIN_COMPRESS_LENGTH = 3e3;
|
|
6877
|
+
var SUMMARY_HEADER = "\u2550\u2550\u2550 INTELLIGENCE SUMMARY (auto-extracted) \u2550\u2550\u2550";
|
|
6878
|
+
var SUMMARY_FOOTER = "\u2550\u2550\u2550 END SUMMARY \u2014 Full output follows \u2550\u2550\u2550";
|
|
6879
|
+
var TOOL_SIGNATURES = [
|
|
6880
|
+
{
|
|
6881
|
+
name: "nmap",
|
|
6882
|
+
signatures: [/Nmap scan report/i, /PORT\s+STATE\s+SERVICE/i, /Nmap done:/i],
|
|
6883
|
+
extract: extractNmapIntel
|
|
6884
|
+
},
|
|
6885
|
+
{
|
|
6886
|
+
name: "linpeas",
|
|
6887
|
+
signatures: [/linpeas/i, /╔══.*╗/, /Linux Privilege Escalation/i],
|
|
6888
|
+
extract: extractLinpeasIntel
|
|
6889
|
+
},
|
|
6890
|
+
{
|
|
6891
|
+
name: "enum4linux",
|
|
6892
|
+
signatures: [/enum4linux/i, /Starting enum4linux/i, /\|\s+Target\s+Information/i],
|
|
6893
|
+
extract: extractEnum4linuxIntel
|
|
6894
|
+
},
|
|
6895
|
+
{
|
|
6896
|
+
name: "gobuster/ffuf/feroxbuster",
|
|
6897
|
+
signatures: [/Gobuster/i, /FFUF/i, /feroxbuster/i, /Status:\s*\d{3}/],
|
|
6898
|
+
extract: extractDirBustIntel
|
|
6899
|
+
},
|
|
6900
|
+
{
|
|
6901
|
+
name: "sqlmap",
|
|
6902
|
+
signatures: [/sqlmap/i, /\[INFO\]\s+testing/i, /Parameter:\s+/i],
|
|
6903
|
+
extract: extractSqlmapIntel
|
|
6904
|
+
},
|
|
6905
|
+
{
|
|
6906
|
+
name: "hash_dump",
|
|
6907
|
+
signatures: [/\$[0-9]\$/, /\$2[aby]\$/, /:[0-9]+:[a-f0-9]{32}:/i],
|
|
6908
|
+
extract: extractHashIntel
|
|
6909
|
+
}
|
|
6910
|
+
];
|
|
6911
|
+
function compressToolOutput(output, toolName) {
|
|
6912
|
+
if (!output || output.length < MIN_COMPRESS_LENGTH) {
|
|
6913
|
+
return output;
|
|
6914
|
+
}
|
|
6915
|
+
let intel = [];
|
|
6916
|
+
let detectedTool = "";
|
|
6917
|
+
for (const sig of TOOL_SIGNATURES) {
|
|
6918
|
+
const matched = sig.signatures.some((s) => s.test(output));
|
|
6919
|
+
if (matched) {
|
|
6920
|
+
detectedTool = sig.name;
|
|
6921
|
+
intel = sig.extract(output);
|
|
6922
|
+
break;
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
if (intel.length === 0) {
|
|
6926
|
+
intel = extractGenericIntel(output);
|
|
6927
|
+
detectedTool = toolName || "unknown";
|
|
6928
|
+
}
|
|
6929
|
+
if (intel.length === 0) {
|
|
6930
|
+
return output;
|
|
6931
|
+
}
|
|
6932
|
+
const summary = [
|
|
6933
|
+
SUMMARY_HEADER,
|
|
6934
|
+
`Tool: ${detectedTool} | Output length: ${output.length} chars`,
|
|
6935
|
+
"",
|
|
6936
|
+
...intel,
|
|
6937
|
+
"",
|
|
6938
|
+
SUMMARY_FOOTER,
|
|
6939
|
+
""
|
|
6940
|
+
].join("\n");
|
|
6941
|
+
return summary + output;
|
|
6942
|
+
}
|
|
6943
|
+
function extractNmapIntel(output) {
|
|
6944
|
+
const intel = [];
|
|
6945
|
+
const lines = output.split("\n");
|
|
6946
|
+
const hostMatches = output.match(/Nmap scan report for\s+(\S+)/gi);
|
|
6947
|
+
const openPorts = output.match(/^\d+\/\w+\s+open\s+/gm);
|
|
6948
|
+
if (hostMatches) intel.push(`Hosts scanned: ${hostMatches.length}`);
|
|
6949
|
+
if (openPorts) intel.push(`Open ports found: ${openPorts.length}`);
|
|
6950
|
+
const portLines = lines.filter((l) => /^\d+\/\w+\s+open\s+/.test(l.trim()));
|
|
6951
|
+
if (portLines.length > 0) {
|
|
6952
|
+
intel.push("Open ports:");
|
|
6953
|
+
for (const pl of portLines.slice(0, 30)) {
|
|
6954
|
+
intel.push(` ${pl.trim()}`);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
const vulnLines = lines.filter(
|
|
6958
|
+
(l) => /VULNERABLE|CVE-\d{4}-\d+|exploit|CRITICAL/i.test(l)
|
|
6959
|
+
);
|
|
6960
|
+
if (vulnLines.length > 0) {
|
|
6961
|
+
intel.push("\u26A0\uFE0F Vulnerability indicators:");
|
|
6962
|
+
for (const vl of vulnLines.slice(0, 10)) {
|
|
6963
|
+
intel.push(` ${vl.trim()}`);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
const osMatch = output.match(/OS details:\s*(.+)/i) || output.match(/Running:\s*(.+)/i);
|
|
6967
|
+
if (osMatch) intel.push(`OS: ${osMatch[1].trim()}`);
|
|
6968
|
+
return intel;
|
|
6969
|
+
}
|
|
6970
|
+
function extractLinpeasIntel(output) {
|
|
6971
|
+
const intel = [];
|
|
6972
|
+
const suidSection = output.match(/SUID[\s\S]*?(?=\n[═╔╗━]+|\n\n\n)/i);
|
|
6973
|
+
if (suidSection) {
|
|
6974
|
+
const suidBins = suidSection[0].match(/\/\S+/g);
|
|
6975
|
+
if (suidBins) {
|
|
6976
|
+
const interestingSuid = suidBins.filter(
|
|
6977
|
+
(b) => /python|perl|ruby|node|bash|vim|less|more|nano|find|nmap|awk|env|php|gcc|gdb|docker|strace|ltrace/i.test(b)
|
|
6978
|
+
);
|
|
6979
|
+
if (interestingSuid.length > 0) {
|
|
6980
|
+
intel.push(`\u{1F534} Exploitable SUID binaries: ${interestingSuid.join(", ")}`);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6984
|
+
const sudoMatch = output.match(/User \S+ may run[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6985
|
+
if (sudoMatch) {
|
|
6986
|
+
intel.push(`\u{1F534} sudo -l: ${sudoMatch[0].trim().slice(0, 500)}`);
|
|
6987
|
+
}
|
|
6988
|
+
const writableMatch = output.match(/Interesting writable[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6989
|
+
if (writableMatch) {
|
|
6990
|
+
intel.push(`\u{1F4DD} Writable: ${writableMatch[0].trim().slice(0, 300)}`);
|
|
6991
|
+
}
|
|
6992
|
+
const cronMatch = output.match(/Cron[\s\S]*?(?=\n\n|\n[═╔╗━])/i);
|
|
6993
|
+
if (cronMatch) {
|
|
6994
|
+
const cronLines = cronMatch[0].split("\n").filter((l) => l.includes("*") || /\/(root|cron)/i.test(l));
|
|
6995
|
+
if (cronLines.length > 0) {
|
|
6996
|
+
intel.push("\u23F0 Cron entries:");
|
|
6997
|
+
cronLines.slice(0, 5).forEach((c) => intel.push(` ${c.trim()}`));
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
const kernelMatch = output.match(/Linux version\s+(\S+)/i) || output.match(/Kernel:\s*(\S+)/i);
|
|
7001
|
+
if (kernelMatch) intel.push(`\u{1F427} Kernel: ${kernelMatch[1]}`);
|
|
7002
|
+
const passLines = output.split("\n").filter(
|
|
7003
|
+
(l) => /password\s*[=:]\s*\S+/i.test(l) && !/\*\*\*|example|sample/i.test(l)
|
|
7004
|
+
);
|
|
7005
|
+
if (passLines.length > 0) {
|
|
7006
|
+
intel.push("\u{1F511} Potential credentials found:");
|
|
7007
|
+
passLines.slice(0, 5).forEach((p) => intel.push(` ${p.trim()}`));
|
|
7008
|
+
}
|
|
7009
|
+
const cveMatches = output.match(/CVE-\d{4}-\d+/gi);
|
|
7010
|
+
if (cveMatches) {
|
|
7011
|
+
const uniqueCves = [...new Set(cveMatches)];
|
|
7012
|
+
intel.push(`\u26A0\uFE0F CVEs mentioned: ${uniqueCves.join(", ")}`);
|
|
7013
|
+
}
|
|
7014
|
+
return intel;
|
|
7015
|
+
}
|
|
7016
|
+
function extractEnum4linuxIntel(output) {
|
|
7017
|
+
const intel = [];
|
|
7018
|
+
const userMatches = output.match(/user:\[(\S+?)\]/gi);
|
|
7019
|
+
if (userMatches) {
|
|
7020
|
+
const users = userMatches.map((u) => u.replace(/user:\[|\]/gi, ""));
|
|
7021
|
+
intel.push(`\u{1F464} Users found: ${users.join(", ")}`);
|
|
7022
|
+
}
|
|
7023
|
+
const shareMatches = output.match(/Mapping: (\S+),\s*Listing: (\S+)/gi) || output.match(/\\\\\S+\\\\\S+/g);
|
|
7024
|
+
if (shareMatches) {
|
|
7025
|
+
intel.push(`\u{1F4C2} Shares: ${shareMatches.slice(0, 10).join(", ")}`);
|
|
7026
|
+
}
|
|
7027
|
+
const domainMatch = output.match(/Domain:\s*\[(\S+?)\]/i) || output.match(/Workgroup:\s*\[(\S+?)\]/i);
|
|
7028
|
+
if (domainMatch) intel.push(`\u{1F3E2} Domain: ${domainMatch[1]}`);
|
|
7029
|
+
const policyMatch = output.match(/Password Complexity|Account Lockout/i);
|
|
7030
|
+
if (policyMatch) intel.push("\u{1F510} Password policy information found");
|
|
7031
|
+
return intel;
|
|
7032
|
+
}
|
|
7033
|
+
function extractDirBustIntel(output) {
|
|
7034
|
+
const intel = [];
|
|
7035
|
+
const lines = output.split("\n");
|
|
7036
|
+
const interestingPaths = [];
|
|
7037
|
+
for (const line of lines) {
|
|
7038
|
+
const match = line.match(/(?:Status:\s*(\d{3})|(\d{3})\s+\d+\s+\d+).*?(\/\S+)/);
|
|
7039
|
+
if (match) {
|
|
7040
|
+
const status = match[1] || match[2];
|
|
7041
|
+
const path2 = match[3];
|
|
7042
|
+
if (["200", "301", "302", "403"].includes(status)) {
|
|
7043
|
+
interestingPaths.push(`[${status}] ${path2}`);
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
const ffufMatch = line.match(/(\S+)\s+\[Status:\s*(\d+),?\s*Size:\s*(\d+)/);
|
|
7047
|
+
if (ffufMatch && ["200", "301", "302", "403"].includes(ffufMatch[2])) {
|
|
7048
|
+
interestingPaths.push(`[${ffufMatch[2]}] ${ffufMatch[1]} (${ffufMatch[3]}b)`);
|
|
7049
|
+
}
|
|
7050
|
+
}
|
|
7051
|
+
if (interestingPaths.length > 0) {
|
|
7052
|
+
intel.push(`\u{1F4C1} Discovered paths (${interestingPaths.length}):`);
|
|
7053
|
+
interestingPaths.slice(0, 20).forEach((p) => intel.push(` ${p}`));
|
|
7054
|
+
if (interestingPaths.length > 20) {
|
|
7055
|
+
intel.push(` ... and ${interestingPaths.length - 20} more`);
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
return intel;
|
|
7059
|
+
}
|
|
7060
|
+
function extractSqlmapIntel(output) {
|
|
7061
|
+
const intel = [];
|
|
7062
|
+
const injectionTypes = output.match(/Type:\s*(\S.*?)(?:\n|$)/gi);
|
|
7063
|
+
if (injectionTypes) {
|
|
7064
|
+
intel.push("\u{1F489} SQL injection found:");
|
|
7065
|
+
injectionTypes.slice(0, 5).forEach((t) => intel.push(` ${t.trim()}`));
|
|
7066
|
+
}
|
|
7067
|
+
const dbMatch = output.match(/back-end DBMS:\s*(.+)/i);
|
|
7068
|
+
if (dbMatch) intel.push(`\u{1F5C4}\uFE0F DBMS: ${dbMatch[1].trim()}`);
|
|
7069
|
+
const dbListMatch = output.match(/available databases.*?:\s*([\s\S]*?)(?=\n\[|\n$)/i);
|
|
7070
|
+
if (dbListMatch) {
|
|
7071
|
+
intel.push(`\u{1F4CA} Databases: ${dbListMatch[1].trim().replace(/\n/g, ", ")}`);
|
|
7072
|
+
}
|
|
7073
|
+
const tableMatches = output.match(/Database:\s*\S+\s+Table:\s*\S+/gi);
|
|
7074
|
+
if (tableMatches) {
|
|
7075
|
+
intel.push(`\u{1F4CB} Tables dumped: ${tableMatches.length}`);
|
|
7076
|
+
}
|
|
7077
|
+
return intel;
|
|
7078
|
+
}
|
|
7079
|
+
function extractHashIntel(output) {
|
|
7080
|
+
const intel = [];
|
|
7081
|
+
const lines = output.split("\n");
|
|
7082
|
+
const md5Hashes = lines.filter((l) => /\b[a-f0-9]{32}\b/i.test(l) && !l.includes("{"));
|
|
7083
|
+
const sha256Hashes = lines.filter((l) => /\b[a-f0-9]{64}\b/i.test(l));
|
|
7084
|
+
const unixHashes = lines.filter((l) => /\$[0-9]\$|\$2[aby]\$|\$6\$|\$5\$/i.test(l));
|
|
7085
|
+
const ntlmHashes = lines.filter((l) => /:[0-9]+:[a-f0-9]{32}:[a-f0-9]{32}:::/i.test(l));
|
|
7086
|
+
if (md5Hashes.length > 0) intel.push(`#\uFE0F\u20E3 MD5 hashes: ${md5Hashes.length}`);
|
|
7087
|
+
if (sha256Hashes.length > 0) intel.push(`#\uFE0F\u20E3 SHA256 hashes: ${sha256Hashes.length}`);
|
|
7088
|
+
if (unixHashes.length > 0) intel.push(`#\uFE0F\u20E3 Unix crypt hashes: ${unixHashes.length}`);
|
|
7089
|
+
if (ntlmHashes.length > 0) {
|
|
7090
|
+
intel.push(`#\uFE0F\u20E3 NTLM hashes: ${ntlmHashes.length}`);
|
|
7091
|
+
ntlmHashes.slice(0, 5).forEach((h) => intel.push(` ${h.trim().slice(0, 100)}`));
|
|
7092
|
+
}
|
|
7093
|
+
return intel;
|
|
7094
|
+
}
|
|
7095
|
+
function extractGenericIntel(output) {
|
|
7096
|
+
const intel = [];
|
|
7097
|
+
const credPatterns = output.match(/(?:password|passwd|pwd|credentials?)\s*[=:]\s*\S+/gi);
|
|
7098
|
+
if (credPatterns) {
|
|
7099
|
+
intel.push("\u{1F511} Potential credentials detected:");
|
|
7100
|
+
credPatterns.slice(0, 5).forEach((c) => intel.push(` ${c.trim()}`));
|
|
7101
|
+
}
|
|
7102
|
+
const cves = output.match(/CVE-\d{4}-\d+/gi);
|
|
7103
|
+
if (cves) {
|
|
7104
|
+
const unique = [...new Set(cves)];
|
|
7105
|
+
intel.push(`\u26A0\uFE0F CVEs mentioned: ${unique.join(", ")}`);
|
|
7106
|
+
}
|
|
7107
|
+
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);
|
|
7108
|
+
if (ips) {
|
|
7109
|
+
const uniqueIps = [...new Set(ips)].filter(
|
|
7110
|
+
(ip) => !ip.startsWith("0.") && !ip.startsWith("255.") && ip !== "127.0.0.1"
|
|
7111
|
+
);
|
|
7112
|
+
if (uniqueIps.length > 0 && uniqueIps.length <= 20) {
|
|
7113
|
+
intel.push(`\u{1F310} IP addresses found: ${uniqueIps.join(", ")}`);
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
const paths = output.match(/\/(?:etc\/(?:shadow|passwd|sudoers)|root\/|home\/\S+|var\/www\/\S+|opt\/\S+)\S*/g);
|
|
7117
|
+
if (paths) {
|
|
7118
|
+
const uniquePaths = [...new Set(paths)].slice(0, 10);
|
|
7119
|
+
intel.push(`\u{1F4C2} Interesting paths: ${uniquePaths.join(", ")}`);
|
|
7120
|
+
}
|
|
7121
|
+
const flagPatterns = output.match(/(?:flag|secret|key|token)\{[^}]+\}/gi);
|
|
7122
|
+
if (flagPatterns) {
|
|
7123
|
+
intel.push("\u{1F3F4} FLAG/SECRET patterns detected:");
|
|
7124
|
+
flagPatterns.forEach((f) => intel.push(` ${f}`));
|
|
7125
|
+
}
|
|
7126
|
+
return intel;
|
|
7127
|
+
}
|
|
7128
|
+
|
|
7129
|
+
// src/shared/utils/context-digest.ts
|
|
7130
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
|
|
7131
|
+
var PASSTHROUGH_THRESHOLD = 3e3;
|
|
7132
|
+
var LAYER2_THRESHOLD = 8e3;
|
|
7133
|
+
var LAYER3_THRESHOLD = 5e4;
|
|
7134
|
+
var MAX_REDUCED_LINES = 500;
|
|
7135
|
+
var getOutputDir = () => WORKSPACE.OUTPUTS;
|
|
7136
|
+
var MAX_DUPLICATE_DISPLAY = 3;
|
|
7137
|
+
var LAYER3_MAX_INPUT_CHARS = 8e4;
|
|
7138
|
+
var FALLBACK_MAX_CHARS = 3e4;
|
|
7139
|
+
var SIGNAL_PATTERNS = [
|
|
7140
|
+
/error|fail|denied|refused|timeout|exception/i,
|
|
7141
|
+
/warning|warn|deprecated|insecure/i,
|
|
7142
|
+
/success|found|detected|discovered|vulnerable|VULNERABLE/i,
|
|
7143
|
+
/password|passwd|credential|secret|key|token|hash/i,
|
|
7144
|
+
/CVE-\d{4}-\d+/i,
|
|
7145
|
+
/\d+\/\w+\s+open\s+/,
|
|
7146
|
+
// nmap open port
|
|
7147
|
+
/flag\{|ctf\{|HTB\{|THM\{/i,
|
|
7148
|
+
// CTF flags
|
|
7149
|
+
/root:|admin:|www-data:|nobody:/,
|
|
7150
|
+
// /etc/passwd entries
|
|
7151
|
+
/\$[0-9]\$|\$2[aby]\$/,
|
|
7152
|
+
// password hashes
|
|
7153
|
+
/\b(?:192\.168|10\.|172\.(?:1[6-9]|2\d|3[01]))\.\d+\.\d+\b/,
|
|
7154
|
+
// internal IPs
|
|
7155
|
+
/BEGIN\s+(?:RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY/i,
|
|
7156
|
+
// private keys
|
|
7157
|
+
/Authorization:|Bearer\s+|Basic\s+/i
|
|
7158
|
+
// auth tokens
|
|
7159
|
+
];
|
|
7160
|
+
var NOISE_PATTERNS = [
|
|
7161
|
+
/^\s*$/,
|
|
7162
|
+
// blank lines
|
|
7163
|
+
/^\[[\d:]+\]\s*$/,
|
|
7164
|
+
// timestamp-only lines
|
|
7165
|
+
/^#+\s*$/,
|
|
7166
|
+
// separator lines
|
|
7167
|
+
/^\s*Progress:\s*\[?[#=\-\s>]+\]?\s*\d+/i,
|
|
7168
|
+
// progress bars
|
|
7169
|
+
/\d+\s+requests?\s+(?:sent|made)/i,
|
|
7170
|
+
// request counters
|
|
7171
|
+
/^\s*(?:\.{3,}|={5,}|-{5,})\s*$/
|
|
7172
|
+
// decoration lines
|
|
7173
|
+
];
|
|
7174
|
+
async function digestToolOutput(output, toolName, toolInput, llmDigestFn) {
|
|
7175
|
+
const originalLength = output.length;
|
|
7176
|
+
if (originalLength < PASSTHROUGH_THRESHOLD) {
|
|
7177
|
+
return {
|
|
7178
|
+
digestedOutput: output,
|
|
7179
|
+
fullOutputPath: null,
|
|
7180
|
+
layersApplied: [],
|
|
7181
|
+
originalLength,
|
|
7182
|
+
digestedLength: originalLength,
|
|
7183
|
+
compressionRatio: 1
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
const layersApplied = [];
|
|
7187
|
+
let processed = output;
|
|
7188
|
+
let savedOutputPath = null;
|
|
7189
|
+
processed = compressToolOutput(processed, toolName);
|
|
7190
|
+
layersApplied.push(1);
|
|
7191
|
+
if (processed.length > LAYER2_THRESHOLD) {
|
|
7192
|
+
processed = structuralReduce(processed);
|
|
7193
|
+
layersApplied.push(2);
|
|
7194
|
+
}
|
|
7195
|
+
if (processed.length > LAYER3_THRESHOLD) {
|
|
7196
|
+
savedOutputPath = saveFullOutput(output, toolName);
|
|
7197
|
+
if (llmDigestFn) {
|
|
7198
|
+
try {
|
|
7199
|
+
const context = `Tool: ${toolName}${toolInput ? ` | Input: ${toolInput}` : ""}`;
|
|
7200
|
+
const digest = await llmDigestFn(processed, context);
|
|
7201
|
+
processed = formatLLMDigest(digest, savedOutputPath, originalLength);
|
|
7202
|
+
layersApplied.push(3);
|
|
7203
|
+
} catch (err) {
|
|
7204
|
+
debugLog("general", "Context Digest Layer 3 failed, falling back", { toolName, error: String(err) });
|
|
7205
|
+
processed = formatFallbackDigest(processed, savedOutputPath, originalLength);
|
|
7206
|
+
layersApplied.push(3);
|
|
7207
|
+
}
|
|
7208
|
+
} else {
|
|
7209
|
+
processed = formatFallbackDigest(processed, savedOutputPath, originalLength);
|
|
7210
|
+
}
|
|
7211
|
+
} else if (layersApplied.includes(2)) {
|
|
7212
|
+
savedOutputPath = saveFullOutput(output, toolName);
|
|
7213
|
+
}
|
|
7214
|
+
return {
|
|
7215
|
+
digestedOutput: processed,
|
|
7216
|
+
fullOutputPath: savedOutputPath,
|
|
7217
|
+
layersApplied,
|
|
7218
|
+
originalLength,
|
|
7219
|
+
digestedLength: processed.length,
|
|
7220
|
+
compressionRatio: processed.length / originalLength
|
|
7221
|
+
};
|
|
7222
|
+
}
|
|
7223
|
+
function structuralReduce(output) {
|
|
7224
|
+
let cleaned = stripAnsi(output);
|
|
7225
|
+
const lines = cleaned.split("\n");
|
|
7226
|
+
const result2 = [];
|
|
7227
|
+
const duplicateCounts = /* @__PURE__ */ new Map();
|
|
7228
|
+
let lastLine = "";
|
|
7229
|
+
let consecutiveDupes = 0;
|
|
7230
|
+
for (const line of lines) {
|
|
7231
|
+
const trimmed = line.trim();
|
|
7232
|
+
if (NOISE_PATTERNS.some((p) => p.test(trimmed))) {
|
|
7233
|
+
continue;
|
|
7234
|
+
}
|
|
7235
|
+
const isSignal = SIGNAL_PATTERNS.some((p) => p.test(trimmed));
|
|
7236
|
+
const normalized = normalizeLine(trimmed);
|
|
7237
|
+
if (normalized === normalizeLine(lastLine) && !isSignal) {
|
|
7238
|
+
consecutiveDupes++;
|
|
7239
|
+
duplicateCounts.set(normalized, (duplicateCounts.get(normalized) || 1) + 1);
|
|
7240
|
+
continue;
|
|
7241
|
+
}
|
|
7242
|
+
if (consecutiveDupes > 0) {
|
|
7243
|
+
if (consecutiveDupes <= MAX_DUPLICATE_DISPLAY) {
|
|
7244
|
+
for (let i = 0; i < consecutiveDupes; i++) {
|
|
7245
|
+
result2.push(lastLine);
|
|
7246
|
+
}
|
|
7247
|
+
} else {
|
|
7248
|
+
result2.push(` ... (${consecutiveDupes} similar lines collapsed)`);
|
|
7249
|
+
}
|
|
7250
|
+
consecutiveDupes = 0;
|
|
7251
|
+
}
|
|
7252
|
+
result2.push(line);
|
|
7253
|
+
lastLine = trimmed;
|
|
7254
|
+
}
|
|
7255
|
+
if (consecutiveDupes > MAX_DUPLICATE_DISPLAY) {
|
|
7256
|
+
result2.push(` ... (${consecutiveDupes} similar lines collapsed)`);
|
|
7257
|
+
} else {
|
|
7258
|
+
for (let i = 0; i < consecutiveDupes; i++) {
|
|
7259
|
+
result2.push(lastLine);
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
if (result2.length > MAX_REDUCED_LINES) {
|
|
7263
|
+
const headSize = Math.floor(MAX_REDUCED_LINES * 0.4);
|
|
7264
|
+
const tailSize = Math.floor(MAX_REDUCED_LINES * 0.3);
|
|
7265
|
+
const signalBudget = MAX_REDUCED_LINES - headSize - tailSize;
|
|
7266
|
+
const head = result2.slice(0, headSize);
|
|
7267
|
+
const tail = result2.slice(-tailSize);
|
|
7268
|
+
const middle = result2.slice(headSize, -tailSize);
|
|
7269
|
+
const middleSignals = middle.filter((line) => SIGNAL_PATTERNS.some((p) => p.test(line))).slice(0, signalBudget);
|
|
7270
|
+
const skipped = middle.length - middleSignals.length;
|
|
7271
|
+
cleaned = [
|
|
7272
|
+
...head,
|
|
7273
|
+
"",
|
|
7274
|
+
`... [${skipped} routine lines skipped \u2014 ${middleSignals.length} important lines preserved] ...`,
|
|
7275
|
+
"",
|
|
7276
|
+
...middleSignals,
|
|
7277
|
+
"",
|
|
7278
|
+
`... [resuming last ${tailSize} lines] ...`,
|
|
7279
|
+
"",
|
|
7280
|
+
...tail
|
|
7281
|
+
].join("\n");
|
|
7282
|
+
} else {
|
|
7283
|
+
cleaned = result2.join("\n");
|
|
7284
|
+
}
|
|
7285
|
+
return cleaned;
|
|
7286
|
+
}
|
|
7287
|
+
var DIGEST_SYSTEM_PROMPT = `You are a pentesting output analyst. Given raw tool output, extract ONLY actionable intelligence. Be terse and structured.
|
|
7288
|
+
|
|
7289
|
+
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
7290
|
+
## Key Findings
|
|
7291
|
+
- [finding 1]
|
|
7292
|
+
- [finding 2]
|
|
7293
|
+
|
|
7294
|
+
## Credentials/Secrets
|
|
7295
|
+
- [any discovered credentials, hashes, tokens, keys]
|
|
7296
|
+
|
|
7297
|
+
## Attack Vectors
|
|
7298
|
+
- [exploitable services, vulnerabilities, misconfigurations]
|
|
7299
|
+
|
|
7300
|
+
## Next Steps
|
|
7301
|
+
- [recommended immediate actions based on findings]
|
|
7302
|
+
|
|
7303
|
+
RULES:
|
|
7304
|
+
- Be EXTREMELY concise \u2014 max 30 lines total
|
|
7305
|
+
- Only include ACTIONABLE findings \u2014 skip routine/expected results
|
|
7306
|
+
- If nothing interesting found, say "No actionable findings in this output"
|
|
7307
|
+
- Include exact values: port numbers, versions, usernames, file paths
|
|
7308
|
+
- Never include decorative output, banners, or progress information`;
|
|
7309
|
+
function formatLLMDigest(digest, filePath, originalChars) {
|
|
7310
|
+
return [
|
|
7311
|
+
"\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",
|
|
7312
|
+
"\u2551 CONTEXT DIGEST (LLM-summarized) \u2551",
|
|
7313
|
+
"\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",
|
|
7314
|
+
"",
|
|
7315
|
+
digest,
|
|
7316
|
+
"",
|
|
7317
|
+
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
7318
|
+
`\u{1F4A1} Use read_file("${filePath}") to see the complete raw output if needed.`
|
|
7319
|
+
].join("\n");
|
|
7320
|
+
}
|
|
7321
|
+
function formatFallbackDigest(processed, filePath, originalChars) {
|
|
7322
|
+
const lines = processed.split("\n");
|
|
7323
|
+
const summaryEndIdx = lines.findIndex((l) => l.includes("END SUMMARY"));
|
|
7324
|
+
const summaryBlock = summaryEndIdx > 0 ? lines.slice(0, summaryEndIdx + 1).join("\n") : "";
|
|
7325
|
+
const remaining = summaryEndIdx > 0 ? lines.slice(summaryEndIdx + 1).join("\n") : processed;
|
|
7326
|
+
const maxChars = FALLBACK_MAX_CHARS;
|
|
7327
|
+
let truncatedRemaining = remaining;
|
|
7328
|
+
if (remaining.length > maxChars) {
|
|
7329
|
+
const headChars = Math.floor(maxChars * 0.6);
|
|
7330
|
+
const tailChars = Math.floor(maxChars * 0.4);
|
|
7331
|
+
const skipped = remaining.length - headChars - tailChars;
|
|
7332
|
+
truncatedRemaining = remaining.slice(0, headChars) + `
|
|
7333
|
+
|
|
7334
|
+
... [${skipped} chars omitted \u2014 read full output from file] ...
|
|
7335
|
+
|
|
7336
|
+
` + remaining.slice(-tailChars);
|
|
7337
|
+
}
|
|
7338
|
+
return [
|
|
7339
|
+
summaryBlock,
|
|
7340
|
+
truncatedRemaining,
|
|
7341
|
+
"",
|
|
7342
|
+
`\u{1F4C2} Full output saved: ${filePath} (${originalChars} chars)`,
|
|
7343
|
+
`\u{1F4A1} Use read_file("${filePath}") to see the complete raw output.`
|
|
7344
|
+
].filter(Boolean).join("\n");
|
|
7345
|
+
}
|
|
7346
|
+
function stripAnsi(text) {
|
|
7347
|
+
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1B\[[\d;]*m/g, "");
|
|
7348
|
+
}
|
|
7349
|
+
function normalizeLine(line) {
|
|
7350
|
+
return line.replace(/\d+/g, "N").replace(/0x[a-f0-9]+/gi, "H").replace(/\s+/g, " ").trim().toLowerCase();
|
|
7351
|
+
}
|
|
7352
|
+
function saveFullOutput(output, toolName) {
|
|
7353
|
+
try {
|
|
7354
|
+
const outputDir = getOutputDir();
|
|
7355
|
+
if (!existsSync6(outputDir)) {
|
|
7356
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
7357
|
+
}
|
|
7358
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
7359
|
+
const safeName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 30);
|
|
7360
|
+
const filePath = `${outputDir}/${timestamp}_${safeName}.txt`;
|
|
7361
|
+
writeFileSync6(filePath, output, "utf-8");
|
|
7362
|
+
return filePath;
|
|
7363
|
+
} catch (err) {
|
|
7364
|
+
debugLog("general", "Failed to save full output to file", { toolName, error: String(err) });
|
|
7365
|
+
return "(failed to save \u2014 output too large or disk full)";
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
function createLLMDigestFn(llmClient) {
|
|
7369
|
+
return async (text, context) => {
|
|
7370
|
+
const truncatedText = text.length > LAYER3_MAX_INPUT_CHARS ? text.slice(0, LAYER3_MAX_INPUT_CHARS) + `
|
|
7371
|
+
... [truncated for summarization, ${text.length - LAYER3_MAX_INPUT_CHARS} chars omitted]` : text;
|
|
7372
|
+
const messages = [{ role: "user", content: `Analyze this pentesting tool output and extract actionable intelligence.
|
|
7373
|
+
|
|
7374
|
+
Context: ${context}
|
|
7375
|
+
|
|
7376
|
+
--- OUTPUT START ---
|
|
7377
|
+
${truncatedText}
|
|
7378
|
+
--- OUTPUT END ---` }];
|
|
7379
|
+
const response = await llmClient.generateResponse(
|
|
7380
|
+
messages,
|
|
7381
|
+
void 0,
|
|
7382
|
+
// no tools — summarization only
|
|
7383
|
+
DIGEST_SYSTEM_PROMPT
|
|
7384
|
+
);
|
|
7385
|
+
return response.content || "No actionable findings extracted.";
|
|
7386
|
+
};
|
|
7387
|
+
}
|
|
7388
|
+
|
|
6768
7389
|
// src/agents/core-agent.ts
|
|
6769
7390
|
var CoreAgent = class _CoreAgent {
|
|
6770
7391
|
llm;
|
|
@@ -7154,14 +7775,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7154
7775
|
input: call.input
|
|
7155
7776
|
});
|
|
7156
7777
|
let outputText = result2.output ?? "";
|
|
7157
|
-
|
|
7158
|
-
const truncated = outputText.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
7159
|
-
const remaining = outputText.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
7160
|
-
outputText = `${truncated}
|
|
7161
|
-
|
|
7162
|
-
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
7163
|
-
\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.`;
|
|
7164
|
-
}
|
|
7778
|
+
this.scanForFlags(outputText);
|
|
7165
7779
|
if (result2.error) {
|
|
7166
7780
|
outputText = this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
|
|
7167
7781
|
if (progress) progress.toolErrors++;
|
|
@@ -7171,8 +7785,26 @@ Please decide how to handle this error and continue.`;
|
|
|
7171
7785
|
progress.blockedCommandPatterns.clear();
|
|
7172
7786
|
}
|
|
7173
7787
|
}
|
|
7788
|
+
try {
|
|
7789
|
+
const llmDigestFn = createLLMDigestFn(this.llm);
|
|
7790
|
+
const digestResult = await digestToolOutput(
|
|
7791
|
+
outputText,
|
|
7792
|
+
call.name,
|
|
7793
|
+
JSON.stringify(call.input).slice(0, 200),
|
|
7794
|
+
llmDigestFn
|
|
7795
|
+
);
|
|
7796
|
+
outputText = digestResult.digestedOutput;
|
|
7797
|
+
} catch {
|
|
7798
|
+
if (outputText.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
|
|
7799
|
+
const truncated = outputText.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
|
|
7800
|
+
const remaining = outputText.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
7801
|
+
outputText = `${truncated}
|
|
7802
|
+
|
|
7803
|
+
... [TRUNCATED ${remaining} characters for context hygiene] ...
|
|
7804
|
+
\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.`;
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7174
7807
|
this.emitToolResult(call.name, result2.success, outputText, result2.error, Date.now() - toolStartTime);
|
|
7175
|
-
this.scanForFlags(outputText);
|
|
7176
7808
|
return { toolCallId: call.id, output: outputText, error: result2.error };
|
|
7177
7809
|
} catch (error) {
|
|
7178
7810
|
const errorMsg = String(error);
|
|
@@ -7248,7 +7880,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7248
7880
|
};
|
|
7249
7881
|
|
|
7250
7882
|
// src/agents/prompt-builder.ts
|
|
7251
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
7883
|
+
import { readFileSync as readFileSync4, existsSync as existsSync7, readdirSync as readdirSync2 } from "fs";
|
|
7252
7884
|
import { join as join9, dirname as dirname5 } from "path";
|
|
7253
7885
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7254
7886
|
|
|
@@ -7363,7 +7995,8 @@ var PromptBuilder = class {
|
|
|
7363
7995
|
* 5. Scope constraints
|
|
7364
7996
|
* 6. Current state (targets, findings, loot, active processes)
|
|
7365
7997
|
* 7. TODO list
|
|
7366
|
-
* 8.
|
|
7998
|
+
* 8. Time awareness (elapsed + deadline)
|
|
7999
|
+
* 9. User context
|
|
7367
8000
|
*/
|
|
7368
8001
|
build(userInput, phase) {
|
|
7369
8002
|
const fragments = [
|
|
@@ -7375,6 +8008,7 @@ var PromptBuilder = class {
|
|
|
7375
8008
|
this.getScopeFragment(),
|
|
7376
8009
|
this.getStateFragment(),
|
|
7377
8010
|
this.getTodoFragment(),
|
|
8011
|
+
this.getTimeFragment(),
|
|
7378
8012
|
PROMPT_DEFAULTS.USER_CONTEXT(userInput)
|
|
7379
8013
|
];
|
|
7380
8014
|
return fragments.filter((f) => !!f).join("\n\n");
|
|
@@ -7395,7 +8029,7 @@ ${content}
|
|
|
7395
8029
|
*/
|
|
7396
8030
|
loadPromptFile(filename) {
|
|
7397
8031
|
const path2 = join9(PROMPTS_DIR, filename);
|
|
7398
|
-
return
|
|
8032
|
+
return existsSync7(path2) ? readFileSync4(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
7399
8033
|
}
|
|
7400
8034
|
/**
|
|
7401
8035
|
* Load phase-specific prompt.
|
|
@@ -7441,14 +8075,14 @@ ${content}
|
|
|
7441
8075
|
* "마크다운 파일 하나를 폴더에 넣으면, PromptBuilder가 자동으로 발견하고 로드한다."
|
|
7442
8076
|
*/
|
|
7443
8077
|
loadPhaseRelevantTechniques(phase) {
|
|
7444
|
-
if (!
|
|
8078
|
+
if (!existsSync7(TECHNIQUES_DIR)) return "";
|
|
7445
8079
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
7446
8080
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
7447
8081
|
const fragments = [];
|
|
7448
8082
|
for (const technique of priorityTechniques) {
|
|
7449
8083
|
const filePath = join9(TECHNIQUES_DIR, `${technique}.md`);
|
|
7450
8084
|
try {
|
|
7451
|
-
if (!
|
|
8085
|
+
if (!existsSync7(filePath)) continue;
|
|
7452
8086
|
const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
|
|
7453
8087
|
if (content) {
|
|
7454
8088
|
fragments.push(`<technique-reference category="${technique}">
|
|
@@ -7494,6 +8128,11 @@ ${content}
|
|
|
7494
8128
|
const list = todo.map((t) => `[${t.status === TODO_STATUSES.DONE ? "x" : " "}] ${t.content} (${t.priority})`).join("\n");
|
|
7495
8129
|
return PROMPT_XML.TODO(list || PROMPT_DEFAULTS.EMPTY_TODO);
|
|
7496
8130
|
}
|
|
8131
|
+
getTimeFragment() {
|
|
8132
|
+
return `<time-status>
|
|
8133
|
+
${this.state.getTimeStatus()}
|
|
8134
|
+
</time-status>`;
|
|
8135
|
+
}
|
|
7497
8136
|
};
|
|
7498
8137
|
|
|
7499
8138
|
// src/agents/main-agent.ts
|
|
@@ -7795,7 +8434,7 @@ var THEME = {
|
|
|
7795
8434
|
},
|
|
7796
8435
|
// Gradients
|
|
7797
8436
|
gradient: {
|
|
7798
|
-
cyber: ["#
|
|
8437
|
+
cyber: ["#38bdf8", "#818cf8", "#c084fc", "#e879f9", "#22d3ee"],
|
|
7799
8438
|
danger: ["#ef4444", "#7f1d1d"],
|
|
7800
8439
|
success: ["#22c55e", "#14532d"],
|
|
7801
8440
|
gold: ["#f59e0b", "#78350f"],
|
|
@@ -7807,32 +8446,13 @@ var THEME = {
|
|
|
7807
8446
|
identity: "#38bdf8"
|
|
7808
8447
|
};
|
|
7809
8448
|
var ASCII_BANNER = `
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
\u2571\u2594 \u2594\u2572
|
|
7818
|
-
\u2572\u2581 \u2581\u2571
|
|
7819
|
-
\u2572\u2581\u2581 \u2581\u2581\u2571
|
|
7820
|
-
\u2572\u2581\u2581 \u2581\u2581\u2571
|
|
7821
|
-
\u2500\u2500\u2500\u2500\u2500\u2573\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2573\u2500\u2500\u2500\u2500\u2500
|
|
7822
|
-
\u2572 \u2571
|
|
7823
|
-
\u2572 \u2571
|
|
7824
|
-
\u2572 \u2571
|
|
7825
|
-
\u2572 \u2571
|
|
7826
|
-
\u2572 \u2571
|
|
7827
|
-
\u25C9
|
|
7828
|
-
|
|
7829
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
7830
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
7831
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551
|
|
7832
|
-
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
7833
|
-
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
7834
|
-
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D
|
|
7835
|
-
A U T O N O M O U S P E N T E S T A G E N T
|
|
8449
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
8450
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
8451
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557
|
|
8452
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
8453
|
+
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
8454
|
+
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
8455
|
+
A U T O N O M O U S S E C U R I T Y A G E N T
|
|
7836
8456
|
`;
|
|
7837
8457
|
var ICONS = {
|
|
7838
8458
|
// Status
|