pentesting 0.49.1 → 0.49.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 +519 -237
- 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.49.
|
|
334
|
+
var APP_VERSION = "0.49.3";
|
|
335
335
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
336
|
var LLM_ROLES = {
|
|
337
337
|
SYSTEM: "system",
|
|
@@ -704,6 +704,16 @@ var ATTACK_VALUE_RANK = {
|
|
|
704
704
|
LOW: 1,
|
|
705
705
|
NONE: 0
|
|
706
706
|
};
|
|
707
|
+
var CONFIDENCE_THRESHOLDS = {
|
|
708
|
+
/** ≥80: exploit confirmed, shell access, flag captured */
|
|
709
|
+
CONFIRMED: 80,
|
|
710
|
+
/** ≥50: strong but not yet proven */
|
|
711
|
+
PROBABLE: 50,
|
|
712
|
+
/** ≥25: initial discovery, weak signal */
|
|
713
|
+
POSSIBLE: 25,
|
|
714
|
+
/** <25: speculation only */
|
|
715
|
+
NONE: 0
|
|
716
|
+
};
|
|
707
717
|
var APPROVAL_STATUSES = {
|
|
708
718
|
AUTO: "auto",
|
|
709
719
|
USER_CONFIRMED: "user_confirmed",
|
|
@@ -794,7 +804,11 @@ var DEFAULTS = {
|
|
|
794
804
|
ENGAGEMENT_NAME: "auto-assessment",
|
|
795
805
|
ENGAGEMENT_CLIENT: "internal",
|
|
796
806
|
UNKNOWN_PHASE: "unknown",
|
|
797
|
-
INIT_PHASE: "init"
|
|
807
|
+
INIT_PHASE: "init",
|
|
808
|
+
/** Fallback service name when port fingerprinting yielded no result */
|
|
809
|
+
UNKNOWN_SERVICE: "unknown",
|
|
810
|
+
/** Default port state when not explicitly specified */
|
|
811
|
+
PORT_STATE_OPEN: "open"
|
|
798
812
|
};
|
|
799
813
|
|
|
800
814
|
// src/engine/process-manager.ts
|
|
@@ -2460,17 +2474,16 @@ var StateSerializer = class {
|
|
|
2460
2474
|
}
|
|
2461
2475
|
const findings = state.getFindings();
|
|
2462
2476
|
if (findings.length > 0) {
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
for (const f of important.slice(0, DISPLAY_LIMITS.FINDING_PREVIEW)) {
|
|
2477
|
+
const confirmed = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
2478
|
+
const probable = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
2479
|
+
const possible = findings.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
2480
|
+
lines.push(`Findings: ${findings.length} total (confirmed:${confirmed} probable:${probable} possible:${possible})`);
|
|
2481
|
+
const highPriority = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).sort((a, b) => b.confidence - a.confidence);
|
|
2482
|
+
if (highPriority.length > 0) {
|
|
2483
|
+
lines.push(` Confirmed Findings (\u2265${CONFIDENCE_THRESHOLDS.CONFIRMED}):`);
|
|
2484
|
+
for (const f of highPriority.slice(0, DISPLAY_LIMITS.FINDING_PREVIEW)) {
|
|
2472
2485
|
const tactic = f.attackPattern ? ` [ATT&CK:${f.attackPattern}]` : "";
|
|
2473
|
-
lines.push(` [
|
|
2486
|
+
lines.push(` [conf:${f.confidence}|${f.severity.toUpperCase()}] ${f.title} (${f.category || "general"})${tactic}`);
|
|
2474
2487
|
}
|
|
2475
2488
|
}
|
|
2476
2489
|
}
|
|
@@ -3350,7 +3363,7 @@ var DynamicTechniqueLibrary = class {
|
|
|
3350
3363
|
});
|
|
3351
3364
|
if (this.techniques.length > this.maxTechniques) {
|
|
3352
3365
|
this.techniques.sort((a, b) => {
|
|
3353
|
-
if (a.
|
|
3366
|
+
if (a.confidence !== b.confidence) return b.confidence - a.confidence;
|
|
3354
3367
|
return b.learnedAt - a.learnedAt;
|
|
3355
3368
|
});
|
|
3356
3369
|
this.techniques = this.techniques.slice(0, this.maxTechniques);
|
|
@@ -3386,18 +3399,20 @@ var DynamicTechniqueLibrary = class {
|
|
|
3386
3399
|
source: `Web search: "${query}"`,
|
|
3387
3400
|
technique: tech,
|
|
3388
3401
|
applicableTo,
|
|
3389
|
-
|
|
3402
|
+
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
3403
|
+
// discovered, not yet tested
|
|
3390
3404
|
fromQuery: query
|
|
3391
3405
|
});
|
|
3392
3406
|
}
|
|
3393
3407
|
}
|
|
3394
3408
|
/**
|
|
3395
3409
|
* Mark a technique as verified (it worked in practice).
|
|
3410
|
+
* Upgrades confidence to 80.
|
|
3396
3411
|
*/
|
|
3397
3412
|
verify(techniqueSubstring) {
|
|
3398
3413
|
for (const t of this.techniques) {
|
|
3399
3414
|
if (t.technique.toLowerCase().includes(techniqueSubstring.toLowerCase())) {
|
|
3400
|
-
t.
|
|
3415
|
+
t.confidence = CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
3401
3416
|
}
|
|
3402
3417
|
}
|
|
3403
3418
|
}
|
|
@@ -3425,18 +3440,18 @@ var DynamicTechniqueLibrary = class {
|
|
|
3425
3440
|
*/
|
|
3426
3441
|
toPrompt() {
|
|
3427
3442
|
if (this.techniques.length === 0) return "";
|
|
3428
|
-
const
|
|
3429
|
-
const
|
|
3443
|
+
const confirmed = this.techniques.filter((t) => t.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED);
|
|
3444
|
+
const discovered = this.techniques.filter((t) => t.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED);
|
|
3430
3445
|
const lines = ["<learned-techniques>"];
|
|
3431
|
-
if (
|
|
3432
|
-
lines.push("
|
|
3433
|
-
for (const t of
|
|
3446
|
+
if (confirmed.length > 0) {
|
|
3447
|
+
lines.push("CONFIRMED (worked in this session):");
|
|
3448
|
+
for (const t of confirmed) {
|
|
3434
3449
|
lines.push(` \u2705 [${t.applicableTo.join(",")}] ${t.technique}`);
|
|
3435
3450
|
}
|
|
3436
3451
|
}
|
|
3437
|
-
if (
|
|
3438
|
-
lines.push(`DISCOVERED (${
|
|
3439
|
-
for (const t of
|
|
3452
|
+
if (discovered.length > 0) {
|
|
3453
|
+
lines.push(`DISCOVERED (${discovered.length} unverified):`);
|
|
3454
|
+
for (const t of discovered.slice(0, MEMORY_LIMITS.PROMPT_UNVERIFIED_TECHNIQUES)) {
|
|
3440
3455
|
lines.push(` \u{1F4A1} [${t.applicableTo.join(",")}] ${t.technique} (from: ${t.source})`);
|
|
3441
3456
|
}
|
|
3442
3457
|
}
|
|
@@ -3659,6 +3674,14 @@ var SharedState = class {
|
|
|
3659
3674
|
getFindingsBySeverity(severity) {
|
|
3660
3675
|
return this.data.findings.filter((f) => f.severity === severity);
|
|
3661
3676
|
}
|
|
3677
|
+
/** Returns findings with confidence >= threshold (default: CONFIRMED = 80) */
|
|
3678
|
+
getFindingsByConfidence(threshold = CONFIDENCE_THRESHOLDS.CONFIRMED) {
|
|
3679
|
+
return this.data.findings.filter((f) => f.confidence >= threshold);
|
|
3680
|
+
}
|
|
3681
|
+
/** True if confidence >= CONFIRMED (80) */
|
|
3682
|
+
isConfirmedFinding(finding) {
|
|
3683
|
+
return finding.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
3684
|
+
}
|
|
3662
3685
|
addLoot(loot) {
|
|
3663
3686
|
this.data.loot.push(loot);
|
|
3664
3687
|
}
|
|
@@ -5044,43 +5067,32 @@ Reason: ${reason}`
|
|
|
5044
5067
|
];
|
|
5045
5068
|
|
|
5046
5069
|
// src/shared/utils/finding-validator.ts
|
|
5047
|
-
var VALIDATION_THRESHOLDS = {
|
|
5048
|
-
/** Divisor for base confidence (N+ pattern matches = 100%) */
|
|
5049
|
-
CONFIDENCE_DIVISOR: 2,
|
|
5050
|
-
/** Penalty per false-positive indicator */
|
|
5051
|
-
FALSE_POSITIVE_PENALTY: 0.15,
|
|
5052
|
-
/** Confidence breakpoints for quality classification */
|
|
5053
|
-
QUALITY_STRONG: 0.8,
|
|
5054
|
-
QUALITY_MODERATE: 0.5,
|
|
5055
|
-
/** Minimum confidence for verification */
|
|
5056
|
-
VERIFICATION_MIN: 0.5
|
|
5057
|
-
};
|
|
5058
5070
|
var SUCCESS_PATTERNS = [
|
|
5059
|
-
//
|
|
5060
|
-
{ pattern: /uid
|
|
5061
|
-
{ pattern: /
|
|
5062
|
-
{ pattern: /
|
|
5063
|
-
|
|
5064
|
-
{ pattern: /\$ whoami\s*\n\s*root/, description: "root whoami output",
|
|
5065
|
-
{ pattern: /
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
{ pattern:
|
|
5069
|
-
{ pattern: /
|
|
5070
|
-
|
|
5071
|
-
{ pattern:
|
|
5072
|
-
{ pattern:
|
|
5073
|
-
{ pattern:
|
|
5074
|
-
//
|
|
5075
|
-
{ pattern:
|
|
5076
|
-
{ pattern: /
|
|
5077
|
-
{ pattern:
|
|
5078
|
-
|
|
5079
|
-
{ pattern: /
|
|
5080
|
-
{ pattern:
|
|
5081
|
-
//
|
|
5082
|
-
{ pattern:
|
|
5083
|
-
{ pattern: /
|
|
5071
|
+
// Absolute proof (100)
|
|
5072
|
+
{ pattern: /uid=0\([^)]+\)\s+gid=0/, description: "uid=0 (root shell confirmed)", score: 100 },
|
|
5073
|
+
{ pattern: /NT AUTHORITY\\SYSTEM/i, description: "Windows SYSTEM access", score: 100 },
|
|
5074
|
+
{ pattern: /meterpreter\s*>/, description: "Meterpreter session", score: 100 },
|
|
5075
|
+
// Shell / access confirmed (90-95)
|
|
5076
|
+
{ pattern: /\$ whoami\s*\n\s*root/, description: "root whoami output", score: 95 },
|
|
5077
|
+
{ pattern: /root:x:0:0/, description: "/etc/passwd root entry read", score: 90 },
|
|
5078
|
+
{ pattern: /uid=\d+\([^)]+\)\s+gid=\d+/, description: "Unix id command output", score: 90 },
|
|
5079
|
+
// Credential / sensitive data extracted (80-85)
|
|
5080
|
+
{ pattern: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/, description: "Private key exposed", score: 85 },
|
|
5081
|
+
{ pattern: /DB_PASSWORD|DATABASE_URL|SECRET_KEY/i, description: "Secret/credential in config", score: 80 },
|
|
5082
|
+
{ pattern: /\d+ rows? in set/i, description: "SQL query result returned", score: 80 },
|
|
5083
|
+
{ pattern: /mysql>|postgres[=#]|sqlite>/, description: "Database shell prompt", score: 80 },
|
|
5084
|
+
{ pattern: /password\s*[:=]\s*\S+/i, description: "Password in output", score: 80 },
|
|
5085
|
+
{ pattern: /\b[a-f0-9]{32}\b:\b[a-f0-9]{32}\b/, description: "Hash pair extracted", score: 80 },
|
|
5086
|
+
// Strong indicators (65-75)
|
|
5087
|
+
{ pattern: /-----BEGIN CERTIFICATE-----/, description: "Certificate exposed", score: 70 },
|
|
5088
|
+
{ pattern: /CREATE TABLE|INSERT INTO|SELECT \*/i, description: "SQL DDL/DML output", score: 70 },
|
|
5089
|
+
{ pattern: /Administrator/i, description: "Windows Administrator context", score: 70 },
|
|
5090
|
+
{ pattern: /Linux\s+\S+\s+\d+\.\d+/, description: "Linux uname output (RCE)", score: 65 },
|
|
5091
|
+
{ pattern: /Windows\s+(Server\s+)?\d{4}/i, description: "Windows systeminfo (RCE)", score: 65 },
|
|
5092
|
+
{ pattern: /\bwww-data\b/, description: "Web server user context", score: 65 },
|
|
5093
|
+
// Circumstantial evidence (50)
|
|
5094
|
+
{ pattern: /Nmap scan report for/, description: "Internal nmap scan (pivot)", score: 50 },
|
|
5095
|
+
{ pattern: /open\s+\w+\/\w+/, description: "Open port in scan output", score: 25 }
|
|
5084
5096
|
];
|
|
5085
5097
|
var FALSE_POSITIVE_PATTERNS = [
|
|
5086
5098
|
{ pattern: /connection refused/i, description: "Connection refused" },
|
|
@@ -5089,66 +5101,83 @@ var FALSE_POSITIVE_PATTERNS = [
|
|
|
5089
5101
|
{ pattern: /404 not found/i, description: "404 response" },
|
|
5090
5102
|
{ pattern: /401 unauthorized/i, description: "Unauthorized" },
|
|
5091
5103
|
{ pattern: /timeout|timed out/i, description: "Timeout" },
|
|
5092
|
-
{ pattern: /error:|exception:/i, description: "Error/Exception" }
|
|
5104
|
+
{ pattern: /error:|exception:/i, description: "Error/Exception header" }
|
|
5093
5105
|
];
|
|
5094
|
-
|
|
5106
|
+
var FALSE_POSITIVE_PENALTY = 15;
|
|
5107
|
+
function validateFinding(evidence) {
|
|
5095
5108
|
if (!evidence || evidence.length === 0) {
|
|
5096
5109
|
return {
|
|
5097
|
-
isVerified: false,
|
|
5098
5110
|
confidence: 0,
|
|
5099
|
-
|
|
5100
|
-
|
|
5111
|
+
evidenceQuality: "none",
|
|
5112
|
+
verificationNote: "No evidence provided \u2014 finding is unsubstantiated."
|
|
5101
5113
|
};
|
|
5102
5114
|
}
|
|
5103
|
-
const
|
|
5104
|
-
const flags = detectFlags(
|
|
5115
|
+
const combined = evidence.join("\n");
|
|
5116
|
+
const flags = detectFlags(combined);
|
|
5105
5117
|
if (flags.length > 0) {
|
|
5106
5118
|
return {
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
verificationNote: `CTF flag detected in evidence: ${flags[0]}
|
|
5110
|
-
evidenceQuality: "strong"
|
|
5119
|
+
confidence: 100,
|
|
5120
|
+
evidenceQuality: "confirmed",
|
|
5121
|
+
verificationNote: `CTF flag detected in evidence: ${flags[0]}`
|
|
5111
5122
|
};
|
|
5112
5123
|
}
|
|
5113
|
-
let
|
|
5114
|
-
const
|
|
5115
|
-
for (const { pattern, description,
|
|
5124
|
+
let maxScore = 0;
|
|
5125
|
+
const matched = [];
|
|
5126
|
+
for (const { pattern, description, score } of SUCCESS_PATTERNS) {
|
|
5116
5127
|
pattern.lastIndex = 0;
|
|
5117
|
-
if (pattern.test(
|
|
5118
|
-
|
|
5119
|
-
|
|
5128
|
+
if (pattern.test(combined)) {
|
|
5129
|
+
if (score > maxScore) maxScore = score;
|
|
5130
|
+
matched.push(`${description} (+${score})`);
|
|
5120
5131
|
}
|
|
5121
5132
|
}
|
|
5122
|
-
let
|
|
5123
|
-
|
|
5133
|
+
let fpCount = 0;
|
|
5134
|
+
const fpMatched = [];
|
|
5135
|
+
for (const { pattern, description } of FALSE_POSITIVE_PATTERNS) {
|
|
5124
5136
|
pattern.lastIndex = 0;
|
|
5125
|
-
if (pattern.test(
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
const
|
|
5131
|
-
const confidence = Math.max(0,
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
return
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5137
|
+
if (pattern.test(combined)) {
|
|
5138
|
+
fpCount++;
|
|
5139
|
+
fpMatched.push(description);
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
5142
|
+
const raw = maxScore - fpCount * FALSE_POSITIVE_PENALTY;
|
|
5143
|
+
const confidence = Math.max(0, Math.min(100, Math.round(raw)));
|
|
5144
|
+
const evidenceQuality = qualityFromScore(confidence);
|
|
5145
|
+
const note = buildNote(matched, fpMatched, confidence);
|
|
5146
|
+
return { confidence, evidenceQuality, verificationNote: note };
|
|
5147
|
+
}
|
|
5148
|
+
function qualityFromScore(score) {
|
|
5149
|
+
if (score >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
|
|
5150
|
+
if (score >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
|
|
5151
|
+
if (score >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
|
|
5152
|
+
return "none";
|
|
5153
|
+
}
|
|
5154
|
+
function buildNote(matched, fpMatched, confidence) {
|
|
5155
|
+
const parts = [];
|
|
5156
|
+
if (matched.length > 0) {
|
|
5157
|
+
parts.push(`Matched: ${matched.join(", ")}`);
|
|
5158
|
+
}
|
|
5159
|
+
if (fpMatched.length > 0) {
|
|
5160
|
+
parts.push(`FP penalties (${fpMatched.length}\xD7): ${fpMatched.join(", ")}`);
|
|
5161
|
+
}
|
|
5162
|
+
if (parts.length === 0) {
|
|
5163
|
+
parts.push("No recognized success patterns");
|
|
5164
|
+
}
|
|
5165
|
+
parts.push(`Confidence: ${confidence}/100`);
|
|
5166
|
+
return parts.join(" | ");
|
|
5145
5167
|
}
|
|
5146
5168
|
function formatValidation(result2) {
|
|
5147
|
-
const icon = result2.
|
|
5148
|
-
return `${icon}
|
|
5169
|
+
const icon = result2.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED ? "\u2705" : result2.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE ? "\u{1F536}" : result2.confidence >= CONFIDENCE_THRESHOLDS.POSSIBLE ? "\u26A0\uFE0F" : "\u2753";
|
|
5170
|
+
return `${icon} [${result2.confidence}/100] ${result2.evidenceQuality.toUpperCase()} | ${result2.verificationNote}`;
|
|
5149
5171
|
}
|
|
5150
5172
|
|
|
5151
5173
|
// src/engine/tools/pentest-target-tools.ts
|
|
5174
|
+
var CRACKABLE_LOOT_TYPES = /* @__PURE__ */ new Set([LOOT_TYPES.HASH]);
|
|
5175
|
+
var SPRAY_LOOT_TYPES = /* @__PURE__ */ new Set([
|
|
5176
|
+
LOOT_TYPES.CREDENTIAL,
|
|
5177
|
+
LOOT_TYPES.TOKEN,
|
|
5178
|
+
LOOT_TYPES.SSH_KEY,
|
|
5179
|
+
LOOT_TYPES.API_KEY
|
|
5180
|
+
]);
|
|
5152
5181
|
function isPortArray(value) {
|
|
5153
5182
|
if (!Array.isArray(value)) return false;
|
|
5154
5183
|
return value.every(
|
|
@@ -5162,13 +5191,13 @@ function isValidSeverity(value) {
|
|
|
5162
5191
|
return typeof value === "string" && Object.values(SEVERITIES).includes(value);
|
|
5163
5192
|
}
|
|
5164
5193
|
function parseSeverity(value) {
|
|
5165
|
-
return isValidSeverity(value) ? value :
|
|
5194
|
+
return isValidSeverity(value) ? value : SEVERITIES.MEDIUM;
|
|
5166
5195
|
}
|
|
5167
5196
|
function isValidLootType(value) {
|
|
5168
5197
|
return typeof value === "string" && Object.values(LOOT_TYPES).includes(value);
|
|
5169
5198
|
}
|
|
5170
5199
|
function parseLootType(value) {
|
|
5171
|
-
return isValidLootType(value) ? value :
|
|
5200
|
+
return isValidLootType(value) ? value : LOOT_TYPES.FILE;
|
|
5172
5201
|
}
|
|
5173
5202
|
function isValidAttackTactic(value) {
|
|
5174
5203
|
return typeof value === "string" && Object.values(ATTACK_TACTICS).includes(value);
|
|
@@ -5219,12 +5248,12 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5219
5248
|
if (!exists) {
|
|
5220
5249
|
existing.ports.push({
|
|
5221
5250
|
port: np.port,
|
|
5222
|
-
service: np.service ||
|
|
5251
|
+
service: np.service || DEFAULTS.UNKNOWN_SERVICE,
|
|
5223
5252
|
version: np.version,
|
|
5224
|
-
state: np.state ||
|
|
5253
|
+
state: np.state || DEFAULTS.PORT_STATE_OPEN,
|
|
5225
5254
|
notes: []
|
|
5226
5255
|
});
|
|
5227
|
-
state.attackGraph.addService(ip, np.port, np.service ||
|
|
5256
|
+
state.attackGraph.addService(ip, np.port, np.service || DEFAULTS.UNKNOWN_SERVICE, np.version);
|
|
5228
5257
|
}
|
|
5229
5258
|
}
|
|
5230
5259
|
if (p.hostname) existing.hostname = parseString(p.hostname);
|
|
@@ -5233,9 +5262,9 @@ The target will be tracked in SharedState and available for all agents.`,
|
|
|
5233
5262
|
}
|
|
5234
5263
|
const ports = parsePorts(p.ports).map((port) => ({
|
|
5235
5264
|
port: port.port,
|
|
5236
|
-
service: port.service ||
|
|
5265
|
+
service: port.service || DEFAULTS.UNKNOWN_SERVICE,
|
|
5237
5266
|
version: port.version,
|
|
5238
|
-
state: port.state ||
|
|
5267
|
+
state: port.state || DEFAULTS.PORT_STATE_OPEN,
|
|
5239
5268
|
notes: []
|
|
5240
5269
|
}));
|
|
5241
5270
|
state.addTarget({
|
|
@@ -5262,18 +5291,18 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
5262
5291
|
required: ["type", "host", "detail"],
|
|
5263
5292
|
execute: async (p) => {
|
|
5264
5293
|
const lootTypeStr = parseString(p.type);
|
|
5265
|
-
const crackableTypes = ["hash"];
|
|
5266
5294
|
const detail = parseString(p.detail);
|
|
5267
5295
|
const host = parseString(p.host);
|
|
5296
|
+
const isCrackable = CRACKABLE_LOOT_TYPES.has(lootTypeStr);
|
|
5268
5297
|
state.addLoot({
|
|
5269
5298
|
type: parseLootType(lootTypeStr),
|
|
5270
5299
|
host,
|
|
5271
5300
|
detail,
|
|
5272
5301
|
obtainedAt: Date.now(),
|
|
5273
|
-
isCrackable
|
|
5302
|
+
isCrackable,
|
|
5274
5303
|
isCracked: false
|
|
5275
5304
|
});
|
|
5276
|
-
if (
|
|
5305
|
+
if (SPRAY_LOOT_TYPES.has(lootTypeStr)) {
|
|
5277
5306
|
const parts = detail.split(":");
|
|
5278
5307
|
if (parts.length >= 2) {
|
|
5279
5308
|
state.attackGraph.addCredential(parts[0], parts.slice(1).join(":"), host);
|
|
@@ -5284,22 +5313,35 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
|
|
|
5284
5313
|
success: true,
|
|
5285
5314
|
output: `Loot recorded: [${lootTypeStr}] from ${host}
|
|
5286
5315
|
Detail: ${detail}
|
|
5287
|
-
` + (
|
|
5316
|
+
` + (isCrackable ? `This is crackable. Consider: hash_crack({ hashes: "${detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
5288
5317
|
};
|
|
5289
5318
|
}
|
|
5290
5319
|
},
|
|
5291
5320
|
{
|
|
5292
5321
|
name: TOOL_NAMES.ADD_FINDING,
|
|
5293
|
-
description: `
|
|
5294
|
-
|
|
5295
|
-
|
|
5322
|
+
description: `Record a security finding with confidence score.
|
|
5323
|
+
|
|
5324
|
+
ALWAYS provide: description (HOW exploited), evidence (actual command output), attackPattern (MITRE ATT&CK tactic).
|
|
5325
|
+
|
|
5326
|
+
confidence score (0-100) \u2014 technical verification level:
|
|
5327
|
+
100 = CTF flag captured, root shell confirmed (uid=0), NT AUTHORITY\\SYSTEM
|
|
5328
|
+
80 = DB query result, private key read, auth bypass proven, credential extracted
|
|
5329
|
+
75 = Stack trace / internal paths / suspicious error message
|
|
5330
|
+
65 = RCE circumstantial (uname output, www-data context)
|
|
5331
|
+
50 = CVE version match, unusual server response
|
|
5332
|
+
25 = Port open, service detected (unverified \u2014 needs further testing)
|
|
5333
|
+
0 = Pure speculation, no actual test performed
|
|
5334
|
+
|
|
5335
|
+
Omit confidence to let the system auto-calculate from evidence.
|
|
5336
|
+
Findings with confidence >= 80 appear as CONFIRMED in reports.`,
|
|
5296
5337
|
parameters: {
|
|
5297
|
-
title: { type: "string", description: 'Concise
|
|
5298
|
-
severity: { type: "string", description: "
|
|
5299
|
-
affected: { type: "array", items: { type: "string" }, description:
|
|
5300
|
-
description: { type: "string", description: "
|
|
5301
|
-
evidence: { type: "array", items: { type: "string" }, description:
|
|
5302
|
-
attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" }
|
|
5338
|
+
title: { type: "string", description: 'Concise title (e.g., "Path Traversal via /download endpoint")' },
|
|
5339
|
+
severity: { type: "string", description: "Business impact severity: critical, high, medium, low, info" },
|
|
5340
|
+
affected: { type: "array", items: { type: "string" }, description: "Affected host:port or URLs" },
|
|
5341
|
+
description: { type: "string", description: "What the vulnerability is, how you exploited it step-by-step, what access it gives, and the impact." },
|
|
5342
|
+
evidence: { type: "array", items: { type: "string" }, description: "Actual command outputs proving the finding. Copy real output here." },
|
|
5343
|
+
attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" },
|
|
5344
|
+
confidence: { type: "number", description: "Optional override (0-100). Omit to auto-calculate from evidence." }
|
|
5303
5345
|
},
|
|
5304
5346
|
required: ["title", "severity", "description", "evidence"],
|
|
5305
5347
|
execute: async (p) => {
|
|
@@ -5308,31 +5350,34 @@ Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
|
5308
5350
|
const severity = parseSeverity(p.severity);
|
|
5309
5351
|
const affected = parseStringArray(p.affected);
|
|
5310
5352
|
const description = parseString(p.description);
|
|
5311
|
-
const validation = validateFinding(evidence, severity);
|
|
5312
5353
|
const attackPattern = parseString(p.attackPattern);
|
|
5354
|
+
const validation = validateFinding(evidence);
|
|
5355
|
+
const rawOverride = p.confidence;
|
|
5356
|
+
const confidence = typeof rawOverride === "number" && rawOverride >= 0 && rawOverride <= 100 ? Math.round(rawOverride) : validation.confidence;
|
|
5313
5357
|
state.addFinding({
|
|
5314
5358
|
id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
|
|
5315
5359
|
title,
|
|
5316
5360
|
severity,
|
|
5361
|
+
confidence,
|
|
5317
5362
|
affected,
|
|
5318
5363
|
description,
|
|
5319
5364
|
evidence,
|
|
5320
|
-
isVerified: validation.isVerified,
|
|
5321
5365
|
remediation: "",
|
|
5322
5366
|
foundAt: Date.now(),
|
|
5323
5367
|
...attackPattern && isValidAttackTactic(attackPattern) ? { attackPattern } : {}
|
|
5324
5368
|
});
|
|
5325
|
-
const hasExploit =
|
|
5326
|
-
const target = affected[0] ||
|
|
5369
|
+
const hasExploit = confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
5370
|
+
const target = affected[0] || DEFAULTS.UNKNOWN_SERVICE;
|
|
5327
5371
|
state.attackGraph.addVulnerability(title, target, severity, hasExploit);
|
|
5372
|
+
const memoryEvent = confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED ? "tool_success" : "tool_failure";
|
|
5328
5373
|
state.episodicMemory.record(
|
|
5329
|
-
|
|
5374
|
+
memoryEvent,
|
|
5330
5375
|
`Finding: ${title} (${severity}) \u2014 ${formatValidation(validation)}`
|
|
5331
5376
|
);
|
|
5332
5377
|
return {
|
|
5333
5378
|
success: true,
|
|
5334
5379
|
output: `Added: ${title}
|
|
5335
|
-
${formatValidation(validation)}`
|
|
5380
|
+
${formatValidation(validation)}${rawOverride !== void 0 ? ` [confidence overridden to ${confidence}]` : ""}`
|
|
5336
5381
|
};
|
|
5337
5382
|
}
|
|
5338
5383
|
}
|
|
@@ -5385,6 +5430,7 @@ function isBrowserHeadless() {
|
|
|
5385
5430
|
var SEARCH_URL_PATTERN = {
|
|
5386
5431
|
GLM: "bigmodel.cn",
|
|
5387
5432
|
ZHIPU: "zhipuai",
|
|
5433
|
+
Z_AI: "z.ai",
|
|
5388
5434
|
BRAVE: "brave.com",
|
|
5389
5435
|
SERPER: "serper.dev"
|
|
5390
5436
|
};
|
|
@@ -6211,7 +6257,7 @@ async function webSearch(query, _engine) {
|
|
|
6211
6257
|
};
|
|
6212
6258
|
}
|
|
6213
6259
|
try {
|
|
6214
|
-
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
6260
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU) || apiUrl.includes(SEARCH_URL_PATTERN.Z_AI)) {
|
|
6215
6261
|
debugLog("search", "Using GLM search");
|
|
6216
6262
|
return await searchWithGLM(query, apiKey, apiUrl);
|
|
6217
6263
|
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
@@ -7164,7 +7210,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7164
7210
|
},
|
|
7165
7211
|
execute: async (p) => {
|
|
7166
7212
|
const { existsSync: existsSync12, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7167
|
-
const { join:
|
|
7213
|
+
const { join: join13 } = await import("path");
|
|
7168
7214
|
const category = p.category || "";
|
|
7169
7215
|
const search = p.search || "";
|
|
7170
7216
|
const minSize = p.min_size || 0;
|
|
@@ -7219,7 +7265,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7219
7265
|
}
|
|
7220
7266
|
for (const entry of entries) {
|
|
7221
7267
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
7222
|
-
const fullPath =
|
|
7268
|
+
const fullPath = join13(dirPath, entry.name);
|
|
7223
7269
|
if (entry.isDirectory()) {
|
|
7224
7270
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
7225
7271
|
continue;
|
|
@@ -9686,15 +9732,9 @@ function logLLM(message, data) {
|
|
|
9686
9732
|
debugLog("llm", message, data);
|
|
9687
9733
|
}
|
|
9688
9734
|
|
|
9689
|
-
// src/engine/orchestrator/orchestrator.ts
|
|
9690
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9691
|
-
import { dirname as dirname4, join as join8 } from "path";
|
|
9692
|
-
var __filename = fileURLToPath2(import.meta.url);
|
|
9693
|
-
var __dirname2 = dirname4(__filename);
|
|
9694
|
-
|
|
9695
9735
|
// src/engine/state-persistence.ts
|
|
9696
9736
|
import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
|
|
9697
|
-
import { join as
|
|
9737
|
+
import { join as join8 } from "path";
|
|
9698
9738
|
function saveState(state) {
|
|
9699
9739
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
9700
9740
|
ensureDirExists(sessionsDir);
|
|
@@ -9712,9 +9752,9 @@ function saveState(state) {
|
|
|
9712
9752
|
missionChecklist: state.getMissionChecklist()
|
|
9713
9753
|
};
|
|
9714
9754
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9715
|
-
const sessionFile =
|
|
9755
|
+
const sessionFile = join8(sessionsDir, `${sessionId}.json`);
|
|
9716
9756
|
writeFileSync6(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
9717
|
-
const latestFile =
|
|
9757
|
+
const latestFile = join8(sessionsDir, "latest.json");
|
|
9718
9758
|
writeFileSync6(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
9719
9759
|
pruneOldSessions(sessionsDir);
|
|
9720
9760
|
return sessionFile;
|
|
@@ -9723,8 +9763,8 @@ function pruneOldSessions(sessionsDir) {
|
|
|
9723
9763
|
try {
|
|
9724
9764
|
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => ({
|
|
9725
9765
|
name: f,
|
|
9726
|
-
path:
|
|
9727
|
-
mtime: statSync(
|
|
9766
|
+
path: join8(sessionsDir, f),
|
|
9767
|
+
mtime: statSync(join8(sessionsDir, f)).mtimeMs
|
|
9728
9768
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
9729
9769
|
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
9730
9770
|
for (const file of toDelete) {
|
|
@@ -9734,7 +9774,7 @@ function pruneOldSessions(sessionsDir) {
|
|
|
9734
9774
|
}
|
|
9735
9775
|
}
|
|
9736
9776
|
function loadState(state) {
|
|
9737
|
-
const latestFile =
|
|
9777
|
+
const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
|
|
9738
9778
|
if (!existsSync6(latestFile)) {
|
|
9739
9779
|
return false;
|
|
9740
9780
|
}
|
|
@@ -9752,7 +9792,11 @@ function loadState(state) {
|
|
|
9752
9792
|
state.addTarget(value);
|
|
9753
9793
|
}
|
|
9754
9794
|
for (const finding of snapshot.findings) {
|
|
9755
|
-
|
|
9795
|
+
const legacyFinding = finding;
|
|
9796
|
+
if (typeof legacyFinding.confidence !== "number") {
|
|
9797
|
+
legacyFinding.confidence = legacyFinding.isVerified === true ? 80 : 25;
|
|
9798
|
+
}
|
|
9799
|
+
state.addFinding(legacyFinding);
|
|
9756
9800
|
}
|
|
9757
9801
|
for (const loot of snapshot.loot) {
|
|
9758
9802
|
state.addLoot(loot);
|
|
@@ -10780,10 +10824,11 @@ RULES:
|
|
|
10780
10824
|
id: generateId(),
|
|
10781
10825
|
title,
|
|
10782
10826
|
severity: "high",
|
|
10827
|
+
// Auto-extracted findings are unverified signals — score POSSIBLE (25)
|
|
10828
|
+
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
10783
10829
|
affected: [],
|
|
10784
10830
|
description: `Auto-extracted by Analyst LLM: ${vector}`,
|
|
10785
10831
|
evidence: digestResult.memo.keyFindings.slice(0, 5),
|
|
10786
|
-
isVerified: false,
|
|
10787
10832
|
remediation: "",
|
|
10788
10833
|
foundAt: Date.now()
|
|
10789
10834
|
});
|
|
@@ -10863,8 +10908,8 @@ RULES:
|
|
|
10863
10908
|
|
|
10864
10909
|
// src/agents/prompt-builder.ts
|
|
10865
10910
|
import { readFileSync as readFileSync6, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
10866
|
-
import { join as
|
|
10867
|
-
import { fileURLToPath as
|
|
10911
|
+
import { join as join10, dirname as dirname4 } from "path";
|
|
10912
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10868
10913
|
|
|
10869
10914
|
// src/shared/constants/prompts.ts
|
|
10870
10915
|
var PROMPT_PATHS = {
|
|
@@ -10909,13 +10954,27 @@ var PROMPT_DEFAULTS = {
|
|
|
10909
10954
|
NO_SCOPE: "<scope>NO SCOPE DEFINED. STOP.</scope>",
|
|
10910
10955
|
EMPTY_TODO: "Create initial plan",
|
|
10911
10956
|
USER_CONTEXT: (context) => `
|
|
10912
|
-
|
|
10913
|
-
\u{1F6A8} CRITICAL: USER INPUT (YOUR OBJECTIVE) \u{1F6A8}
|
|
10914
|
-
=========================================
|
|
10957
|
+
<user-input>
|
|
10915
10958
|
"${context}"
|
|
10959
|
+
</user-input>
|
|
10960
|
+
|
|
10961
|
+
<intent-rules>
|
|
10962
|
+
ANALYZE the user's intent before acting. Classify into ONE:
|
|
10963
|
+
ABORT \u2192 stop current work, confirm with \`ask_user\`
|
|
10964
|
+
CORRECTION \u2192 adjust approach, continue
|
|
10965
|
+
INFORMATION \u2192 store and USE immediately (credentials, paths, hints)
|
|
10966
|
+
COMMAND \u2192 execute EXACTLY what was asked, nothing more
|
|
10967
|
+
TARGET_CHANGE \u2192 \`add_target\`, then begin testing
|
|
10968
|
+
GUIDANCE \u2192 acknowledge via \`ask_user\`, adjust strategy, continue
|
|
10969
|
+
STATUS_QUERY \u2192 report via \`ask_user\`, then RESUME previous work
|
|
10970
|
+
CONVERSATION \u2192 respond via \`ask_user\`, do NOT scan or attack
|
|
10916
10971
|
|
|
10917
|
-
|
|
10918
|
-
|
|
10972
|
+
RULES:
|
|
10973
|
+
- No target set and none provided \u2192 \`ask_user\` to request target.
|
|
10974
|
+
- Conversation or greeting \u2192 respond conversationally, do NOT attack.
|
|
10975
|
+
- Uncertain intent \u2192 ask for clarification with \`ask_user\`.
|
|
10976
|
+
- This is a collaborative tool. The user is your partner.
|
|
10977
|
+
</intent-rules>`
|
|
10919
10978
|
};
|
|
10920
10979
|
var PROMPT_CONFIG = {
|
|
10921
10980
|
ENCODING: "utf-8"
|
|
@@ -11124,7 +11183,7 @@ function getAttacksForService(service, port) {
|
|
|
11124
11183
|
|
|
11125
11184
|
// src/shared/utils/journal.ts
|
|
11126
11185
|
import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
|
|
11127
|
-
import { join as
|
|
11186
|
+
import { join as join9 } from "path";
|
|
11128
11187
|
var MAX_JOURNAL_ENTRIES = 50;
|
|
11129
11188
|
var MAX_OUTPUT_FILES = 30;
|
|
11130
11189
|
var TURN_PREFIX = "turn-";
|
|
@@ -11134,7 +11193,7 @@ function writeJournalEntry(entry) {
|
|
|
11134
11193
|
const journalDir = WORKSPACE.JOURNAL;
|
|
11135
11194
|
ensureDirExists(journalDir);
|
|
11136
11195
|
const padded = String(entry.turn).padStart(4, "0");
|
|
11137
|
-
const filePath =
|
|
11196
|
+
const filePath = join9(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11138
11197
|
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11139
11198
|
return filePath;
|
|
11140
11199
|
} catch (err) {
|
|
@@ -11144,7 +11203,7 @@ function writeJournalEntry(entry) {
|
|
|
11144
11203
|
}
|
|
11145
11204
|
function readJournalSummary() {
|
|
11146
11205
|
try {
|
|
11147
|
-
const summaryPath =
|
|
11206
|
+
const summaryPath = join9(WORKSPACE.JOURNAL, SUMMARY_FILE);
|
|
11148
11207
|
if (!existsSync8(summaryPath)) return "";
|
|
11149
11208
|
return readFileSync5(summaryPath, "utf-8");
|
|
11150
11209
|
} catch {
|
|
@@ -11159,7 +11218,7 @@ function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
|
11159
11218
|
const entries = [];
|
|
11160
11219
|
for (const file of files) {
|
|
11161
11220
|
try {
|
|
11162
|
-
const raw = readFileSync5(
|
|
11221
|
+
const raw = readFileSync5(join9(journalDir, file), "utf-8");
|
|
11163
11222
|
entries.push(JSON.parse(raw));
|
|
11164
11223
|
} catch {
|
|
11165
11224
|
}
|
|
@@ -11189,7 +11248,7 @@ function regenerateJournalSummary() {
|
|
|
11189
11248
|
const journalDir = WORKSPACE.JOURNAL;
|
|
11190
11249
|
ensureDirExists(journalDir);
|
|
11191
11250
|
const summary = buildSummaryFromEntries(entries);
|
|
11192
|
-
const summaryPath =
|
|
11251
|
+
const summaryPath = join9(journalDir, SUMMARY_FILE);
|
|
11193
11252
|
writeFileSync8(summaryPath, summary, "utf-8");
|
|
11194
11253
|
debugLog("general", "Journal summary regenerated", {
|
|
11195
11254
|
entries: entries.length,
|
|
@@ -11301,7 +11360,7 @@ function rotateJournalEntries() {
|
|
|
11301
11360
|
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11302
11361
|
for (const file of toDelete) {
|
|
11303
11362
|
try {
|
|
11304
|
-
unlinkSync5(
|
|
11363
|
+
unlinkSync5(join9(journalDir, file));
|
|
11305
11364
|
} catch {
|
|
11306
11365
|
}
|
|
11307
11366
|
}
|
|
@@ -11318,8 +11377,8 @@ function rotateOutputFiles() {
|
|
|
11318
11377
|
if (!existsSync8(outputDir)) return;
|
|
11319
11378
|
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11320
11379
|
name: f,
|
|
11321
|
-
path:
|
|
11322
|
-
mtime: statSync2(
|
|
11380
|
+
path: join9(outputDir, f),
|
|
11381
|
+
mtime: statSync2(join9(outputDir, f)).mtimeMs
|
|
11323
11382
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
11324
11383
|
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11325
11384
|
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
@@ -11345,7 +11404,7 @@ function rotateTurnRecords() {
|
|
|
11345
11404
|
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11346
11405
|
for (const file of toDelete) {
|
|
11347
11406
|
try {
|
|
11348
|
-
unlinkSync5(
|
|
11407
|
+
unlinkSync5(join9(turnsDir, file));
|
|
11349
11408
|
} catch {
|
|
11350
11409
|
}
|
|
11351
11410
|
}
|
|
@@ -11358,9 +11417,9 @@ function rotateTurnRecords() {
|
|
|
11358
11417
|
}
|
|
11359
11418
|
|
|
11360
11419
|
// src/agents/prompt-builder.ts
|
|
11361
|
-
var
|
|
11362
|
-
var PROMPTS_DIR =
|
|
11363
|
-
var TECHNIQUES_DIR =
|
|
11420
|
+
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
11421
|
+
var PROMPTS_DIR = join10(__dirname2, "prompts");
|
|
11422
|
+
var TECHNIQUES_DIR = join10(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
11364
11423
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
11365
11424
|
var PHASE_PROMPT_MAP = {
|
|
11366
11425
|
// Direct mappings — phase has its own prompt file
|
|
@@ -11493,7 +11552,7 @@ ${content}
|
|
|
11493
11552
|
* Load a prompt file from src/agents/prompts/
|
|
11494
11553
|
*/
|
|
11495
11554
|
loadPromptFile(filename) {
|
|
11496
|
-
const path2 =
|
|
11555
|
+
const path2 = join10(PROMPTS_DIR, filename);
|
|
11497
11556
|
return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
11498
11557
|
}
|
|
11499
11558
|
/**
|
|
@@ -11545,7 +11604,7 @@ ${content}
|
|
|
11545
11604
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11546
11605
|
const fragments = [];
|
|
11547
11606
|
for (const technique of priorityTechniques) {
|
|
11548
|
-
const filePath =
|
|
11607
|
+
const filePath = join10(TECHNIQUES_DIR, `${technique}.md`);
|
|
11549
11608
|
try {
|
|
11550
11609
|
if (!existsSync9(filePath)) continue;
|
|
11551
11610
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
@@ -11561,7 +11620,7 @@ ${content}
|
|
|
11561
11620
|
try {
|
|
11562
11621
|
const allFiles = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11563
11622
|
for (const file of allFiles) {
|
|
11564
|
-
const filePath =
|
|
11623
|
+
const filePath = join10(TECHNIQUES_DIR, file);
|
|
11565
11624
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11566
11625
|
if (content) {
|
|
11567
11626
|
const category = file.replace(".md", "");
|
|
@@ -11672,7 +11731,7 @@ ${lines.join("\n")}
|
|
|
11672
11731
|
*/
|
|
11673
11732
|
getJournalFragment() {
|
|
11674
11733
|
try {
|
|
11675
|
-
const summaryPath =
|
|
11734
|
+
const summaryPath = join10(WORKSPACE.TURNS, "summary.md");
|
|
11676
11735
|
if (existsSync9(summaryPath)) {
|
|
11677
11736
|
const summary2 = readFileSync6(summaryPath, "utf-8");
|
|
11678
11737
|
if (summary2.trim()) {
|
|
@@ -11703,10 +11762,10 @@ ${summary}
|
|
|
11703
11762
|
|
|
11704
11763
|
// src/agents/strategist.ts
|
|
11705
11764
|
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
11706
|
-
import { join as
|
|
11707
|
-
import { fileURLToPath as
|
|
11708
|
-
var
|
|
11709
|
-
var STRATEGIST_PROMPT_PATH =
|
|
11765
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
11766
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11767
|
+
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
11768
|
+
var STRATEGIST_PROMPT_PATH = join11(__dirname3, "prompts", "strategist-system.md");
|
|
11710
11769
|
var Strategist = class {
|
|
11711
11770
|
llm;
|
|
11712
11771
|
state;
|
|
@@ -11765,7 +11824,7 @@ var Strategist = class {
|
|
|
11765
11824
|
}
|
|
11766
11825
|
try {
|
|
11767
11826
|
let journalSummary = "";
|
|
11768
|
-
const summaryPath =
|
|
11827
|
+
const summaryPath = join11(WORKSPACE.TURNS, "summary.md");
|
|
11769
11828
|
if (existsSync10(summaryPath)) {
|
|
11770
11829
|
journalSummary = readFileSync7(summaryPath, "utf-8").trim();
|
|
11771
11830
|
}
|
|
@@ -11888,6 +11947,198 @@ Detect stalls (repeated failures, no progress) and force completely different at
|
|
|
11888
11947
|
Chain every finding: "If X works \u2192 immediately do Y \u2192 which enables Z."
|
|
11889
11948
|
Maximum 50 lines. Zero preamble. Direct imperatives only. Never repeat failed approaches.`;
|
|
11890
11949
|
|
|
11950
|
+
// src/agents/user-input-queue.ts
|
|
11951
|
+
var RECENT_MESSAGE_THRESHOLD_SECONDS = 5;
|
|
11952
|
+
var USER_INPUT_INTENT_PROMPT = `
|
|
11953
|
+
<user-message priority="INTERRUPT">
|
|
11954
|
+
\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\u2550
|
|
11955
|
+
\u26A1 USER MESSAGE \u2014 STOP. ANALYZE INTENT. THEN ACT.
|
|
11956
|
+
\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\u2550
|
|
11957
|
+
|
|
11958
|
+
The user sent the following message(s) WHILE you are actively working.
|
|
11959
|
+
This message takes PRECEDENCE over your current plan. Process it NOW.
|
|
11960
|
+
|
|
11961
|
+
<<USER_MESSAGES>>
|
|
11962
|
+
|
|
11963
|
+
\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\u2550
|
|
11964
|
+
\xA71. INTENT CLASSIFICATION (Chain-of-Thought \u2014 MANDATORY)
|
|
11965
|
+
\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\u2550
|
|
11966
|
+
|
|
11967
|
+
You MUST internally reason through this decision tree IN ORDER.
|
|
11968
|
+
Stop at the FIRST matching category \u2014 do NOT skip ahead.
|
|
11969
|
+
|
|
11970
|
+
\u250C\u2500 STEP 1: Is the user telling you to STOP or ABORT?
|
|
11971
|
+
\u2502 Signals: "stop", "abort", "cancel", "enough", "halt", "wait"
|
|
11972
|
+
\u2502 \u2192 CATEGORY: ABORT
|
|
11973
|
+
\u2502 \u2192 ACTION: Immediately stop current tool execution.
|
|
11974
|
+
\u2502 Use \`ask_user\` to confirm: "Understood, stopping. What would you like me to do next?"
|
|
11975
|
+
\u2502
|
|
11976
|
+
\u251C\u2500 STEP 2: Is the user CORRECTING you or saying you're WRONG?
|
|
11977
|
+
\u2502 Signals: "that's wrong", "don't do that", "stop doing X", "you already tried that",
|
|
11978
|
+
\u2502 "not that way", "I said X not Y", negative feedback on your actions
|
|
11979
|
+
\u2502 \u2192 CATEGORY: CORRECTION
|
|
11980
|
+
\u2502 \u2192 ACTION: 1. Acknowledge the error briefly.
|
|
11981
|
+
\u2502 2. State what you will do differently.
|
|
11982
|
+
\u2502 3. Resume work with the corrected approach.
|
|
11983
|
+
\u2502 Do NOT use \`ask_user\` unless clarification is needed.
|
|
11984
|
+
\u2502
|
|
11985
|
+
\u251C\u2500 STEP 3: Is the user providing ACTIONABLE INFORMATION?
|
|
11986
|
+
\u2502 Signals: credentials, passwords, usernames, file paths, endpoints, API keys,
|
|
11987
|
+
\u2502 ports, version numbers, IP addresses, hints about the target
|
|
11988
|
+
\u2502 \u2192 CATEGORY: INFORMATION
|
|
11989
|
+
\u2502 \u2192 ACTION: 1. Store with \`add_loot\` (if credentials) or remember contextually.
|
|
11990
|
+
\u2502 2. USE this information immediately in your next tool call.
|
|
11991
|
+
\u2502 3. Do NOT ask "should I use this?" \u2014 just use it.
|
|
11992
|
+
\u2502 Example: User says "password is admin123"
|
|
11993
|
+
\u2502 \u2192 Immediately try those credentials on all discovered login surfaces.
|
|
11994
|
+
\u2502
|
|
11995
|
+
\u251C\u2500 STEP 4: Is the user giving a DIRECT COMMAND to execute something?
|
|
11996
|
+
\u2502 Signals: imperative verb + specific action: "run X", "scan Y", "exploit Z",
|
|
11997
|
+
\u2502 "try X on Y", "use sqlmap", "brute force SSH"
|
|
11998
|
+
\u2502 \u2192 CATEGORY: COMMAND
|
|
11999
|
+
\u2502 \u2192 ACTION: Execute EXACTLY what the user asked. No more, no less.
|
|
12000
|
+
\u2502 Do NOT add extra scans or "while we're at it" actions.
|
|
12001
|
+
\u2502 Do NOT ask for confirmation \u2014 the user already decided.
|
|
12002
|
+
\u2502
|
|
12003
|
+
\u251C\u2500 STEP 5: Is the user changing the TARGET or SCOPE?
|
|
12004
|
+
\u2502 Signals: new IP/domain, "switch to", "add target", "remove target",
|
|
12005
|
+
\u2502 "also attack X", "change scope"
|
|
12006
|
+
\u2502 \u2192 CATEGORY: TARGET_CHANGE
|
|
12007
|
+
\u2502 \u2192 ACTION: 1. Call \`add_target\` with the new target.
|
|
12008
|
+
\u2502 2. Confirm briefly with \`ask_user\`: "Added [target]. Starting reconnaissance."
|
|
12009
|
+
\u2502 3. Begin testing the new target.
|
|
12010
|
+
\u2502
|
|
12011
|
+
\u251C\u2500 STEP 6: Is the user providing STRATEGIC GUIDANCE?
|
|
12012
|
+
\u2502 Signals: "focus on X", "prioritize Y", "skip Z", "try X approach",
|
|
12013
|
+
\u2502 "what about X?", "have you considered X?", tactical suggestions
|
|
12014
|
+
\u2502 \u2192 CATEGORY: GUIDANCE
|
|
12015
|
+
\u2502 \u2192 ACTION: 1. Acknowledge the guidance briefly via \`ask_user\`:
|
|
12016
|
+
\u2502 "Understood \u2014 adjusting strategy to focus on [X]."
|
|
12017
|
+
\u2502 2. Immediately adjust your approach and continue working.
|
|
12018
|
+
\u2502 3. The acknowledgment and next action should be in the SAME turn.
|
|
12019
|
+
\u2502
|
|
12020
|
+
\u251C\u2500 STEP 7: Is the user asking about PROGRESS or STATUS?
|
|
12021
|
+
\u2502 Signals: "what did you find?", "any progress?", "status?", "what are you doing?",
|
|
12022
|
+
\u2502 "show me", "report", "findings so far", "how's it going?"
|
|
12023
|
+
\u2502 \u2192 CATEGORY: STATUS_QUERY
|
|
12024
|
+
\u2502 \u2192 ACTION: Use \`ask_user\` to provide a structured status report:
|
|
12025
|
+
\u2502 FORMAT:
|
|
12026
|
+
\u2502 "\u{1F4CA} Status Report:
|
|
12027
|
+
\u2502 \u2022 Phase: [current phase]
|
|
12028
|
+
\u2502 \u2022 Targets: [count] ([list key ones])
|
|
12029
|
+
\u2502 \u2022 Key Findings: [count] ([summarize top findings])
|
|
12030
|
+
\u2502 \u2022 Current Action: [what you were doing]
|
|
12031
|
+
\u2502 \u2022 Next Steps: [what you plan to do next]"
|
|
12032
|
+
\u2502 Then RESUME your previous work \u2014 do NOT stop after reporting.
|
|
12033
|
+
\u2502
|
|
12034
|
+
\u2514\u2500 STEP 8: Everything else \u2192 CONVERSATION
|
|
12035
|
+
Signals: greetings, questions, discussions, explanations, opinions,
|
|
12036
|
+
casual talk, "hello", "how does X work?", "explain Y"
|
|
12037
|
+
\u2192 CATEGORY: CONVERSATION
|
|
12038
|
+
\u2192 ACTION: Use \`ask_user\` to respond naturally and conversationally.
|
|
12039
|
+
Answer questions with your knowledge.
|
|
12040
|
+
Then ask if they want you to continue with the current task.
|
|
12041
|
+
Do NOT start any scans or attacks.
|
|
12042
|
+
|
|
12043
|
+
\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\u2550
|
|
12044
|
+
\xA72. MULTI-MESSAGE RESOLUTION
|
|
12045
|
+
\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\u2550
|
|
12046
|
+
|
|
12047
|
+
If multiple user messages are queued, process them as follows:
|
|
12048
|
+
1. Read ALL messages first to understand the full context.
|
|
12049
|
+
2. Later messages may OVERRIDE or CLARIFY earlier ones.
|
|
12050
|
+
Example: [1] "try brute force" \u2192 [2] "actually, skip brute force, try SQLi"
|
|
12051
|
+
\u2192 Only execute the SQLi instruction (message 2 overrides message 1).
|
|
12052
|
+
3. If messages are independent, process the HIGHEST PRIORITY category first
|
|
12053
|
+
(ABORT > CORRECTION > INFORMATION > COMMAND > TARGET > GUIDANCE > STATUS > CONVERSATION).
|
|
12054
|
+
4. Acknowledge all messages but act on the most recent directive.
|
|
12055
|
+
|
|
12056
|
+
\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\u2550
|
|
12057
|
+
\xA73. WORK RESUMPTION RULES
|
|
12058
|
+
\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\u2550
|
|
12059
|
+
|
|
12060
|
+
After handling the user's message, you MUST resume productive work:
|
|
12061
|
+
|
|
12062
|
+
\u251C\u2500 ABORT/CONVERSATION \u2192 Wait for next user instruction. Do NOT auto-resume.
|
|
12063
|
+
\u251C\u2500 STATUS_QUERY \u2192 Report status, then RESUME previous work in the same turn.
|
|
12064
|
+
\u251C\u2500 GUIDANCE/CORRECTION \u2192 Adjust plan, then CONTINUE with modified approach.
|
|
12065
|
+
\u251C\u2500 INFORMATION \u2192 USE the information immediately in your next action.
|
|
12066
|
+
\u251C\u2500 COMMAND \u2192 Execute the command. After completion, resume prior work.
|
|
12067
|
+
\u251C\u2500 TARGET_CHANGE \u2192 Switch to new target, begin fresh workflow.
|
|
12068
|
+
|
|
12069
|
+
KEY PRINCIPLE: Never leave a turn empty-handed.
|
|
12070
|
+
If you used \`ask_user\` to respond, you may ALSO call other tools in the same turn
|
|
12071
|
+
(except for ABORT and CONVERSATION categories).
|
|
12072
|
+
|
|
12073
|
+
\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\u2550
|
|
12074
|
+
\xA74. ANTI-PATTERNS \u2014 NEVER DO THESE
|
|
12075
|
+
\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\u2550
|
|
12076
|
+
|
|
12077
|
+
\u251C\u2500 \u274C Ignore the user's message and continue your previous plan
|
|
12078
|
+
\u251C\u2500 \u274C Start scanning/attacking after a greeting
|
|
12079
|
+
\u251C\u2500 \u274C Ask "should I use this?" when the user provides credentials \u2192 JUST USE THEM
|
|
12080
|
+
\u251C\u2500 \u274C Respond with only text (no tool call) \u2014 always use \`ask_user\` for responses
|
|
12081
|
+
\u251C\u2500 \u274C Stop all work after answering a status query \u2014 RESUME immediately
|
|
12082
|
+
\u251C\u2500 \u274C Add extra actions the user didn't ask for when handling a COMMAND
|
|
12083
|
+
\u251C\u2500 \u274C Repeat the same failed approach after a CORRECTION
|
|
12084
|
+
\u2514\u2500 \u274C Treat every input as an attack command \u2014 MOST inputs are collaborative
|
|
12085
|
+
</user-message>`;
|
|
12086
|
+
var UserInputQueue = class {
|
|
12087
|
+
queue = [];
|
|
12088
|
+
/**
|
|
12089
|
+
* Add a user input to the queue.
|
|
12090
|
+
* Called from TUI when user submits text during active agent processing.
|
|
12091
|
+
*/
|
|
12092
|
+
enqueue(text) {
|
|
12093
|
+
this.queue.push({
|
|
12094
|
+
text,
|
|
12095
|
+
timestamp: Date.now()
|
|
12096
|
+
});
|
|
12097
|
+
}
|
|
12098
|
+
/**
|
|
12099
|
+
* Check if there are pending user inputs.
|
|
12100
|
+
*/
|
|
12101
|
+
hasPending() {
|
|
12102
|
+
return this.queue.length > 0;
|
|
12103
|
+
}
|
|
12104
|
+
/**
|
|
12105
|
+
* Get the count of pending inputs.
|
|
12106
|
+
*/
|
|
12107
|
+
pendingCount() {
|
|
12108
|
+
return this.queue.length;
|
|
12109
|
+
}
|
|
12110
|
+
/**
|
|
12111
|
+
* Drain all queued inputs and format them with intent analysis prompt.
|
|
12112
|
+
* Returns null if queue is empty.
|
|
12113
|
+
* Clears the queue after draining.
|
|
12114
|
+
*/
|
|
12115
|
+
drainAndFormat() {
|
|
12116
|
+
if (this.queue.length === 0) return null;
|
|
12117
|
+
const messages = [...this.queue];
|
|
12118
|
+
this.queue = [];
|
|
12119
|
+
const formattedMessages = messages.map((m, i) => {
|
|
12120
|
+
const timeAgo = Math.round((Date.now() - m.timestamp) / 1e3);
|
|
12121
|
+
const timeLabel = timeAgo < RECENT_MESSAGE_THRESHOLD_SECONDS ? "just now" : `${timeAgo}s ago`;
|
|
12122
|
+
return `[${i + 1}] (${timeLabel}) "${m.text}"`;
|
|
12123
|
+
}).join("\n");
|
|
12124
|
+
return USER_INPUT_INTENT_PROMPT.replace("<<USER_MESSAGES>>", formattedMessages);
|
|
12125
|
+
}
|
|
12126
|
+
/**
|
|
12127
|
+
* Peek at the queue without draining.
|
|
12128
|
+
* Useful for diagnostics or TUI display.
|
|
12129
|
+
*/
|
|
12130
|
+
peek() {
|
|
12131
|
+
return this.queue;
|
|
12132
|
+
}
|
|
12133
|
+
/**
|
|
12134
|
+
* Clear the queue without processing.
|
|
12135
|
+
* Used during /clear or abort.
|
|
12136
|
+
*/
|
|
12137
|
+
clear() {
|
|
12138
|
+
this.queue = [];
|
|
12139
|
+
}
|
|
12140
|
+
};
|
|
12141
|
+
|
|
11891
12142
|
// src/shared/utils/turn-record.ts
|
|
11892
12143
|
function formatTurnRecord(input) {
|
|
11893
12144
|
const { turn, timestamp, phase, tools, memo: memo6, reflection } = input;
|
|
@@ -11975,7 +12226,7 @@ function formatReflectionInput(input) {
|
|
|
11975
12226
|
|
|
11976
12227
|
// src/agents/main-agent.ts
|
|
11977
12228
|
import { writeFileSync as writeFileSync9, existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
11978
|
-
import { join as
|
|
12229
|
+
import { join as join12 } from "path";
|
|
11979
12230
|
var MainAgent = class extends CoreAgent {
|
|
11980
12231
|
promptBuilder;
|
|
11981
12232
|
strategist;
|
|
@@ -11984,6 +12235,11 @@ var MainAgent = class extends CoreAgent {
|
|
|
11984
12235
|
userInput = "";
|
|
11985
12236
|
/** Monotonic turn counter for journal entries */
|
|
11986
12237
|
turnCounter = 0;
|
|
12238
|
+
/**
|
|
12239
|
+
* Queue for user inputs received during agent execution.
|
|
12240
|
+
* Inputs are drained and injected at iteration boundaries.
|
|
12241
|
+
*/
|
|
12242
|
+
userInputQueue = new UserInputQueue();
|
|
11987
12243
|
constructor(state, events, toolRegistry, approvalGate, scopeGuard) {
|
|
11988
12244
|
super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
|
|
11989
12245
|
this.approvalGate = approvalGate;
|
|
@@ -12024,6 +12280,15 @@ var MainAgent = class extends CoreAgent {
|
|
|
12024
12280
|
if (this.turnCounter === 0) {
|
|
12025
12281
|
this.turnCounter = getNextTurnNumber();
|
|
12026
12282
|
}
|
|
12283
|
+
if (this.userInputQueue.hasPending()) {
|
|
12284
|
+
const userMessage = this.userInputQueue.drainAndFormat();
|
|
12285
|
+
if (userMessage) {
|
|
12286
|
+
messages.push({
|
|
12287
|
+
role: "user",
|
|
12288
|
+
content: userMessage
|
|
12289
|
+
});
|
|
12290
|
+
}
|
|
12291
|
+
}
|
|
12027
12292
|
this.turnToolJournal = [];
|
|
12028
12293
|
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
12029
12294
|
this.turnReflections = [];
|
|
@@ -12083,7 +12348,7 @@ ${extraction.content.trim()}
|
|
|
12083
12348
|
ensureDirExists(WORKSPACE.TURNS);
|
|
12084
12349
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
12085
12350
|
const turnFileName = `turn-${String(this.turnCounter).padStart(4, "0")}_${ts}.md`;
|
|
12086
|
-
const turnPath =
|
|
12351
|
+
const turnPath = join12(WORKSPACE.TURNS, turnFileName);
|
|
12087
12352
|
const turnContent = formatTurnRecord({
|
|
12088
12353
|
turn: this.turnCounter,
|
|
12089
12354
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12096,7 +12361,7 @@ ${extraction.content.trim()}
|
|
|
12096
12361
|
} catch {
|
|
12097
12362
|
}
|
|
12098
12363
|
try {
|
|
12099
|
-
const summaryPath =
|
|
12364
|
+
const summaryPath = join12(WORKSPACE.TURNS, "summary.md");
|
|
12100
12365
|
const existingSummary = existsSync11(summaryPath) ? readFileSync8(summaryPath, "utf-8") : "";
|
|
12101
12366
|
const turnData = formatTurnRecord({
|
|
12102
12367
|
turn: this.turnCounter,
|
|
@@ -12213,6 +12478,7 @@ ${turnData}`
|
|
|
12213
12478
|
});
|
|
12214
12479
|
this.state.reset();
|
|
12215
12480
|
this.userInput = "";
|
|
12481
|
+
this.userInputQueue.clear();
|
|
12216
12482
|
this.strategist.reset();
|
|
12217
12483
|
return clearWorkspace();
|
|
12218
12484
|
}
|
|
@@ -12239,6 +12505,20 @@ ${turnData}`
|
|
|
12239
12505
|
getStrategist() {
|
|
12240
12506
|
return this.strategist;
|
|
12241
12507
|
}
|
|
12508
|
+
/**
|
|
12509
|
+
* Enqueue a user input for processing at the next iteration boundary.
|
|
12510
|
+
* Called from TUI when user sends a message while agent is actively processing.
|
|
12511
|
+
*/
|
|
12512
|
+
enqueueUserInput(text) {
|
|
12513
|
+
this.userInputQueue.enqueue(text);
|
|
12514
|
+
}
|
|
12515
|
+
/**
|
|
12516
|
+
* Check if there are pending user inputs in the queue.
|
|
12517
|
+
* Useful for TUI to show pending input indicator.
|
|
12518
|
+
*/
|
|
12519
|
+
hasPendingUserInput() {
|
|
12520
|
+
return this.userInputQueue.hasPending();
|
|
12521
|
+
}
|
|
12242
12522
|
};
|
|
12243
12523
|
|
|
12244
12524
|
// src/agents/factory.ts
|
|
@@ -12719,7 +12999,7 @@ ${firstLine}`);
|
|
|
12719
12999
|
setInputHandler((p) => {
|
|
12720
13000
|
return new Promise((resolve) => {
|
|
12721
13001
|
const isPassword = /password|passphrase/i.test(p);
|
|
12722
|
-
const inputType = /sudo/i.test(p) ?
|
|
13002
|
+
const inputType = /sudo/i.test(p) ? INPUT_TYPES.SUDO_PASSWORD : isPassword ? INPUT_TYPES.PASSWORD : INPUT_TYPES.TEXT;
|
|
12723
13003
|
setInputRequest({
|
|
12724
13004
|
status: "active",
|
|
12725
13005
|
prompt: p.trim(),
|
|
@@ -12731,8 +13011,7 @@ ${firstLine}`);
|
|
|
12731
13011
|
});
|
|
12732
13012
|
setCredentialHandler((request) => {
|
|
12733
13013
|
return new Promise((resolve) => {
|
|
12734
|
-
const
|
|
12735
|
-
const isPassword = hiddenTypes.includes(request.type);
|
|
13014
|
+
const isPassword = SENSITIVE_INPUT_TYPES.includes(request.type);
|
|
12736
13015
|
const displayPrompt = buildCredentialPrompt(request);
|
|
12737
13016
|
setInputRequest({
|
|
12738
13017
|
status: "active",
|
|
@@ -13247,7 +13526,7 @@ import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "r
|
|
|
13247
13526
|
import { Text as Text3 } from "ink";
|
|
13248
13527
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
13249
13528
|
var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
|
|
13250
|
-
var INTERVAL =
|
|
13529
|
+
var INTERVAL = 500;
|
|
13251
13530
|
var MusicSpinner = memo2(({ color }) => {
|
|
13252
13531
|
const [index, setIndex] = useState3(0);
|
|
13253
13532
|
useEffect3(() => {
|
|
@@ -13276,8 +13555,8 @@ var StatusDisplay = memo3(({
|
|
|
13276
13555
|
const isThinkingStatus = currentStatus.startsWith("Reasoning");
|
|
13277
13556
|
const statusLines = currentStatus ? currentStatus.split("\n").filter(Boolean) : [];
|
|
13278
13557
|
const statusMain = statusLines[0] || "Processing...";
|
|
13279
|
-
|
|
13280
|
-
|
|
13558
|
+
if (retryState.status === "retrying") {
|
|
13559
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13281
13560
|
/* @__PURE__ */ jsx4(Text4, { color: THEME.yellow, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.yellow }) }),
|
|
13282
13561
|
/* @__PURE__ */ jsxs3(Text4, { color: THEME.yellow, bold: true, children: [
|
|
13283
13562
|
" \u27F3 Retry #",
|
|
@@ -13291,8 +13570,10 @@ var StatusDisplay = memo3(({
|
|
|
13291
13570
|
"s \xB7 ",
|
|
13292
13571
|
truncateError(retryState.error)
|
|
13293
13572
|
] })
|
|
13294
|
-
] })
|
|
13295
|
-
|
|
13573
|
+
] });
|
|
13574
|
+
}
|
|
13575
|
+
if (isProcessing) {
|
|
13576
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
13296
13577
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13297
13578
|
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13298
13579
|
/* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
|
|
@@ -13312,8 +13593,9 @@ var StatusDisplay = memo3(({
|
|
|
13312
13593
|
"\u2502 ",
|
|
13313
13594
|
statusLines.slice(1).join(" ")
|
|
13314
13595
|
] }) })
|
|
13315
|
-
] })
|
|
13316
|
-
|
|
13596
|
+
] });
|
|
13597
|
+
}
|
|
13598
|
+
return /* @__PURE__ */ jsx4(Box3, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) });
|
|
13317
13599
|
});
|
|
13318
13600
|
|
|
13319
13601
|
// src/platform/tui/components/ChatInput.tsx
|
|
@@ -13362,17 +13644,16 @@ var ChatInput = memo4(({
|
|
|
13362
13644
|
}
|
|
13363
13645
|
}, []));
|
|
13364
13646
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
13365
|
-
/* @__PURE__ */ jsx5(
|
|
13647
|
+
showPreview && /* @__PURE__ */ jsx5(
|
|
13366
13648
|
Box4,
|
|
13367
13649
|
{
|
|
13368
13650
|
flexDirection: "column",
|
|
13369
|
-
borderStyle:
|
|
13370
|
-
borderColor:
|
|
13371
|
-
paddingX:
|
|
13651
|
+
borderStyle: "single",
|
|
13652
|
+
borderColor: THEME.border.default,
|
|
13653
|
+
paddingX: 1,
|
|
13372
13654
|
marginBottom: 0,
|
|
13373
|
-
height: showPreview ? void 0 : 0,
|
|
13374
13655
|
overflowX: "hidden",
|
|
13375
|
-
children:
|
|
13656
|
+
children: suggestions.map((cmd, i) => {
|
|
13376
13657
|
const isFirst = i === 0;
|
|
13377
13658
|
const nameColor = isFirst ? THEME.white : THEME.gray;
|
|
13378
13659
|
const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
|
|
@@ -13581,62 +13862,60 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
13581
13862
|
executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
13582
13863
|
break;
|
|
13583
13864
|
case UI_COMMANDS.FINDINGS:
|
|
13584
|
-
case UI_COMMANDS.FINDINGS_SHORT:
|
|
13865
|
+
case UI_COMMANDS.FINDINGS_SHORT: {
|
|
13585
13866
|
const findings = agent.getState().getFindings();
|
|
13586
13867
|
if (!findings.length) {
|
|
13587
13868
|
addMessage("system", "No findings.");
|
|
13588
13869
|
break;
|
|
13589
13870
|
}
|
|
13590
|
-
const
|
|
13591
|
-
const
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13871
|
+
const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
|
|
13872
|
+
const confIcon = (c) => {
|
|
13873
|
+
if (c >= 100) return "\u{1F534}";
|
|
13874
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
|
|
13875
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
|
|
13876
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
|
|
13877
|
+
return "\u26AA";
|
|
13878
|
+
};
|
|
13879
|
+
const confLabel = (c) => {
|
|
13880
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
|
|
13881
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
|
|
13882
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
|
|
13883
|
+
return "speculative";
|
|
13597
13884
|
};
|
|
13598
|
-
const grouped = {};
|
|
13599
|
-
for (const f of findings) {
|
|
13600
|
-
const sev = f.severity.toLowerCase();
|
|
13601
|
-
if (!grouped[sev]) grouped[sev] = [];
|
|
13602
|
-
grouped[sev].push(f);
|
|
13603
|
-
}
|
|
13604
13885
|
const findingLines = [];
|
|
13605
|
-
const
|
|
13606
|
-
|
|
13886
|
+
const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
13887
|
+
const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
13888
|
+
const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
13889
|
+
findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 \u{1F534}\u{1F7E0} confirmed:${nConfirmed} \u{1F7E1} probable:${nProbable} \u{1F7E2}\u26AA possible:${nPossible} \u2500\u2500\u2500`);
|
|
13607
13890
|
findingLines.push("");
|
|
13608
|
-
|
|
13609
|
-
const
|
|
13610
|
-
|
|
13611
|
-
const
|
|
13612
|
-
|
|
13891
|
+
sorted.forEach((f, i) => {
|
|
13892
|
+
const icon = confIcon(f.confidence);
|
|
13893
|
+
const label = confLabel(f.confidence);
|
|
13894
|
+
const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
|
|
13895
|
+
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
13896
|
+
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
13897
|
+
findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
|
|
13898
|
+
findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
|
|
13899
|
+
if (f.affected.length > 0) {
|
|
13900
|
+
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
13901
|
+
}
|
|
13902
|
+
if (f.description) {
|
|
13903
|
+
findingLines.push(` ${f.description}`);
|
|
13904
|
+
}
|
|
13905
|
+
if (f.evidence.length > 0) {
|
|
13906
|
+
findingLines.push(` Evidence:`);
|
|
13907
|
+
f.evidence.forEach((e) => {
|
|
13908
|
+
findingLines.push(` \u25B8 ${e}`);
|
|
13909
|
+
});
|
|
13910
|
+
}
|
|
13911
|
+
if (f.remediation) {
|
|
13912
|
+
findingLines.push(` Fix: ${f.remediation}`);
|
|
13913
|
+
}
|
|
13613
13914
|
findingLines.push("");
|
|
13614
|
-
|
|
13615
|
-
const verified = f.isVerified ? `\u2713 Verified` : `? Unverified`;
|
|
13616
|
-
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
13617
|
-
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
13618
|
-
findingLines.push(` [${i + 1}] ${f.title}`);
|
|
13619
|
-
findingLines.push(` ${verified}${atk}${cat}`);
|
|
13620
|
-
if (f.affected.length > 0) {
|
|
13621
|
-
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
13622
|
-
}
|
|
13623
|
-
if (f.description) {
|
|
13624
|
-
findingLines.push(` ${f.description}`);
|
|
13625
|
-
}
|
|
13626
|
-
if (f.evidence.length > 0) {
|
|
13627
|
-
findingLines.push(` Evidence:`);
|
|
13628
|
-
f.evidence.forEach((e) => {
|
|
13629
|
-
findingLines.push(` \u25B8 ${e}`);
|
|
13630
|
-
});
|
|
13631
|
-
}
|
|
13632
|
-
if (f.remediation) {
|
|
13633
|
-
findingLines.push(` Fix: ${f.remediation}`);
|
|
13634
|
-
}
|
|
13635
|
-
findingLines.push("");
|
|
13636
|
-
});
|
|
13637
|
-
}
|
|
13915
|
+
});
|
|
13638
13916
|
addMessage("system", findingLines.join("\n"));
|
|
13639
13917
|
break;
|
|
13918
|
+
}
|
|
13640
13919
|
case UI_COMMANDS.ASSETS:
|
|
13641
13920
|
case UI_COMMANDS.ASSETS_SHORT:
|
|
13642
13921
|
addMessage("status", formatInlineStatus());
|
|
@@ -13693,10 +13972,13 @@ ${procData.stdout || "(no output)"}
|
|
|
13693
13972
|
if (trimmed.startsWith("/")) {
|
|
13694
13973
|
const [cmd, ...args] = trimmed.slice(1).split(" ");
|
|
13695
13974
|
await handleCommand(cmd, args);
|
|
13975
|
+
} else if (isProcessingRef.current) {
|
|
13976
|
+
agent.enqueueUserInput(trimmed);
|
|
13977
|
+
addMessage("system", "\u{1F4AC} Message queued \u2014 will be processed at next iteration");
|
|
13696
13978
|
} else {
|
|
13697
13979
|
await executeTask(trimmed);
|
|
13698
13980
|
}
|
|
13699
|
-
}, [addMessage, executeTask, handleCommand]);
|
|
13981
|
+
}, [agent, addMessage, executeTask, handleCommand]);
|
|
13700
13982
|
const handleSecretSubmit = useCallback4((value) => {
|
|
13701
13983
|
const ir = inputRequestRef.current;
|
|
13702
13984
|
if (ir.status !== "active") return;
|
|
@@ -13745,7 +14027,7 @@ ${procData.stdout || "(no output)"}
|
|
|
13745
14027
|
};
|
|
13746
14028
|
}, [handleCtrlC]);
|
|
13747
14029
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
13748
|
-
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column",
|
|
14030
|
+
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
13749
14031
|
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
13750
14032
|
/* @__PURE__ */ jsx7(
|
|
13751
14033
|
StatusDisplay,
|