pentesting 0.73.2 → 0.73.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/agent-tool-JEFUBDZE.js +989 -0
- package/dist/{chunk-BGEXGHPB.js → chunk-BKWCGMSV.js} +879 -427
- package/dist/{chunk-YFDJI3GO.js → chunk-GLO6TOJN.js} +2 -0
- package/dist/{chunk-KBJPZDIL.js → chunk-UB7RW6LM.js} +267 -153
- package/dist/main.js +1377 -196
- package/dist/{persistence-VFIOGTRC.js → persistence-2WKQHGOL.js} +2 -2
- package/dist/{process-registry-GSHEX2LT.js → process-registry-QIW7ZIUT.js} +1 -1
- package/dist/prompts/main-agent.md +35 -1
- package/dist/prompts/strategist-system.md +34 -0
- package/package.json +1 -1
- package/dist/agent-tool-HYQGTZC4.js +0 -256
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CategorizedToolRegistry,
|
|
3
|
+
CoreAgent,
|
|
4
|
+
buildShellSupervisorLifecycleSection,
|
|
5
|
+
createContextExtractor,
|
|
6
|
+
getLLMClient,
|
|
7
|
+
getShellSupervisorLifecycleSnapshot
|
|
8
|
+
} from "./chunk-BKWCGMSV.js";
|
|
9
|
+
import {
|
|
10
|
+
AGENT_ROLES,
|
|
11
|
+
EVENT_TYPES,
|
|
12
|
+
LLM_ROLES,
|
|
13
|
+
TOOL_NAMES,
|
|
14
|
+
getProcessOutput,
|
|
15
|
+
listBackgroundProcesses
|
|
16
|
+
} from "./chunk-UB7RW6LM.js";
|
|
17
|
+
import {
|
|
18
|
+
DETECTION_PATTERNS,
|
|
19
|
+
PROCESS_EVENTS,
|
|
20
|
+
PROCESS_ROLES,
|
|
21
|
+
getActiveProcessSummary,
|
|
22
|
+
getProcessEventLog
|
|
23
|
+
} from "./chunk-GLO6TOJN.js";
|
|
24
|
+
|
|
25
|
+
// src/engine/agent-tool/completion-box.ts
|
|
26
|
+
function createCompletionBox() {
|
|
27
|
+
return { done: false, result: null };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/engine/agent-tool/task-complete.ts
|
|
31
|
+
function createTaskCompleteTool(completion) {
|
|
32
|
+
return {
|
|
33
|
+
name: TOOL_NAMES.TASK_COMPLETE,
|
|
34
|
+
description: `Signal task completion. Call this when the delegated task is done.
|
|
35
|
+
Include all findings and loot discovered during the task.
|
|
36
|
+
Use status: 'success' if goal achieved, 'partial' if partially done, 'failed' if blocked.
|
|
37
|
+
Use 'waiting' or 'running' when the task created a durable operational asset that should be resumed later.`,
|
|
38
|
+
parameters: {
|
|
39
|
+
status: {
|
|
40
|
+
type: "string",
|
|
41
|
+
enum: ["success", "partial", "failed", "waiting", "running"],
|
|
42
|
+
description: "Task completion status"
|
|
43
|
+
},
|
|
44
|
+
summary: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "What was accomplished (or why it failed)"
|
|
47
|
+
},
|
|
48
|
+
tried: {
|
|
49
|
+
type: "array",
|
|
50
|
+
items: { type: "string" },
|
|
51
|
+
description: "Approaches attempted during the task"
|
|
52
|
+
},
|
|
53
|
+
findings: {
|
|
54
|
+
type: "array",
|
|
55
|
+
items: { type: "string" },
|
|
56
|
+
description: "Security findings discovered (summary for main loop)"
|
|
57
|
+
},
|
|
58
|
+
loot: {
|
|
59
|
+
type: "array",
|
|
60
|
+
items: { type: "string" },
|
|
61
|
+
description: "Credentials, flags, or sensitive data obtained"
|
|
62
|
+
},
|
|
63
|
+
sessions: {
|
|
64
|
+
type: "array",
|
|
65
|
+
items: { type: "string" },
|
|
66
|
+
description: "Active session IDs established during the task"
|
|
67
|
+
},
|
|
68
|
+
assets: {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: { type: "string" },
|
|
71
|
+
description: "Operational assets established during the task (listeners, shells, payloads, etc.)"
|
|
72
|
+
},
|
|
73
|
+
suggested_next: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Recommended next action for the main agent"
|
|
76
|
+
},
|
|
77
|
+
waiting_on: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "External event or condition the task is waiting on"
|
|
80
|
+
},
|
|
81
|
+
resume_hint: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "How the task should be resumed later"
|
|
84
|
+
},
|
|
85
|
+
next_worker_type: {
|
|
86
|
+
type: "string",
|
|
87
|
+
enum: ["general", "shell-supervisor", "exploit", "pwn"],
|
|
88
|
+
description: "Recommended worker type for the next delegated step"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
required: ["status", "summary"],
|
|
92
|
+
execute: async (params) => {
|
|
93
|
+
const result = {
|
|
94
|
+
status: params["status"] ?? "partial",
|
|
95
|
+
summary: params["summary"] ?? "",
|
|
96
|
+
tried: params["tried"] ?? [],
|
|
97
|
+
findings: params["findings"] ?? [],
|
|
98
|
+
loot: params["loot"] ?? [],
|
|
99
|
+
sessions: params["sessions"] ?? [],
|
|
100
|
+
assets: params["assets"] ?? [],
|
|
101
|
+
suggestedNext: params["suggested_next"] ?? "",
|
|
102
|
+
waitingOn: params["waiting_on"] ?? void 0,
|
|
103
|
+
resumeHint: params["resume_hint"] ?? void 0,
|
|
104
|
+
nextWorkerType: params["next_worker_type"] ?? void 0
|
|
105
|
+
};
|
|
106
|
+
completion.done = true;
|
|
107
|
+
completion.result = result;
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
output: [
|
|
111
|
+
"[TASK_COMPLETE]",
|
|
112
|
+
`[Status] ${result.status}`,
|
|
113
|
+
`[Summary] ${result.summary}`,
|
|
114
|
+
result.waitingOn ? `[Waiting] ${result.waitingOn}` : "",
|
|
115
|
+
result.resumeHint ? `[Resume] ${result.resumeHint}` : ""
|
|
116
|
+
].join("\n")
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/engine/agent-tool/agent-registry.ts
|
|
123
|
+
var AgentRegistry = class extends CategorizedToolRegistry {
|
|
124
|
+
constructor(state, scopeGuard, approvalGate, events, completion) {
|
|
125
|
+
super(state, scopeGuard, approvalGate, events);
|
|
126
|
+
const taskCompleteTool = createTaskCompleteTool(completion);
|
|
127
|
+
this.tools.set(taskCompleteTool.name, taskCompleteTool);
|
|
128
|
+
}
|
|
129
|
+
initializeRegistry() {
|
|
130
|
+
super.initializeRegistry();
|
|
131
|
+
this.tools.delete(TOOL_NAMES.RUN_TASK);
|
|
132
|
+
this.tools.delete(TOOL_NAMES.ASK_USER);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/engine/agent-tool/agent-runner.ts
|
|
137
|
+
var MAX_AGENT_TOOL_ITERATIONS = 30;
|
|
138
|
+
var COMPRESS_EVERY_N_STEPS = 5;
|
|
139
|
+
var MAX_COMPRESS_FAILURES = 3;
|
|
140
|
+
var AgentRunner = class extends CoreAgent {
|
|
141
|
+
completion;
|
|
142
|
+
contextExtractor;
|
|
143
|
+
stepCount = 0;
|
|
144
|
+
consecutiveCompressFailures = 0;
|
|
145
|
+
constructor(state, events, registry, completion) {
|
|
146
|
+
super({
|
|
147
|
+
agentType: AGENT_ROLES.AGENT_TOOL,
|
|
148
|
+
state,
|
|
149
|
+
events,
|
|
150
|
+
toolRegistry: registry,
|
|
151
|
+
maxIterations: MAX_AGENT_TOOL_ITERATIONS
|
|
152
|
+
});
|
|
153
|
+
this.completion = completion;
|
|
154
|
+
this.contextExtractor = createContextExtractor(getLLMClient());
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* CoreAgent.step() 오버라이드
|
|
158
|
+
*
|
|
159
|
+
* 추가 동작 (super.step() 이후):
|
|
160
|
+
* 1. completion.done 확인 → task_complete 호출됐으면 즉시 완료 신호
|
|
161
|
+
* 2. COMPRESS_EVERY_N_STEPS마다 ContextExtractor 호출
|
|
162
|
+
*/
|
|
163
|
+
async step(iteration, messages, systemPrompt, progress) {
|
|
164
|
+
const result = await super.step(iteration, messages, systemPrompt, progress);
|
|
165
|
+
if (this.completion.done) {
|
|
166
|
+
return {
|
|
167
|
+
output: JSON.stringify(this.completion.result),
|
|
168
|
+
toolsExecuted: result.toolsExecuted,
|
|
169
|
+
isCompleted: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
this.stepCount++;
|
|
173
|
+
if (this.stepCount % COMPRESS_EVERY_N_STEPS === 0) {
|
|
174
|
+
await this.compressContext(messages);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* ContextExtractor를 사용해 messages[]를 1개 session-context로 압축.
|
|
180
|
+
*
|
|
181
|
+
* WHY: 실패 시 무시 (try/catch). CoreAgent의 trimMessagesIfNeeded(50)이
|
|
182
|
+
* 최후 안전망이므로 압축 실패가 치명적이지 않다.
|
|
183
|
+
* WHY (failure tracking): 연속 실패가 MAX_COMPRESS_FAILURES 초과 시 경고 emit.
|
|
184
|
+
* sub-agent가 초기 task를 잃어버릴 위험을 TUI에 노출해 사용자가 인지할 수 있게 한다.
|
|
185
|
+
*/
|
|
186
|
+
async compressContext(messages) {
|
|
187
|
+
try {
|
|
188
|
+
const result = await this.contextExtractor.execute({ messages });
|
|
189
|
+
if (result.success && result.extractedContext) {
|
|
190
|
+
messages.length = 0;
|
|
191
|
+
messages.push({
|
|
192
|
+
role: LLM_ROLES.USER,
|
|
193
|
+
content: `<session-context>
|
|
194
|
+
${result.extractedContext}
|
|
195
|
+
</session-context>`
|
|
196
|
+
});
|
|
197
|
+
this.consecutiveCompressFailures = 0;
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
this.consecutiveCompressFailures++;
|
|
201
|
+
if (this.consecutiveCompressFailures === MAX_COMPRESS_FAILURES) {
|
|
202
|
+
this.events.emit({
|
|
203
|
+
type: EVENT_TYPES.NOTIFICATION,
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
data: {
|
|
206
|
+
title: "Sub-Agent Context Warning",
|
|
207
|
+
message: `Context compression failed ${this.consecutiveCompressFailures}x in a row. Sub-agent may lose task direction. Consider reducing task scope.`,
|
|
208
|
+
level: "warning"
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// src/engine/agent-tool/agent-prompt.ts
|
|
217
|
+
function buildAgentPrompt(input, workerExecutor) {
|
|
218
|
+
const parts = [
|
|
219
|
+
"You are an autonomous execution agent. Complete the delegated task using available tools.",
|
|
220
|
+
"",
|
|
221
|
+
`## Task
|
|
222
|
+
${input.task}`
|
|
223
|
+
];
|
|
224
|
+
if (input.target) {
|
|
225
|
+
parts.push(`
|
|
226
|
+
## Target
|
|
227
|
+
${input.target}`);
|
|
228
|
+
}
|
|
229
|
+
if (input.context) {
|
|
230
|
+
parts.push(`
|
|
231
|
+
## Context
|
|
232
|
+
${input.context}`);
|
|
233
|
+
}
|
|
234
|
+
const activeProcesses = getActiveProcessSummary();
|
|
235
|
+
if (activeProcesses) {
|
|
236
|
+
parts.push(`
|
|
237
|
+
## Active Background Processes
|
|
238
|
+
${activeProcesses}`);
|
|
239
|
+
}
|
|
240
|
+
if (workerExecutor) {
|
|
241
|
+
for (const section of workerExecutor.buildPromptSections(input)) {
|
|
242
|
+
parts.push(`
|
|
243
|
+
${section}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
parts.push(`
|
|
247
|
+
## Rules
|
|
248
|
+
- Do NOT call ask_user. You are autonomous. Make your best judgment.
|
|
249
|
+
- Call task_complete when the task is done (status: success, partial, failed, waiting, or running).
|
|
250
|
+
- Record findings with add_finding, loot with add_loot as you discover them.
|
|
251
|
+
- If you hit 3 consecutive failures on the same approach, switch vectors or declare failed.
|
|
252
|
+
- Use waiting/running when you created a durable operational asset that should be resumed later.
|
|
253
|
+
- Be decisive \u2014 do not loop indefinitely on the same approach.`);
|
|
254
|
+
return parts.join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/engine/agent-tool/worker-executor.ts
|
|
258
|
+
function joinContextParts(parts) {
|
|
259
|
+
const filtered = parts.filter(Boolean);
|
|
260
|
+
if (filtered.length === 0) return void 0;
|
|
261
|
+
return filtered.join("\n\n");
|
|
262
|
+
}
|
|
263
|
+
function buildWorkerGuidance(input) {
|
|
264
|
+
switch (input.workerType) {
|
|
265
|
+
case "shell-supervisor":
|
|
266
|
+
return [
|
|
267
|
+
"## Worker Specialization",
|
|
268
|
+
"You are the shell-supervisor worker.",
|
|
269
|
+
"",
|
|
270
|
+
"- Prioritize reusing existing listeners, shell sessions, and promoted shell assets.",
|
|
271
|
+
"- First action: inspect relevant background processes and detect whether a connection already landed.",
|
|
272
|
+
"- If a listener has a connection, promote it before launching any new listener.",
|
|
273
|
+
"- Validate shell quality quickly: whoami, id, hostname, pwd, uname -a.",
|
|
274
|
+
"- If the shell is dumb, attempt PTY upgrade and shell stabilization immediately.",
|
|
275
|
+
"- After stabilization, continue lightweight post-exploitation enumeration using the existing shell.",
|
|
276
|
+
"- Do not create duplicate listeners unless the current asset is clearly dead."
|
|
277
|
+
].join("\n");
|
|
278
|
+
case "exploit":
|
|
279
|
+
return [
|
|
280
|
+
"## Worker Specialization",
|
|
281
|
+
"You are the exploit worker.",
|
|
282
|
+
"",
|
|
283
|
+
"- Focus on exploit chain execution and branching follow-up.",
|
|
284
|
+
"- Prefer adapting the current vector before switching to an unrelated attack.",
|
|
285
|
+
"- Record what was tried and why each branch succeeded or failed."
|
|
286
|
+
].join("\n");
|
|
287
|
+
case "pwn":
|
|
288
|
+
return [
|
|
289
|
+
"## Worker Specialization",
|
|
290
|
+
"You are the pwn worker.",
|
|
291
|
+
"",
|
|
292
|
+
"- Focus on iterative exploit development, repeated run/debug cycles, and precise observations.",
|
|
293
|
+
"- Preserve crash details, offsets, gadgets, payload changes, and solver state."
|
|
294
|
+
].join("\n");
|
|
295
|
+
default:
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
var DefaultDelegatedWorkerExecutor = class {
|
|
300
|
+
prepareInput(input) {
|
|
301
|
+
return input;
|
|
302
|
+
}
|
|
303
|
+
buildPromptSections(input) {
|
|
304
|
+
const guidance = buildWorkerGuidance(input);
|
|
305
|
+
return guidance ? [guidance] : [];
|
|
306
|
+
}
|
|
307
|
+
finalizeResult(_input, result) {
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
function mergeWorkerContext(input, extraContext) {
|
|
312
|
+
return {
|
|
313
|
+
...input,
|
|
314
|
+
context: joinContextParts([input.context, extraContext])
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/engine/agent-tool/exploit-worker-plan.ts
|
|
319
|
+
function getContextValue(context, key) {
|
|
320
|
+
if (!context) return null;
|
|
321
|
+
const match = context.match(new RegExp(`${key}=([^|\\n]+)`));
|
|
322
|
+
return match?.[1]?.trim() ?? null;
|
|
323
|
+
}
|
|
324
|
+
function buildExploitWorkerPlan(input) {
|
|
325
|
+
const phase = getContextValue(input.context, "exploit_phase") || "vector_active";
|
|
326
|
+
if (phase === "foothold_active") {
|
|
327
|
+
return [
|
|
328
|
+
"## Exploit Worker Plan",
|
|
329
|
+
"Immediate actions:",
|
|
330
|
+
"1. Reuse the current foothold before launching any new exploit branch.",
|
|
331
|
+
'2. Prefer exploit_foothold_check(command="...") for bounded access validation before broadening the chain.',
|
|
332
|
+
"3. Validate what that foothold can already reach or dump.",
|
|
333
|
+
"4. Continue the exploit chain from the live access path instead of restarting delivery."
|
|
334
|
+
].join("\n");
|
|
335
|
+
}
|
|
336
|
+
if (phase === "credential_followup") {
|
|
337
|
+
return [
|
|
338
|
+
"## Exploit Worker Plan",
|
|
339
|
+
"Immediate actions:",
|
|
340
|
+
"1. Validate the dumped credentials or tokens first.",
|
|
341
|
+
'2. Prefer exploit_credential_check(command="...") for bounded credential/token validation before changing vectors.',
|
|
342
|
+
"3. Reuse them against the current target or nearest pivot target before changing vectors.",
|
|
343
|
+
"4. If reuse succeeds, convert that into a foothold and report it explicitly."
|
|
344
|
+
].join("\n");
|
|
345
|
+
}
|
|
346
|
+
if (phase === "artifact_ready") {
|
|
347
|
+
return [
|
|
348
|
+
"## Exploit Worker Plan",
|
|
349
|
+
"Immediate actions:",
|
|
350
|
+
"1. Validate the current exploit artifact before building a replacement.",
|
|
351
|
+
'2. Prefer exploit_artifact_check(command="...") for bounded artifact verification.',
|
|
352
|
+
"3. Keep the artifact tied to the current exploit chain in assets.",
|
|
353
|
+
"4. If the artifact lands access or data, pivot the chain from that result."
|
|
354
|
+
].join("\n");
|
|
355
|
+
}
|
|
356
|
+
return [
|
|
357
|
+
"## Exploit Worker Plan",
|
|
358
|
+
"Immediate actions:",
|
|
359
|
+
"1. Continue the strongest surviving exploit vector.",
|
|
360
|
+
"2. Preserve branch decisions and observed responses.",
|
|
361
|
+
"3. Only switch vectors after concrete failure evidence."
|
|
362
|
+
].join("\n");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/engine/agent-tool/exploit-worker.ts
|
|
366
|
+
function getContextValue2(context, key) {
|
|
367
|
+
if (!context) return null;
|
|
368
|
+
const match = context.match(new RegExp(`${key}=([^|\\n]+)`));
|
|
369
|
+
return match?.[1]?.trim() ?? null;
|
|
370
|
+
}
|
|
371
|
+
function getExploitPhase(context) {
|
|
372
|
+
return getContextValue2(context, "exploit_phase") || "vector_active";
|
|
373
|
+
}
|
|
374
|
+
function getRecommendation(context) {
|
|
375
|
+
return getContextValue2(context, "recommendation");
|
|
376
|
+
}
|
|
377
|
+
function buildExploitExecutionPolicy() {
|
|
378
|
+
return [
|
|
379
|
+
"## Exploit Worker Execution Policy",
|
|
380
|
+
"- Treat exploit work as a branching chain, not a one-shot attempt.",
|
|
381
|
+
"- Preserve the current vector unless concrete evidence says it is dead.",
|
|
382
|
+
'- If the chain created a durable artifact or needs another exploit iteration, prefer status="running".',
|
|
383
|
+
"- Record payload variants, observed responses, and why each branch succeeded or failed."
|
|
384
|
+
].join("\n");
|
|
385
|
+
}
|
|
386
|
+
function buildExploitLifecycleSection(input) {
|
|
387
|
+
const phase = getExploitPhase(input.context);
|
|
388
|
+
const recommendation = getRecommendation(input.context) || "Continue the current exploit chain before switching vectors.";
|
|
389
|
+
return [
|
|
390
|
+
"## Exploit Lifecycle",
|
|
391
|
+
`Current phase: ${phase}`,
|
|
392
|
+
`Recommendation: ${recommendation}`
|
|
393
|
+
].join("\n");
|
|
394
|
+
}
|
|
395
|
+
function buildExploitCompletionGuidance(input) {
|
|
396
|
+
const phase = getExploitPhase(input.context);
|
|
397
|
+
if (phase === "foothold_active") {
|
|
398
|
+
return [
|
|
399
|
+
"## Exploit Completion Guidance",
|
|
400
|
+
'If the chain already has a foothold, prefer status="running" and report the session/asset explicitly.',
|
|
401
|
+
"Resume hint should keep the current foothold as the primary access path."
|
|
402
|
+
].join("\n");
|
|
403
|
+
}
|
|
404
|
+
if (phase === "credential_followup") {
|
|
405
|
+
return [
|
|
406
|
+
"## Exploit Completion Guidance",
|
|
407
|
+
'If the chain produced credentials or tokens, prefer status="running" until reuse or validation is complete.',
|
|
408
|
+
"Record loot explicitly and resume from credential validation before changing exploit vectors."
|
|
409
|
+
].join("\n");
|
|
410
|
+
}
|
|
411
|
+
if (phase === "artifact_ready") {
|
|
412
|
+
return [
|
|
413
|
+
"## Exploit Completion Guidance",
|
|
414
|
+
'If the chain produced a reusable artifact, prefer status="running" until the artifact is validated or consumed.',
|
|
415
|
+
"Report the artifact in assets and keep resumeHint focused on that artifact."
|
|
416
|
+
].join("\n");
|
|
417
|
+
}
|
|
418
|
+
return [
|
|
419
|
+
"## Exploit Completion Guidance",
|
|
420
|
+
'If the current vector remains viable but incomplete, prefer status="running" and keep the resume hint tied to the strongest surviving vector.'
|
|
421
|
+
].join("\n");
|
|
422
|
+
}
|
|
423
|
+
var ExploitWorker = class extends DefaultDelegatedWorkerExecutor {
|
|
424
|
+
buildPromptSections(input) {
|
|
425
|
+
return [
|
|
426
|
+
...super.buildPromptSections(input),
|
|
427
|
+
buildExploitLifecycleSection(input),
|
|
428
|
+
buildExploitExecutionPolicy(),
|
|
429
|
+
buildExploitWorkerPlan(input),
|
|
430
|
+
buildExploitCompletionGuidance(input)
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
finalizeResult(input, result) {
|
|
434
|
+
const phase = getExploitPhase(input.context);
|
|
435
|
+
const resumeHint = phase === "foothold_active" ? "Reuse the current foothold and continue the exploit chain from that access path." : phase === "credential_followup" ? "Validate and reuse the obtained credentials or tokens before changing exploit vectors." : phase === "artifact_ready" ? "Validate and advance the current exploit artifact before switching vectors." : "Continue the current exploit chain from the strongest surviving vector.";
|
|
436
|
+
const waitingOn = phase === "foothold_active" ? "follow-up work on the current foothold such as privilege escalation or pivot preparation" : phase === "credential_followup" ? "credential or token validation and reuse" : phase === "artifact_ready" ? "artifact validation or consumption" : "next exploit chain step or artifact validation";
|
|
437
|
+
if (result.status === "success" || result.status === "partial") {
|
|
438
|
+
return {
|
|
439
|
+
...result,
|
|
440
|
+
status: result.assets.length > 0 || result.sessions.length > 0 || Boolean(result.resumeHint) ? "running" : result.status,
|
|
441
|
+
waitingOn: result.waitingOn ?? (result.status === "success" ? void 0 : waitingOn),
|
|
442
|
+
resumeHint: result.resumeHint ?? (result.status === "success" ? void 0 : resumeHint),
|
|
443
|
+
nextWorkerType: result.nextWorkerType ?? "exploit"
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
...result,
|
|
448
|
+
nextWorkerType: result.nextWorkerType ?? "exploit"
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// src/engine/agent-tool/pwn-worker-plan.ts
|
|
454
|
+
function getContextValue3(context, key) {
|
|
455
|
+
if (!context) return null;
|
|
456
|
+
const match = context.match(new RegExp(`${key}=([^|\\n]+)`));
|
|
457
|
+
return match?.[1]?.trim() ?? null;
|
|
458
|
+
}
|
|
459
|
+
function buildPwnWorkerPlan(input) {
|
|
460
|
+
const phase = getContextValue3(input.context, "pwn_phase") || "payload_iteration";
|
|
461
|
+
if (phase === "foothold_active") {
|
|
462
|
+
return [
|
|
463
|
+
"## Pwn Worker Plan",
|
|
464
|
+
"Immediate actions:",
|
|
465
|
+
"1. Reuse the achieved execution foothold before reopening the exploit loop.",
|
|
466
|
+
"2. Convert that execution into objective completion or post-exploitation steps.",
|
|
467
|
+
"3. Preserve the working exploit state as the current best chain."
|
|
468
|
+
].join("\n");
|
|
469
|
+
}
|
|
470
|
+
if (phase === "offset_known") {
|
|
471
|
+
return [
|
|
472
|
+
"## Pwn Worker Plan",
|
|
473
|
+
"Immediate actions:",
|
|
474
|
+
"1. Resume from the known offset and latest payload revision.",
|
|
475
|
+
'2. Prefer pwn_offset_check(command="...") for bounded control verification.',
|
|
476
|
+
"3. Avoid recomputing control primitives that are already established.",
|
|
477
|
+
"4. Focus the next iteration on reliable code execution or leak stabilization."
|
|
478
|
+
].join("\n");
|
|
479
|
+
}
|
|
480
|
+
if (phase === "crash_iteration") {
|
|
481
|
+
return [
|
|
482
|
+
"## Pwn Worker Plan",
|
|
483
|
+
"Immediate actions:",
|
|
484
|
+
"1. Reproduce the latest crash exactly.",
|
|
485
|
+
'2. Prefer pwn_crash_repro(command="...") for bounded crash reproduction.',
|
|
486
|
+
"3. Preserve crash evidence, registers, and payload delta from the last iteration.",
|
|
487
|
+
"4. Move one step closer to control, leak, or execution rather than restarting analysis."
|
|
488
|
+
].join("\n");
|
|
489
|
+
}
|
|
490
|
+
return [
|
|
491
|
+
"## Pwn Worker Plan",
|
|
492
|
+
"Immediate actions:",
|
|
493
|
+
"1. Continue the active payload-design loop.",
|
|
494
|
+
'2. Prefer pwn_payload_smoke(command="...") for the narrowest next payload test.',
|
|
495
|
+
"3. Preserve each payload revision and observable behavior.",
|
|
496
|
+
"4. Make the next test iteration narrower, not broader."
|
|
497
|
+
].join("\n");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/engine/agent-tool/pwn-worker.ts
|
|
501
|
+
function getContextValue4(context, key) {
|
|
502
|
+
if (!context) return null;
|
|
503
|
+
const match = context.match(new RegExp(`${key}=([^|\\n]+)`));
|
|
504
|
+
return match?.[1]?.trim() ?? null;
|
|
505
|
+
}
|
|
506
|
+
function getPwnPhase(context) {
|
|
507
|
+
return getContextValue4(context, "pwn_phase") || "payload_iteration";
|
|
508
|
+
}
|
|
509
|
+
function getRecommendation2(context) {
|
|
510
|
+
return getContextValue4(context, "recommendation");
|
|
511
|
+
}
|
|
512
|
+
function buildPwnExecutionPolicy() {
|
|
513
|
+
return [
|
|
514
|
+
"## Pwn Worker Execution Policy",
|
|
515
|
+
"- Treat pwn work as an iterative solver loop with repeatable checkpoints.",
|
|
516
|
+
"- Preserve offsets, crash signatures, gadgets, leaked pointers, and payload revisions.",
|
|
517
|
+
'- If another debug or payload iteration is required, prefer status="running".',
|
|
518
|
+
"- Make the next iteration obvious in resumeHint rather than restarting from scratch."
|
|
519
|
+
].join("\n");
|
|
520
|
+
}
|
|
521
|
+
function buildPwnLifecycleSection(input) {
|
|
522
|
+
const phase = getPwnPhase(input.context);
|
|
523
|
+
const recommendation = getRecommendation2(input.context) || "Continue from the latest crash, offset, or payload revision.";
|
|
524
|
+
return [
|
|
525
|
+
"## Pwn Lifecycle",
|
|
526
|
+
`Current phase: ${phase}`,
|
|
527
|
+
`Recommendation: ${recommendation}`
|
|
528
|
+
].join("\n");
|
|
529
|
+
}
|
|
530
|
+
function buildPwnCompletionGuidance(input) {
|
|
531
|
+
const phase = getPwnPhase(input.context);
|
|
532
|
+
if (phase === "foothold_active") {
|
|
533
|
+
return [
|
|
534
|
+
"## Pwn Completion Guidance",
|
|
535
|
+
'If the pwn chain already has execution, prefer status="running" and keep the existing foothold as the primary path.'
|
|
536
|
+
].join("\n");
|
|
537
|
+
}
|
|
538
|
+
if (phase === "offset_known") {
|
|
539
|
+
return [
|
|
540
|
+
"## Pwn Completion Guidance",
|
|
541
|
+
'If offsets are known but the exploit is incomplete, prefer status="running" and resume from the known control primitive.'
|
|
542
|
+
].join("\n");
|
|
543
|
+
}
|
|
544
|
+
if (phase === "crash_iteration") {
|
|
545
|
+
return [
|
|
546
|
+
"## Pwn Completion Guidance",
|
|
547
|
+
'If a crash state exists, prefer status="running" and resume from the preserved crash evidence rather than restarting.'
|
|
548
|
+
].join("\n");
|
|
549
|
+
}
|
|
550
|
+
return [
|
|
551
|
+
"## Pwn Completion Guidance",
|
|
552
|
+
'If the payload loop is still active, prefer status="running" and make the next payload iteration explicit.'
|
|
553
|
+
].join("\n");
|
|
554
|
+
}
|
|
555
|
+
var PwnWorker = class extends DefaultDelegatedWorkerExecutor {
|
|
556
|
+
buildPromptSections(input) {
|
|
557
|
+
return [
|
|
558
|
+
...super.buildPromptSections(input),
|
|
559
|
+
buildPwnLifecycleSection(input),
|
|
560
|
+
buildPwnExecutionPolicy(),
|
|
561
|
+
buildPwnWorkerPlan(input),
|
|
562
|
+
buildPwnCompletionGuidance(input)
|
|
563
|
+
];
|
|
564
|
+
}
|
|
565
|
+
finalizeResult(input, result) {
|
|
566
|
+
const phase = getPwnPhase(input.context);
|
|
567
|
+
const resumeHint = phase === "foothold_active" ? "Reuse the existing execution foothold and continue objective completion from that access path." : phase === "offset_known" ? "Resume from the known offset and latest payload revision." : phase === "crash_iteration" ? "Resume from the latest crash state, preserved offsets, and debugger observations." : "Resume from the latest payload revision and solver observations.";
|
|
568
|
+
const waitingOn = phase === "foothold_active" ? "follow-up work on the existing execution foothold" : phase === "offset_known" ? "next payload iteration from the known offset" : phase === "crash_iteration" ? "next exploit-development or debugging iteration" : "next payload-design iteration";
|
|
569
|
+
if (result.status === "success" || result.status === "partial") {
|
|
570
|
+
return {
|
|
571
|
+
...result,
|
|
572
|
+
status: result.status === "partial" || Boolean(result.resumeHint) ? "running" : result.status,
|
|
573
|
+
waitingOn: result.waitingOn ?? (result.status === "partial" ? waitingOn : void 0),
|
|
574
|
+
resumeHint: result.resumeHint ?? (result.status === "partial" ? resumeHint : void 0),
|
|
575
|
+
nextWorkerType: result.nextWorkerType ?? "pwn"
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
...result,
|
|
580
|
+
nextWorkerType: result.nextWorkerType ?? "pwn"
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// src/engine/agent-tool/shell-supervisor-action-plan.ts
|
|
586
|
+
function buildShellSupervisorActionPlan() {
|
|
587
|
+
const snapshot = getShellSupervisorLifecycleSnapshot();
|
|
588
|
+
if (snapshot.phase === "active_shell_ready" && snapshot.activeShellId) {
|
|
589
|
+
return [
|
|
590
|
+
"## Shell Supervisor Action Plan",
|
|
591
|
+
"Immediate actions:",
|
|
592
|
+
`1. Reuse the existing shell first: shell_check({ process_id: "${snapshot.activeShellId}", profile: "identity" })`,
|
|
593
|
+
`2. Confirm environment: shell_exec({ process_id: "${snapshot.activeShellId}", command: "pwd && uname -a" })`,
|
|
594
|
+
`3. If the shell is unstable or dumb, run shell_upgrade({ process_id: "${snapshot.activeShellId}", method: "python_pty" }) before wider enumeration.`
|
|
595
|
+
].join("\n");
|
|
596
|
+
}
|
|
597
|
+
if (snapshot.phase === "listener_callback_detected" && snapshot.listenerId) {
|
|
598
|
+
return [
|
|
599
|
+
"## Shell Supervisor Action Plan",
|
|
600
|
+
"Immediate actions:",
|
|
601
|
+
`1. Confirm listener output: listener_status({ process_id: "${snapshot.listenerId}" })`,
|
|
602
|
+
`2. Promote the callback path: shell_promote({ process_id: "${snapshot.listenerId}" })`,
|
|
603
|
+
`3. Validate the new shell: shell_check({ process_id: "${snapshot.listenerId}", profile: "identity" })`
|
|
604
|
+
].join("\n");
|
|
605
|
+
}
|
|
606
|
+
if (snapshot.phase === "listener_waiting" && snapshot.listenerId) {
|
|
607
|
+
return [
|
|
608
|
+
"## Shell Supervisor Action Plan",
|
|
609
|
+
"Immediate actions:",
|
|
610
|
+
`1. Poll the current listener: listener_status({ process_id: "${snapshot.listenerId}" })`,
|
|
611
|
+
`2. Reuse ${snapshot.listenerId} unless status proves it is dead or unusable.`,
|
|
612
|
+
"3. Continue the exploit chain against the current callback path instead of opening a duplicate listener."
|
|
613
|
+
].join("\n");
|
|
614
|
+
}
|
|
615
|
+
return [
|
|
616
|
+
"## Shell Supervisor Action Plan",
|
|
617
|
+
"Immediate actions:",
|
|
618
|
+
"1. No reusable shell asset exists yet. If a callback path is required, create exactly one listener with listener_start().",
|
|
619
|
+
"2. After launch, poll it with listener_status() and promote on connection with shell_promote() before creating any replacement."
|
|
620
|
+
].join("\n");
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/engine/agent-tool/shell-supervisor-completion-guidance.ts
|
|
624
|
+
function formatList(values) {
|
|
625
|
+
return values.length > 0 ? values.map((value) => `"${value}"`).join(", ") : "";
|
|
626
|
+
}
|
|
627
|
+
function buildShellSupervisorCompletionGuidance() {
|
|
628
|
+
const snapshot = getShellSupervisorLifecycleSnapshot();
|
|
629
|
+
if (snapshot.phase === "active_shell_ready" && snapshot.activeShellId) {
|
|
630
|
+
const assets = [`shell:${snapshot.activeShellId}`];
|
|
631
|
+
return [
|
|
632
|
+
"## Shell Supervisor Completion Guidance",
|
|
633
|
+
"Preferred completion shape when a reusable shell already exists:",
|
|
634
|
+
"```text",
|
|
635
|
+
"task_complete({",
|
|
636
|
+
' status: "running",',
|
|
637
|
+
' summary: "Reused the active shell and kept the shell chain alive for follow-up.",',
|
|
638
|
+
` sessions: [${formatList([snapshot.activeShellId])}],`,
|
|
639
|
+
` assets: [${formatList(assets)}],`,
|
|
640
|
+
' waiting_on: "shell follow-up actions such as PTY upgrade or host enumeration",',
|
|
641
|
+
` resume_hint: "Reuse ${snapshot.activeShellId}, validate shell quality, upgrade PTY if needed, then continue enumeration.",`,
|
|
642
|
+
' next_worker_type: "shell-supervisor"',
|
|
643
|
+
"})",
|
|
644
|
+
"```"
|
|
645
|
+
].join("\n");
|
|
646
|
+
}
|
|
647
|
+
if ((snapshot.phase === "active_shell_stabilized" || snapshot.phase === "post_exploitation_active") && snapshot.activeShellId) {
|
|
648
|
+
const assets = [`shell:${snapshot.activeShellId}`, `stabilized_shell:${snapshot.activeShellId}`];
|
|
649
|
+
if (snapshot.phase === "post_exploitation_active") {
|
|
650
|
+
assets.push(`post_exploitation_shell:${snapshot.activeShellId}`);
|
|
651
|
+
}
|
|
652
|
+
return [
|
|
653
|
+
"## Shell Supervisor Completion Guidance",
|
|
654
|
+
snapshot.phase === "post_exploitation_active" ? "Preferred completion shape when a stabilized shell is already driving post-exploitation:" : "Preferred completion shape when a stabilized shell already exists:",
|
|
655
|
+
"```text",
|
|
656
|
+
"task_complete({",
|
|
657
|
+
' status: "running",',
|
|
658
|
+
snapshot.phase === "post_exploitation_active" ? ' summary: "Reused the stabilized shell and kept the post-exploitation chain active for follow-up.",' : ' summary: "Reused the stabilized shell and kept it available for controlled follow-up.",',
|
|
659
|
+
` sessions: [${formatList([snapshot.activeShellId])}],`,
|
|
660
|
+
` assets: [${formatList(assets)}],`,
|
|
661
|
+
snapshot.phase === "post_exploitation_active" ? ' waiting_on: "controlled post-exploitation follow-up such as privilege escalation, credential harvesting, or pivot preparation",' : ' waiting_on: "controlled shell follow-up actions such as host enumeration or privilege escalation",',
|
|
662
|
+
snapshot.phase === "post_exploitation_active" ? ` resume_hint: "Reuse ${snapshot.activeShellId}, continue controlled post-exploitation through the existing shell, and avoid opening duplicate access paths.",` : ` resume_hint: "Reuse ${snapshot.activeShellId}, keep the stabilized shell as the primary access path, and continue controlled enumeration.",`,
|
|
663
|
+
' next_worker_type: "shell-supervisor"',
|
|
664
|
+
"})",
|
|
665
|
+
"```"
|
|
666
|
+
].join("\n");
|
|
667
|
+
}
|
|
668
|
+
if (snapshot.phase === "listener_callback_detected" && snapshot.listenerId) {
|
|
669
|
+
return [
|
|
670
|
+
"## Shell Supervisor Completion Guidance",
|
|
671
|
+
"Preferred completion shape when a listener has received a callback but promotion is still in progress:",
|
|
672
|
+
"```text",
|
|
673
|
+
"task_complete({",
|
|
674
|
+
' status: "running",',
|
|
675
|
+
' summary: "Detected a live callback on the listener and kept the shell acquisition chain active.",',
|
|
676
|
+
` assets: [${formatList([`listener:${snapshot.listenerId}`])}],`,
|
|
677
|
+
' waiting_on: "listener callback needs promotion and shell validation",',
|
|
678
|
+
` resume_hint: "Check ${snapshot.listenerId}, promote it to an active shell, then interact and stabilize it.",`,
|
|
679
|
+
' next_worker_type: "shell-supervisor"',
|
|
680
|
+
"})",
|
|
681
|
+
"```"
|
|
682
|
+
].join("\n");
|
|
683
|
+
}
|
|
684
|
+
if (snapshot.phase === "listener_waiting" && snapshot.listenerId) {
|
|
685
|
+
return [
|
|
686
|
+
"## Shell Supervisor Completion Guidance",
|
|
687
|
+
"Preferred completion shape when supervision is waiting on an external callback:",
|
|
688
|
+
"```text",
|
|
689
|
+
"task_complete({",
|
|
690
|
+
' status: "waiting",',
|
|
691
|
+
' summary: "Listener remains active and is waiting for a reverse-shell callback.",',
|
|
692
|
+
` assets: [${formatList([`listener:${snapshot.listenerId}`])}],`,
|
|
693
|
+
' waiting_on: "incoming reverse-shell callback",',
|
|
694
|
+
` resume_hint: "Poll ${snapshot.listenerId} with bg_process status, then promote immediately if a connection appears.",`,
|
|
695
|
+
' next_worker_type: "shell-supervisor"',
|
|
696
|
+
"})",
|
|
697
|
+
"```",
|
|
698
|
+
"Do not mark this as failed just because the external callback has not arrived yet."
|
|
699
|
+
].join("\n");
|
|
700
|
+
}
|
|
701
|
+
return [
|
|
702
|
+
"## Shell Supervisor Completion Guidance",
|
|
703
|
+
'If no listener or shell asset exists, only use status="failed" when the callback path is clearly broken.',
|
|
704
|
+
"If you created no durable asset, close with success/partial/failed and explain the next setup step explicitly."
|
|
705
|
+
].join("\n");
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/engine/agent-tool/shell-supervisor-context.ts
|
|
709
|
+
var MAX_RELEVANT_PROCESSES = 3;
|
|
710
|
+
var MAX_RELEVANT_EVENTS = 3;
|
|
711
|
+
var MAX_SNIPPET_LINES = 3;
|
|
712
|
+
function getRolePriority(role) {
|
|
713
|
+
switch (role) {
|
|
714
|
+
case PROCESS_ROLES.ACTIVE_SHELL:
|
|
715
|
+
return 0;
|
|
716
|
+
case PROCESS_ROLES.LISTENER:
|
|
717
|
+
return 1;
|
|
718
|
+
default:
|
|
719
|
+
return 2;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function getSnippet(stdout, stderr) {
|
|
723
|
+
const source = stdout.trim() || stderr.trim();
|
|
724
|
+
if (!source) return "";
|
|
725
|
+
const lines = source.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
726
|
+
return lines.slice(-MAX_SNIPPET_LINES).join(" | ");
|
|
727
|
+
}
|
|
728
|
+
function getRecommendation3(role) {
|
|
729
|
+
if (role === PROCESS_ROLES.ACTIVE_SHELL) {
|
|
730
|
+
return "Reuse this shell first. Validate with whoami, id, hostname, pwd, and uname -a before expanding.";
|
|
731
|
+
}
|
|
732
|
+
if (role === PROCESS_ROLES.LISTENER) {
|
|
733
|
+
return "Check whether a connection landed before creating a new listener. Promote immediately if the callback is live.";
|
|
734
|
+
}
|
|
735
|
+
return "Reuse this process asset if it still matches the delegated objective.";
|
|
736
|
+
}
|
|
737
|
+
function buildShellSupervisorContext() {
|
|
738
|
+
const relevantProcesses = listBackgroundProcesses().filter((process) => process.isRunning && (process.role === PROCESS_ROLES.ACTIVE_SHELL || process.role === PROCESS_ROLES.LISTENER)).sort((left, right) => getRolePriority(left.role) - getRolePriority(right.role)).slice(0, MAX_RELEVANT_PROCESSES);
|
|
739
|
+
const relevantIds = new Set(relevantProcesses.map((process) => process.id));
|
|
740
|
+
const recentEvents = getProcessEventLog().filter((event) => relevantIds.has(event.processId)).slice(-MAX_RELEVANT_EVENTS);
|
|
741
|
+
if (relevantProcesses.length === 0 && recentEvents.length === 0) {
|
|
742
|
+
return "";
|
|
743
|
+
}
|
|
744
|
+
const lines = [
|
|
745
|
+
"## Shell Supervisor Context",
|
|
746
|
+
"Inspect these runtime assets before opening new listeners or spawning duplicate shells."
|
|
747
|
+
];
|
|
748
|
+
if (relevantProcesses.length > 0) {
|
|
749
|
+
lines.push("Relevant Assets:");
|
|
750
|
+
for (const process of relevantProcesses) {
|
|
751
|
+
const output = getProcessOutput(process.id);
|
|
752
|
+
if (!output) continue;
|
|
753
|
+
const port = output.listeningPort ? ` port=${output.listeningPort}` : "";
|
|
754
|
+
const interactive = output.isInteractive ? " interactive=true" : "";
|
|
755
|
+
lines.push(`- ${process.id} [${output.role}]${port}${interactive} purpose=${output.purpose}`);
|
|
756
|
+
const snippet = getSnippet(output.stdout, output.stderr);
|
|
757
|
+
if (snippet) {
|
|
758
|
+
lines.push(` Recent Output: ${snippet}`);
|
|
759
|
+
}
|
|
760
|
+
lines.push(` Recommendation: ${getRecommendation3(output.role)}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (recentEvents.length > 0) {
|
|
764
|
+
lines.push("Recent Events:");
|
|
765
|
+
for (const event of recentEvents) {
|
|
766
|
+
const ageSeconds = Math.round((Date.now() - event.timestamp) / 1e3);
|
|
767
|
+
lines.push(`- ${event.processId} ${event.event} (${ageSeconds}s ago): ${event.detail}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return lines.join("\n");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/engine/agent-tool/shell-supervisor-pty-playbook.ts
|
|
774
|
+
function buildShellSupervisorPtyPlaybook() {
|
|
775
|
+
return [
|
|
776
|
+
"## Shell Supervisor PTY Upgrade Playbook",
|
|
777
|
+
"If the callback lands but the shell is dumb, try these in order and stop once one works:",
|
|
778
|
+
'0. Use `shell_check({ process_id: "...", profile: "stability" })` to confirm whether the shell is still dumb before widening the operation.',
|
|
779
|
+
"1. `python3 -c 'import pty;pty.spawn(\"/bin/bash\")'` or `python -c 'import pty;pty.spawn(\"/bin/bash\")'`",
|
|
780
|
+
' Or send the bounded helper: `shell_upgrade({ process_id: "...", method: "python_pty" })`',
|
|
781
|
+
"2. `script -qc /bin/bash /dev/null` or `script /dev/null -c bash`",
|
|
782
|
+
' Or send the bounded helper: `shell_upgrade({ process_id: "...", method: "script" })`',
|
|
783
|
+
"3. `perl -e 'exec \"/bin/bash\"'`, `ruby -e 'exec \"/bin/bash\"'`, or `/usr/bin/expect -c 'spawn bash; interact'`",
|
|
784
|
+
"4. After PTY spawn, finish stabilization on the attacker side with `stty raw -echo; fg`, then set `TERM`, `SHELL`, and terminal rows/columns.",
|
|
785
|
+
"5. If socat is present, prefer a socat PTY relay for a higher quality shell.",
|
|
786
|
+
"Do not expand into wide post-exploitation until the shell is at least minimally stable."
|
|
787
|
+
].join("\n");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/engine/agent-tool/worker-preflight.ts
|
|
791
|
+
function hasConnectionSignal(processId, stdout) {
|
|
792
|
+
if (DETECTION_PATTERNS.CONNECTION.some((pattern) => pattern.test(stdout))) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
return getProcessEventLog().some(
|
|
796
|
+
(event) => event.processId === processId && event.event === PROCESS_EVENTS.CONNECTION_DETECTED
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
function buildShellSupervisorPreflight() {
|
|
800
|
+
const processes = listBackgroundProcesses().filter(
|
|
801
|
+
(process) => process.isRunning && (process.role === PROCESS_ROLES.ACTIVE_SHELL || process.role === PROCESS_ROLES.LISTENER)
|
|
802
|
+
);
|
|
803
|
+
const activeShell = processes.find((process) => process.role === PROCESS_ROLES.ACTIVE_SHELL);
|
|
804
|
+
if (activeShell) {
|
|
805
|
+
return [
|
|
806
|
+
"## Shell Supervisor Preflight",
|
|
807
|
+
`Primary asset: ${activeShell.id} is already an active shell.`,
|
|
808
|
+
"First actions:",
|
|
809
|
+
"- Reuse this shell before opening any new listener.",
|
|
810
|
+
"- Validate shell quality with whoami, id, hostname, pwd, and uname -a.",
|
|
811
|
+
"- If the shell is unstable or dumb, upgrade PTY immediately."
|
|
812
|
+
].join("\n");
|
|
813
|
+
}
|
|
814
|
+
const listeners = processes.filter((process) => process.role === PROCESS_ROLES.LISTENER);
|
|
815
|
+
for (const listener of listeners) {
|
|
816
|
+
const output = getProcessOutput(listener.id);
|
|
817
|
+
if (!output) continue;
|
|
818
|
+
if (hasConnectionSignal(listener.id, output.stdout)) {
|
|
819
|
+
return [
|
|
820
|
+
"## Shell Supervisor Preflight",
|
|
821
|
+
`Primary asset: ${listener.id} listener shows a callback signal.`,
|
|
822
|
+
"First actions:",
|
|
823
|
+
`- Check bg_process status for ${listener.id} before creating a new listener.`,
|
|
824
|
+
`- Promote ${listener.id} immediately if the callback is live.`,
|
|
825
|
+
`- After promotion, interact with ${listener.id} and validate shell quality.`
|
|
826
|
+
].join("\n");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (listeners.length > 0) {
|
|
830
|
+
return [
|
|
831
|
+
"## Shell Supervisor Preflight",
|
|
832
|
+
`Primary asset: ${listeners[0].id} listener is still running.`,
|
|
833
|
+
"First actions:",
|
|
834
|
+
`- Check bg_process status for ${listeners[0].id} before creating a duplicate listener.`,
|
|
835
|
+
"- Reuse the existing listener unless it is dead or clearly unsuitable.",
|
|
836
|
+
"- If no callback is present, continue the exploit chain against the existing listener."
|
|
837
|
+
].join("\n");
|
|
838
|
+
}
|
|
839
|
+
return [
|
|
840
|
+
"## Shell Supervisor Preflight",
|
|
841
|
+
"No reusable listener or active shell is currently available.",
|
|
842
|
+
"First actions:",
|
|
843
|
+
"- If delegation needs a callback path, create one listener only.",
|
|
844
|
+
"- After launch, monitor it with bg_process status and promote on connection."
|
|
845
|
+
].join("\n");
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/engine/agent-tool/shell-supervisor-worker.ts
|
|
849
|
+
function buildShellCompletionPolicy() {
|
|
850
|
+
return [
|
|
851
|
+
"## Shell Supervisor Completion Policy",
|
|
852
|
+
'- Use status="waiting" when a listener or shell asset must be supervised for an external event.',
|
|
853
|
+
'- Use status="running" when the shell chain is active but still needs additional delegated follow-up.',
|
|
854
|
+
"- Report shell assets explicitly in assets/sessions so the next resume step reuses them.",
|
|
855
|
+
"- If a shell is upgraded or stabilized, mention that in summary and resumeHint."
|
|
856
|
+
].join("\n");
|
|
857
|
+
}
|
|
858
|
+
function uniq(values) {
|
|
859
|
+
return [...new Set(values)];
|
|
860
|
+
}
|
|
861
|
+
function getShellAssets(shellId, phase) {
|
|
862
|
+
const assets = [`shell:${shellId}`];
|
|
863
|
+
if (phase === "active_shell_stabilized" || phase === "post_exploitation_active") {
|
|
864
|
+
assets.push(`stabilized_shell:${shellId}`);
|
|
865
|
+
}
|
|
866
|
+
if (phase === "post_exploitation_active") {
|
|
867
|
+
assets.push(`post_exploitation_shell:${shellId}`);
|
|
868
|
+
}
|
|
869
|
+
return assets;
|
|
870
|
+
}
|
|
871
|
+
var ShellSupervisorWorker = class extends DefaultDelegatedWorkerExecutor {
|
|
872
|
+
prepareInput(input) {
|
|
873
|
+
return mergeWorkerContext(input, buildShellSupervisorPreflight());
|
|
874
|
+
}
|
|
875
|
+
buildPromptSections(input) {
|
|
876
|
+
const sections = super.buildPromptSections(input);
|
|
877
|
+
const shellContext = buildShellSupervisorContext();
|
|
878
|
+
if (shellContext) {
|
|
879
|
+
sections.unshift(shellContext);
|
|
880
|
+
}
|
|
881
|
+
sections.push(buildShellSupervisorLifecycleSection());
|
|
882
|
+
sections.push(buildShellSupervisorActionPlan());
|
|
883
|
+
sections.push(buildShellSupervisorPtyPlaybook());
|
|
884
|
+
sections.push(buildShellSupervisorCompletionGuidance());
|
|
885
|
+
sections.push(buildShellCompletionPolicy());
|
|
886
|
+
return sections;
|
|
887
|
+
}
|
|
888
|
+
finalizeResult(_input, result) {
|
|
889
|
+
const snapshot = getShellSupervisorLifecycleSnapshot();
|
|
890
|
+
if ((snapshot.phase === "active_shell_ready" || snapshot.phase === "active_shell_stabilizing" || snapshot.phase === "active_shell_stabilized" || snapshot.phase === "post_exploitation_active") && snapshot.activeShellId) {
|
|
891
|
+
const waitingOn = snapshot.phase === "post_exploitation_active" ? "controlled post-exploitation follow-up such as privilege escalation, credential harvesting, or pivot preparation" : snapshot.phase === "active_shell_stabilized" ? "controlled shell follow-up actions such as host enumeration or privilege escalation" : "shell follow-up actions such as PTY upgrade or host enumeration";
|
|
892
|
+
const resumeHint = snapshot.phase === "post_exploitation_active" ? `Reuse ${snapshot.activeShellId}, continue controlled post-exploitation through the existing shell, and avoid opening duplicate access paths.` : snapshot.phase === "active_shell_stabilized" ? `Reuse ${snapshot.activeShellId}, keep the stabilized shell as the primary access path, and continue controlled enumeration.` : snapshot.phase === "active_shell_stabilizing" ? `Reuse ${snapshot.activeShellId}, verify TERM/TTY quality, finish PTY stabilization, then continue enumeration.` : `Reuse ${snapshot.activeShellId}, validate shell quality, upgrade PTY if needed, then continue enumeration.`;
|
|
893
|
+
return {
|
|
894
|
+
...result,
|
|
895
|
+
status: result.status === "success" || result.status === "partial" ? "running" : result.status,
|
|
896
|
+
sessions: uniq([...result.sessions, snapshot.activeShellId]),
|
|
897
|
+
assets: uniq([...result.assets, ...getShellAssets(snapshot.activeShellId, snapshot.phase)]),
|
|
898
|
+
waitingOn: result.waitingOn ?? waitingOn,
|
|
899
|
+
resumeHint: result.resumeHint ?? resumeHint,
|
|
900
|
+
nextWorkerType: result.nextWorkerType ?? "shell-supervisor"
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (snapshot.phase === "listener_callback_detected" && snapshot.listenerId) {
|
|
904
|
+
return {
|
|
905
|
+
...result,
|
|
906
|
+
status: result.status === "success" || result.status === "partial" ? "running" : result.status,
|
|
907
|
+
assets: uniq([...result.assets, `listener:${snapshot.listenerId}`]),
|
|
908
|
+
waitingOn: result.waitingOn ?? "listener callback needs promotion and shell validation",
|
|
909
|
+
resumeHint: result.resumeHint ?? `Check ${snapshot.listenerId}, promote it to an active shell, then interact and stabilize it.`,
|
|
910
|
+
nextWorkerType: result.nextWorkerType ?? "shell-supervisor"
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
if (snapshot.phase === "listener_waiting" && snapshot.listenerId) {
|
|
914
|
+
return {
|
|
915
|
+
...result,
|
|
916
|
+
status: result.status === "success" || result.status === "partial" ? "waiting" : result.status,
|
|
917
|
+
assets: uniq([...result.assets, `listener:${snapshot.listenerId}`]),
|
|
918
|
+
waitingOn: result.waitingOn ?? "incoming reverse-shell callback",
|
|
919
|
+
resumeHint: result.resumeHint ?? `Poll ${snapshot.listenerId} with bg_process status, then promote immediately if a connection appears.`,
|
|
920
|
+
nextWorkerType: result.nextWorkerType ?? "shell-supervisor"
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/engine/agent-tool/worker-executor-factory.ts
|
|
928
|
+
function createDelegatedWorkerExecutor(input) {
|
|
929
|
+
if (input.workerType === "shell-supervisor") {
|
|
930
|
+
return new ShellSupervisorWorker();
|
|
931
|
+
}
|
|
932
|
+
if (input.workerType === "exploit") {
|
|
933
|
+
return new ExploitWorker();
|
|
934
|
+
}
|
|
935
|
+
if (input.workerType === "pwn") {
|
|
936
|
+
return new PwnWorker();
|
|
937
|
+
}
|
|
938
|
+
return new DefaultDelegatedWorkerExecutor();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/engine/agent-tool/agent-tool.ts
|
|
942
|
+
var TIMEOUT_RESULT = {
|
|
943
|
+
status: "failed",
|
|
944
|
+
summary: `Agent-tool timed out: max ${MAX_AGENT_TOOL_ITERATIONS} iterations reached without task_complete.`,
|
|
945
|
+
tried: [],
|
|
946
|
+
findings: [],
|
|
947
|
+
loot: [],
|
|
948
|
+
sessions: [],
|
|
949
|
+
assets: [],
|
|
950
|
+
suggestedNext: "Break the task into smaller sub-tasks and retry."
|
|
951
|
+
};
|
|
952
|
+
var AgentTool = class {
|
|
953
|
+
constructor(state, events, scopeGuard, approvalGate) {
|
|
954
|
+
this.state = state;
|
|
955
|
+
this.events = events;
|
|
956
|
+
this.scopeGuard = scopeGuard;
|
|
957
|
+
this.approvalGate = approvalGate;
|
|
958
|
+
}
|
|
959
|
+
async execute(input) {
|
|
960
|
+
const workerExecutor = createDelegatedWorkerExecutor(input);
|
|
961
|
+
const preparedInput = workerExecutor.prepareInput(input);
|
|
962
|
+
const completion = createCompletionBox();
|
|
963
|
+
const registry = new AgentRegistry(
|
|
964
|
+
this.state,
|
|
965
|
+
this.scopeGuard,
|
|
966
|
+
this.approvalGate,
|
|
967
|
+
this.events,
|
|
968
|
+
completion
|
|
969
|
+
);
|
|
970
|
+
const runner = new AgentRunner(
|
|
971
|
+
this.state,
|
|
972
|
+
this.events,
|
|
973
|
+
registry,
|
|
974
|
+
completion
|
|
975
|
+
);
|
|
976
|
+
const prompt = buildAgentPrompt(preparedInput, workerExecutor);
|
|
977
|
+
const loopResult = await runner.run(preparedInput.task, prompt);
|
|
978
|
+
if (completion.done && completion.result) {
|
|
979
|
+
return workerExecutor.finalizeResult(preparedInput, completion.result);
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
...TIMEOUT_RESULT,
|
|
983
|
+
tried: [`Reached ${loopResult.iterations} iterations without calling task_complete`]
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
export {
|
|
988
|
+
AgentTool
|
|
989
|
+
};
|