pentesting 0.14.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -21
- package/dist/main.js +3103 -0
- package/package.json +8 -8
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/chunk-5KIJPRTS.js +0 -832
- package/dist/chunk-M2IFHZDV.js +0 -602
- package/dist/index.js +0 -18815
- package/dist/skill-NGH4KQUH.js +0 -611
- package/dist/web-search-IOD4SUIR.js +0 -49
- package/src/agents/specs/crypto.yaml +0 -79
- package/src/agents/specs/default.yaml +0 -60
- package/src/agents/specs/exploit.yaml +0 -70
- package/src/agents/specs/privesc.yaml +0 -83
- package/src/agents/specs/recon.yaml +0 -65
- package/src/agents/specs/web.yaml +0 -73
- /package/dist/{index.d.ts → main.d.ts} +0 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,3103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/platform/tui/main.tsx
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import gradient from "gradient-string";
|
|
8
|
+
|
|
9
|
+
// src/platform/tui/app.tsx
|
|
10
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
11
|
+
import { Box as Box2, Text as Text2, useInput, useApp, Static } from "ink";
|
|
12
|
+
import TextInput from "ink-text-input";
|
|
13
|
+
import Spinner from "ink-spinner";
|
|
14
|
+
|
|
15
|
+
// src/shared/constants/agent.ts
|
|
16
|
+
var AGENT_LIMITS = {
|
|
17
|
+
/** Maximum number of iterations before stopping */
|
|
18
|
+
MAX_ITERATIONS: 50,
|
|
19
|
+
/** Maximum number of retries for failed tool execution */
|
|
20
|
+
MAX_RETRIES: 3,
|
|
21
|
+
/** Maximum tokens for LLM response */
|
|
22
|
+
MAX_TOKENS: 4096
|
|
23
|
+
};
|
|
24
|
+
var DISPLAY_LIMITS = {
|
|
25
|
+
/** Maximum characters for console output summary */
|
|
26
|
+
OUTPUT_SUMMARY: 200,
|
|
27
|
+
/** Maximum characters for thought display */
|
|
28
|
+
THOUGHT_SNIPPET: 100,
|
|
29
|
+
/** Maximum characters for tool input display */
|
|
30
|
+
TOOL_INPUT_SNIPPET: 100,
|
|
31
|
+
/** Maximum characters for final response preview */
|
|
32
|
+
RESPONSE_PREVIEW: 200,
|
|
33
|
+
/** Maximum items to display in summary lists */
|
|
34
|
+
SUMMARY_LIST_ITEMS: 10,
|
|
35
|
+
/** Maximum items to display in compact lists */
|
|
36
|
+
COMPACT_LIST_ITEMS: 7
|
|
37
|
+
};
|
|
38
|
+
var ID_LENGTH = 7;
|
|
39
|
+
var ID_RADIX = 36;
|
|
40
|
+
var APP_NAME = "Pentest AI";
|
|
41
|
+
var APP_VERSION = "0.15.0";
|
|
42
|
+
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
43
|
+
|
|
44
|
+
// src/shared/constants/protocol.ts
|
|
45
|
+
var PHASES = {
|
|
46
|
+
RECON: "recon",
|
|
47
|
+
VULN_ANALYSIS: "vulnerability_analysis",
|
|
48
|
+
EXPLOIT: "exploit",
|
|
49
|
+
POST_EXPLOIT: "post_exploitation",
|
|
50
|
+
PRIV_ESC: "privilege_escalation",
|
|
51
|
+
LATERAL: "lateral_movement",
|
|
52
|
+
PERSISTENCE: "persistence",
|
|
53
|
+
EXFIL: "exfiltration",
|
|
54
|
+
REPORT: "report"
|
|
55
|
+
};
|
|
56
|
+
var AGENT_ROLES = {
|
|
57
|
+
ORCHESTRATOR: "orchestrator",
|
|
58
|
+
RECON: "recon",
|
|
59
|
+
WEB: "web",
|
|
60
|
+
EXPLOTER: "exploit",
|
|
61
|
+
DATABASE: "database",
|
|
62
|
+
INFRA: "infra"
|
|
63
|
+
};
|
|
64
|
+
var SERVICES = {
|
|
65
|
+
HTTP: "http",
|
|
66
|
+
HTTPS: "https",
|
|
67
|
+
SSH: "ssh",
|
|
68
|
+
FTP: "ftp",
|
|
69
|
+
SMB: "smb",
|
|
70
|
+
AD: "ad",
|
|
71
|
+
MYSQL: "mysql",
|
|
72
|
+
MSSQL: "mssql",
|
|
73
|
+
POSTGRES: "postgresql",
|
|
74
|
+
MONGODB: "mongodb",
|
|
75
|
+
REDIS: "redis",
|
|
76
|
+
ELASTIC: "elasticsearch",
|
|
77
|
+
API: "api"
|
|
78
|
+
};
|
|
79
|
+
var SERVICE_CATEGORIES = {
|
|
80
|
+
NETWORK: "network",
|
|
81
|
+
WEB: "web",
|
|
82
|
+
DATABASE: "database",
|
|
83
|
+
AD: "ad",
|
|
84
|
+
EMAIL: "email",
|
|
85
|
+
REMOTE_ACCESS: "remote_access",
|
|
86
|
+
FILE_SHARING: "file_sharing",
|
|
87
|
+
CLOUD: "cloud",
|
|
88
|
+
CONTAINER: "container",
|
|
89
|
+
API: "api",
|
|
90
|
+
WIRELESS: "wireless",
|
|
91
|
+
ICS: "ics"
|
|
92
|
+
};
|
|
93
|
+
var APPROVAL_LEVELS = {
|
|
94
|
+
AUTO: "auto",
|
|
95
|
+
CONFIRM: "confirm",
|
|
96
|
+
REVIEW: "review",
|
|
97
|
+
BLOCK: "block"
|
|
98
|
+
};
|
|
99
|
+
var DANGER_LEVELS = {
|
|
100
|
+
PASSIVE: "passive",
|
|
101
|
+
ACTIVE: "active",
|
|
102
|
+
EXPLOIT: "exploit"
|
|
103
|
+
};
|
|
104
|
+
var TOOL_NAMES = {
|
|
105
|
+
// Basic Tools
|
|
106
|
+
RUN_CMD: "run_cmd",
|
|
107
|
+
READ_FILE: "read_file",
|
|
108
|
+
WRITE_FILE: "write_file",
|
|
109
|
+
// Mid-level Tools
|
|
110
|
+
PARSE_NMAP: "parse_nmap",
|
|
111
|
+
SEARCH_CVE: "search_cve",
|
|
112
|
+
WEB_SEARCH: "web_search",
|
|
113
|
+
EXTRACT_URLS: "extract_urls",
|
|
114
|
+
// High-level / Engine Tools
|
|
115
|
+
SPAWN_SUB: "spawn_sub",
|
|
116
|
+
ADD_FINDING: "add_finding",
|
|
117
|
+
UPDATE_TODO: "update_todo",
|
|
118
|
+
GET_STATE: "get_state",
|
|
119
|
+
SET_SCOPE: "set_scope",
|
|
120
|
+
ADD_TARGET: "add_target",
|
|
121
|
+
ASK_USER: "ask_user",
|
|
122
|
+
HELP: "help",
|
|
123
|
+
// Database Specialized
|
|
124
|
+
SQLMAP_BASIC: "sqlmap_basic",
|
|
125
|
+
SQLMAP_ADVANCED: "sqlmap_advanced",
|
|
126
|
+
MYSQL_ENUM: "mysql_enum",
|
|
127
|
+
POSTGRES_ENUM: "postgres_enum",
|
|
128
|
+
REDIS_ENUM: "redis_enum",
|
|
129
|
+
DB_BRUTE: "db_brute_common",
|
|
130
|
+
// Network Specialized
|
|
131
|
+
NMAP_QUICK: "nmap_quick",
|
|
132
|
+
NMAP_FULL: "nmap_full",
|
|
133
|
+
RUSTSCAN: "rustscan_fast",
|
|
134
|
+
// Web Specialized
|
|
135
|
+
HTTP_FINGERPRINT: "http_fingerprint",
|
|
136
|
+
WAF_DETECT: "waf_detect",
|
|
137
|
+
DIRSEARCH: "dirsearch",
|
|
138
|
+
NUCLEI_WEB: "nuclei_web",
|
|
139
|
+
// AD Specialized
|
|
140
|
+
BLOODHOUND_COLLECT: "bloodhound_collect",
|
|
141
|
+
KERBEROAST: "kerberoast",
|
|
142
|
+
LDAP_ENUM: "ldap_enum",
|
|
143
|
+
// API Specialized
|
|
144
|
+
API_DISCOVER: "api_discover",
|
|
145
|
+
GRAPHQL_INTROSPECT: "graphql_introspect",
|
|
146
|
+
SWAGGER_PARSE: "swagger_parse",
|
|
147
|
+
API_FUZZ: "api_fuzz",
|
|
148
|
+
// Cloud Specialized
|
|
149
|
+
AWS_S3_CHECK: "aws_s3_check",
|
|
150
|
+
CLOUD_META_CHECK: "cloud_meta_check",
|
|
151
|
+
// Container Specialized
|
|
152
|
+
DOCKER_PS: "docker_ps",
|
|
153
|
+
KUBE_GET_PODS: "kube_get_pods",
|
|
154
|
+
// Service Enumeration
|
|
155
|
+
SMTP_ENUM: "smtp_enum",
|
|
156
|
+
FTP_ENUM: "ftp_enum",
|
|
157
|
+
SMB_ENUM: "smb_enum",
|
|
158
|
+
MODBUS_ENUM: "modbus_enum",
|
|
159
|
+
SSH_ENUM: "ssh_enum",
|
|
160
|
+
RDP_ENUM: "rdp_enum",
|
|
161
|
+
WIFI_SCAN: "wifi_scan"
|
|
162
|
+
};
|
|
163
|
+
var TODO_STATUSES = {
|
|
164
|
+
PENDING: "pending",
|
|
165
|
+
IN_PROGRESS: "in_progress",
|
|
166
|
+
DONE: "done",
|
|
167
|
+
SKIPPED: "skipped"
|
|
168
|
+
};
|
|
169
|
+
var TODO_ACTIONS = {
|
|
170
|
+
ADD: "add",
|
|
171
|
+
COMPLETE: "complete",
|
|
172
|
+
REMOVE: "remove"
|
|
173
|
+
};
|
|
174
|
+
var CORE_BINARIES = {
|
|
175
|
+
NMAP: "nmap",
|
|
176
|
+
MASSCAN: "masscan",
|
|
177
|
+
NUCLEI: "nuclei",
|
|
178
|
+
NIKTO: "nikto",
|
|
179
|
+
FFUF: "ffuf",
|
|
180
|
+
GOBUSTER: "gobuster",
|
|
181
|
+
SQLMAP: "sqlmap",
|
|
182
|
+
METASPLOIT: "msfconsole"
|
|
183
|
+
};
|
|
184
|
+
var PRIORITIES = {
|
|
185
|
+
HIGH: "high",
|
|
186
|
+
MEDIUM: "medium",
|
|
187
|
+
LOW: "low"
|
|
188
|
+
};
|
|
189
|
+
var NOISE_LEVELS = {
|
|
190
|
+
LOW: "low",
|
|
191
|
+
MEDIUM: "medium",
|
|
192
|
+
HIGH: "high"
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/engine/state.ts
|
|
196
|
+
var SharedState = class {
|
|
197
|
+
data;
|
|
198
|
+
constructor() {
|
|
199
|
+
this.data = {
|
|
200
|
+
engagement: null,
|
|
201
|
+
targets: /* @__PURE__ */ new Map(),
|
|
202
|
+
findings: [],
|
|
203
|
+
loot: [],
|
|
204
|
+
todo: [],
|
|
205
|
+
actionLog: [],
|
|
206
|
+
currentPhase: PHASES.RECON
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Engagement
|
|
210
|
+
setEngagement(engagement) {
|
|
211
|
+
this.data.engagement = engagement;
|
|
212
|
+
}
|
|
213
|
+
getEngagement() {
|
|
214
|
+
return this.data.engagement;
|
|
215
|
+
}
|
|
216
|
+
// Scope
|
|
217
|
+
getScope() {
|
|
218
|
+
return this.data.engagement?.scope || null;
|
|
219
|
+
}
|
|
220
|
+
setScope(scope) {
|
|
221
|
+
if (this.data.engagement) {
|
|
222
|
+
this.data.engagement.scope = scope;
|
|
223
|
+
} else {
|
|
224
|
+
this.data.engagement = {
|
|
225
|
+
id: Math.random().toString(36).slice(2, 10),
|
|
226
|
+
name: "auto-engagement",
|
|
227
|
+
client: "unknown",
|
|
228
|
+
scope,
|
|
229
|
+
phase: this.data.currentPhase,
|
|
230
|
+
startedAt: Date.now()
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Targets
|
|
235
|
+
addTarget(target) {
|
|
236
|
+
this.data.targets.set(target.ip, target);
|
|
237
|
+
}
|
|
238
|
+
getTarget(ip) {
|
|
239
|
+
return this.data.targets.get(ip);
|
|
240
|
+
}
|
|
241
|
+
getAllTargets() {
|
|
242
|
+
return Array.from(this.data.targets.values());
|
|
243
|
+
}
|
|
244
|
+
getTargets() {
|
|
245
|
+
return this.data.targets;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Update target with service fingerprint
|
|
249
|
+
*/
|
|
250
|
+
addServiceFingerprint(ip, fingerprint) {
|
|
251
|
+
const target = this.getTarget(ip);
|
|
252
|
+
if (target) {
|
|
253
|
+
if (!target.services) {
|
|
254
|
+
target.services = [];
|
|
255
|
+
}
|
|
256
|
+
target.services.push(fingerprint);
|
|
257
|
+
target.primaryCategory = this.getMostCommonCategory(target.services);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get most common service category for target
|
|
262
|
+
*/
|
|
263
|
+
getMostCommonCategory(services) {
|
|
264
|
+
const counts = /* @__PURE__ */ new Map();
|
|
265
|
+
for (const s of services) {
|
|
266
|
+
counts.set(s.category, (counts.get(s.category) || 0) + 1);
|
|
267
|
+
}
|
|
268
|
+
let max = 0;
|
|
269
|
+
let result = SERVICE_CATEGORIES.NETWORK;
|
|
270
|
+
for (const [cat, count] of counts) {
|
|
271
|
+
if (count > max) {
|
|
272
|
+
max = count;
|
|
273
|
+
result = cat;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get targets by category
|
|
280
|
+
*/
|
|
281
|
+
getTargetsByCategory(category) {
|
|
282
|
+
return this.getAllTargets().filter(
|
|
283
|
+
(t) => t.primaryCategory === category || t.services?.some((s) => s.category === category)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get high-risk targets
|
|
288
|
+
*/
|
|
289
|
+
getHighRiskTargets() {
|
|
290
|
+
const highRisk = [SERVICE_CATEGORIES.ICS, SERVICE_CATEGORIES.AD, SERVICE_CATEGORIES.DATABASE, SERVICE_CATEGORIES.CLOUD, SERVICE_CATEGORIES.CONTAINER];
|
|
291
|
+
return this.getAllTargets().filter(
|
|
292
|
+
(t) => highRisk.includes(t.primaryCategory || SERVICE_CATEGORIES.NETWORK)
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
// Findings
|
|
296
|
+
addFinding(finding) {
|
|
297
|
+
this.data.findings.push(finding);
|
|
298
|
+
}
|
|
299
|
+
getFindings() {
|
|
300
|
+
return this.data.findings;
|
|
301
|
+
}
|
|
302
|
+
getFindingsBySeverity(severity) {
|
|
303
|
+
return this.data.findings.filter((f) => f.severity === severity);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get findings by category
|
|
307
|
+
*/
|
|
308
|
+
getFindingsByCategory(category) {
|
|
309
|
+
return this.data.findings.filter((f) => f.category === category);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get findings by attack tactic
|
|
313
|
+
*/
|
|
314
|
+
getFindingsByTactic(tactic) {
|
|
315
|
+
return this.data.findings.filter((f) => f.attackPattern === tactic);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get verified findings only
|
|
319
|
+
*/
|
|
320
|
+
getVerifiedFindings() {
|
|
321
|
+
return this.data.findings.filter((f) => f.verified);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get findings with available exploits
|
|
325
|
+
*/
|
|
326
|
+
getExploitableFindings() {
|
|
327
|
+
return this.data.findings.filter((f) => f.exploitAvailable === true);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get chainable findings (attack chains)
|
|
331
|
+
*/
|
|
332
|
+
getChainableFindings() {
|
|
333
|
+
const chains = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const f of this.data.findings) {
|
|
335
|
+
if (f.chainable && f.chainable.length > 0) {
|
|
336
|
+
chains.set(f.id, [f, ...this.data.findings.filter((o) => f.chainable?.includes(o.id))]);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return chains;
|
|
340
|
+
}
|
|
341
|
+
// Loot
|
|
342
|
+
addLoot(loot) {
|
|
343
|
+
this.data.loot.push(loot);
|
|
344
|
+
}
|
|
345
|
+
getLoot() {
|
|
346
|
+
return this.data.loot;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get loot by category
|
|
350
|
+
*/
|
|
351
|
+
getLootByCategory(category) {
|
|
352
|
+
return this.data.loot.filter((l) => l.category === category);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get loot by type
|
|
356
|
+
*/
|
|
357
|
+
getLootByType(type) {
|
|
358
|
+
return this.data.loot.filter((l) => l.type === type);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get crackable loot (hashes, tickets)
|
|
362
|
+
*/
|
|
363
|
+
getCrackableLoot() {
|
|
364
|
+
return this.data.loot.filter((l) => l.crackable === true && !l.cracked);
|
|
365
|
+
}
|
|
366
|
+
// TODO List Management
|
|
367
|
+
addTodo(content, priority = "medium") {
|
|
368
|
+
const id = Math.random().toString(ID_RADIX).substring(ID_LENGTH);
|
|
369
|
+
const todo = {
|
|
370
|
+
id,
|
|
371
|
+
content,
|
|
372
|
+
status: TODO_STATUSES.PENDING,
|
|
373
|
+
priority
|
|
374
|
+
};
|
|
375
|
+
this.data.todo.push(todo);
|
|
376
|
+
return id;
|
|
377
|
+
}
|
|
378
|
+
updateTodo(id, updates) {
|
|
379
|
+
const index = this.data.todo.findIndex((t) => t.id === id);
|
|
380
|
+
if (index !== -1) {
|
|
381
|
+
this.data.todo[index] = { ...this.data.todo[index], ...updates };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
getTodo() {
|
|
385
|
+
return this.data.todo;
|
|
386
|
+
}
|
|
387
|
+
completeTodo(id) {
|
|
388
|
+
this.updateTodo(id, { status: TODO_STATUSES.DONE });
|
|
389
|
+
}
|
|
390
|
+
// Action Log
|
|
391
|
+
logAction(action) {
|
|
392
|
+
const log = {
|
|
393
|
+
id: Math.random().toString(ID_RADIX).substring(ID_LENGTH),
|
|
394
|
+
timestamp: Date.now(),
|
|
395
|
+
...action
|
|
396
|
+
};
|
|
397
|
+
this.data.actionLog.push(log);
|
|
398
|
+
}
|
|
399
|
+
getRecentActions(count = DISPLAY_LIMITS.COMPACT_LIST_ITEMS) {
|
|
400
|
+
return this.data.actionLog.slice(-count);
|
|
401
|
+
}
|
|
402
|
+
getActionLog() {
|
|
403
|
+
return this.data.actionLog;
|
|
404
|
+
}
|
|
405
|
+
// Phase
|
|
406
|
+
setPhase(phase) {
|
|
407
|
+
this.data.currentPhase = phase;
|
|
408
|
+
}
|
|
409
|
+
getPhase() {
|
|
410
|
+
return this.data.currentPhase;
|
|
411
|
+
}
|
|
412
|
+
// Export to prompt (minimal summary)
|
|
413
|
+
toPrompt() {
|
|
414
|
+
const lines = [];
|
|
415
|
+
if (this.data.engagement) {
|
|
416
|
+
lines.push(`Engagement: ${this.data.engagement.name} (${this.data.engagement.client})`);
|
|
417
|
+
}
|
|
418
|
+
const scope = this.getScope();
|
|
419
|
+
if (scope) {
|
|
420
|
+
lines.push(`Scope: CIDR=[${scope.allowedCidrs.join(", ")}] Domains=[${scope.allowedDomains.join(", ")}]`);
|
|
421
|
+
}
|
|
422
|
+
const targets = this.getAllTargets();
|
|
423
|
+
if (targets.length > 0) {
|
|
424
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const t of targets) {
|
|
426
|
+
const cat = t.primaryCategory || SERVICE_CATEGORIES.NETWORK;
|
|
427
|
+
if (!byCategory.has(cat)) {
|
|
428
|
+
byCategory.set(cat, []);
|
|
429
|
+
}
|
|
430
|
+
byCategory.get(cat).push(t);
|
|
431
|
+
}
|
|
432
|
+
lines.push(`Targets (${targets.length}):`);
|
|
433
|
+
for (const [cat, catTargets] of byCategory) {
|
|
434
|
+
lines.push(` [${cat}] ${catTargets.length} hosts`);
|
|
435
|
+
for (const t of catTargets.slice(0, 3)) {
|
|
436
|
+
const ports = t.ports.map((p) => `${p.port}/${p.service}`).join(", ");
|
|
437
|
+
lines.push(` ${t.ip}${t.hostname ? ` (${t.hostname})` : ""}: ${ports || "unknown"}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const findings = this.getFindings();
|
|
442
|
+
if (findings.length > 0) {
|
|
443
|
+
const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
444
|
+
for (const f of findings) {
|
|
445
|
+
bySeverity[f.severity]++;
|
|
446
|
+
}
|
|
447
|
+
lines.push(`Findings: ${findings.length} total (crit:${bySeverity.critical} high:${bySeverity.high} medium:${bySeverity.medium})`);
|
|
448
|
+
const criticalHigh = findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
449
|
+
if (criticalHigh.length > 0) {
|
|
450
|
+
lines.push(` Critical/High:`);
|
|
451
|
+
for (const f of criticalHigh.slice(0, 5)) {
|
|
452
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.title} (${f.category || "general"})`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const loot = this.getLoot();
|
|
457
|
+
if (loot.length > 0) {
|
|
458
|
+
const byType = /* @__PURE__ */ new Map();
|
|
459
|
+
for (const l of loot) {
|
|
460
|
+
byType.set(l.type, (byType.get(l.type) || 0) + 1);
|
|
461
|
+
}
|
|
462
|
+
lines.push(`Loot: ${loot.length} items (${Array.from(byType.entries()).map(([t, c]) => `${t}:${c}`).join(", ")})`);
|
|
463
|
+
}
|
|
464
|
+
const todo = this.getTodo();
|
|
465
|
+
if (todo.length > 0) {
|
|
466
|
+
lines.push(`TODO (${todo.length}):`);
|
|
467
|
+
for (const t of todo.slice(0, DISPLAY_LIMITS.COMPACT_LIST_ITEMS)) {
|
|
468
|
+
const status = t.status === "done" ? "[x]" : t.status === "in_progress" ? "[->]" : "[ ]";
|
|
469
|
+
lines.push(` ${status} ${t.content} (${t.priority})`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
lines.push(`Phase: ${this.getPhase()}`);
|
|
473
|
+
return lines.join("\n");
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/engine/events.ts
|
|
478
|
+
var AgentEventEmitter = class {
|
|
479
|
+
listeners = /* @__PURE__ */ new Map();
|
|
480
|
+
/**
|
|
481
|
+
* Subscribe to events
|
|
482
|
+
*/
|
|
483
|
+
on(eventType, listener) {
|
|
484
|
+
if (!this.listeners.has(eventType)) {
|
|
485
|
+
this.listeners.set(eventType, []);
|
|
486
|
+
}
|
|
487
|
+
this.listeners.get(eventType).push(listener);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Subscribe to all events
|
|
491
|
+
*/
|
|
492
|
+
onAny(listener) {
|
|
493
|
+
const allKey = "*";
|
|
494
|
+
if (!this.listeners.has(allKey)) {
|
|
495
|
+
this.listeners.set(allKey, []);
|
|
496
|
+
}
|
|
497
|
+
this.listeners.get(allKey).push(listener);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Unsubscribe from events
|
|
501
|
+
*/
|
|
502
|
+
off(eventType, listener) {
|
|
503
|
+
const listeners = this.listeners.get(eventType);
|
|
504
|
+
if (listeners) {
|
|
505
|
+
const index = listeners.indexOf(listener);
|
|
506
|
+
if (index > -1) {
|
|
507
|
+
listeners.splice(index, 1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Emit an event
|
|
513
|
+
*/
|
|
514
|
+
emit(event) {
|
|
515
|
+
const listeners = this.listeners.get(event.type);
|
|
516
|
+
if (listeners) {
|
|
517
|
+
for (const listener of listeners) {
|
|
518
|
+
listener(event);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const anyListeners = this.listeners.get("*");
|
|
522
|
+
if (anyListeners) {
|
|
523
|
+
for (const listener of anyListeners) {
|
|
524
|
+
listener(event);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Remove all listeners
|
|
530
|
+
*/
|
|
531
|
+
removeAllListeners() {
|
|
532
|
+
this.listeners.clear();
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get listener count for an event type
|
|
536
|
+
*/
|
|
537
|
+
listenerCount(eventType) {
|
|
538
|
+
return this.listeners.get(eventType)?.length || 0;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/engine/scope.ts
|
|
543
|
+
var ScopeGuard = class {
|
|
544
|
+
constructor(state) {
|
|
545
|
+
this.state = state;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Check if a tool call is within allowed scope
|
|
549
|
+
*/
|
|
550
|
+
check(toolCall) {
|
|
551
|
+
const passiveTools = [
|
|
552
|
+
TOOL_NAMES.READ_FILE,
|
|
553
|
+
TOOL_NAMES.SEARCH_CVE,
|
|
554
|
+
TOOL_NAMES.PARSE_NMAP,
|
|
555
|
+
TOOL_NAMES.WEB_SEARCH,
|
|
556
|
+
TOOL_NAMES.ADD_FINDING,
|
|
557
|
+
TOOL_NAMES.UPDATE_TODO,
|
|
558
|
+
TOOL_NAMES.GET_STATE,
|
|
559
|
+
TOOL_NAMES.HELP
|
|
560
|
+
];
|
|
561
|
+
if (passiveTools.includes(toolCall.name)) {
|
|
562
|
+
return { allowed: true };
|
|
563
|
+
}
|
|
564
|
+
const scope = this.state.getScope();
|
|
565
|
+
if (!scope) {
|
|
566
|
+
return { allowed: false, reason: "No scope defined. Use /scope to set allowed targets." };
|
|
567
|
+
}
|
|
568
|
+
let command = "";
|
|
569
|
+
if (toolCall.name === TOOL_NAMES.RUN_CMD) {
|
|
570
|
+
command = String(toolCall.input.command || "");
|
|
571
|
+
}
|
|
572
|
+
if (!command) {
|
|
573
|
+
return { allowed: true };
|
|
574
|
+
}
|
|
575
|
+
const targets = this.extractTargets(command);
|
|
576
|
+
const violations = [];
|
|
577
|
+
for (const target of targets) {
|
|
578
|
+
if (!this.isTargetInScope(target)) {
|
|
579
|
+
violations.push(target);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (violations.length > 0) {
|
|
583
|
+
return {
|
|
584
|
+
allowed: false,
|
|
585
|
+
reason: `Target(s) outside approved scope: ${violations.join(", ")}`,
|
|
586
|
+
violations
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
return { allowed: true };
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Public helper to check if a specific target is in scope
|
|
593
|
+
*/
|
|
594
|
+
isTargetInScope(target) {
|
|
595
|
+
const scope = this.state.getScope();
|
|
596
|
+
if (!scope) return false;
|
|
597
|
+
if (scope.exclusions.some((e) => target === e || target.endsWith(`.${e}`))) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
if (scope.allowedDomains.some((d) => target === d)) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
if (scope.allowedCidrs.some((c) => this.matchesCidr(target, c))) {
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Simple CIDR matching (IP-only for now)
|
|
610
|
+
*/
|
|
611
|
+
matchesCidr(target, cidr) {
|
|
612
|
+
if (target === cidr) return true;
|
|
613
|
+
if (cidr.includes("/")) {
|
|
614
|
+
const [network, mask] = cidr.split("/");
|
|
615
|
+
if (mask === "24") {
|
|
616
|
+
const networkPrefix = network.split(".").slice(0, 3).join(".");
|
|
617
|
+
const targetPrefix = target.split(".").slice(0, 3).join(".");
|
|
618
|
+
return networkPrefix === targetPrefix;
|
|
619
|
+
}
|
|
620
|
+
if (mask === "8") {
|
|
621
|
+
const networkPrefix = network.split(".")[0];
|
|
622
|
+
const targetPrefix = target.split(".")[0];
|
|
623
|
+
return networkPrefix === targetPrefix;
|
|
624
|
+
}
|
|
625
|
+
if (mask === "16") {
|
|
626
|
+
const networkPrefix = network.split(".").slice(0, 2).join(".");
|
|
627
|
+
const targetPrefix = target.split(".").slice(0, 2).join(".");
|
|
628
|
+
return networkPrefix === targetPrefix;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return target === cidr;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Extract IPs and Domains from string
|
|
635
|
+
*/
|
|
636
|
+
extractTargets(text) {
|
|
637
|
+
const targets = /* @__PURE__ */ new Set();
|
|
638
|
+
const ipv4Regex = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
|
|
639
|
+
(text.match(ipv4Regex) || []).forEach((ip) => targets.add(ip));
|
|
640
|
+
const domainRegex = /\b[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}\b/gi;
|
|
641
|
+
(text.match(domainRegex) || []).forEach((d) => targets.add(d.toLowerCase()));
|
|
642
|
+
return targets;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/engine/approval.ts
|
|
647
|
+
var CATEGORY_APPROVAL = {
|
|
648
|
+
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
649
|
+
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
650
|
+
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
651
|
+
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
652
|
+
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
653
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
654
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
655
|
+
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
656
|
+
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
657
|
+
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
658
|
+
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
659
|
+
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
660
|
+
};
|
|
661
|
+
function getApprovalLevel(toolCall) {
|
|
662
|
+
if (toolCall.metadata?.approval) return toolCall.metadata.approval;
|
|
663
|
+
if (toolCall.metadata?.category) {
|
|
664
|
+
const categoryLevel = CATEGORY_APPROVAL[toolCall.metadata.category];
|
|
665
|
+
if (categoryLevel === APPROVAL_LEVELS.BLOCK) return APPROVAL_LEVELS.BLOCK;
|
|
666
|
+
}
|
|
667
|
+
const tool = toolCall.name;
|
|
668
|
+
const input = toolCall.input;
|
|
669
|
+
if (tool === TOOL_NAMES.RUN_CMD) {
|
|
670
|
+
const command = String(input.command || "").toLowerCase();
|
|
671
|
+
if (["whois", "dig", "curl -i"].some((p) => command.includes(p))) {
|
|
672
|
+
return APPROVAL_LEVELS.AUTO;
|
|
673
|
+
}
|
|
674
|
+
if ([CORE_BINARIES.NMAP, CORE_BINARIES.FFUF, CORE_BINARIES.NUCLEI].some((p) => command.includes(p))) {
|
|
675
|
+
if (toolCall.metadata?.category && CATEGORY_APPROVAL[toolCall.metadata.category] === APPROVAL_LEVELS.REVIEW) {
|
|
676
|
+
return APPROVAL_LEVELS.REVIEW;
|
|
677
|
+
}
|
|
678
|
+
return APPROVAL_LEVELS.CONFIRM;
|
|
679
|
+
}
|
|
680
|
+
if ([CORE_BINARIES.SQLMAP, CORE_BINARIES.METASPLOIT, "impacket"].some((p) => command.includes(p))) {
|
|
681
|
+
return APPROVAL_LEVELS.REVIEW;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const autoTools = [TOOL_NAMES.READ_FILE, TOOL_NAMES.SEARCH_CVE, TOOL_NAMES.PARSE_NMAP, TOOL_NAMES.WEB_SEARCH, TOOL_NAMES.ADD_FINDING];
|
|
685
|
+
if (autoTools.includes(tool)) return APPROVAL_LEVELS.AUTO;
|
|
686
|
+
return APPROVAL_LEVELS.CONFIRM;
|
|
687
|
+
}
|
|
688
|
+
var ApprovalGate = class {
|
|
689
|
+
constructor(autoApprove = false) {
|
|
690
|
+
this.autoApprove = autoApprove;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Set auto-approve mode
|
|
694
|
+
*/
|
|
695
|
+
setAutoApprove(enabled) {
|
|
696
|
+
this.autoApprove = enabled;
|
|
697
|
+
}
|
|
698
|
+
async request(toolCall) {
|
|
699
|
+
if (this.autoApprove) return { approved: true, reason: "Auto-approve enabled" };
|
|
700
|
+
const level = getApprovalLevel(toolCall);
|
|
701
|
+
if (level === APPROVAL_LEVELS.AUTO) return { approved: true };
|
|
702
|
+
if (level === APPROVAL_LEVELS.BLOCK) return { approved: false, reason: "Policy blocked execution" };
|
|
703
|
+
return { approved: true, reason: `Auto-approved in non-interactive: ${toolCall.name}` };
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// src/engine/tools-base.ts
|
|
708
|
+
import { execFileSync } from "child_process";
|
|
709
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
710
|
+
import { join } from "path";
|
|
711
|
+
var DEFAULT_COMMAND_TIMEOUT = 3e4;
|
|
712
|
+
async function runCommand(command, args = [], options = {}) {
|
|
713
|
+
try {
|
|
714
|
+
const parts = command.trim().split(/\s+/);
|
|
715
|
+
const execPath = parts[0];
|
|
716
|
+
const execArgs = [...parts.slice(1), ...args];
|
|
717
|
+
const result = execFileSync(execPath, execArgs, {
|
|
718
|
+
encoding: "utf-8",
|
|
719
|
+
stdio: "pipe",
|
|
720
|
+
timeout: DEFAULT_COMMAND_TIMEOUT,
|
|
721
|
+
...options
|
|
722
|
+
});
|
|
723
|
+
return {
|
|
724
|
+
success: true,
|
|
725
|
+
output: result.toString().trim()
|
|
726
|
+
};
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return {
|
|
729
|
+
success: false,
|
|
730
|
+
output: "",
|
|
731
|
+
error: error.message || String(error)
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function readFileContent(filePath) {
|
|
736
|
+
try {
|
|
737
|
+
if (!existsSync(filePath)) {
|
|
738
|
+
return {
|
|
739
|
+
success: false,
|
|
740
|
+
output: "",
|
|
741
|
+
error: `File not found: ${filePath}`
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const content = readFileSync(filePath, "utf-8");
|
|
745
|
+
return {
|
|
746
|
+
success: true,
|
|
747
|
+
output: content
|
|
748
|
+
};
|
|
749
|
+
} catch (error) {
|
|
750
|
+
return {
|
|
751
|
+
success: false,
|
|
752
|
+
output: "",
|
|
753
|
+
error: error.message || String(error)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function writeFileContent(filePath, content) {
|
|
758
|
+
try {
|
|
759
|
+
const dir = join(filePath, "..");
|
|
760
|
+
if (!existsSync(dir)) {
|
|
761
|
+
mkdirSync(dir, { recursive: true });
|
|
762
|
+
}
|
|
763
|
+
writeFileSync(filePath, content, "utf-8");
|
|
764
|
+
return {
|
|
765
|
+
success: true,
|
|
766
|
+
output: `Written to ${filePath}`
|
|
767
|
+
};
|
|
768
|
+
} catch (error) {
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
output: "",
|
|
772
|
+
error: error.message || String(error)
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/engine/tools-mid.ts
|
|
778
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
779
|
+
async function parseNmap(xmlPath) {
|
|
780
|
+
try {
|
|
781
|
+
const fileResult = await readFileContent(xmlPath);
|
|
782
|
+
if (!fileResult.success) {
|
|
783
|
+
return fileResult;
|
|
784
|
+
}
|
|
785
|
+
const xmlContent = fileResult.output;
|
|
786
|
+
const results = {
|
|
787
|
+
targets: [],
|
|
788
|
+
summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
|
|
789
|
+
};
|
|
790
|
+
const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
|
|
791
|
+
const hosts = xmlContent.match(hostRegex) || [];
|
|
792
|
+
for (const hostBlock of hosts) {
|
|
793
|
+
const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
|
|
794
|
+
const ip = ipMatch ? ipMatch[1] : "";
|
|
795
|
+
const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
|
|
796
|
+
const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
|
|
797
|
+
const ports = [];
|
|
798
|
+
const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
|
|
799
|
+
let portMatch;
|
|
800
|
+
while ((portMatch = portRegex.exec(hostBlock)) !== null) {
|
|
801
|
+
const protocol = portMatch[1];
|
|
802
|
+
const port = parseInt(portMatch[2]);
|
|
803
|
+
const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
|
|
804
|
+
const state = stateMatch ? stateMatch[1] : "";
|
|
805
|
+
const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
|
|
806
|
+
const service = serviceMatch ? serviceMatch[1] : void 0;
|
|
807
|
+
const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
|
|
808
|
+
if (state === "open") {
|
|
809
|
+
ports.push({ port, protocol, state, service, version });
|
|
810
|
+
results.summary.openPorts++;
|
|
811
|
+
if (service) results.summary.servicesFound++;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (ip) {
|
|
815
|
+
results.targets.push({ ip, hostname, ports });
|
|
816
|
+
results.summary.totalTargets++;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
output: JSON.stringify(results, null, 2)
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
success: false,
|
|
826
|
+
output: "",
|
|
827
|
+
error: error.message || String(error)
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async function searchCVE(service, version) {
|
|
832
|
+
try {
|
|
833
|
+
return searchExploitDB(service, version);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
return {
|
|
836
|
+
success: false,
|
|
837
|
+
output: "",
|
|
838
|
+
error: error.message || String(error)
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function searchExploitDB(service, version) {
|
|
843
|
+
try {
|
|
844
|
+
const query = version ? `${service} ${version}` : service;
|
|
845
|
+
try {
|
|
846
|
+
const output = execFileSync2("searchsploit", [query, "--color", "never"], {
|
|
847
|
+
encoding: "utf-8",
|
|
848
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
849
|
+
timeout: 1e4
|
|
850
|
+
});
|
|
851
|
+
const lines = output.trim().split("\n").slice(0, 20);
|
|
852
|
+
return {
|
|
853
|
+
success: true,
|
|
854
|
+
output: lines.join("\n") || `No exploits found for ${query}`
|
|
855
|
+
};
|
|
856
|
+
} catch (e) {
|
|
857
|
+
const stderr = String(e.stderr || "");
|
|
858
|
+
const stdout = String(e.stdout || "");
|
|
859
|
+
if (stderr.includes("No results")) {
|
|
860
|
+
return {
|
|
861
|
+
success: true,
|
|
862
|
+
output: `No exploits found for ${query}`
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
success: true,
|
|
867
|
+
output: stdout || `No exploits found for ${query}`
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
} catch (error) {
|
|
871
|
+
return {
|
|
872
|
+
success: false,
|
|
873
|
+
output: "",
|
|
874
|
+
error: error.message || String(error)
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function webSearch(query, engine = "duckduckgo") {
|
|
879
|
+
try {
|
|
880
|
+
let results = [];
|
|
881
|
+
if (engine === "duckduckgo") {
|
|
882
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
883
|
+
try {
|
|
884
|
+
const html = execFileSync2("curl", ["-s", "-L", "-A", "Mozilla/5.0", url], {
|
|
885
|
+
encoding: "utf-8",
|
|
886
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
887
|
+
timeout: 15e3
|
|
888
|
+
});
|
|
889
|
+
const titleRegex = /class="result__a"[^>]*>([^<]+)</g;
|
|
890
|
+
const matches = html.match(titleRegex) || [];
|
|
891
|
+
results = matches.map((m) => m.replace(/class="result__a"[^>]*>/, "")).slice(0, 10);
|
|
892
|
+
} catch (e) {
|
|
893
|
+
results = [`Web search failed: ${e.message}`];
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
results = ["Search engine not supported. Use: duckduckgo"];
|
|
897
|
+
}
|
|
898
|
+
const formatted = results.map((r, i) => `${i + 1}. ${r}`).join("\n");
|
|
899
|
+
return {
|
|
900
|
+
success: true,
|
|
901
|
+
output: formatted || `No results found for: ${query}`
|
|
902
|
+
};
|
|
903
|
+
} catch (error) {
|
|
904
|
+
return {
|
|
905
|
+
success: false,
|
|
906
|
+
output: "",
|
|
907
|
+
error: error.message || String(error)
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async function extractURLs(text) {
|
|
912
|
+
try {
|
|
913
|
+
const urlRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g;
|
|
914
|
+
const urls = text.match(urlRegex) || [];
|
|
915
|
+
const uniqueURLs = [...new Set(urls)];
|
|
916
|
+
return {
|
|
917
|
+
success: true,
|
|
918
|
+
output: JSON.stringify({
|
|
919
|
+
count: uniqueURLs.length,
|
|
920
|
+
urls: uniqueURLs
|
|
921
|
+
}, null, 2)
|
|
922
|
+
};
|
|
923
|
+
} catch (error) {
|
|
924
|
+
return {
|
|
925
|
+
success: false,
|
|
926
|
+
output: "",
|
|
927
|
+
error: error.message || String(error)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/engine/tools.ts
|
|
933
|
+
var ToolRegistry = class {
|
|
934
|
+
constructor(state, scopeGuard, approvalGate, events, subAgentExecutor) {
|
|
935
|
+
this.state = state;
|
|
936
|
+
this.scopeGuard = scopeGuard;
|
|
937
|
+
this.approvalGate = approvalGate;
|
|
938
|
+
this.events = events;
|
|
939
|
+
this.subAgentExecutor = subAgentExecutor;
|
|
940
|
+
this.registerLowLevelTools();
|
|
941
|
+
this.registerMidLevelTools();
|
|
942
|
+
this.registerHighLevelTools();
|
|
943
|
+
}
|
|
944
|
+
tools = /* @__PURE__ */ new Map();
|
|
945
|
+
/**
|
|
946
|
+
* Get all registered tools
|
|
947
|
+
*/
|
|
948
|
+
getAll() {
|
|
949
|
+
return Array.from(this.tools.values());
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Get tool by name
|
|
953
|
+
*/
|
|
954
|
+
getTool(name) {
|
|
955
|
+
return this.tools.get(name);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Execute tool with full pipeline
|
|
959
|
+
*/
|
|
960
|
+
async execute(toolCall) {
|
|
961
|
+
const tool = this.getTool(toolCall.name);
|
|
962
|
+
if (!tool) {
|
|
963
|
+
return {
|
|
964
|
+
success: false,
|
|
965
|
+
output: "",
|
|
966
|
+
error: `Unknown tool: ${toolCall.name}`
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const scopeResult = this.scopeGuard.check(toolCall);
|
|
970
|
+
if (!scopeResult.allowed) {
|
|
971
|
+
return {
|
|
972
|
+
success: false,
|
|
973
|
+
output: "",
|
|
974
|
+
error: scopeResult.reason
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
const approval = await this.approvalGate.request(toolCall);
|
|
978
|
+
if (!approval.approved) {
|
|
979
|
+
this.state.logAction({
|
|
980
|
+
tool: toolCall.name,
|
|
981
|
+
command: JSON.stringify(toolCall.input),
|
|
982
|
+
approval: "denied",
|
|
983
|
+
noiseLevel: "none",
|
|
984
|
+
outputSummary: approval.reason || "Denied"
|
|
985
|
+
});
|
|
986
|
+
return {
|
|
987
|
+
success: false,
|
|
988
|
+
output: "",
|
|
989
|
+
error: approval.reason || "Execution denied"
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const result = await tool.execute(toolCall.input);
|
|
993
|
+
this.state.logAction({
|
|
994
|
+
tool: toolCall.name,
|
|
995
|
+
command: JSON.stringify(toolCall.input),
|
|
996
|
+
approval: approval.approved ? "auto" : "user_confirmed",
|
|
997
|
+
noiseLevel: getNoiseLevel(toolCall),
|
|
998
|
+
outputSummary: result.output.slice(0, 200)
|
|
999
|
+
});
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Register low-level tools
|
|
1004
|
+
*/
|
|
1005
|
+
registerLowLevelTools() {
|
|
1006
|
+
this.tools.set(TOOL_NAMES.RUN_CMD, {
|
|
1007
|
+
name: TOOL_NAMES.RUN_CMD,
|
|
1008
|
+
description: "Execute shell command safely. Use for reconnaissance, scanning, and exploitation.",
|
|
1009
|
+
parameters: {
|
|
1010
|
+
command: { type: "string", description: 'The shell command to execute (e.g., "nmap -sV 10.0.0.1")' }
|
|
1011
|
+
},
|
|
1012
|
+
required: ["command"],
|
|
1013
|
+
execute: async (params) => {
|
|
1014
|
+
const command = params.command;
|
|
1015
|
+
return await runCommand(command, []);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
this.tools.set(TOOL_NAMES.READ_FILE, {
|
|
1019
|
+
name: TOOL_NAMES.READ_FILE,
|
|
1020
|
+
description: "Read local file content (logs, configs, evidence)",
|
|
1021
|
+
parameters: {
|
|
1022
|
+
path: { type: "string", description: "Path to the file" }
|
|
1023
|
+
},
|
|
1024
|
+
required: ["path"],
|
|
1025
|
+
execute: async (params) => {
|
|
1026
|
+
const filePath = params.path;
|
|
1027
|
+
return await readFileContent(filePath);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
this.tools.set(TOOL_NAMES.WRITE_FILE, {
|
|
1031
|
+
name: TOOL_NAMES.WRITE_FILE,
|
|
1032
|
+
description: "Write content to file (creates parent directories if needed)",
|
|
1033
|
+
parameters: {
|
|
1034
|
+
path: { type: "string", description: "Absolute path to the file" },
|
|
1035
|
+
content: { type: "string", description: "File content" }
|
|
1036
|
+
},
|
|
1037
|
+
required: ["path", "content"],
|
|
1038
|
+
execute: async (params) => {
|
|
1039
|
+
const filePath = params.path;
|
|
1040
|
+
const content = params.content;
|
|
1041
|
+
return await writeFileContent(filePath, content);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Register mid-level tools
|
|
1047
|
+
*/
|
|
1048
|
+
registerMidLevelTools() {
|
|
1049
|
+
this.tools.set(TOOL_NAMES.PARSE_NMAP, {
|
|
1050
|
+
name: TOOL_NAMES.PARSE_NMAP,
|
|
1051
|
+
description: "Parse nmap XML output to structured JSON",
|
|
1052
|
+
parameters: {
|
|
1053
|
+
path: { type: "string", description: "Path to nmap XML output file" }
|
|
1054
|
+
},
|
|
1055
|
+
required: ["path"],
|
|
1056
|
+
execute: async (params) => {
|
|
1057
|
+
const xmlPath = params.path;
|
|
1058
|
+
return await parseNmap(xmlPath);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
this.tools.set(TOOL_NAMES.SEARCH_CVE, {
|
|
1062
|
+
name: TOOL_NAMES.SEARCH_CVE,
|
|
1063
|
+
description: "Search CVE and Exploit-DB for vulnerabilities",
|
|
1064
|
+
parameters: {
|
|
1065
|
+
service: { type: "string", description: 'Service name (e.g., "apache", "ssh")' },
|
|
1066
|
+
version: { type: "string", description: "Version number (optional)" }
|
|
1067
|
+
},
|
|
1068
|
+
required: ["service"],
|
|
1069
|
+
execute: async (params) => {
|
|
1070
|
+
const service = params.service;
|
|
1071
|
+
const version = params.version;
|
|
1072
|
+
return await searchCVE(service, version);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
this.tools.set(TOOL_NAMES.WEB_SEARCH, {
|
|
1076
|
+
name: TOOL_NAMES.WEB_SEARCH,
|
|
1077
|
+
description: "Search web for reconnaissance info",
|
|
1078
|
+
parameters: {
|
|
1079
|
+
query: { type: "string", description: "Search query" }
|
|
1080
|
+
},
|
|
1081
|
+
required: ["query"],
|
|
1082
|
+
execute: async (params) => {
|
|
1083
|
+
const query = params.query;
|
|
1084
|
+
const engine = params.engine || "duckduckgo";
|
|
1085
|
+
return await webSearch(query, engine);
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
this.tools.set(TOOL_NAMES.EXTRACT_URLS, {
|
|
1089
|
+
name: TOOL_NAMES.EXTRACT_URLS,
|
|
1090
|
+
description: "Extract and deduplicate URLs from text",
|
|
1091
|
+
parameters: {
|
|
1092
|
+
text: { type: "string", description: "Input text containing URLs" }
|
|
1093
|
+
},
|
|
1094
|
+
required: ["text"],
|
|
1095
|
+
execute: async (params) => {
|
|
1096
|
+
const text = params.text;
|
|
1097
|
+
return await extractURLs(text);
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Register high-level tools
|
|
1103
|
+
*/
|
|
1104
|
+
registerHighLevelTools() {
|
|
1105
|
+
this.tools.set(TOOL_NAMES.SPAWN_SUB, {
|
|
1106
|
+
name: TOOL_NAMES.SPAWN_SUB,
|
|
1107
|
+
description: "Spawn a sub-agent with specialized prompt",
|
|
1108
|
+
parameters: {
|
|
1109
|
+
task: { type: "string", description: "Specific task for sub-agent" },
|
|
1110
|
+
agent_type: { type: "string", description: "Sub-agent type (recon, web, exploit)" }
|
|
1111
|
+
},
|
|
1112
|
+
required: ["task"],
|
|
1113
|
+
execute: async (params) => {
|
|
1114
|
+
const task = params.task;
|
|
1115
|
+
const agentType = params.agent_type || AGENT_ROLES.RECON;
|
|
1116
|
+
if (!this.subAgentExecutor) {
|
|
1117
|
+
return { success: false, output: "SubAgentExecutor not initialized in registry" };
|
|
1118
|
+
}
|
|
1119
|
+
const systemPrompt = this.getSpecializedPrompt(agentType, task);
|
|
1120
|
+
const result = await this.subAgentExecutor.executeSubTask(task, systemPrompt);
|
|
1121
|
+
return {
|
|
1122
|
+
success: result.completed,
|
|
1123
|
+
output: result.output
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
this.tools.set(TOOL_NAMES.ADD_FINDING, {
|
|
1128
|
+
name: TOOL_NAMES.ADD_FINDING,
|
|
1129
|
+
description: "Add a security finding",
|
|
1130
|
+
parameters: {
|
|
1131
|
+
title: { type: "string", description: "Finding title" },
|
|
1132
|
+
severity: { type: "string", description: "Severity (info, low, medium, high, critical)" },
|
|
1133
|
+
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" },
|
|
1134
|
+
description: { type: "string", description: "Finding detail" }
|
|
1135
|
+
},
|
|
1136
|
+
required: ["title", "severity"],
|
|
1137
|
+
execute: async (params) => {
|
|
1138
|
+
this.state.addFinding({
|
|
1139
|
+
id: Math.random().toString(ID_RADIX).substring(ID_LENGTH),
|
|
1140
|
+
title: params.title,
|
|
1141
|
+
severity: params.severity,
|
|
1142
|
+
affected: params.affected || [],
|
|
1143
|
+
description: params.description || "",
|
|
1144
|
+
evidence: [],
|
|
1145
|
+
verified: false,
|
|
1146
|
+
remediation: "",
|
|
1147
|
+
foundAt: Date.now()
|
|
1148
|
+
});
|
|
1149
|
+
return {
|
|
1150
|
+
success: true,
|
|
1151
|
+
output: `Finding added: ${params.title}`
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
this.tools.set(TOOL_NAMES.UPDATE_TODO, {
|
|
1156
|
+
name: TOOL_NAMES.UPDATE_TODO,
|
|
1157
|
+
description: "Update plan / TODO list",
|
|
1158
|
+
parameters: {
|
|
1159
|
+
action: { type: "string", description: "add, complete" },
|
|
1160
|
+
content: { type: "string", description: "Task content" }
|
|
1161
|
+
},
|
|
1162
|
+
required: ["action"],
|
|
1163
|
+
execute: async (params) => {
|
|
1164
|
+
if (params.action === TODO_ACTIONS.ADD) {
|
|
1165
|
+
this.state.addTodo(params.content, params.priority);
|
|
1166
|
+
} else if (params.action === TODO_ACTIONS.COMPLETE) {
|
|
1167
|
+
this.state.completeTodo(params.id);
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
success: true,
|
|
1171
|
+
output: "TODO updated"
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
this.tools.set(TOOL_NAMES.GET_STATE, {
|
|
1176
|
+
name: TOOL_NAMES.GET_STATE,
|
|
1177
|
+
description: "Get current engagement state summary",
|
|
1178
|
+
parameters: {},
|
|
1179
|
+
execute: async (params) => {
|
|
1180
|
+
return {
|
|
1181
|
+
success: true,
|
|
1182
|
+
output: this.state.toPrompt()
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
this.tools.set(TOOL_NAMES.SET_SCOPE, {
|
|
1187
|
+
name: TOOL_NAMES.SET_SCOPE,
|
|
1188
|
+
description: "Set engagement scope",
|
|
1189
|
+
parameters: {
|
|
1190
|
+
allowed: { type: "array", items: { type: "string" }, description: "Allowed CIDRs or domains" },
|
|
1191
|
+
exclusions: { type: "array", items: { type: "string" }, description: "Excluded targets" }
|
|
1192
|
+
},
|
|
1193
|
+
required: ["allowed"],
|
|
1194
|
+
execute: async (params) => {
|
|
1195
|
+
const allowed = params.allowed || [];
|
|
1196
|
+
const exclusions = params.exclusions || [];
|
|
1197
|
+
this.state.setScope({
|
|
1198
|
+
allowedCidrs: allowed.filter((a) => a.includes("/")),
|
|
1199
|
+
allowedDomains: allowed.filter((a) => !a.includes("/")),
|
|
1200
|
+
exclusions,
|
|
1201
|
+
noDOS: true,
|
|
1202
|
+
noSocial: true
|
|
1203
|
+
});
|
|
1204
|
+
return {
|
|
1205
|
+
success: true,
|
|
1206
|
+
output: `Scope set: ${allowed.length} targets`
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
this.tools.set(TOOL_NAMES.ADD_TARGET, {
|
|
1211
|
+
name: TOOL_NAMES.ADD_TARGET,
|
|
1212
|
+
description: "Add target to engagement",
|
|
1213
|
+
parameters: {
|
|
1214
|
+
ip: { type: "string", description: "IP address" },
|
|
1215
|
+
hostname: { type: "string", description: "Hostname (optional)" }
|
|
1216
|
+
},
|
|
1217
|
+
required: ["ip"],
|
|
1218
|
+
execute: async (params) => {
|
|
1219
|
+
const ip = params.ip;
|
|
1220
|
+
const hostname = params.hostname;
|
|
1221
|
+
this.state.addTarget({
|
|
1222
|
+
ip,
|
|
1223
|
+
hostname,
|
|
1224
|
+
ports: [],
|
|
1225
|
+
tags: [],
|
|
1226
|
+
firstSeen: Date.now()
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
success: true,
|
|
1230
|
+
output: `Target added: ${ip}`
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
this.tools.set(TOOL_NAMES.ASK_USER, {
|
|
1235
|
+
name: TOOL_NAMES.ASK_USER,
|
|
1236
|
+
description: "Ask the user a question and wait for input. Use when you need clarification, authorization, or additional information.",
|
|
1237
|
+
parameters: {
|
|
1238
|
+
question: { type: "string", description: "Question to ask" }
|
|
1239
|
+
},
|
|
1240
|
+
required: ["question"],
|
|
1241
|
+
execute: async (params) => {
|
|
1242
|
+
const question = params.question;
|
|
1243
|
+
return {
|
|
1244
|
+
success: true,
|
|
1245
|
+
output: `[ASK_USER] ${question}
|
|
1246
|
+
(Waiting for user response via TUI input)`
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Get specialized prompt for sub-agent type
|
|
1253
|
+
* Opus4.7: Agents are prompts, not code
|
|
1254
|
+
*/
|
|
1255
|
+
getSpecializedPrompt(agentType, task) {
|
|
1256
|
+
const prompts = {
|
|
1257
|
+
[AGENT_ROLES.RECON]: `You are a RECONNAISSANCE specialist.
|
|
1258
|
+
Your task: ${task}
|
|
1259
|
+
Focus on: Information gathering, OSINT, passive reconnaissance, target discovery
|
|
1260
|
+
Return: Structured target and port information for the main agent.`,
|
|
1261
|
+
[AGENT_ROLES.WEB]: `You are a WEB APPLICATION specialist.
|
|
1262
|
+
Your task: ${task}
|
|
1263
|
+
Focus on: Web app discovery, enumeration, vulnerability scanning
|
|
1264
|
+
Return: Web application structure, endpoints, vulnerabilities found.`,
|
|
1265
|
+
[AGENT_ROLES.EXPLOTER]: `You are an EXPLOITATION specialist.
|
|
1266
|
+
Your task: ${task}
|
|
1267
|
+
Focus on: Exploit research, proof-of-concept testing
|
|
1268
|
+
Return: Exploit results, shell access, credentials obtained.`
|
|
1269
|
+
};
|
|
1270
|
+
return prompts[agentType] || prompts[AGENT_ROLES.RECON];
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
function getNoiseLevel(toolCall) {
|
|
1274
|
+
if (toolCall.name === TOOL_NAMES.RUN_CMD) {
|
|
1275
|
+
const command = String(toolCall.input.command || "").toLowerCase();
|
|
1276
|
+
if (command.includes(CORE_BINARIES.NMAP) || command.includes(CORE_BINARIES.MASSCAN)) return NOISE_LEVELS.HIGH;
|
|
1277
|
+
if (command.includes(CORE_BINARIES.NUCLEI) || command.includes(CORE_BINARIES.NIKTO)) return NOISE_LEVELS.HIGH;
|
|
1278
|
+
if (command.includes(CORE_BINARIES.FFUF) || command.includes(CORE_BINARIES.GOBUSTER)) return NOISE_LEVELS.MEDIUM;
|
|
1279
|
+
}
|
|
1280
|
+
return NOISE_LEVELS.LOW;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/domains/registry.ts
|
|
1284
|
+
import { join as join2, dirname } from "path";
|
|
1285
|
+
import { fileURLToPath } from "url";
|
|
1286
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1287
|
+
var DOMAINS = {
|
|
1288
|
+
[SERVICE_CATEGORIES.NETWORK]: {
|
|
1289
|
+
id: SERVICE_CATEGORIES.NETWORK,
|
|
1290
|
+
name: "Network Infrastructure",
|
|
1291
|
+
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
1292
|
+
promptPath: join2(__dirname, "network/prompt.md")
|
|
1293
|
+
},
|
|
1294
|
+
[SERVICE_CATEGORIES.WEB]: {
|
|
1295
|
+
id: SERVICE_CATEGORIES.WEB,
|
|
1296
|
+
name: "Web Application",
|
|
1297
|
+
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
1298
|
+
promptPath: join2(__dirname, "web/prompt.md")
|
|
1299
|
+
},
|
|
1300
|
+
[SERVICE_CATEGORIES.DATABASE]: {
|
|
1301
|
+
id: SERVICE_CATEGORIES.DATABASE,
|
|
1302
|
+
name: "Database Security",
|
|
1303
|
+
description: "SQL injection, database enumeration, and data extraction.",
|
|
1304
|
+
promptPath: join2(__dirname, "database/prompt.md")
|
|
1305
|
+
},
|
|
1306
|
+
[SERVICE_CATEGORIES.AD]: {
|
|
1307
|
+
id: SERVICE_CATEGORIES.AD,
|
|
1308
|
+
name: "Active Directory",
|
|
1309
|
+
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
1310
|
+
promptPath: join2(__dirname, "ad/prompt.md")
|
|
1311
|
+
},
|
|
1312
|
+
[SERVICE_CATEGORIES.EMAIL]: {
|
|
1313
|
+
id: SERVICE_CATEGORIES.EMAIL,
|
|
1314
|
+
name: "Email Services",
|
|
1315
|
+
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
1316
|
+
promptPath: join2(__dirname, "email/prompt.md")
|
|
1317
|
+
},
|
|
1318
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
1319
|
+
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1320
|
+
name: "Remote Access",
|
|
1321
|
+
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
1322
|
+
promptPath: join2(__dirname, "remote-access/prompt.md")
|
|
1323
|
+
},
|
|
1324
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
1325
|
+
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1326
|
+
name: "File Sharing",
|
|
1327
|
+
description: "SMB, NFS, FTP and shared resource security.",
|
|
1328
|
+
promptPath: join2(__dirname, "file-sharing/prompt.md")
|
|
1329
|
+
},
|
|
1330
|
+
[SERVICE_CATEGORIES.CLOUD]: {
|
|
1331
|
+
id: SERVICE_CATEGORIES.CLOUD,
|
|
1332
|
+
name: "Cloud Infrastructure",
|
|
1333
|
+
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
1334
|
+
promptPath: join2(__dirname, "cloud/prompt.md")
|
|
1335
|
+
},
|
|
1336
|
+
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
1337
|
+
id: SERVICE_CATEGORIES.CONTAINER,
|
|
1338
|
+
name: "Container Systems",
|
|
1339
|
+
description: "Docker and Kubernetes security testing.",
|
|
1340
|
+
promptPath: join2(__dirname, "container/prompt.md")
|
|
1341
|
+
},
|
|
1342
|
+
[SERVICE_CATEGORIES.API]: {
|
|
1343
|
+
id: SERVICE_CATEGORIES.API,
|
|
1344
|
+
name: "API Security",
|
|
1345
|
+
description: "REST, GraphQL, and SOAP API security testing.",
|
|
1346
|
+
promptPath: join2(__dirname, "api/prompt.md")
|
|
1347
|
+
},
|
|
1348
|
+
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
1349
|
+
id: SERVICE_CATEGORIES.WIRELESS,
|
|
1350
|
+
name: "Wireless Networks",
|
|
1351
|
+
description: "WiFi and Bluetooth security testing.",
|
|
1352
|
+
promptPath: join2(__dirname, "wireless/prompt.md")
|
|
1353
|
+
},
|
|
1354
|
+
[SERVICE_CATEGORIES.ICS]: {
|
|
1355
|
+
id: SERVICE_CATEGORIES.ICS,
|
|
1356
|
+
name: "Industrial Systems",
|
|
1357
|
+
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
1358
|
+
promptPath: join2(__dirname, "ics/prompt.md")
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// src/engine/tools-registry.ts
|
|
1363
|
+
var PORT_CATEGORY_MAP = {
|
|
1364
|
+
// Web
|
|
1365
|
+
80: SERVICE_CATEGORIES.WEB,
|
|
1366
|
+
443: SERVICE_CATEGORIES.WEB,
|
|
1367
|
+
8080: SERVICE_CATEGORIES.WEB,
|
|
1368
|
+
8443: SERVICE_CATEGORIES.WEB,
|
|
1369
|
+
8e3: SERVICE_CATEGORIES.WEB,
|
|
1370
|
+
8888: SERVICE_CATEGORIES.WEB,
|
|
1371
|
+
// Database
|
|
1372
|
+
1433: SERVICE_CATEGORIES.DATABASE,
|
|
1373
|
+
// MSSQL
|
|
1374
|
+
3306: SERVICE_CATEGORIES.DATABASE,
|
|
1375
|
+
// MySQL
|
|
1376
|
+
5432: SERVICE_CATEGORIES.DATABASE,
|
|
1377
|
+
// PostgreSQL
|
|
1378
|
+
27017: SERVICE_CATEGORIES.DATABASE,
|
|
1379
|
+
// MongoDB
|
|
1380
|
+
6379: SERVICE_CATEGORIES.DATABASE,
|
|
1381
|
+
// Redis
|
|
1382
|
+
9042: SERVICE_CATEGORIES.DATABASE,
|
|
1383
|
+
// Cassandra
|
|
1384
|
+
9200: SERVICE_CATEGORIES.DATABASE,
|
|
1385
|
+
// Elasticsearch
|
|
1386
|
+
// Active Directory
|
|
1387
|
+
88: SERVICE_CATEGORIES.AD,
|
|
1388
|
+
// Kerberos
|
|
1389
|
+
389: SERVICE_CATEGORIES.AD,
|
|
1390
|
+
// LDAP
|
|
1391
|
+
636: SERVICE_CATEGORIES.AD,
|
|
1392
|
+
// LDAPS
|
|
1393
|
+
3268: SERVICE_CATEGORIES.AD,
|
|
1394
|
+
// LDAP GC
|
|
1395
|
+
3269: SERVICE_CATEGORIES.AD,
|
|
1396
|
+
// LDAP GC SSL
|
|
1397
|
+
445: SERVICE_CATEGORIES.AD,
|
|
1398
|
+
// SMB (also file_sharing)
|
|
1399
|
+
// Email
|
|
1400
|
+
25: SERVICE_CATEGORIES.EMAIL,
|
|
1401
|
+
// SMTP
|
|
1402
|
+
587: SERVICE_CATEGORIES.EMAIL,
|
|
1403
|
+
// SMTP Submission
|
|
1404
|
+
465: SERVICE_CATEGORIES.EMAIL,
|
|
1405
|
+
// SMTPS
|
|
1406
|
+
110: SERVICE_CATEGORIES.EMAIL,
|
|
1407
|
+
// POP3
|
|
1408
|
+
143: SERVICE_CATEGORIES.EMAIL,
|
|
1409
|
+
// IMAP
|
|
1410
|
+
993: SERVICE_CATEGORIES.EMAIL,
|
|
1411
|
+
// IMAPS
|
|
1412
|
+
995: SERVICE_CATEGORIES.EMAIL,
|
|
1413
|
+
// POP3S
|
|
1414
|
+
// Remote Access
|
|
1415
|
+
22: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1416
|
+
// SSH
|
|
1417
|
+
3389: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1418
|
+
// RDP
|
|
1419
|
+
5900: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1420
|
+
// VNC
|
|
1421
|
+
5901: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1422
|
+
// VNC
|
|
1423
|
+
// File Sharing
|
|
1424
|
+
21: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1425
|
+
// FTP
|
|
1426
|
+
139: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1427
|
+
// NetBIOS
|
|
1428
|
+
2049: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1429
|
+
// NFS
|
|
1430
|
+
111: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1431
|
+
// RPC
|
|
1432
|
+
// Container
|
|
1433
|
+
2375: SERVICE_CATEGORIES.CONTAINER,
|
|
1434
|
+
// Docker API
|
|
1435
|
+
2376: SERVICE_CATEGORIES.CONTAINER,
|
|
1436
|
+
// Docker API TLS
|
|
1437
|
+
5e3: SERVICE_CATEGORIES.CONTAINER,
|
|
1438
|
+
// Docker Registry
|
|
1439
|
+
// Cloud (detected via banner, common on 443)
|
|
1440
|
+
// API
|
|
1441
|
+
3e3: SERVICE_CATEGORIES.API,
|
|
1442
|
+
5001: SERVICE_CATEGORIES.API,
|
|
1443
|
+
8001: SERVICE_CATEGORIES.API,
|
|
1444
|
+
// Wireless
|
|
1445
|
+
// ICS
|
|
1446
|
+
502: SERVICE_CATEGORIES.ICS,
|
|
1447
|
+
// Modbus
|
|
1448
|
+
102: SERVICE_CATEGORIES.ICS,
|
|
1449
|
+
// ISO-TSAP
|
|
1450
|
+
2e4: SERVICE_CATEGORIES.ICS,
|
|
1451
|
+
// DNP3
|
|
1452
|
+
44818: SERVICE_CATEGORIES.ICS
|
|
1453
|
+
// Ethernet/IP
|
|
1454
|
+
};
|
|
1455
|
+
var SERVICE_CATEGORY_MAP = {
|
|
1456
|
+
// Web
|
|
1457
|
+
[SERVICES.HTTP]: SERVICE_CATEGORIES.WEB,
|
|
1458
|
+
[SERVICES.HTTPS]: SERVICE_CATEGORIES.WEB,
|
|
1459
|
+
"http-proxy": SERVICE_CATEGORIES.WEB,
|
|
1460
|
+
"ssl/http": SERVICE_CATEGORIES.WEB,
|
|
1461
|
+
"nginx": SERVICE_CATEGORIES.WEB,
|
|
1462
|
+
"apache": SERVICE_CATEGORIES.WEB,
|
|
1463
|
+
"iis": SERVICE_CATEGORIES.WEB,
|
|
1464
|
+
// Database
|
|
1465
|
+
[SERVICES.MYSQL]: SERVICE_CATEGORIES.DATABASE,
|
|
1466
|
+
[SERVICES.MSSQL]: SERVICE_CATEGORIES.DATABASE,
|
|
1467
|
+
[SERVICES.POSTGRES]: SERVICE_CATEGORIES.DATABASE,
|
|
1468
|
+
[SERVICES.MONGODB]: SERVICE_CATEGORIES.DATABASE,
|
|
1469
|
+
[SERVICES.REDIS]: SERVICE_CATEGORIES.DATABASE,
|
|
1470
|
+
[SERVICES.ELASTIC]: SERVICE_CATEGORIES.DATABASE,
|
|
1471
|
+
"cassandra": SERVICE_CATEGORIES.DATABASE,
|
|
1472
|
+
// Active Directory
|
|
1473
|
+
"kerberos": SERVICE_CATEGORIES.AD,
|
|
1474
|
+
"ldap": SERVICE_CATEGORIES.AD,
|
|
1475
|
+
"ldaps": SERVICE_CATEGORIES.AD,
|
|
1476
|
+
"microsoft-ds": SERVICE_CATEGORIES.AD,
|
|
1477
|
+
"netbios-ssn": SERVICE_CATEGORIES.AD,
|
|
1478
|
+
// Email
|
|
1479
|
+
"smtp": SERVICE_CATEGORIES.EMAIL,
|
|
1480
|
+
"pop3": SERVICE_CATEGORIES.EMAIL,
|
|
1481
|
+
"imap": SERVICE_CATEGORIES.EMAIL,
|
|
1482
|
+
// Remote Access
|
|
1483
|
+
[SERVICES.SSH]: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1484
|
+
"ms-wbt-server": SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1485
|
+
// RDP
|
|
1486
|
+
"vnc": SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1487
|
+
// File Sharing
|
|
1488
|
+
[SERVICES.FTP]: SERVICE_CATEGORIES.FILE_SHARING,
|
|
1489
|
+
"ftp-data": SERVICE_CATEGORIES.FILE_SHARING,
|
|
1490
|
+
"nfs": SERVICE_CATEGORIES.FILE_SHARING,
|
|
1491
|
+
"rpcbind": SERVICE_CATEGORIES.FILE_SHARING,
|
|
1492
|
+
// Container
|
|
1493
|
+
"docker": SERVICE_CATEGORIES.CONTAINER,
|
|
1494
|
+
"docker-swap": SERVICE_CATEGORIES.CONTAINER,
|
|
1495
|
+
// Cloud APIs
|
|
1496
|
+
"aws-s3": SERVICE_CATEGORIES.CLOUD,
|
|
1497
|
+
"azure-storage": SERVICE_CATEGORIES.CLOUD,
|
|
1498
|
+
// Wireless
|
|
1499
|
+
// ICS
|
|
1500
|
+
"modbus": SERVICE_CATEGORIES.ICS,
|
|
1501
|
+
"iso-tsap": SERVICE_CATEGORIES.ICS,
|
|
1502
|
+
"dnp3": SERVICE_CATEGORIES.ICS,
|
|
1503
|
+
"enip": SERVICE_CATEGORIES.ICS
|
|
1504
|
+
};
|
|
1505
|
+
var CATEGORY_APPROVAL2 = {
|
|
1506
|
+
[SERVICE_CATEGORIES.NETWORK]: APPROVAL_LEVELS.CONFIRM,
|
|
1507
|
+
[SERVICE_CATEGORIES.WEB]: APPROVAL_LEVELS.CONFIRM,
|
|
1508
|
+
[SERVICE_CATEGORIES.DATABASE]: APPROVAL_LEVELS.REVIEW,
|
|
1509
|
+
[SERVICE_CATEGORIES.AD]: APPROVAL_LEVELS.REVIEW,
|
|
1510
|
+
[SERVICE_CATEGORIES.EMAIL]: APPROVAL_LEVELS.CONFIRM,
|
|
1511
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: APPROVAL_LEVELS.REVIEW,
|
|
1512
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: APPROVAL_LEVELS.CONFIRM,
|
|
1513
|
+
[SERVICE_CATEGORIES.CLOUD]: APPROVAL_LEVELS.REVIEW,
|
|
1514
|
+
[SERVICE_CATEGORIES.CONTAINER]: APPROVAL_LEVELS.REVIEW,
|
|
1515
|
+
[SERVICE_CATEGORIES.API]: APPROVAL_LEVELS.CONFIRM,
|
|
1516
|
+
[SERVICE_CATEGORIES.WIRELESS]: APPROVAL_LEVELS.REVIEW,
|
|
1517
|
+
[SERVICE_CATEGORIES.ICS]: APPROVAL_LEVELS.BLOCK
|
|
1518
|
+
// Requires explicit written authorization
|
|
1519
|
+
};
|
|
1520
|
+
var CATEGORY_DESCRIPTIONS = Object.entries(DOMAINS).reduce((acc, [id, info]) => {
|
|
1521
|
+
acc[id] = info.description;
|
|
1522
|
+
return acc;
|
|
1523
|
+
}, {});
|
|
1524
|
+
var CategorizedToolRegistry = class extends ToolRegistry {
|
|
1525
|
+
categories;
|
|
1526
|
+
constructor(state, scopeGuard, approvalGate, events, subAgentExecutor) {
|
|
1527
|
+
super(state, scopeGuard, approvalGate, events, subAgentExecutor);
|
|
1528
|
+
this.categories = /* @__PURE__ */ new Map();
|
|
1529
|
+
this.initializeCategories();
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Initialize all tool categories with core tools from parent ToolRegistry
|
|
1533
|
+
*
|
|
1534
|
+
* Every service category gets the base tools (run_cmd, read_file, write_file, etc.)
|
|
1535
|
+
* Sub-agents need at least these core tools to function.
|
|
1536
|
+
* spawn_sub is explicitly excluded to prevent recursion (1-level only).
|
|
1537
|
+
*/
|
|
1538
|
+
initializeCategories() {
|
|
1539
|
+
const coreToolNames = [
|
|
1540
|
+
TOOL_NAMES.RUN_CMD,
|
|
1541
|
+
TOOL_NAMES.READ_FILE,
|
|
1542
|
+
TOOL_NAMES.WRITE_FILE,
|
|
1543
|
+
TOOL_NAMES.PARSE_NMAP,
|
|
1544
|
+
TOOL_NAMES.SEARCH_CVE,
|
|
1545
|
+
TOOL_NAMES.WEB_SEARCH,
|
|
1546
|
+
TOOL_NAMES.EXTRACT_URLS,
|
|
1547
|
+
TOOL_NAMES.ADD_FINDING,
|
|
1548
|
+
TOOL_NAMES.UPDATE_TODO,
|
|
1549
|
+
TOOL_NAMES.GET_STATE,
|
|
1550
|
+
TOOL_NAMES.ADD_TARGET,
|
|
1551
|
+
TOOL_NAMES.ASK_USER
|
|
1552
|
+
];
|
|
1553
|
+
const coreTools = [];
|
|
1554
|
+
for (const name of coreToolNames) {
|
|
1555
|
+
const tool = this.getTool(name);
|
|
1556
|
+
if (tool) {
|
|
1557
|
+
coreTools.push(tool);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
for (const category of Object.keys(CATEGORY_DESCRIPTIONS)) {
|
|
1561
|
+
this.categories.set(category, {
|
|
1562
|
+
name: category,
|
|
1563
|
+
description: CATEGORY_DESCRIPTIONS[category],
|
|
1564
|
+
tools: [...coreTools],
|
|
1565
|
+
// Each category gets a copy of core tools
|
|
1566
|
+
subAgentPrompt: "",
|
|
1567
|
+
// Loaded from Domain Registry or prompt files at runtime
|
|
1568
|
+
dangerLevel: this.getDangerLevel(category),
|
|
1569
|
+
defaultApproval: CATEGORY_APPROVAL2[category],
|
|
1570
|
+
commonPorts: this.getCommonPorts(category),
|
|
1571
|
+
commonServices: this.getCommonServices(category)
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Get danger level for category
|
|
1577
|
+
*/
|
|
1578
|
+
getDangerLevel(category) {
|
|
1579
|
+
const passive = [SERVICE_CATEGORIES.NETWORK];
|
|
1580
|
+
const active = [SERVICE_CATEGORIES.WEB, SERVICE_CATEGORIES.API, SERVICE_CATEGORIES.EMAIL, SERVICE_CATEGORIES.FILE_SHARING];
|
|
1581
|
+
if (passive.includes(category)) return DANGER_LEVELS.PASSIVE;
|
|
1582
|
+
if (active.includes(category)) return DANGER_LEVELS.ACTIVE;
|
|
1583
|
+
return DANGER_LEVELS.EXPLOIT;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get common ports for category
|
|
1587
|
+
*/
|
|
1588
|
+
getCommonPorts(category) {
|
|
1589
|
+
const ports = {
|
|
1590
|
+
[SERVICE_CATEGORIES.NETWORK]: [1, 7, 9, 21, 22, 23, 25, 53, 79, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1723, 3306, 3389, 5432, 5900, 8080],
|
|
1591
|
+
[SERVICE_CATEGORIES.WEB]: [80, 443, 8e3, 8080, 8443, 8888],
|
|
1592
|
+
[SERVICE_CATEGORIES.DATABASE]: [1433, 3306, 5432, 6379, 9042, 9200, 27017, 1521],
|
|
1593
|
+
[SERVICE_CATEGORIES.AD]: [88, 135, 139, 389, 445, 636, 3268, 3269, 5722],
|
|
1594
|
+
[SERVICE_CATEGORIES.EMAIL]: [25, 110, 143, 465, 587, 993, 995],
|
|
1595
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: [22, 23, 3389, 5900, 5901],
|
|
1596
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: [21, 139, 445, 2049, 111],
|
|
1597
|
+
[SERVICE_CATEGORIES.CLOUD]: [443],
|
|
1598
|
+
// Detected via banner
|
|
1599
|
+
[SERVICE_CATEGORIES.CONTAINER]: [2375, 2376, 5e3],
|
|
1600
|
+
[SERVICE_CATEGORIES.API]: [3e3, 5e3, 5001, 8e3, 8001, 8080],
|
|
1601
|
+
[SERVICE_CATEGORIES.WIRELESS]: [],
|
|
1602
|
+
[SERVICE_CATEGORIES.ICS]: [102, 502, 44818, 2e4]
|
|
1603
|
+
};
|
|
1604
|
+
return ports[category] || [];
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Get common services for category
|
|
1608
|
+
*/
|
|
1609
|
+
getCommonServices(category) {
|
|
1610
|
+
const services = {
|
|
1611
|
+
[SERVICE_CATEGORIES.NETWORK]: [SERVICES.FTP, SERVICES.SSH, "telnet", "smtp", SERVICES.HTTP, "pop3", "imap", SERVICES.HTTPS],
|
|
1612
|
+
[SERVICE_CATEGORIES.WEB]: [SERVICES.HTTP, SERVICES.HTTPS, "nginx", "apache", "iis"],
|
|
1613
|
+
[SERVICE_CATEGORIES.DATABASE]: [SERVICES.MYSQL, SERVICES.MSSQL, SERVICES.POSTGRES, SERVICES.MONGODB, SERVICES.REDIS, SERVICES.ELASTIC],
|
|
1614
|
+
[SERVICE_CATEGORIES.AD]: ["kerberos", "ldap", "microsoft-ds", "netbios-ssn"],
|
|
1615
|
+
[SERVICE_CATEGORIES.EMAIL]: ["smtp", "pop3", "imap"],
|
|
1616
|
+
[SERVICE_CATEGORIES.REMOTE_ACCESS]: [SERVICES.SSH, "ms-wbt-server", "vnc"],
|
|
1617
|
+
[SERVICE_CATEGORIES.FILE_SHARING]: [SERVICES.FTP, "microsoft-ds", "nfs"],
|
|
1618
|
+
[SERVICE_CATEGORIES.CLOUD]: [SERVICES.HTTP, SERVICES.HTTPS],
|
|
1619
|
+
// Detected via banner analysis
|
|
1620
|
+
[SERVICE_CATEGORIES.CONTAINER]: ["docker"],
|
|
1621
|
+
[SERVICE_CATEGORIES.API]: [SERVICES.HTTP, SERVICES.HTTPS],
|
|
1622
|
+
[SERVICE_CATEGORIES.WIRELESS]: [],
|
|
1623
|
+
[SERVICE_CATEGORIES.ICS]: ["modbus", "iso-tsap", "dnp3", "enip"]
|
|
1624
|
+
};
|
|
1625
|
+
return services[category] || [];
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get all tools in a category
|
|
1629
|
+
*/
|
|
1630
|
+
getByCategory(category) {
|
|
1631
|
+
return this.categories.get(category)?.tools || [];
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Get category definition
|
|
1635
|
+
*/
|
|
1636
|
+
getCategory(category) {
|
|
1637
|
+
return this.categories.get(category);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Get all categories
|
|
1641
|
+
*/
|
|
1642
|
+
getAllCategories() {
|
|
1643
|
+
return Array.from(this.categories.values());
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Suggest tools based on discovered target
|
|
1647
|
+
*/
|
|
1648
|
+
suggestTools(target) {
|
|
1649
|
+
const suggestions = [];
|
|
1650
|
+
for (const port of target.ports) {
|
|
1651
|
+
const category = this.detectCategoryFromPort(port.port, port.service);
|
|
1652
|
+
if (category && !suggestions.find((s) => s.category === category)) {
|
|
1653
|
+
const catTools = this.getByCategory(category);
|
|
1654
|
+
suggestions.push({
|
|
1655
|
+
category,
|
|
1656
|
+
tools: catTools.map((t) => t.name)
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (target.hostname) {
|
|
1661
|
+
const cloudCat = this.detectCloudProvider(target.hostname);
|
|
1662
|
+
if (cloudCat && !suggestions.find((s) => s.category === cloudCat)) {
|
|
1663
|
+
const catTools = this.getByCategory(cloudCat);
|
|
1664
|
+
suggestions.push({
|
|
1665
|
+
category: cloudCat,
|
|
1666
|
+
tools: catTools.map((t) => t.name)
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return suggestions;
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Detect category from port number and service name
|
|
1674
|
+
*/
|
|
1675
|
+
detectCategoryFromPort(port, serviceName) {
|
|
1676
|
+
if (PORT_CATEGORY_MAP[port]) {
|
|
1677
|
+
return PORT_CATEGORY_MAP[port];
|
|
1678
|
+
}
|
|
1679
|
+
if (serviceName && SERVICE_CATEGORY_MAP[serviceName.toLowerCase()]) {
|
|
1680
|
+
return SERVICE_CATEGORY_MAP[serviceName.toLowerCase()];
|
|
1681
|
+
}
|
|
1682
|
+
if (port >= 8e3 && port <= 9e3) return SERVICE_CATEGORIES.WEB;
|
|
1683
|
+
if (port >= 3e3 && port <= 3500) return SERVICE_CATEGORIES.API;
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Detect cloud provider from hostname
|
|
1688
|
+
*/
|
|
1689
|
+
detectCloudProvider(hostname) {
|
|
1690
|
+
const cloudProviders = [
|
|
1691
|
+
"amazonaws.com",
|
|
1692
|
+
"aws",
|
|
1693
|
+
"s3.amazonaws.com",
|
|
1694
|
+
"azure.com",
|
|
1695
|
+
"azurewebsites.net",
|
|
1696
|
+
"windows.net",
|
|
1697
|
+
"gcp",
|
|
1698
|
+
"googleusercontent.com",
|
|
1699
|
+
"appspot.com",
|
|
1700
|
+
"digitalocean.com",
|
|
1701
|
+
"do.co",
|
|
1702
|
+
"herokuapp.com",
|
|
1703
|
+
"vercel.app",
|
|
1704
|
+
"netlify.app"
|
|
1705
|
+
];
|
|
1706
|
+
const lowerHostname = hostname.toLowerCase();
|
|
1707
|
+
for (const provider of cloudProviders) {
|
|
1708
|
+
if (lowerHostname.includes(provider)) {
|
|
1709
|
+
return SERVICE_CATEGORIES.CLOUD;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Suggest sub-agent type for target
|
|
1716
|
+
*/
|
|
1717
|
+
suggestSubAgent(target) {
|
|
1718
|
+
const suggestions = this.suggestTools(target);
|
|
1719
|
+
const priority = [
|
|
1720
|
+
SERVICE_CATEGORIES.ICS,
|
|
1721
|
+
// Critical - block
|
|
1722
|
+
SERVICE_CATEGORIES.AD,
|
|
1723
|
+
// High value - review
|
|
1724
|
+
SERVICE_CATEGORIES.DATABASE,
|
|
1725
|
+
// High value - review
|
|
1726
|
+
SERVICE_CATEGORIES.CLOUD,
|
|
1727
|
+
// High value - review
|
|
1728
|
+
SERVICE_CATEGORIES.CONTAINER,
|
|
1729
|
+
// Escalation - review
|
|
1730
|
+
SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
1731
|
+
// Access - review
|
|
1732
|
+
SERVICE_CATEGORIES.WEB,
|
|
1733
|
+
// Common - confirm
|
|
1734
|
+
SERVICE_CATEGORIES.API,
|
|
1735
|
+
// Common - confirm
|
|
1736
|
+
SERVICE_CATEGORIES.EMAIL,
|
|
1737
|
+
// Info - confirm
|
|
1738
|
+
SERVICE_CATEGORIES.FILE_SHARING,
|
|
1739
|
+
// Access - confirm
|
|
1740
|
+
SERVICE_CATEGORIES.NETWORK,
|
|
1741
|
+
// Basic - confirm
|
|
1742
|
+
SERVICE_CATEGORIES.WIRELESS
|
|
1743
|
+
// Special - review
|
|
1744
|
+
];
|
|
1745
|
+
for (const cat of priority) {
|
|
1746
|
+
if (suggestions.find((s) => s.category === cat)) {
|
|
1747
|
+
return cat;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return PHASES.RECON;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Get approval level for category
|
|
1754
|
+
*/
|
|
1755
|
+
getApprovalForCategory(category) {
|
|
1756
|
+
return CATEGORY_APPROVAL2[category];
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Check if category requires special handling
|
|
1760
|
+
*/
|
|
1761
|
+
isHighRiskCategory(category) {
|
|
1762
|
+
return [SERVICE_CATEGORIES.ICS, SERVICE_CATEGORIES.AD, SERVICE_CATEGORIES.DATABASE, SERVICE_CATEGORIES.CLOUD, SERVICE_CATEGORIES.CONTAINER].includes(category);
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Get service fingerprint from port data
|
|
1766
|
+
*/
|
|
1767
|
+
fingerprintService(port) {
|
|
1768
|
+
const category = this.detectCategoryFromPort(port.port, port.service);
|
|
1769
|
+
if (!category) return null;
|
|
1770
|
+
return {
|
|
1771
|
+
port: port.port,
|
|
1772
|
+
service: port.service,
|
|
1773
|
+
version: port.version,
|
|
1774
|
+
banner: port.notes[0] || void 0,
|
|
1775
|
+
category,
|
|
1776
|
+
confidence: port.version ? 0.9 : 0.7
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Get category description
|
|
1781
|
+
*/
|
|
1782
|
+
getCategoryDescription(category) {
|
|
1783
|
+
return CATEGORY_DESCRIPTIONS[category];
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
// src/shared/utils/logger.ts
|
|
1788
|
+
var COLORS = {
|
|
1789
|
+
reset: "\x1B[0m",
|
|
1790
|
+
dim: "\x1B[2m",
|
|
1791
|
+
red: "\x1B[31m",
|
|
1792
|
+
yellow: "\x1B[33m",
|
|
1793
|
+
green: "\x1B[32m",
|
|
1794
|
+
blue: "\x1B[34m",
|
|
1795
|
+
magenta: "\x1B[35m",
|
|
1796
|
+
cyan: "\x1B[36m"
|
|
1797
|
+
};
|
|
1798
|
+
var levelColors = {
|
|
1799
|
+
[0 /* DEBUG */]: COLORS.dim,
|
|
1800
|
+
[1 /* INFO */]: COLORS.green,
|
|
1801
|
+
[2 /* WARN */]: COLORS.yellow,
|
|
1802
|
+
[3 /* ERROR */]: COLORS.red,
|
|
1803
|
+
[999 /* SILENT */]: COLORS.reset
|
|
1804
|
+
};
|
|
1805
|
+
var levelNames = {
|
|
1806
|
+
[0 /* DEBUG */]: "DEBUG",
|
|
1807
|
+
[1 /* INFO */]: "INFO",
|
|
1808
|
+
[2 /* WARN */]: "WARN",
|
|
1809
|
+
[3 /* ERROR */]: "ERROR",
|
|
1810
|
+
[999 /* SILENT */]: "SILENT"
|
|
1811
|
+
};
|
|
1812
|
+
var Logger = class {
|
|
1813
|
+
constructor(component, config = {}) {
|
|
1814
|
+
this.component = component;
|
|
1815
|
+
this.config = {
|
|
1816
|
+
minLevel: config.minLevel ?? 1 /* INFO */,
|
|
1817
|
+
includeTimestamp: config.includeTimestamp ?? true,
|
|
1818
|
+
includeComponent: config.includeComponent ?? true,
|
|
1819
|
+
colorOutput: config.colorOutput ?? true,
|
|
1820
|
+
outputToFile: config.outputToFile ?? false,
|
|
1821
|
+
logFilePath: config.logFilePath
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
config;
|
|
1825
|
+
logBuffer = [];
|
|
1826
|
+
maxBufferSize = 1e3;
|
|
1827
|
+
/**
|
|
1828
|
+
* Set minimum log level
|
|
1829
|
+
*/
|
|
1830
|
+
setMinLevel(level) {
|
|
1831
|
+
this.config.minLevel = level;
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Log at a specific level
|
|
1835
|
+
*/
|
|
1836
|
+
log(level, message, data) {
|
|
1837
|
+
if (level < this.config.minLevel) {
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
const entry = {
|
|
1841
|
+
timestamp: Date.now(),
|
|
1842
|
+
level,
|
|
1843
|
+
component: this.component,
|
|
1844
|
+
message,
|
|
1845
|
+
data
|
|
1846
|
+
};
|
|
1847
|
+
this.logBuffer.push(entry);
|
|
1848
|
+
if (this.logBuffer.length > this.maxBufferSize) {
|
|
1849
|
+
this.logBuffer.shift();
|
|
1850
|
+
}
|
|
1851
|
+
const formatted = this.formatEntry(entry);
|
|
1852
|
+
console.log(formatted);
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Format a log entry for output
|
|
1856
|
+
*/
|
|
1857
|
+
formatEntry(entry) {
|
|
1858
|
+
const parts = [];
|
|
1859
|
+
if (this.config.includeTimestamp) {
|
|
1860
|
+
const date = new Date(entry.timestamp);
|
|
1861
|
+
const ts = date.toISOString().split("T")[1].slice(0, -1);
|
|
1862
|
+
parts.push(this.config.colorOutput ? COLORS.dim + ts + COLORS.reset : ts);
|
|
1863
|
+
}
|
|
1864
|
+
const levelName = levelNames[entry.level];
|
|
1865
|
+
const levelColor = this.config.colorOutput ? levelColors[entry.level] : "";
|
|
1866
|
+
parts.push(levelColor + `[${levelName}]` + (this.config.colorOutput ? COLORS.reset : ""));
|
|
1867
|
+
if (this.config.includeComponent) {
|
|
1868
|
+
const comp = this.config.colorOutput ? COLORS.cyan + entry.component + COLORS.reset : entry.component;
|
|
1869
|
+
parts.push(`[${comp}]`);
|
|
1870
|
+
}
|
|
1871
|
+
parts.push(entry.message);
|
|
1872
|
+
if (entry.data) {
|
|
1873
|
+
const dataStr = Object.entries(entry.data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
1874
|
+
parts.push(this.config.colorOutput ? COLORS.dim + dataStr + COLORS.reset : dataStr);
|
|
1875
|
+
}
|
|
1876
|
+
return parts.join(" ");
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Debug level logging
|
|
1880
|
+
*/
|
|
1881
|
+
debug(message, data) {
|
|
1882
|
+
this.log(0 /* DEBUG */, message, data);
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Info level logging
|
|
1886
|
+
*/
|
|
1887
|
+
info(message, data) {
|
|
1888
|
+
this.log(1 /* INFO */, message, data);
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Warning level logging
|
|
1892
|
+
*/
|
|
1893
|
+
warn(message, data) {
|
|
1894
|
+
this.log(2 /* WARN */, message, data);
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Error level logging
|
|
1898
|
+
*/
|
|
1899
|
+
error(message, data) {
|
|
1900
|
+
this.log(3 /* ERROR */, message, data);
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Get all log entries
|
|
1904
|
+
*/
|
|
1905
|
+
getLogs() {
|
|
1906
|
+
return [...this.logBuffer];
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Get logs by level
|
|
1910
|
+
*/
|
|
1911
|
+
getLogsByLevel(minLevel) {
|
|
1912
|
+
return this.logBuffer.filter((entry) => entry.level >= minLevel);
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Clear log buffer
|
|
1916
|
+
*/
|
|
1917
|
+
clearLogs() {
|
|
1918
|
+
this.logBuffer = [];
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Export logs to string
|
|
1922
|
+
*/
|
|
1923
|
+
exportLogs() {
|
|
1924
|
+
return this.logBuffer.map((entry) => this.formatEntry(entry)).join("\n");
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
var agentLogger = new Logger("Agent", {
|
|
1928
|
+
minLevel: 1 /* INFO */,
|
|
1929
|
+
colorOutput: true
|
|
1930
|
+
});
|
|
1931
|
+
|
|
1932
|
+
// src/shared/utils/config.ts
|
|
1933
|
+
import path from "path";
|
|
1934
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1935
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
1936
|
+
var __dirname2 = path.dirname(__filename);
|
|
1937
|
+
function getApiKey() {
|
|
1938
|
+
return process.env.PENTEST_API_KEY || "";
|
|
1939
|
+
}
|
|
1940
|
+
function getBaseUrl() {
|
|
1941
|
+
return process.env.PENTEST_BASE_URL || void 0;
|
|
1942
|
+
}
|
|
1943
|
+
function getModel() {
|
|
1944
|
+
return process.env.PENTEST_MODEL || "";
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// src/engine/llm.ts
|
|
1948
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
1949
|
+
var LLMClient = class {
|
|
1950
|
+
client;
|
|
1951
|
+
model;
|
|
1952
|
+
constructor() {
|
|
1953
|
+
this.client = new Anthropic({
|
|
1954
|
+
apiKey: getApiKey(),
|
|
1955
|
+
baseURL: getBaseUrl(),
|
|
1956
|
+
dangerouslyAllowBrowser: true
|
|
1957
|
+
});
|
|
1958
|
+
this.model = getModel();
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Generate response from messages and tools (non-streaming)
|
|
1962
|
+
*/
|
|
1963
|
+
async generateResponse(messages, tools, systemPrompt) {
|
|
1964
|
+
const response = await this.client.messages.create({
|
|
1965
|
+
model: this.model,
|
|
1966
|
+
max_tokens: 4096,
|
|
1967
|
+
system: systemPrompt,
|
|
1968
|
+
messages: messages.filter((m) => m.role !== "system"),
|
|
1969
|
+
tools
|
|
1970
|
+
});
|
|
1971
|
+
const textBlock = response.content.find((b) => b.type === "text");
|
|
1972
|
+
const toolBlocks = response.content.filter((b) => b.type === "tool_use");
|
|
1973
|
+
return {
|
|
1974
|
+
content: textBlock?.type === "text" ? textBlock.text : "",
|
|
1975
|
+
toolCalls: toolBlocks.map((b) => ({
|
|
1976
|
+
id: b.id,
|
|
1977
|
+
name: b.name,
|
|
1978
|
+
input: b.input
|
|
1979
|
+
})),
|
|
1980
|
+
rawResponse: response
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Generate response with streaming support for real-time thinking display
|
|
1985
|
+
* This is like opencode's reasoning stream handling
|
|
1986
|
+
*/
|
|
1987
|
+
async generateResponseStream(messages, tools, systemPrompt, callbacks) {
|
|
1988
|
+
const stream = await this.client.messages.create({
|
|
1989
|
+
model: this.model,
|
|
1990
|
+
max_tokens: 4096,
|
|
1991
|
+
system: systemPrompt,
|
|
1992
|
+
messages: messages.filter((m) => m.role !== "system"),
|
|
1993
|
+
tools,
|
|
1994
|
+
stream: true
|
|
1995
|
+
});
|
|
1996
|
+
let fullContent = "";
|
|
1997
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
1998
|
+
callbacks?.onThinkingStart?.(systemPrompt ? "processing" : "default", 0);
|
|
1999
|
+
for await (const event of stream) {
|
|
2000
|
+
switch (event.type) {
|
|
2001
|
+
case "content_block_start":
|
|
2002
|
+
if (event.content_block.type === "text") {
|
|
2003
|
+
} else if (event.content_block.type === "tool_use") {
|
|
2004
|
+
const toolId = event.content_block.id;
|
|
2005
|
+
toolCallsMap.set(toolId, { id: toolId, name: "", input: {} });
|
|
2006
|
+
}
|
|
2007
|
+
break;
|
|
2008
|
+
case "content_block_delta":
|
|
2009
|
+
if (event.delta.type === "text_delta") {
|
|
2010
|
+
const text = event.delta.text;
|
|
2011
|
+
fullContent += text;
|
|
2012
|
+
callbacks?.onThinkingDelta?.(text);
|
|
2013
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
2014
|
+
}
|
|
2015
|
+
break;
|
|
2016
|
+
case "content_block_stop":
|
|
2017
|
+
break;
|
|
2018
|
+
case "message_start":
|
|
2019
|
+
break;
|
|
2020
|
+
case "message_delta":
|
|
2021
|
+
break;
|
|
2022
|
+
case "message_stop":
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
callbacks?.onThinkingEnd?.();
|
|
2027
|
+
const toolCalls = Array.from(toolCallsMap.values());
|
|
2028
|
+
return {
|
|
2029
|
+
content: fullContent,
|
|
2030
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
2031
|
+
rawResponse: null
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Get current model name
|
|
2036
|
+
*/
|
|
2037
|
+
getModelName() {
|
|
2038
|
+
return this.model;
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var llmInstance = null;
|
|
2042
|
+
function getLLMClient() {
|
|
2043
|
+
if (!llmInstance) {
|
|
2044
|
+
llmInstance = new LLMClient();
|
|
2045
|
+
}
|
|
2046
|
+
return llmInstance;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// src/agents/core-agent.ts
|
|
2050
|
+
var CoreAgent = class {
|
|
2051
|
+
llm;
|
|
2052
|
+
state;
|
|
2053
|
+
events;
|
|
2054
|
+
toolRegistry;
|
|
2055
|
+
agentType;
|
|
2056
|
+
maxIterations;
|
|
2057
|
+
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
2058
|
+
this.agentType = agentType;
|
|
2059
|
+
this.state = state;
|
|
2060
|
+
this.events = events;
|
|
2061
|
+
this.toolRegistry = toolRegistry;
|
|
2062
|
+
this.llm = getLLMClient();
|
|
2063
|
+
this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* The core loop: think -> act -> observe
|
|
2067
|
+
*/
|
|
2068
|
+
async run(task, systemPrompt) {
|
|
2069
|
+
const messages = [{ role: "user", content: task }];
|
|
2070
|
+
let toolsExecutedCount = 0;
|
|
2071
|
+
for (let iteration = 0; iteration < this.maxIterations; iteration++) {
|
|
2072
|
+
const result = await this.step(iteration, messages, systemPrompt);
|
|
2073
|
+
toolsExecutedCount += result.toolsExecuted;
|
|
2074
|
+
if (result.completed) {
|
|
2075
|
+
return {
|
|
2076
|
+
output: result.output,
|
|
2077
|
+
iterations: iteration + 1,
|
|
2078
|
+
toolsExecuted: toolsExecutedCount,
|
|
2079
|
+
completed: true
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return {
|
|
2084
|
+
output: "Max iterations reached",
|
|
2085
|
+
iterations: this.maxIterations,
|
|
2086
|
+
toolsExecuted: toolsExecutedCount,
|
|
2087
|
+
completed: false
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Execute a single iteration step with real-time thinking display
|
|
2092
|
+
*/
|
|
2093
|
+
async step(iteration, messages, systemPrompt) {
|
|
2094
|
+
const phase = this.state.getPhase();
|
|
2095
|
+
this.emitThinkingStart(phase, iteration);
|
|
2096
|
+
this.emitThink(iteration);
|
|
2097
|
+
const response = await this.llm.generateResponseStream(
|
|
2098
|
+
messages,
|
|
2099
|
+
this.getToolSchemas(),
|
|
2100
|
+
systemPrompt,
|
|
2101
|
+
{
|
|
2102
|
+
onThinkingStart: (phase2, iter) => {
|
|
2103
|
+
},
|
|
2104
|
+
onThinkingDelta: (content) => {
|
|
2105
|
+
this.emitThinkingDelta(content, phase);
|
|
2106
|
+
},
|
|
2107
|
+
onThinkingEnd: () => {
|
|
2108
|
+
this.emitThinkingEnd(phase);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
);
|
|
2112
|
+
messages.push({ role: "assistant", content: response.content });
|
|
2113
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
2114
|
+
this.emitComplete(response.content, iteration, 0);
|
|
2115
|
+
return { output: response.content, toolsExecuted: 0, completed: true };
|
|
2116
|
+
}
|
|
2117
|
+
const results = await this.processToolCalls(response.toolCalls);
|
|
2118
|
+
this.addToolResultsToMessages(messages, results);
|
|
2119
|
+
return { output: "", toolsExecuted: results.length, completed: false };
|
|
2120
|
+
}
|
|
2121
|
+
emitThink(iteration) {
|
|
2122
|
+
this.events.emit({
|
|
2123
|
+
type: "think",
|
|
2124
|
+
timestamp: Date.now(),
|
|
2125
|
+
data: {
|
|
2126
|
+
thought: `${this.agentType} agent iteration ${iteration + 1}: Decision making`,
|
|
2127
|
+
phase: this.state.getPhase()
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Emit thinking start event - like opencode's reasoning-start
|
|
2133
|
+
*/
|
|
2134
|
+
emitThinkingStart(phase, iteration) {
|
|
2135
|
+
this.events.emit({
|
|
2136
|
+
type: "thinking_start",
|
|
2137
|
+
timestamp: Date.now(),
|
|
2138
|
+
data: {
|
|
2139
|
+
phase,
|
|
2140
|
+
iteration
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Emit thinking delta event - like opencode's reasoning-delta
|
|
2146
|
+
* This provides real-time transparency of the model's thinking process
|
|
2147
|
+
*/
|
|
2148
|
+
emitThinkingDelta(content, phase) {
|
|
2149
|
+
this.events.emit({
|
|
2150
|
+
type: "thinking_delta",
|
|
2151
|
+
timestamp: Date.now(),
|
|
2152
|
+
data: {
|
|
2153
|
+
content,
|
|
2154
|
+
phase
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Emit thinking end event - like opencode's reasoning-end
|
|
2160
|
+
*/
|
|
2161
|
+
emitThinkingEnd(phase) {
|
|
2162
|
+
this.events.emit({
|
|
2163
|
+
type: "thinking_end",
|
|
2164
|
+
timestamp: Date.now(),
|
|
2165
|
+
data: {
|
|
2166
|
+
phase
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
emitComplete(output, iteration, toolsExecuted) {
|
|
2171
|
+
this.events.emit({
|
|
2172
|
+
type: "complete",
|
|
2173
|
+
timestamp: Date.now(),
|
|
2174
|
+
data: {
|
|
2175
|
+
finalOutput: output,
|
|
2176
|
+
iterations: iteration + 1,
|
|
2177
|
+
toolsExecuted
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
addToolResultsToMessages(messages, results) {
|
|
2182
|
+
for (const res of results) {
|
|
2183
|
+
messages.push({
|
|
2184
|
+
role: "user",
|
|
2185
|
+
content: [
|
|
2186
|
+
{
|
|
2187
|
+
type: "tool_result",
|
|
2188
|
+
tool_use_id: res.toolCallId,
|
|
2189
|
+
content: res.output,
|
|
2190
|
+
is_error: !!res.error
|
|
2191
|
+
}
|
|
2192
|
+
]
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async processToolCalls(toolCalls) {
|
|
2197
|
+
const results = [];
|
|
2198
|
+
for (const call of toolCalls) {
|
|
2199
|
+
const toolName = call.name;
|
|
2200
|
+
const toolInput = call.input;
|
|
2201
|
+
try {
|
|
2202
|
+
const result = await this.toolRegistry.execute({ name: toolName, input: toolInput });
|
|
2203
|
+
results.push({ toolCallId: call.id, output: result.output, error: result.error });
|
|
2204
|
+
} catch (error) {
|
|
2205
|
+
results.push({ toolCallId: call.id, output: String(error), error: String(error) });
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return results;
|
|
2209
|
+
}
|
|
2210
|
+
getToolSchemas() {
|
|
2211
|
+
return this.toolRegistry.getAll().map((t) => ({
|
|
2212
|
+
name: t.name,
|
|
2213
|
+
description: t.description,
|
|
2214
|
+
input_schema: {
|
|
2215
|
+
type: "object",
|
|
2216
|
+
properties: t.parameters,
|
|
2217
|
+
required: t.required || []
|
|
2218
|
+
}
|
|
2219
|
+
}));
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2223
|
+
// src/agents/prompt-builder.ts
|
|
2224
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2225
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
2226
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2227
|
+
var __dirname3 = dirname2(fileURLToPath3(import.meta.url));
|
|
2228
|
+
var PromptBuilder = class {
|
|
2229
|
+
state;
|
|
2230
|
+
promptDir;
|
|
2231
|
+
constructor(state) {
|
|
2232
|
+
this.state = state;
|
|
2233
|
+
this.promptDir = join3(__dirname3, "../shared/prompts");
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Build complete prompt for LLM iteration
|
|
2237
|
+
*/
|
|
2238
|
+
build(userInput, phase) {
|
|
2239
|
+
return [
|
|
2240
|
+
this.loadFragment("base.md"),
|
|
2241
|
+
this.loadPhasePrompt(phase),
|
|
2242
|
+
this.getScopeFragment(),
|
|
2243
|
+
this.getStateFragment(),
|
|
2244
|
+
this.getTodoFragment(),
|
|
2245
|
+
`User Context: ${userInput}`
|
|
2246
|
+
].join("\n\n");
|
|
2247
|
+
}
|
|
2248
|
+
loadFragment(filename) {
|
|
2249
|
+
const path2 = join3(this.promptDir, filename);
|
|
2250
|
+
if (!existsSync2(path2)) return "";
|
|
2251
|
+
return readFileSync2(path2, "utf-8");
|
|
2252
|
+
}
|
|
2253
|
+
loadPhasePrompt(phase) {
|
|
2254
|
+
const path2 = join3(this.promptDir, "phases", `${phase}.md`);
|
|
2255
|
+
return existsSync2(path2) ? `<phase-instructions phase="${phase}">
|
|
2256
|
+
${readFileSync2(path2, "utf-8")}
|
|
2257
|
+
</phase-instructions>` : "";
|
|
2258
|
+
}
|
|
2259
|
+
getScopeFragment() {
|
|
2260
|
+
const scope = this.state.getScope();
|
|
2261
|
+
if (!scope) return "<scope>NO SCOPE DEFINED. STOP.</scope>";
|
|
2262
|
+
return `<scope type="ABSOLUTE_CONSTRAINT">
|
|
2263
|
+
Authorized CIDR: ${scope.allowedCidrs.join(", ")}
|
|
2264
|
+
Authorized Domains: ${scope.allowedDomains.join(", ")}
|
|
2265
|
+
Exclusions: ${scope.exclusions.join(", ") || "none"}
|
|
2266
|
+
NoDoS: ${scope.noDOS} | NoSocial: ${scope.noSocial}
|
|
2267
|
+
</scope>`;
|
|
2268
|
+
}
|
|
2269
|
+
getStateFragment() {
|
|
2270
|
+
return `<current-state>
|
|
2271
|
+
${this.state.toPrompt()}
|
|
2272
|
+
</current-state>`;
|
|
2273
|
+
}
|
|
2274
|
+
getTodoFragment() {
|
|
2275
|
+
const todo = this.state.getTodo();
|
|
2276
|
+
const list = todo.map((t) => `[${t.status === "done" ? "x" : " "}] ${t.content} (${t.priority})`).join("\n");
|
|
2277
|
+
return `<todo>
|
|
2278
|
+
${list || "Create initial plan"}
|
|
2279
|
+
</todo>`;
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
// src/agents/sub-agent-executor.ts
|
|
2284
|
+
var SubAgentExecutor = class extends CoreAgent {
|
|
2285
|
+
constructor(state, events, toolRegistry) {
|
|
2286
|
+
super(AGENT_ROLES.RECON, state, events, toolRegistry);
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Entry point for executing a sub-task
|
|
2290
|
+
*/
|
|
2291
|
+
async executeSubTask(task, systemPrompt) {
|
|
2292
|
+
console.log(`[SubAgent] Executing specialized task: ${task.slice(0, 50)}...`);
|
|
2293
|
+
return await this.run(task, systemPrompt);
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
// src/agents/main-agent.ts
|
|
2298
|
+
var MainAgent = class extends CoreAgent {
|
|
2299
|
+
promptBuilder;
|
|
2300
|
+
approvalGate;
|
|
2301
|
+
scopeGuard;
|
|
2302
|
+
userInput = "";
|
|
2303
|
+
constructor() {
|
|
2304
|
+
const state = new SharedState();
|
|
2305
|
+
const events = new AgentEventEmitter();
|
|
2306
|
+
const approvalGate = new ApprovalGate(false);
|
|
2307
|
+
const scopeGuard = new ScopeGuard(state);
|
|
2308
|
+
const subAgentExecutor = new SubAgentExecutor(state, events, null);
|
|
2309
|
+
const toolRegistry = new CategorizedToolRegistry(state, scopeGuard, approvalGate, events, subAgentExecutor);
|
|
2310
|
+
subAgentExecutor.toolRegistry = toolRegistry;
|
|
2311
|
+
super(AGENT_ROLES.ORCHESTRATOR, state, events, toolRegistry);
|
|
2312
|
+
this.approvalGate = approvalGate;
|
|
2313
|
+
this.scopeGuard = scopeGuard;
|
|
2314
|
+
this.promptBuilder = new PromptBuilder(state);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Public entry point for running the agent with a simple string input
|
|
2318
|
+
*/
|
|
2319
|
+
async execute(userInput) {
|
|
2320
|
+
this.userInput = userInput;
|
|
2321
|
+
this.emitStart(userInput);
|
|
2322
|
+
this.initializeTask();
|
|
2323
|
+
const result = await this.run(userInput, this.getCurrentPrompt());
|
|
2324
|
+
return result.output;
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Override CoreAgent.run to enforce dynamic prompt injection per iteration
|
|
2328
|
+
* But keep the signature compatible with CoreAgent
|
|
2329
|
+
*/
|
|
2330
|
+
async run(task, _systemPrompt) {
|
|
2331
|
+
return super.run(task, _systemPrompt);
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Override step to ensure prompt is always fresh with latest state
|
|
2335
|
+
*/
|
|
2336
|
+
async step(iteration, messages, _unusedPrompt) {
|
|
2337
|
+
const dynamicPrompt = this.getCurrentPrompt();
|
|
2338
|
+
const result = await super.step(iteration, messages, dynamicPrompt);
|
|
2339
|
+
this.emitStateChange();
|
|
2340
|
+
return result;
|
|
2341
|
+
}
|
|
2342
|
+
// --- Internal Helpers (Refactored for Readability) ---
|
|
2343
|
+
initializeTask() {
|
|
2344
|
+
if (this.state.getTodo().length === 0) {
|
|
2345
|
+
this.state.addTodo("Initial reconnaissance and target discovery", PRIORITIES.HIGH);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
getCurrentPrompt() {
|
|
2349
|
+
const phase = this.state.getPhase() || PHASES.RECON;
|
|
2350
|
+
return this.promptBuilder.build(this.userInput, phase);
|
|
2351
|
+
}
|
|
2352
|
+
emitStart(userInput) {
|
|
2353
|
+
this.events.emit({
|
|
2354
|
+
type: "start",
|
|
2355
|
+
timestamp: Date.now(),
|
|
2356
|
+
data: { userInput, phase: this.state.getPhase() }
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
emitStateChange() {
|
|
2360
|
+
this.events.emit({
|
|
2361
|
+
type: "state_change",
|
|
2362
|
+
timestamp: Date.now(),
|
|
2363
|
+
data: {
|
|
2364
|
+
phase: this.state.getPhase(),
|
|
2365
|
+
targets: this.state.getTargets().size,
|
|
2366
|
+
findings: this.state.getFindings().length,
|
|
2367
|
+
todo: this.state.getTodo().length,
|
|
2368
|
+
loot: this.state.getLoot().length
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
// --- Public API Surface ---
|
|
2373
|
+
setAutoApprove(enabled) {
|
|
2374
|
+
this.approvalGate.setAutoApprove(enabled);
|
|
2375
|
+
}
|
|
2376
|
+
getState() {
|
|
2377
|
+
return this.state;
|
|
2378
|
+
}
|
|
2379
|
+
getPhase() {
|
|
2380
|
+
return this.state.getPhase() || PHASES.RECON;
|
|
2381
|
+
}
|
|
2382
|
+
getEventEmitter() {
|
|
2383
|
+
return this.events;
|
|
2384
|
+
}
|
|
2385
|
+
setScope(allowed, exclusions = []) {
|
|
2386
|
+
this.state.setScope({
|
|
2387
|
+
allowedCidrs: allowed.filter((a) => a.includes("/")),
|
|
2388
|
+
allowedDomains: allowed.filter((a) => !a.includes("/")),
|
|
2389
|
+
exclusions,
|
|
2390
|
+
noDOS: true,
|
|
2391
|
+
noSocial: true
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
addTarget(ip) {
|
|
2395
|
+
this.state.addTarget({
|
|
2396
|
+
ip,
|
|
2397
|
+
ports: [],
|
|
2398
|
+
tags: [],
|
|
2399
|
+
firstSeen: Date.now(),
|
|
2400
|
+
hostname: ""
|
|
2401
|
+
});
|
|
2402
|
+
this.emitStateChange();
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
// src/shared/constants/thought.ts
|
|
2407
|
+
var THOUGHT_TYPE = {
|
|
2408
|
+
THINKING: "thinking",
|
|
2409
|
+
// LLM text streaming
|
|
2410
|
+
REASONING: "reasoning",
|
|
2411
|
+
// LLM extended thinking
|
|
2412
|
+
PLANNING: "planning",
|
|
2413
|
+
// Strategic planning
|
|
2414
|
+
OBSERVATION: "observation",
|
|
2415
|
+
// Observing results
|
|
2416
|
+
HYPOTHESIS: "hypothesis",
|
|
2417
|
+
// Forming hypothesis
|
|
2418
|
+
REFLECTION: "reflection",
|
|
2419
|
+
// Self-reflection
|
|
2420
|
+
ACTION: "action",
|
|
2421
|
+
// Taking action
|
|
2422
|
+
RESULT: "result",
|
|
2423
|
+
// Action result
|
|
2424
|
+
STUCK: "stuck",
|
|
2425
|
+
// Detected stuck state
|
|
2426
|
+
BREAKTHROUGH: "breakthrough"
|
|
2427
|
+
// Found breakthrough
|
|
2428
|
+
};
|
|
2429
|
+
|
|
2430
|
+
// src/shared/constants/theme.ts
|
|
2431
|
+
var THEME = {
|
|
2432
|
+
// Backgrounds (deep dark with blue tint)
|
|
2433
|
+
bg: {
|
|
2434
|
+
primary: "#050505",
|
|
2435
|
+
// Deepest black
|
|
2436
|
+
secondary: "#0a0c10",
|
|
2437
|
+
// Dark void
|
|
2438
|
+
tertiary: "#0f172a",
|
|
2439
|
+
// Slate dark
|
|
2440
|
+
elevated: "#1e293b",
|
|
2441
|
+
// Bright slate
|
|
2442
|
+
input: "#020617"
|
|
2443
|
+
// Midnight
|
|
2444
|
+
},
|
|
2445
|
+
// Text colors
|
|
2446
|
+
text: {
|
|
2447
|
+
primary: "#f8fafc",
|
|
2448
|
+
// Near white
|
|
2449
|
+
secondary: "#94a3b8",
|
|
2450
|
+
// Muted slate
|
|
2451
|
+
muted: "#64748b",
|
|
2452
|
+
// Blue-gray
|
|
2453
|
+
accent: "#38bdf8",
|
|
2454
|
+
// Sky blue
|
|
2455
|
+
highlight: "#ffffff"
|
|
2456
|
+
// Pure white
|
|
2457
|
+
},
|
|
2458
|
+
// Status colors
|
|
2459
|
+
status: {
|
|
2460
|
+
success: "#22c55e",
|
|
2461
|
+
// Green
|
|
2462
|
+
warning: "#f59e0b",
|
|
2463
|
+
// Amber
|
|
2464
|
+
error: "#ef4444",
|
|
2465
|
+
// Red
|
|
2466
|
+
info: "#3b82f6",
|
|
2467
|
+
// Blue
|
|
2468
|
+
running: "#0ea5e9",
|
|
2469
|
+
// Sky
|
|
2470
|
+
pending: "#64748b"
|
|
2471
|
+
// Slate
|
|
2472
|
+
},
|
|
2473
|
+
// Severity colors
|
|
2474
|
+
semantic: {
|
|
2475
|
+
critical: "#7f1d1d",
|
|
2476
|
+
// Dark red
|
|
2477
|
+
high: "#ef4444",
|
|
2478
|
+
// Red
|
|
2479
|
+
medium: "#f97316",
|
|
2480
|
+
// Orange
|
|
2481
|
+
low: "#eab308",
|
|
2482
|
+
// Yellow
|
|
2483
|
+
info: "#3b82f6"
|
|
2484
|
+
// Blue
|
|
2485
|
+
},
|
|
2486
|
+
// Border colors
|
|
2487
|
+
border: {
|
|
2488
|
+
default: "#1e293b",
|
|
2489
|
+
focus: "#38bdf8",
|
|
2490
|
+
error: "#ef4444",
|
|
2491
|
+
success: "#22c55e"
|
|
2492
|
+
},
|
|
2493
|
+
// Phase colors
|
|
2494
|
+
phase: {
|
|
2495
|
+
recon: "#94a3b8",
|
|
2496
|
+
enum: "#38bdf8",
|
|
2497
|
+
vuln: "#f59e0b",
|
|
2498
|
+
exploit: "#ef4444",
|
|
2499
|
+
privesc: "#8b5cf6",
|
|
2500
|
+
persist: "#22c55e",
|
|
2501
|
+
report: "#64748b"
|
|
2502
|
+
},
|
|
2503
|
+
// Accent colors
|
|
2504
|
+
accent: {
|
|
2505
|
+
pink: "#f472b6",
|
|
2506
|
+
rose: "#fb7185",
|
|
2507
|
+
fuchsia: "#e879f9",
|
|
2508
|
+
purple: "#a78bfa",
|
|
2509
|
+
violet: "#8b5cf6",
|
|
2510
|
+
indigo: "#818cf8",
|
|
2511
|
+
blue: "#60a5fa",
|
|
2512
|
+
cyan: "#22d3ee",
|
|
2513
|
+
teal: "#2dd4bf",
|
|
2514
|
+
emerald: "#34d399",
|
|
2515
|
+
green: "#4ade80",
|
|
2516
|
+
lime: "#a3e635",
|
|
2517
|
+
yellow: "#facc15",
|
|
2518
|
+
amber: "#fbbf24",
|
|
2519
|
+
orange: "#fb923c",
|
|
2520
|
+
red: "#f87171"
|
|
2521
|
+
},
|
|
2522
|
+
// Gradients
|
|
2523
|
+
gradient: {
|
|
2524
|
+
cyber: ["#00d4ff", "#00ff9f"],
|
|
2525
|
+
danger: ["#ef4444", "#7f1d1d"],
|
|
2526
|
+
success: ["#22c55e", "#14532d"],
|
|
2527
|
+
gold: ["#f59e0b", "#78350f"],
|
|
2528
|
+
royal: ["#818cf8", "#312e81"]
|
|
2529
|
+
},
|
|
2530
|
+
// Spinner color (Sky blue)
|
|
2531
|
+
spinner: "#38bdf8",
|
|
2532
|
+
// Identity color
|
|
2533
|
+
identity: "#38bdf8"
|
|
2534
|
+
};
|
|
2535
|
+
var ASCII_BANNER = `
|
|
2536
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
2537
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
2538
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557
|
|
2539
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
2540
|
+
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
2541
|
+
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
2542
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2543
|
+
A U T O N O M O U S S E C U R I T Y A G E N T
|
|
2544
|
+
`;
|
|
2545
|
+
var THOUGHT_LABELS = {
|
|
2546
|
+
[THOUGHT_TYPE.THINKING]: "[think]",
|
|
2547
|
+
[THOUGHT_TYPE.REASONING]: "[reason]",
|
|
2548
|
+
[THOUGHT_TYPE.PLANNING]: "[plan]",
|
|
2549
|
+
[THOUGHT_TYPE.OBSERVATION]: "[observe]",
|
|
2550
|
+
[THOUGHT_TYPE.HYPOTHESIS]: "[hypothesis]",
|
|
2551
|
+
[THOUGHT_TYPE.REFLECTION]: "[reflect]",
|
|
2552
|
+
[THOUGHT_TYPE.ACTION]: "[action]",
|
|
2553
|
+
[THOUGHT_TYPE.RESULT]: "[result]",
|
|
2554
|
+
[THOUGHT_TYPE.STUCK]: "[stuck]",
|
|
2555
|
+
[THOUGHT_TYPE.BREAKTHROUGH]: "[!]"
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2558
|
+
// src/platform/tui/components/footer.tsx
|
|
2559
|
+
import { Box, Text } from "ink";
|
|
2560
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2561
|
+
var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
|
|
2562
|
+
return /* @__PURE__ */ jsxs(
|
|
2563
|
+
Box,
|
|
2564
|
+
{
|
|
2565
|
+
paddingX: 1,
|
|
2566
|
+
marginTop: 0,
|
|
2567
|
+
justifyContent: "space-between",
|
|
2568
|
+
children: [
|
|
2569
|
+
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
2570
|
+
/* @__PURE__ */ jsxs(Text, { color: THEME.text.accent, children: [
|
|
2571
|
+
"Phase: ",
|
|
2572
|
+
/* @__PURE__ */ jsx(Text, { color: THEME.text.primary, children: phase })
|
|
2573
|
+
] }),
|
|
2574
|
+
/* @__PURE__ */ jsxs(Text, { color: THEME.text.secondary, children: [
|
|
2575
|
+
"Targets: ",
|
|
2576
|
+
/* @__PURE__ */ jsx(Text, { color: THEME.text.primary, children: targets })
|
|
2577
|
+
] }),
|
|
2578
|
+
/* @__PURE__ */ jsxs(Text, { color: THEME.status.warning, children: [
|
|
2579
|
+
"Findings: ",
|
|
2580
|
+
/* @__PURE__ */ jsx(Text, { color: THEME.text.primary, children: findings })
|
|
2581
|
+
] }),
|
|
2582
|
+
/* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
|
|
2583
|
+
"Tasks: ",
|
|
2584
|
+
/* @__PURE__ */ jsx(Text, { color: THEME.text.primary, children: todo })
|
|
2585
|
+
] })
|
|
2586
|
+
] }),
|
|
2587
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2588
|
+
/* @__PURE__ */ jsx(Text, { color: isProcessing ? THEME.status.running : THEME.text.muted, children: isProcessing ? "\u25CF Running " : "\u25CB Idle " }),
|
|
2589
|
+
/* @__PURE__ */ jsxs(Text, { color: THEME.text.secondary, children: [
|
|
2590
|
+
Math.floor(elapsedTime / 60),
|
|
2591
|
+
"m ",
|
|
2592
|
+
Math.floor(elapsedTime % 60),
|
|
2593
|
+
"s"
|
|
2594
|
+
] })
|
|
2595
|
+
] })
|
|
2596
|
+
]
|
|
2597
|
+
}
|
|
2598
|
+
);
|
|
2599
|
+
};
|
|
2600
|
+
var footer_default = Footer;
|
|
2601
|
+
|
|
2602
|
+
// src/platform/tui/app.tsx
|
|
2603
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2604
|
+
var App = ({ autoApprove = false, target }) => {
|
|
2605
|
+
const { exit } = useApp();
|
|
2606
|
+
const [messages, setMessages] = useState([]);
|
|
2607
|
+
const [input, setInput] = useState("");
|
|
2608
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
2609
|
+
const [currentStatus, setCurrentStatus] = useState("");
|
|
2610
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
2611
|
+
const [thinking, setThinking] = useState({ isActive: false, content: "", phase: "" });
|
|
2612
|
+
const [stats, setStats] = useState({
|
|
2613
|
+
phase: "init",
|
|
2614
|
+
targets: 0,
|
|
2615
|
+
findings: 0,
|
|
2616
|
+
todo: 0
|
|
2617
|
+
});
|
|
2618
|
+
const [agent] = useState(() => {
|
|
2619
|
+
const ag = new MainAgent();
|
|
2620
|
+
if (autoApprove) {
|
|
2621
|
+
ag.setAutoApprove(true);
|
|
2622
|
+
}
|
|
2623
|
+
if (target) {
|
|
2624
|
+
ag.addTarget(target);
|
|
2625
|
+
ag.setScope([target]);
|
|
2626
|
+
}
|
|
2627
|
+
return ag;
|
|
2628
|
+
});
|
|
2629
|
+
const startTimeRef = useRef(0);
|
|
2630
|
+
const timerRef = useRef(null);
|
|
2631
|
+
const startTimer = useCallback(() => {
|
|
2632
|
+
startTimeRef.current = Date.now();
|
|
2633
|
+
timerRef.current = setInterval(() => {
|
|
2634
|
+
setElapsedTime(Math.floor((Date.now() - startTimeRef.current) / 1e3));
|
|
2635
|
+
}, 1e3);
|
|
2636
|
+
}, []);
|
|
2637
|
+
const stopTimer = useCallback(() => {
|
|
2638
|
+
if (timerRef.current) {
|
|
2639
|
+
clearInterval(timerRef.current);
|
|
2640
|
+
timerRef.current = null;
|
|
2641
|
+
}
|
|
2642
|
+
const duration = Math.floor((Date.now() - startTimeRef.current) / 100) / 10;
|
|
2643
|
+
setElapsedTime(0);
|
|
2644
|
+
return duration;
|
|
2645
|
+
}, []);
|
|
2646
|
+
const addMessage = useCallback((type, content) => {
|
|
2647
|
+
const id = Math.random().toString(36).substring(7);
|
|
2648
|
+
setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
|
|
2649
|
+
}, []);
|
|
2650
|
+
useEffect(() => {
|
|
2651
|
+
if (target) {
|
|
2652
|
+
addMessage("system", `Target: ${target}`);
|
|
2653
|
+
}
|
|
2654
|
+
if (autoApprove) {
|
|
2655
|
+
addMessage("system", "YOLO Mode: Auto-approving all tool executions");
|
|
2656
|
+
}
|
|
2657
|
+
const events = agent.getEventEmitter();
|
|
2658
|
+
events.on("thinking_start", (event) => {
|
|
2659
|
+
setThinking({ isActive: true, content: "", phase: event.data.phase });
|
|
2660
|
+
});
|
|
2661
|
+
events.on("thinking_delta", (event) => {
|
|
2662
|
+
setThinking((prev) => {
|
|
2663
|
+
const newContent = prev.content + event.data.content;
|
|
2664
|
+
return {
|
|
2665
|
+
...prev,
|
|
2666
|
+
content: newContent.slice(-500)
|
|
2667
|
+
// Keep only last 500 chars for display
|
|
2668
|
+
};
|
|
2669
|
+
});
|
|
2670
|
+
});
|
|
2671
|
+
events.on("thinking_end", (event) => {
|
|
2672
|
+
setThinking((prev) => ({ ...prev, isActive: false }));
|
|
2673
|
+
});
|
|
2674
|
+
events.on("tool_call", (event) => {
|
|
2675
|
+
const data = event.data;
|
|
2676
|
+
setCurrentStatus(`Executing: ${data.toolName}`);
|
|
2677
|
+
let inputStr = "";
|
|
2678
|
+
try {
|
|
2679
|
+
if (data.input) {
|
|
2680
|
+
const str = JSON.stringify(data.input);
|
|
2681
|
+
inputStr = str.length > 50 ? str.substring(0, 47) + "..." : str;
|
|
2682
|
+
}
|
|
2683
|
+
} catch (e) {
|
|
2684
|
+
}
|
|
2685
|
+
addMessage("tool", ` \u23BF ${data.toolName} ${inputStr}`);
|
|
2686
|
+
});
|
|
2687
|
+
events.on("tool_result", (event) => {
|
|
2688
|
+
const data = event.data;
|
|
2689
|
+
const icon = data.success ? "\u2713" : "\u2717";
|
|
2690
|
+
const output = data.output || "";
|
|
2691
|
+
const preview = output.slice(0, 300).replace(/\n/g, " ");
|
|
2692
|
+
const more = output.length > 300 ? "..." : "";
|
|
2693
|
+
addMessage("result", ` ${icon} ${preview}${more}`);
|
|
2694
|
+
});
|
|
2695
|
+
events.on("think", (event) => {
|
|
2696
|
+
const data = event.data;
|
|
2697
|
+
const thought = data.thought.length > 100 ? data.thought.substring(0, 97) + "..." : data.thought;
|
|
2698
|
+
setCurrentStatus(thought);
|
|
2699
|
+
});
|
|
2700
|
+
events.on("complete", (event) => {
|
|
2701
|
+
const data = event.data;
|
|
2702
|
+
addMessage("system", `\u2713 Complete (${data.toolsExecuted} tools)`);
|
|
2703
|
+
});
|
|
2704
|
+
events.on("error", (event) => {
|
|
2705
|
+
const data = event.data;
|
|
2706
|
+
addMessage("error", data.message || "An error occurred");
|
|
2707
|
+
});
|
|
2708
|
+
const updateStats = () => {
|
|
2709
|
+
const state = agent.getState();
|
|
2710
|
+
setStats({
|
|
2711
|
+
phase: agent.getPhase(),
|
|
2712
|
+
targets: state.getTargets().size,
|
|
2713
|
+
findings: state.getFindings().length,
|
|
2714
|
+
todo: state.getTodo().length
|
|
2715
|
+
});
|
|
2716
|
+
};
|
|
2717
|
+
events.on("state_change", updateStats);
|
|
2718
|
+
events.on("start", updateStats);
|
|
2719
|
+
events.on("complete", updateStats);
|
|
2720
|
+
updateStats();
|
|
2721
|
+
return () => {
|
|
2722
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
2723
|
+
events.off("state_change", updateStats);
|
|
2724
|
+
events.off("start", updateStats);
|
|
2725
|
+
events.off("complete", updateStats);
|
|
2726
|
+
};
|
|
2727
|
+
}, [agent, target, autoApprove, addMessage, startTimer]);
|
|
2728
|
+
const handleExit = useCallback(async () => {
|
|
2729
|
+
setCurrentStatus("Exiting");
|
|
2730
|
+
if (timerRef.current) {
|
|
2731
|
+
clearInterval(timerRef.current);
|
|
2732
|
+
}
|
|
2733
|
+
exit();
|
|
2734
|
+
}, [exit]);
|
|
2735
|
+
useEffect(() => {
|
|
2736
|
+
const onExit = () => {
|
|
2737
|
+
handleExit();
|
|
2738
|
+
};
|
|
2739
|
+
process.on("SIGINT", onExit);
|
|
2740
|
+
process.on("SIGTERM", onExit);
|
|
2741
|
+
return () => {
|
|
2742
|
+
process.off("SIGINT", onExit);
|
|
2743
|
+
process.off("SIGTERM", onExit);
|
|
2744
|
+
};
|
|
2745
|
+
}, [handleExit, exit]);
|
|
2746
|
+
const handleSubmit = useCallback(async (value) => {
|
|
2747
|
+
const trimmed = value.trim();
|
|
2748
|
+
if (!trimmed) return;
|
|
2749
|
+
setInput("");
|
|
2750
|
+
addMessage("user", trimmed);
|
|
2751
|
+
if (trimmed.startsWith("/")) {
|
|
2752
|
+
const [cmd, ...args] = trimmed.slice(1).split(" ");
|
|
2753
|
+
switch (cmd) {
|
|
2754
|
+
case "help":
|
|
2755
|
+
case "h":
|
|
2756
|
+
addMessage("system", `
|
|
2757
|
+
\u2500\u2500 Commands \u2500\u2500
|
|
2758
|
+
/target <ip> Set target
|
|
2759
|
+
/start [goal] Start autonomous pentest
|
|
2760
|
+
/stop Stop operation
|
|
2761
|
+
/findings Show findings
|
|
2762
|
+
/status Show status
|
|
2763
|
+
/clear Clear screen
|
|
2764
|
+
/exit Exit
|
|
2765
|
+
|
|
2766
|
+
\u2500\u2500 Examples \u2500\u2500
|
|
2767
|
+
/target 192.168.1.1
|
|
2768
|
+
/start "Recon the target"
|
|
2769
|
+
/findings
|
|
2770
|
+
`);
|
|
2771
|
+
return;
|
|
2772
|
+
case "target":
|
|
2773
|
+
case "t":
|
|
2774
|
+
if (args[0]) {
|
|
2775
|
+
agent.addTarget(args[0]);
|
|
2776
|
+
agent.setScope([args[0]]);
|
|
2777
|
+
addMessage("system", `Target \u2192 ${args[0]}`);
|
|
2778
|
+
} else {
|
|
2779
|
+
addMessage("system", "Usage: /target <ip>");
|
|
2780
|
+
}
|
|
2781
|
+
return;
|
|
2782
|
+
case "start":
|
|
2783
|
+
case "s":
|
|
2784
|
+
if (!agent.getState().getTargets().size) {
|
|
2785
|
+
addMessage("error", "Set target first: /target <ip>");
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
setIsProcessing(true);
|
|
2789
|
+
startTimer();
|
|
2790
|
+
setCurrentStatus("Initializing");
|
|
2791
|
+
addMessage("system", `Starting: ${args.join(" ") || "Perform comprehensive penetration testing"}`);
|
|
2792
|
+
try {
|
|
2793
|
+
await agent.execute(args.join(" ") || "Perform comprehensive penetration testing");
|
|
2794
|
+
} catch (e) {
|
|
2795
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
2796
|
+
}
|
|
2797
|
+
stopTimer();
|
|
2798
|
+
setIsProcessing(false);
|
|
2799
|
+
setCurrentStatus("");
|
|
2800
|
+
return;
|
|
2801
|
+
case "stop":
|
|
2802
|
+
addMessage("system", "Stopping...");
|
|
2803
|
+
setIsProcessing(false);
|
|
2804
|
+
setCurrentStatus("");
|
|
2805
|
+
return;
|
|
2806
|
+
case "findings":
|
|
2807
|
+
case "f":
|
|
2808
|
+
const findings = agent.getState().getFindings();
|
|
2809
|
+
if (findings.length === 0) {
|
|
2810
|
+
addMessage("system", "No findings.");
|
|
2811
|
+
} else {
|
|
2812
|
+
addMessage("system", `--- ${findings.length} Findings ---`);
|
|
2813
|
+
findings.forEach((f) => addMessage("system", `[${f.severity}] ${f.title}`));
|
|
2814
|
+
}
|
|
2815
|
+
return;
|
|
2816
|
+
case "status":
|
|
2817
|
+
const state = agent.getState();
|
|
2818
|
+
addMessage("system", `Status:
|
|
2819
|
+
Phase: ${agent.getPhase()}
|
|
2820
|
+
Targets: ${state.getTargets().size}
|
|
2821
|
+
Findings: ${state.getFindings().length}
|
|
2822
|
+
TODO: ${state.getTodo().length}
|
|
2823
|
+
`);
|
|
2824
|
+
return;
|
|
2825
|
+
case "clear":
|
|
2826
|
+
case "c":
|
|
2827
|
+
setMessages([]);
|
|
2828
|
+
return;
|
|
2829
|
+
case "exit":
|
|
2830
|
+
case "quit":
|
|
2831
|
+
case "q":
|
|
2832
|
+
await handleExit();
|
|
2833
|
+
return;
|
|
2834
|
+
default:
|
|
2835
|
+
addMessage("error", `Unknown command: /${cmd}`);
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
setIsProcessing(true);
|
|
2840
|
+
startTimer();
|
|
2841
|
+
setCurrentStatus("Thinking");
|
|
2842
|
+
try {
|
|
2843
|
+
const response = await agent.execute(trimmed);
|
|
2844
|
+
addMessage("assistant", response);
|
|
2845
|
+
} catch (e) {
|
|
2846
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
2847
|
+
} finally {
|
|
2848
|
+
stopTimer();
|
|
2849
|
+
setIsProcessing(false);
|
|
2850
|
+
setCurrentStatus("");
|
|
2851
|
+
}
|
|
2852
|
+
}, [agent, addMessage, startTimer, stopTimer, handleExit]);
|
|
2853
|
+
useInput((input2, key) => {
|
|
2854
|
+
if (key.ctrl && input2 === "c") {
|
|
2855
|
+
if (isProcessing) {
|
|
2856
|
+
setIsProcessing(false);
|
|
2857
|
+
stopTimer();
|
|
2858
|
+
setCurrentStatus("");
|
|
2859
|
+
addMessage("system", "Interrupted");
|
|
2860
|
+
} else {
|
|
2861
|
+
exit();
|
|
2862
|
+
}
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
2867
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, flexGrow: 1, children: /* @__PURE__ */ jsx2(Static, { items: messages, children: (msg) => {
|
|
2868
|
+
const colors = {
|
|
2869
|
+
user: THEME.text.accent,
|
|
2870
|
+
assistant: THEME.text.primary,
|
|
2871
|
+
system: THEME.text.muted,
|
|
2872
|
+
error: THEME.status.error,
|
|
2873
|
+
tool: THEME.status.running,
|
|
2874
|
+
result: THEME.text.muted,
|
|
2875
|
+
thinking: THEME.status.warning
|
|
2876
|
+
};
|
|
2877
|
+
const prefixes = {
|
|
2878
|
+
user: "\u276F",
|
|
2879
|
+
assistant: ">",
|
|
2880
|
+
system: "\u2022",
|
|
2881
|
+
error: "\u2717",
|
|
2882
|
+
tool: " \u23BF",
|
|
2883
|
+
result: " \u2713",
|
|
2884
|
+
thinking: "\u{1F9E0}"
|
|
2885
|
+
};
|
|
2886
|
+
return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: colors[msg.type], dimColor: msg.type === "result", children: [
|
|
2887
|
+
prefixes[msg.type],
|
|
2888
|
+
" ",
|
|
2889
|
+
msg.content
|
|
2890
|
+
] }) }, msg.id);
|
|
2891
|
+
} }) }),
|
|
2892
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 0, children: [
|
|
2893
|
+
thinking.isActive && thinking.content && /* @__PURE__ */ jsxs2(Box2, { marginBottom: 1, children: [
|
|
2894
|
+
/* @__PURE__ */ jsx2(Text2, { color: THEME.status.running, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
|
|
2895
|
+
/* @__PURE__ */ jsxs2(Text2, { color: THEME.text.muted, children: [
|
|
2896
|
+
" ",
|
|
2897
|
+
"Thinking: "
|
|
2898
|
+
] }),
|
|
2899
|
+
/* @__PURE__ */ jsx2(Text2, { color: THEME.text.secondary, dimColor: true, children: thinking.content.slice(-200) })
|
|
2900
|
+
] }),
|
|
2901
|
+
isProcessing && !thinking.isActive && /* @__PURE__ */ jsxs2(Box2, { marginBottom: 1, children: [
|
|
2902
|
+
/* @__PURE__ */ jsx2(Text2, { color: THEME.spinner, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
|
|
2903
|
+
/* @__PURE__ */ jsxs2(Text2, { color: THEME.text.muted, children: [
|
|
2904
|
+
" ",
|
|
2905
|
+
currentStatus || "Processing"
|
|
2906
|
+
] })
|
|
2907
|
+
] }),
|
|
2908
|
+
/* @__PURE__ */ jsx2(
|
|
2909
|
+
Box2,
|
|
2910
|
+
{
|
|
2911
|
+
borderStyle: "single",
|
|
2912
|
+
borderColor: THEME.border.default,
|
|
2913
|
+
paddingX: 1,
|
|
2914
|
+
children: /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
2915
|
+
/* @__PURE__ */ jsx2(Text2, { color: THEME.text.secondary, children: "\u25B8" }),
|
|
2916
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " }),
|
|
2917
|
+
/* @__PURE__ */ jsx2(
|
|
2918
|
+
TextInput,
|
|
2919
|
+
{
|
|
2920
|
+
value: input,
|
|
2921
|
+
onChange: setInput,
|
|
2922
|
+
onSubmit: handleSubmit,
|
|
2923
|
+
placeholder: "Message or /help..."
|
|
2924
|
+
}
|
|
2925
|
+
)
|
|
2926
|
+
] })
|
|
2927
|
+
}
|
|
2928
|
+
),
|
|
2929
|
+
/* @__PURE__ */ jsx2(
|
|
2930
|
+
footer_default,
|
|
2931
|
+
{
|
|
2932
|
+
phase: stats.phase,
|
|
2933
|
+
targets: stats.targets,
|
|
2934
|
+
findings: stats.findings,
|
|
2935
|
+
todo: stats.todo,
|
|
2936
|
+
elapsedTime,
|
|
2937
|
+
isProcessing
|
|
2938
|
+
}
|
|
2939
|
+
)
|
|
2940
|
+
] })
|
|
2941
|
+
] });
|
|
2942
|
+
};
|
|
2943
|
+
var app_default = App;
|
|
2944
|
+
|
|
2945
|
+
// src/shared/constants/_shared/signal.const.ts
|
|
2946
|
+
var EXIT_CODE = {
|
|
2947
|
+
SUCCESS: 0,
|
|
2948
|
+
ERROR: 1,
|
|
2949
|
+
// Unix convention: 128 + signal number
|
|
2950
|
+
SIGINT: 130,
|
|
2951
|
+
// 128 + 2
|
|
2952
|
+
SIGTERM: 143,
|
|
2953
|
+
// 128 + 15
|
|
2954
|
+
SIGKILL: 137
|
|
2955
|
+
// 128 + 9
|
|
2956
|
+
};
|
|
2957
|
+
|
|
2958
|
+
// src/platform/tui/main.tsx
|
|
2959
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
2960
|
+
var program = new Command();
|
|
2961
|
+
program.name("pentesting").version(APP_VERSION).description(APP_DESCRIPTION).option("--dangerously-skip-permissions", "Skip all permission prompts (dangerous!)").option("-t, --target <target>", "Set initial target");
|
|
2962
|
+
program.command("interactive", { isDefault: true }).alias("i").description("Start interactive TUI mode").action(async () => {
|
|
2963
|
+
const opts = program.opts();
|
|
2964
|
+
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
2965
|
+
console.clear();
|
|
2966
|
+
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
2967
|
+
console.log(
|
|
2968
|
+
" " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.text.accent)("Type /help for commands") + "\n"
|
|
2969
|
+
);
|
|
2970
|
+
if (skipPermissions) {
|
|
2971
|
+
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
|
|
2972
|
+
console.log(chalk.hex(THEME.status.error)("[!] All tool executions will be auto-approved!\n"));
|
|
2973
|
+
}
|
|
2974
|
+
const { waitUntilExit } = render(
|
|
2975
|
+
/* @__PURE__ */ jsx3(
|
|
2976
|
+
app_default,
|
|
2977
|
+
{
|
|
2978
|
+
autoApprove: skipPermissions,
|
|
2979
|
+
target: opts.target
|
|
2980
|
+
}
|
|
2981
|
+
)
|
|
2982
|
+
);
|
|
2983
|
+
await waitUntilExit();
|
|
2984
|
+
});
|
|
2985
|
+
program.command("run <objective>").alias("r").description("Run a single objective and exit").option("-o, --output <file>", "Output file for results").option("--max-steps <n>", "Maximum number of steps", "50").action(async (objective, options) => {
|
|
2986
|
+
const opts = program.opts();
|
|
2987
|
+
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
2988
|
+
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
2989
|
+
if (skipPermissions) {
|
|
2990
|
+
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
|
|
2991
|
+
}
|
|
2992
|
+
console.log(chalk.hex(THEME.text.accent)(`[target] Objective: ${objective}
|
|
2993
|
+
`));
|
|
2994
|
+
const agent = new MainAgent();
|
|
2995
|
+
if (skipPermissions) {
|
|
2996
|
+
agent.setAutoApprove(true);
|
|
2997
|
+
}
|
|
2998
|
+
if (opts.target) {
|
|
2999
|
+
agent.addTarget(opts.target);
|
|
3000
|
+
agent.setScope([opts.target]);
|
|
3001
|
+
}
|
|
3002
|
+
const shutdown = async (exitCode = 0) => {
|
|
3003
|
+
process.exit(exitCode);
|
|
3004
|
+
};
|
|
3005
|
+
process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
|
|
3006
|
+
process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
|
|
3007
|
+
try {
|
|
3008
|
+
const result = await agent.execute(objective);
|
|
3009
|
+
console.log(chalk.hex(THEME.status.success)("\n[+] Assessment complete!\n"));
|
|
3010
|
+
console.log(result);
|
|
3011
|
+
if (options.output) {
|
|
3012
|
+
const fs = await import("fs/promises");
|
|
3013
|
+
await fs.writeFile(options.output, JSON.stringify({ result }, null, 2));
|
|
3014
|
+
console.log(chalk.hex(THEME.text.accent)(`
|
|
3015
|
+
[+] Report saved to: ${options.output}`));
|
|
3016
|
+
}
|
|
3017
|
+
await shutdown(0);
|
|
3018
|
+
} catch (error) {
|
|
3019
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3020
|
+
console.error(chalk.hex(THEME.status.error)(`
|
|
3021
|
+
[-] Failed: ${errorMessage}`));
|
|
3022
|
+
await shutdown(1);
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
program.command("scan <target>").description("Quick scan a target").option("-s, --scan-type <type>", "Scan type (quick|full|stealth|service|vuln)", "quick").option("-p, --ports <ports>", "Specific ports to scan").action(async (target, options) => {
|
|
3026
|
+
const opts = program.opts();
|
|
3027
|
+
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
3028
|
+
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
3029
|
+
console.log(chalk.hex(THEME.text.accent)(`
|
|
3030
|
+
[scan] Target: ${target} (${options.scanType})
|
|
3031
|
+
`));
|
|
3032
|
+
const agent = new MainAgent();
|
|
3033
|
+
if (skipPermissions) {
|
|
3034
|
+
agent.setAutoApprove(true);
|
|
3035
|
+
}
|
|
3036
|
+
agent.addTarget(target);
|
|
3037
|
+
agent.setScope([target]);
|
|
3038
|
+
const shutdown = async (exitCode = 0) => {
|
|
3039
|
+
process.exit(exitCode);
|
|
3040
|
+
};
|
|
3041
|
+
process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
|
|
3042
|
+
process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
|
|
3043
|
+
try {
|
|
3044
|
+
await agent.execute(`Perform a ${options.scanType} scan on ${target}${options.ports ? ` focusing on ports ${options.ports}` : ""}. Analyze the results and identify potential vulnerabilities.`);
|
|
3045
|
+
console.log(chalk.hex(THEME.status.success)("[+] Scan complete!"));
|
|
3046
|
+
await shutdown(0);
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3049
|
+
console.error(chalk.hex(THEME.status.error)(`[-] Scan failed: ${errorMessage}`));
|
|
3050
|
+
await shutdown(1);
|
|
3051
|
+
}
|
|
3052
|
+
});
|
|
3053
|
+
program.command("help-extended").description("Show extended help with examples").action(() => {
|
|
3054
|
+
console.log(gradient([...THEME.gradient.cyber]).multiline(ASCII_BANNER));
|
|
3055
|
+
console.log(`
|
|
3056
|
+
${chalk.hex(THEME.text.accent)(APP_NAME + " - Autonomous Penetration Testing AI")}
|
|
3057
|
+
|
|
3058
|
+
${chalk.hex(THEME.status.warning)("Usage:")}
|
|
3059
|
+
|
|
3060
|
+
${chalk.hex(THEME.status.success)("$ pentesting")} Start interactive mode
|
|
3061
|
+
${chalk.hex(THEME.status.success)("$ pentesting -t 192.168.1.1")} Start with target
|
|
3062
|
+
${chalk.hex(THEME.status.success)("$ pentesting --dangerously-skip-permissions")} Auto-approve all tools
|
|
3063
|
+
|
|
3064
|
+
${chalk.hex(THEME.status.warning)("Commands:")}
|
|
3065
|
+
|
|
3066
|
+
${chalk.hex(THEME.text.accent)("pentesting")} Interactive TUI mode
|
|
3067
|
+
${chalk.hex(THEME.text.accent)("pentesting run <objective>")} Run single objective
|
|
3068
|
+
${chalk.hex(THEME.text.accent)("pentesting scan <target>")} Quick scan target
|
|
3069
|
+
|
|
3070
|
+
${chalk.hex(THEME.status.warning)("Options:")}
|
|
3071
|
+
|
|
3072
|
+
${chalk.hex(THEME.text.accent)("--dangerously-skip-permissions")} Skip all permission prompts
|
|
3073
|
+
${chalk.hex(THEME.text.accent)("-t, --target <ip>")} Set target
|
|
3074
|
+
${chalk.hex(THEME.text.accent)("-o, --output <file>")} Save results to file
|
|
3075
|
+
|
|
3076
|
+
${chalk.hex(THEME.status.warning)("Interactive Commands:")}
|
|
3077
|
+
|
|
3078
|
+
${chalk.hex(THEME.text.accent)("/target <ip>")} Set target
|
|
3079
|
+
${chalk.hex(THEME.text.accent)("/start")} Start autonomous mode
|
|
3080
|
+
${chalk.hex(THEME.text.accent)("/config")} Manage configuration
|
|
3081
|
+
${chalk.hex(THEME.text.accent)("/hint <text>")} Provide hint
|
|
3082
|
+
${chalk.hex(THEME.text.accent)("/findings")} Show findings
|
|
3083
|
+
${chalk.hex(THEME.text.accent)("/reset")} Reset session
|
|
3084
|
+
|
|
3085
|
+
${chalk.hex(THEME.status.warning)("Examples:")}
|
|
3086
|
+
|
|
3087
|
+
${chalk.hex(THEME.text.muted)("# Full autonomous mode")}
|
|
3088
|
+
$ pentesting --dangerously-skip-permissions -t 10.10.10.5
|
|
3089
|
+
|
|
3090
|
+
${chalk.hex(THEME.text.muted)("# Run specific objective")}
|
|
3091
|
+
$ pentesting run "Find SQL injection" -t http://target.com -o report.json
|
|
3092
|
+
|
|
3093
|
+
${chalk.hex(THEME.text.muted)("# Quick vulnerability scan")}
|
|
3094
|
+
$ pentesting scan 192.168.1.1 -s vuln
|
|
3095
|
+
|
|
3096
|
+
${chalk.hex(THEME.status.warning)("Environment:")}
|
|
3097
|
+
|
|
3098
|
+
${chalk.hex(THEME.text.accent)("PENTEST_API_KEY")} Required - LLM API key
|
|
3099
|
+
${chalk.hex(THEME.text.accent)("PENTEST_BASE_URL")} Optional - AI API base URL
|
|
3100
|
+
${chalk.hex(THEME.text.accent)("PENTEST_MODEL")} Optional - Model override
|
|
3101
|
+
`);
|
|
3102
|
+
});
|
|
3103
|
+
program.parse();
|