pentesting 0.55.3 → 0.55.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +1009 -498
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -27,8 +27,8 @@ import { Command } from "commander";
|
|
|
27
27
|
import chalk from "chalk";
|
|
28
28
|
|
|
29
29
|
// src/platform/tui/app.tsx
|
|
30
|
-
import { useState as useState6, useCallback as
|
|
31
|
-
import { Box as Box6,
|
|
30
|
+
import { useState as useState6, useCallback as useCallback6, useRef as useRef7 } from "react";
|
|
31
|
+
import { Box as Box6, useApp, useStdout } from "ink";
|
|
32
32
|
|
|
33
33
|
// src/platform/tui/hooks/useAgent.ts
|
|
34
34
|
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef3 } from "react";
|
|
@@ -1240,7 +1240,7 @@ var INPUT_PROMPT_PATTERNS = [
|
|
|
1240
1240
|
|
|
1241
1241
|
// src/shared/constants/agent.ts
|
|
1242
1242
|
var APP_NAME = "Pentest AI";
|
|
1243
|
-
var APP_VERSION = "0.55.
|
|
1243
|
+
var APP_VERSION = "0.55.4";
|
|
1244
1244
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
1245
1245
|
var LLM_ROLES = {
|
|
1246
1246
|
SYSTEM: "system",
|
|
@@ -1516,6 +1516,181 @@ var IMPACT_WEIGHT = {
|
|
|
1516
1516
|
low: 1
|
|
1517
1517
|
};
|
|
1518
1518
|
|
|
1519
|
+
// src/shared/utils/attack-graph/node.ts
|
|
1520
|
+
function generateNodeId(type, label) {
|
|
1521
|
+
return `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
|
|
1522
|
+
}
|
|
1523
|
+
function createNode(type, label, data = {}, status = NODE_STATUS.DISCOVERED) {
|
|
1524
|
+
const id = generateNodeId(type, label);
|
|
1525
|
+
return {
|
|
1526
|
+
id,
|
|
1527
|
+
type,
|
|
1528
|
+
label,
|
|
1529
|
+
data,
|
|
1530
|
+
status,
|
|
1531
|
+
discoveredAt: Date.now()
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function canMarkAttempted(node) {
|
|
1535
|
+
return node.status === NODE_STATUS.DISCOVERED;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/shared/utils/attack-graph/edge.ts
|
|
1539
|
+
function createEdge(from, to, relation, confidence = 0.5, status = EDGE_STATUS.UNTESTED) {
|
|
1540
|
+
return {
|
|
1541
|
+
from,
|
|
1542
|
+
to,
|
|
1543
|
+
relation,
|
|
1544
|
+
confidence,
|
|
1545
|
+
status
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
function edgeExists(edges, fromId, toId, relation) {
|
|
1549
|
+
return edges.some(
|
|
1550
|
+
(e) => e.from === fromId && e.to === toId && e.relation === relation
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
function getIncomingEdges(edges, nodeId) {
|
|
1554
|
+
return edges.filter((e) => e.to === nodeId);
|
|
1555
|
+
}
|
|
1556
|
+
function markEdgeSucceeded(edge) {
|
|
1557
|
+
return {
|
|
1558
|
+
...edge,
|
|
1559
|
+
status: EDGE_STATUS.SUCCEEDED
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
function markEdgeFailed(edge, reason) {
|
|
1563
|
+
return {
|
|
1564
|
+
...edge,
|
|
1565
|
+
status: EDGE_STATUS.FAILED,
|
|
1566
|
+
failReason: reason
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function isTestableEdge(edge) {
|
|
1570
|
+
return edge.status === EDGE_STATUS.UNTESTED || edge.status === EDGE_STATUS.TESTING;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/shared/utils/attack-graph/utils.ts
|
|
1574
|
+
var AUTH_SERVICES = [
|
|
1575
|
+
"ssh",
|
|
1576
|
+
"ftp",
|
|
1577
|
+
"rdp",
|
|
1578
|
+
"smb",
|
|
1579
|
+
"http",
|
|
1580
|
+
"mysql",
|
|
1581
|
+
"postgresql",
|
|
1582
|
+
"mssql",
|
|
1583
|
+
"winrm",
|
|
1584
|
+
"vnc",
|
|
1585
|
+
"telnet"
|
|
1586
|
+
];
|
|
1587
|
+
var HIGH_PRIVILEGE_LEVELS = ["root", "admin", "SYSTEM", "Administrator"];
|
|
1588
|
+
function createHostNode(ip, hostname) {
|
|
1589
|
+
return createNode(NODE_TYPE.HOST, ip, { ip, hostname });
|
|
1590
|
+
}
|
|
1591
|
+
function createServiceNode(host, port, service, version) {
|
|
1592
|
+
const hostId = generateNodeId(NODE_TYPE.HOST, host);
|
|
1593
|
+
const node = createNode(
|
|
1594
|
+
NODE_TYPE.SERVICE,
|
|
1595
|
+
`${host}:${port}`,
|
|
1596
|
+
{ host, port, service, version }
|
|
1597
|
+
);
|
|
1598
|
+
const hostEdge = createEdge(hostId, node.id, "has_service", 0.95);
|
|
1599
|
+
return { node, hostEdge };
|
|
1600
|
+
}
|
|
1601
|
+
function createVersionSearchNode(service, version) {
|
|
1602
|
+
return createNode(
|
|
1603
|
+
NODE_TYPE.VULNERABILITY,
|
|
1604
|
+
`CVE search: ${service} ${version}`,
|
|
1605
|
+
{ service, version, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
function createCredentialNode(username, password, source) {
|
|
1609
|
+
return createNode(
|
|
1610
|
+
NODE_TYPE.CREDENTIAL,
|
|
1611
|
+
`${username}:***`,
|
|
1612
|
+
{ username, password, source }
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
function createCredentialSprayEdges(credId, nodes) {
|
|
1616
|
+
const edges = [];
|
|
1617
|
+
for (const [id, node] of nodes) {
|
|
1618
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
1619
|
+
const svc = String(node.data.service || "");
|
|
1620
|
+
if (AUTH_SERVICES.some((s) => svc.includes(s))) {
|
|
1621
|
+
edges.push(createEdge(credId, id, "can_try_on", 0.6));
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return edges;
|
|
1626
|
+
}
|
|
1627
|
+
function createVulnerabilityNode(title, target, severity, hasExploit = false) {
|
|
1628
|
+
return createNode(
|
|
1629
|
+
NODE_TYPE.VULNERABILITY,
|
|
1630
|
+
title,
|
|
1631
|
+
{ target, severity, hasExploit }
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
function findTargetServices(nodes, target) {
|
|
1635
|
+
const results = [];
|
|
1636
|
+
for (const node of nodes.values()) {
|
|
1637
|
+
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
1638
|
+
results.push(node);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return results;
|
|
1642
|
+
}
|
|
1643
|
+
function createAccessNode(host, level, via) {
|
|
1644
|
+
return createNode(
|
|
1645
|
+
NODE_TYPE.ACCESS,
|
|
1646
|
+
`${level}@${host}`,
|
|
1647
|
+
{ host, level, via }
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
function createPotentialAccessNode(exploitTitle) {
|
|
1651
|
+
return createNode(
|
|
1652
|
+
NODE_TYPE.ACCESS,
|
|
1653
|
+
`shell via ${exploitTitle}`,
|
|
1654
|
+
{ via: exploitTitle, status: GRAPH_STATUS.POTENTIAL }
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
function createLootNode(host) {
|
|
1658
|
+
return createNode(
|
|
1659
|
+
NODE_TYPE.LOOT,
|
|
1660
|
+
`flags on ${host}`,
|
|
1661
|
+
{ host, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
function createOSINTNode(category, detail, data = {}) {
|
|
1665
|
+
return createNode(
|
|
1666
|
+
NODE_TYPE.OSINT,
|
|
1667
|
+
`${category}: ${detail}`,
|
|
1668
|
+
{ category, detail, ...data }
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
function findOSINTRelatedNodes(nodes, detail) {
|
|
1672
|
+
const results = [];
|
|
1673
|
+
for (const [id, node] of nodes) {
|
|
1674
|
+
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
1675
|
+
const hostIp = String(node.data.ip || node.data.host || "");
|
|
1676
|
+
const hostname = String(node.data.hostname || "");
|
|
1677
|
+
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
1678
|
+
results.push({ id, node });
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return results;
|
|
1683
|
+
}
|
|
1684
|
+
function isHighPrivilege(level) {
|
|
1685
|
+
return HIGH_PRIVILEGE_LEVELS.includes(level);
|
|
1686
|
+
}
|
|
1687
|
+
function formatFailedPath(from, to, reason) {
|
|
1688
|
+
return `${from} \u2192 ${to}${reason ? ` (${reason})` : ""}`;
|
|
1689
|
+
}
|
|
1690
|
+
function formatFailedNode(label, reason) {
|
|
1691
|
+
return `${label}${reason ? ` (${reason})` : ""}`;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1519
1694
|
// src/shared/utils/attack-graph/chain-discovery.ts
|
|
1520
1695
|
function recommendChains(nodes, edges) {
|
|
1521
1696
|
const chains = [];
|
|
@@ -1770,7 +1945,152 @@ function getGraphStats(nodes, edges) {
|
|
|
1770
1945
|
};
|
|
1771
1946
|
}
|
|
1772
1947
|
|
|
1773
|
-
// src/shared/utils/attack-graph/
|
|
1948
|
+
// src/shared/utils/attack-graph/domain-operations.ts
|
|
1949
|
+
function registerHost(graph, ip, hostname) {
|
|
1950
|
+
const node = createHostNode(ip, hostname);
|
|
1951
|
+
if (!graph.nodes.has(node.id)) {
|
|
1952
|
+
graph.nodes.set(node.id, node);
|
|
1953
|
+
}
|
|
1954
|
+
return node.id;
|
|
1955
|
+
}
|
|
1956
|
+
function registerService(graph, host, port, service, version) {
|
|
1957
|
+
const hostId = registerHost(graph, host);
|
|
1958
|
+
const { node, hostEdge } = createServiceNode(host, port, service, version);
|
|
1959
|
+
if (!graph.nodes.has(node.id)) {
|
|
1960
|
+
graph.nodes.set(node.id, node);
|
|
1961
|
+
}
|
|
1962
|
+
if (hostEdge && !edgeExists(graph.edges, hostEdge.from, hostEdge.to, hostEdge.relation)) {
|
|
1963
|
+
graph.edges.push(hostEdge);
|
|
1964
|
+
}
|
|
1965
|
+
if (version) {
|
|
1966
|
+
const vulnNode = createVersionSearchNode(service, version);
|
|
1967
|
+
if (!graph.nodes.has(vulnNode.id)) {
|
|
1968
|
+
graph.nodes.set(vulnNode.id, vulnNode);
|
|
1969
|
+
}
|
|
1970
|
+
graph.addEdge(node.id, vulnNode.id, "may_have", 0.3);
|
|
1971
|
+
}
|
|
1972
|
+
return node.id;
|
|
1973
|
+
}
|
|
1974
|
+
function registerCredential(graph, username, password, source) {
|
|
1975
|
+
const node = createCredentialNode(username, password, source);
|
|
1976
|
+
if (!graph.nodes.has(node.id)) {
|
|
1977
|
+
graph.nodes.set(node.id, node);
|
|
1978
|
+
}
|
|
1979
|
+
const sprayEdges = createCredentialSprayEdges(node.id, graph.nodes);
|
|
1980
|
+
for (const edge of sprayEdges) {
|
|
1981
|
+
if (!edgeExists(graph.edges, edge.from, edge.to, edge.relation)) {
|
|
1982
|
+
graph.edges.push(edge);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
return node.id;
|
|
1986
|
+
}
|
|
1987
|
+
function registerVulnerability(graph, title, target, severity, hasExploit = false) {
|
|
1988
|
+
const vulnNode = createVulnerabilityNode(title, target, severity, hasExploit);
|
|
1989
|
+
if (!graph.nodes.has(vulnNode.id)) {
|
|
1990
|
+
graph.nodes.set(vulnNode.id, vulnNode);
|
|
1991
|
+
}
|
|
1992
|
+
const targetServices = findTargetServices(graph.nodes, target);
|
|
1993
|
+
for (const svc of targetServices) {
|
|
1994
|
+
graph.addEdge(svc.id, vulnNode.id, "has_vulnerability", 0.8);
|
|
1995
|
+
}
|
|
1996
|
+
if (hasExploit) {
|
|
1997
|
+
const accessNode = createPotentialAccessNode(title);
|
|
1998
|
+
if (!graph.nodes.has(accessNode.id)) {
|
|
1999
|
+
graph.nodes.set(accessNode.id, accessNode);
|
|
2000
|
+
}
|
|
2001
|
+
graph.addEdge(vulnNode.id, accessNode.id, "leads_to", 0.7);
|
|
2002
|
+
}
|
|
2003
|
+
return vulnNode.id;
|
|
2004
|
+
}
|
|
2005
|
+
function registerAccess(graph, host, level, via) {
|
|
2006
|
+
const accessNode = createAccessNode(host, level, via);
|
|
2007
|
+
if (!graph.nodes.has(accessNode.id)) {
|
|
2008
|
+
graph.nodes.set(accessNode.id, accessNode);
|
|
2009
|
+
}
|
|
2010
|
+
const node = graph.nodes.get(accessNode.id);
|
|
2011
|
+
if (node) {
|
|
2012
|
+
node.status = NODE_STATUS.SUCCEEDED;
|
|
2013
|
+
node.resolvedAt = Date.now();
|
|
2014
|
+
const incomingEdges = getIncomingEdges(graph.edges, accessNode.id);
|
|
2015
|
+
for (const edge of incomingEdges) {
|
|
2016
|
+
Object.assign(edge, markEdgeSucceeded(edge));
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (isHighPrivilege(level)) {
|
|
2020
|
+
const lootNode = createLootNode(host);
|
|
2021
|
+
if (!graph.nodes.has(lootNode.id)) {
|
|
2022
|
+
graph.nodes.set(lootNode.id, lootNode);
|
|
2023
|
+
}
|
|
2024
|
+
graph.addEdge(accessNode.id, lootNode.id, "can_access", 0.9);
|
|
2025
|
+
}
|
|
2026
|
+
return accessNode.id;
|
|
2027
|
+
}
|
|
2028
|
+
function registerOSINT(graph, category, detail, data = {}) {
|
|
2029
|
+
const osintNode = createOSINTNode(category, detail, data);
|
|
2030
|
+
if (!graph.nodes.has(osintNode.id)) {
|
|
2031
|
+
graph.nodes.set(osintNode.id, osintNode);
|
|
2032
|
+
}
|
|
2033
|
+
const relatedNodes = findOSINTRelatedNodes(graph.nodes, detail);
|
|
2034
|
+
for (const { id } of relatedNodes) {
|
|
2035
|
+
graph.addEdge(osintNode.id, id, "relates_to", 0.5);
|
|
2036
|
+
}
|
|
2037
|
+
return osintNode.id;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// src/shared/utils/attack-graph/status-management.ts
|
|
2041
|
+
function markNodeAttemptedInGraph(nodes, nodeId) {
|
|
2042
|
+
const node = nodes.get(nodeId);
|
|
2043
|
+
if (node && canMarkAttempted(node)) {
|
|
2044
|
+
node.status = NODE_STATUS.ATTEMPTED;
|
|
2045
|
+
node.attemptedAt = Date.now();
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
function markNodeSucceededInGraph(nodes, edges, nodeId) {
|
|
2049
|
+
const node = nodes.get(nodeId);
|
|
2050
|
+
if (node) {
|
|
2051
|
+
node.status = NODE_STATUS.SUCCEEDED;
|
|
2052
|
+
node.resolvedAt = Date.now();
|
|
2053
|
+
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
2054
|
+
for (const edge of incomingEdges) {
|
|
2055
|
+
if (edge.status === EDGE_STATUS.TESTING) {
|
|
2056
|
+
Object.assign(edge, markEdgeSucceeded(edge));
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
function markNodeFailedInGraph(nodes, edges, nodeId, reason, callbacks) {
|
|
2062
|
+
const node = nodes.get(nodeId);
|
|
2063
|
+
if (node) {
|
|
2064
|
+
node.status = NODE_STATUS.FAILED;
|
|
2065
|
+
node.resolvedAt = Date.now();
|
|
2066
|
+
node.failReason = reason;
|
|
2067
|
+
callbacks.onFailedPath(formatFailedNode(node.label, reason));
|
|
2068
|
+
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
2069
|
+
for (const edge of incomingEdges) {
|
|
2070
|
+
if (isTestableEdge(edge)) {
|
|
2071
|
+
Object.assign(edge, markEdgeFailed(edge, reason));
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
function markEdgeTestingInList(edges, fromId, toId) {
|
|
2077
|
+
for (const edge of edges) {
|
|
2078
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
2079
|
+
edge.status = EDGE_STATUS.TESTING;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
function markEdgeFailedInList(edges, fromId, toId, reason, callbacks) {
|
|
2084
|
+
for (const edge of edges) {
|
|
2085
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
2086
|
+
edge.status = EDGE_STATUS.FAILED;
|
|
2087
|
+
edge.failReason = reason;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
callbacks.onFailedPath(formatFailedPath(fromId, toId, reason));
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// src/shared/utils/attack-graph/graph.ts
|
|
1774
2094
|
var AttackGraph = class {
|
|
1775
2095
|
nodes = /* @__PURE__ */ new Map();
|
|
1776
2096
|
edges = [];
|
|
@@ -1786,26 +2106,18 @@ var AttackGraph = class {
|
|
|
1786
2106
|
* Add a node to the attack graph.
|
|
1787
2107
|
*/
|
|
1788
2108
|
addNode(type, label, data = {}) {
|
|
1789
|
-
const
|
|
1790
|
-
if (!this.nodes.has(id)) {
|
|
1791
|
-
this.nodes.set(id,
|
|
1792
|
-
id,
|
|
1793
|
-
type,
|
|
1794
|
-
label,
|
|
1795
|
-
data,
|
|
1796
|
-
status: NODE_STATUS.DISCOVERED,
|
|
1797
|
-
discoveredAt: Date.now()
|
|
1798
|
-
});
|
|
2109
|
+
const node = createNode(type, label, data);
|
|
2110
|
+
if (!this.nodes.has(node.id)) {
|
|
2111
|
+
this.nodes.set(node.id, node);
|
|
1799
2112
|
}
|
|
1800
|
-
return id;
|
|
2113
|
+
return node.id;
|
|
1801
2114
|
}
|
|
1802
2115
|
/**
|
|
1803
2116
|
* Add an edge (relationship) between two nodes.
|
|
1804
2117
|
*/
|
|
1805
2118
|
addEdge(fromId, toId, relation, confidence = 0.5) {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
this.edges.push({ from: fromId, to: toId, relation, confidence, status: EDGE_STATUS.UNTESTED });
|
|
2119
|
+
if (!edgeExists(this.edges, fromId, toId, relation)) {
|
|
2120
|
+
this.edges.push(createEdge(fromId, toId, relation, confidence));
|
|
1809
2121
|
}
|
|
1810
2122
|
}
|
|
1811
2123
|
/**
|
|
@@ -1819,176 +2131,81 @@ var AttackGraph = class {
|
|
|
1819
2131
|
* Mark a node as being attempted (in-progress attack).
|
|
1820
2132
|
*/
|
|
1821
2133
|
markAttempted(nodeId) {
|
|
1822
|
-
|
|
1823
|
-
if (node && node.status === NODE_STATUS.DISCOVERED) {
|
|
1824
|
-
node.status = NODE_STATUS.ATTEMPTED;
|
|
1825
|
-
node.attemptedAt = Date.now();
|
|
1826
|
-
}
|
|
2134
|
+
markNodeAttemptedInGraph(this.nodes, nodeId);
|
|
1827
2135
|
}
|
|
1828
2136
|
/**
|
|
1829
2137
|
* Mark a node as successfully exploited/achieved.
|
|
1830
2138
|
*/
|
|
1831
2139
|
markSucceeded(nodeId) {
|
|
1832
|
-
|
|
1833
|
-
if (node) {
|
|
1834
|
-
node.status = NODE_STATUS.SUCCEEDED;
|
|
1835
|
-
node.resolvedAt = Date.now();
|
|
1836
|
-
}
|
|
1837
|
-
for (const edge of this.edges) {
|
|
1838
|
-
if (edge.to === nodeId && edge.status === EDGE_STATUS.TESTING) {
|
|
1839
|
-
edge.status = EDGE_STATUS.SUCCEEDED;
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
2140
|
+
markNodeSucceededInGraph(this.nodes, this.edges, nodeId);
|
|
1842
2141
|
}
|
|
1843
2142
|
/**
|
|
1844
2143
|
* Mark a node as failed (attack didn't work).
|
|
1845
2144
|
*/
|
|
1846
2145
|
markFailed(nodeId, reason) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
node.resolvedAt = Date.now();
|
|
1851
|
-
node.failReason = reason;
|
|
1852
|
-
this.failedPaths.push(`${node.label}${reason ? ` (${reason})` : ""}`);
|
|
1853
|
-
}
|
|
1854
|
-
for (const edge of this.edges) {
|
|
1855
|
-
if (edge.to === nodeId && (edge.status === EDGE_STATUS.TESTING || edge.status === EDGE_STATUS.UNTESTED)) {
|
|
1856
|
-
edge.status = EDGE_STATUS.FAILED;
|
|
1857
|
-
edge.failReason = reason;
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
2146
|
+
markNodeFailedInGraph(this.nodes, this.edges, nodeId, reason, {
|
|
2147
|
+
onFailedPath: (path2) => this.failedPaths.push(path2)
|
|
2148
|
+
});
|
|
1860
2149
|
}
|
|
1861
2150
|
/**
|
|
1862
2151
|
* Mark an edge as being tested.
|
|
1863
2152
|
*/
|
|
1864
2153
|
markEdgeTesting(fromId, toId) {
|
|
1865
|
-
|
|
1866
|
-
if (edge.from === fromId && edge.to === toId) {
|
|
1867
|
-
edge.status = EDGE_STATUS.TESTING;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
2154
|
+
markEdgeTestingInList(this.edges, fromId, toId);
|
|
1870
2155
|
}
|
|
1871
2156
|
/**
|
|
1872
2157
|
* Mark an edge as failed.
|
|
1873
2158
|
*/
|
|
1874
2159
|
markEdgeFailed(fromId, toId, reason) {
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
edge.failReason = reason;
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
|
|
2160
|
+
markEdgeFailedInList(this.edges, fromId, toId, reason, {
|
|
2161
|
+
onFailedPath: (path2) => this.failedPaths.push(path2)
|
|
2162
|
+
});
|
|
1882
2163
|
}
|
|
1883
2164
|
// ─── Domain-Specific Registration ───────────────────────────
|
|
2165
|
+
/** Internal graph container for domain operations */
|
|
2166
|
+
get graphContainer() {
|
|
2167
|
+
return {
|
|
2168
|
+
nodes: this.nodes,
|
|
2169
|
+
edges: this.edges,
|
|
2170
|
+
addEdge: this.addEdge.bind(this),
|
|
2171
|
+
markSucceeded: this.markSucceeded.bind(this)
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
1884
2174
|
/**
|
|
1885
2175
|
* Record a host discovery.
|
|
1886
2176
|
*/
|
|
1887
2177
|
addHost(ip, hostname) {
|
|
1888
|
-
return this.
|
|
2178
|
+
return registerHost(this.graphContainer, ip, hostname);
|
|
1889
2179
|
}
|
|
1890
2180
|
/**
|
|
1891
2181
|
* Record a service discovery and auto-create edges.
|
|
1892
2182
|
*/
|
|
1893
2183
|
addService(host, port, service, version) {
|
|
1894
|
-
|
|
1895
|
-
const serviceId = this.addNode(NODE_TYPE.SERVICE, `${host}:${port}`, {
|
|
1896
|
-
host,
|
|
1897
|
-
port,
|
|
1898
|
-
service,
|
|
1899
|
-
version
|
|
1900
|
-
});
|
|
1901
|
-
this.addEdge(hostId, serviceId, "has_service", 0.95);
|
|
1902
|
-
if (version) {
|
|
1903
|
-
const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, `CVE search: ${service} ${version}`, {
|
|
1904
|
-
service,
|
|
1905
|
-
version,
|
|
1906
|
-
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
1907
|
-
});
|
|
1908
|
-
this.addEdge(serviceId, vulnId, "may_have", 0.3);
|
|
1909
|
-
}
|
|
1910
|
-
return serviceId;
|
|
2184
|
+
return registerService(this.graphContainer, host, port, service, version);
|
|
1911
2185
|
}
|
|
1912
2186
|
/**
|
|
1913
2187
|
* Record a credential discovery and create spray edges.
|
|
1914
2188
|
*/
|
|
1915
2189
|
addCredential(username, password, source) {
|
|
1916
|
-
|
|
1917
|
-
username,
|
|
1918
|
-
password,
|
|
1919
|
-
source
|
|
1920
|
-
});
|
|
1921
|
-
for (const [id, node] of this.nodes) {
|
|
1922
|
-
if (node.type === NODE_TYPE.SERVICE) {
|
|
1923
|
-
const svc = String(node.data.service || "");
|
|
1924
|
-
if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
|
|
1925
|
-
this.addEdge(credId, id, "can_try_on", 0.6);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
return credId;
|
|
2190
|
+
return registerCredential(this.graphContainer, username, password, source);
|
|
1930
2191
|
}
|
|
1931
2192
|
/**
|
|
1932
2193
|
* Record a vulnerability finding.
|
|
1933
2194
|
*/
|
|
1934
2195
|
addVulnerability(title, target, severity, hasExploit = false) {
|
|
1935
|
-
|
|
1936
|
-
target,
|
|
1937
|
-
severity,
|
|
1938
|
-
hasExploit
|
|
1939
|
-
});
|
|
1940
|
-
for (const [id, node] of this.nodes) {
|
|
1941
|
-
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
1942
|
-
this.addEdge(id, vulnId, "has_vulnerability", 0.8);
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
if (hasExploit) {
|
|
1946
|
-
const accessId = this.addNode(NODE_TYPE.ACCESS, `shell via ${title}`, {
|
|
1947
|
-
via: title,
|
|
1948
|
-
status: GRAPH_STATUS.POTENTIAL
|
|
1949
|
-
});
|
|
1950
|
-
this.addEdge(vulnId, accessId, "leads_to", 0.7);
|
|
1951
|
-
}
|
|
1952
|
-
return vulnId;
|
|
2196
|
+
return registerVulnerability(this.graphContainer, title, target, severity, hasExploit);
|
|
1953
2197
|
}
|
|
1954
2198
|
/**
|
|
1955
2199
|
* Record gained access.
|
|
1956
2200
|
*/
|
|
1957
2201
|
addAccess(host, level, via) {
|
|
1958
|
-
|
|
1959
|
-
host,
|
|
1960
|
-
level,
|
|
1961
|
-
via
|
|
1962
|
-
});
|
|
1963
|
-
this.markSucceeded(accessId);
|
|
1964
|
-
if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
|
|
1965
|
-
const lootId = this.addNode(NODE_TYPE.LOOT, `flags on ${host}`, {
|
|
1966
|
-
host,
|
|
1967
|
-
status: GRAPH_STATUS.NEEDS_SEARCH
|
|
1968
|
-
});
|
|
1969
|
-
this.addEdge(accessId, lootId, "can_access", 0.9);
|
|
1970
|
-
}
|
|
1971
|
-
return accessId;
|
|
2202
|
+
return registerAccess(this.graphContainer, host, level, via);
|
|
1972
2203
|
}
|
|
1973
2204
|
/**
|
|
1974
2205
|
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
1975
2206
|
*/
|
|
1976
2207
|
addOSINT(category, detail, data = {}) {
|
|
1977
|
-
|
|
1978
|
-
category,
|
|
1979
|
-
detail,
|
|
1980
|
-
...data
|
|
1981
|
-
});
|
|
1982
|
-
for (const [id, node] of this.nodes) {
|
|
1983
|
-
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
1984
|
-
const hostIp = String(node.data.ip || node.data.host || "");
|
|
1985
|
-
const hostname = String(node.data.hostname || "");
|
|
1986
|
-
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
1987
|
-
this.addEdge(osintId, id, "relates_to", 0.5);
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
return osintId;
|
|
2208
|
+
return registerOSINT(this.graphContainer, category, detail, data);
|
|
1992
2209
|
}
|
|
1993
2210
|
// ─── Chain Discovery (DFS) ──────────────────────────────────
|
|
1994
2211
|
/**
|
|
@@ -2610,11 +2827,86 @@ var DynamicTechniqueLibrary = class {
|
|
|
2610
2827
|
}
|
|
2611
2828
|
};
|
|
2612
2829
|
|
|
2613
|
-
// src/engine/state/state.ts
|
|
2830
|
+
// src/engine/state/target-state.ts
|
|
2831
|
+
var TargetState = class {
|
|
2832
|
+
targets = /* @__PURE__ */ new Map();
|
|
2833
|
+
attackGraph;
|
|
2834
|
+
constructor(attackGraph) {
|
|
2835
|
+
this.attackGraph = attackGraph;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Add a target and register it in the attack graph.
|
|
2839
|
+
*/
|
|
2840
|
+
add(target) {
|
|
2841
|
+
this.targets.set(target.ip, target);
|
|
2842
|
+
this.attackGraph.addHost(target.ip, target.hostname);
|
|
2843
|
+
for (const port of target.ports) {
|
|
2844
|
+
this.attackGraph.addService(target.ip, port.port, port.service, port.version);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
get(ip) {
|
|
2848
|
+
return this.targets.get(ip);
|
|
2849
|
+
}
|
|
2850
|
+
getAll() {
|
|
2851
|
+
return Array.from(this.targets.values());
|
|
2852
|
+
}
|
|
2853
|
+
getMap() {
|
|
2854
|
+
return this.targets;
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Clear all targets (for reset).
|
|
2858
|
+
*/
|
|
2859
|
+
clear() {
|
|
2860
|
+
this.targets.clear();
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
|
|
2864
|
+
// src/engine/state/finding-state.ts
|
|
2865
|
+
var FindingState = class {
|
|
2866
|
+
findings = [];
|
|
2867
|
+
add(finding) {
|
|
2868
|
+
this.findings.push(finding);
|
|
2869
|
+
}
|
|
2870
|
+
getAll() {
|
|
2871
|
+
return this.findings;
|
|
2872
|
+
}
|
|
2873
|
+
getBySeverity(severity) {
|
|
2874
|
+
return this.findings.filter((f) => f.severity === severity);
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Clear all findings (for reset).
|
|
2878
|
+
*/
|
|
2879
|
+
clear() {
|
|
2880
|
+
this.findings = [];
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
|
|
2884
|
+
// src/engine/state/loot-state.ts
|
|
2885
|
+
var LootState = class {
|
|
2886
|
+
loot = [];
|
|
2887
|
+
add(item) {
|
|
2888
|
+
this.loot.push(item);
|
|
2889
|
+
}
|
|
2890
|
+
getAll() {
|
|
2891
|
+
return this.loot;
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Clear all loot (for reset).
|
|
2895
|
+
*/
|
|
2896
|
+
clear() {
|
|
2897
|
+
this.loot = [];
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
// src/engine/state/shared-state.ts
|
|
2614
2902
|
var SharedState = class {
|
|
2615
2903
|
data;
|
|
2616
|
-
// ───
|
|
2904
|
+
// ─── Enhancement #6: Attack Graph ──────────────────────────────
|
|
2617
2905
|
attackGraph = new AttackGraph();
|
|
2906
|
+
// ─── Focused State Modules ─────────────────────────────────────
|
|
2907
|
+
targetState;
|
|
2908
|
+
findingState;
|
|
2909
|
+
lootState;
|
|
2618
2910
|
// ─── Improvement #12: Multi-Tier Memory ───────────────────────
|
|
2619
2911
|
workingMemory = new WorkingMemory();
|
|
2620
2912
|
episodicMemory = new EpisodicMemory();
|
|
@@ -2623,11 +2915,11 @@ var SharedState = class {
|
|
|
2623
2915
|
dynamicTechniques = new DynamicTechniqueLibrary();
|
|
2624
2916
|
constructor() {
|
|
2625
2917
|
const now = Date.now();
|
|
2918
|
+
this.targetState = new TargetState(this.attackGraph);
|
|
2919
|
+
this.findingState = new FindingState();
|
|
2920
|
+
this.lootState = new LootState();
|
|
2626
2921
|
this.data = {
|
|
2627
2922
|
engagement: null,
|
|
2628
|
-
targets: /* @__PURE__ */ new Map(),
|
|
2629
|
-
findings: [],
|
|
2630
|
-
loot: [],
|
|
2631
2923
|
todo: [],
|
|
2632
2924
|
actionLog: [],
|
|
2633
2925
|
currentPhase: PHASES.RECON,
|
|
@@ -2651,9 +2943,6 @@ var SharedState = class {
|
|
|
2651
2943
|
const now = Date.now();
|
|
2652
2944
|
this.data = {
|
|
2653
2945
|
engagement: null,
|
|
2654
|
-
targets: /* @__PURE__ */ new Map(),
|
|
2655
|
-
findings: [],
|
|
2656
|
-
loot: [],
|
|
2657
2946
|
todo: [],
|
|
2658
2947
|
actionLog: [],
|
|
2659
2948
|
currentPhase: PHASES.RECON,
|
|
@@ -2665,12 +2954,17 @@ var SharedState = class {
|
|
|
2665
2954
|
deadlineAt: computeDeadline(now),
|
|
2666
2955
|
challengeAnalysis: null
|
|
2667
2956
|
};
|
|
2957
|
+
this.targetState.clear();
|
|
2958
|
+
this.findingState.clear();
|
|
2959
|
+
this.lootState.clear();
|
|
2668
2960
|
this.attackGraph.reset();
|
|
2669
2961
|
this.workingMemory.clear();
|
|
2670
2962
|
this.episodicMemory.clear();
|
|
2671
2963
|
this.dynamicTechniques.clear();
|
|
2672
2964
|
}
|
|
2673
|
-
//
|
|
2965
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2966
|
+
// SECTION: Mission & Persistent Context
|
|
2967
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2674
2968
|
setMissionSummary(summary) {
|
|
2675
2969
|
this.data.missionSummary = summary;
|
|
2676
2970
|
}
|
|
@@ -2715,7 +3009,9 @@ var SharedState = class {
|
|
|
2715
3009
|
restoreTodoItem(item) {
|
|
2716
3010
|
this.data.todo.push({ ...item });
|
|
2717
3011
|
}
|
|
2718
|
-
//
|
|
3012
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3013
|
+
// SECTION: Engagement & Scope
|
|
3014
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2719
3015
|
setEngagement(engagement) {
|
|
2720
3016
|
this.data.engagement = engagement;
|
|
2721
3017
|
this.data.currentPhase = engagement.phase;
|
|
@@ -2740,40 +3036,42 @@ var SharedState = class {
|
|
|
2740
3036
|
};
|
|
2741
3037
|
}
|
|
2742
3038
|
}
|
|
2743
|
-
//
|
|
3039
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3040
|
+
// SECTION: Targets (delegated to TargetState)
|
|
3041
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2744
3042
|
addTarget(target) {
|
|
2745
|
-
this.
|
|
2746
|
-
this.attackGraph.addHost(target.ip, target.hostname);
|
|
2747
|
-
for (const port of target.ports) {
|
|
2748
|
-
this.attackGraph.addService(target.ip, port.port, port.service, port.version);
|
|
2749
|
-
}
|
|
3043
|
+
this.targetState.add(target);
|
|
2750
3044
|
}
|
|
2751
3045
|
getTarget(ip) {
|
|
2752
|
-
return this.
|
|
3046
|
+
return this.targetState.get(ip);
|
|
2753
3047
|
}
|
|
2754
3048
|
getAllTargets() {
|
|
2755
|
-
return
|
|
3049
|
+
return this.targetState.getAll();
|
|
2756
3050
|
}
|
|
2757
3051
|
getTargets() {
|
|
2758
|
-
return this.
|
|
3052
|
+
return this.targetState.getMap();
|
|
2759
3053
|
}
|
|
2760
|
-
//
|
|
3054
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3055
|
+
// SECTION: Findings & Loot (delegated to focused modules)
|
|
3056
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2761
3057
|
addFinding(finding) {
|
|
2762
|
-
this.
|
|
3058
|
+
this.findingState.add(finding);
|
|
2763
3059
|
}
|
|
2764
3060
|
getFindings() {
|
|
2765
|
-
return this.
|
|
3061
|
+
return this.findingState.getAll();
|
|
2766
3062
|
}
|
|
2767
3063
|
getFindingsBySeverity(severity) {
|
|
2768
|
-
return this.
|
|
3064
|
+
return this.findingState.getBySeverity(severity);
|
|
2769
3065
|
}
|
|
2770
3066
|
addLoot(loot) {
|
|
2771
|
-
this.
|
|
3067
|
+
this.lootState.add(loot);
|
|
2772
3068
|
}
|
|
2773
3069
|
getLoot() {
|
|
2774
|
-
return this.
|
|
3070
|
+
return this.lootState.getAll();
|
|
2775
3071
|
}
|
|
2776
|
-
//
|
|
3072
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3073
|
+
// SECTION: TODO Management
|
|
3074
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2777
3075
|
addTodo(content, priority = PRIORITIES.MEDIUM) {
|
|
2778
3076
|
const id = generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH);
|
|
2779
3077
|
const item = { id, content, status: TODO_STATUSES.PENDING, priority };
|
|
@@ -2790,7 +3088,9 @@ var SharedState = class {
|
|
|
2790
3088
|
completeTodo(id) {
|
|
2791
3089
|
this.updateTodo(id, { status: TODO_STATUSES.DONE });
|
|
2792
3090
|
}
|
|
2793
|
-
//
|
|
3091
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3092
|
+
// SECTION: Logs & Phase
|
|
3093
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2794
3094
|
logAction(action) {
|
|
2795
3095
|
this.data.actionLog.push({
|
|
2796
3096
|
id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
|
|
@@ -2810,7 +3110,9 @@ var SharedState = class {
|
|
|
2810
3110
|
getPhase() {
|
|
2811
3111
|
return this.data.currentPhase;
|
|
2812
3112
|
}
|
|
2813
|
-
//
|
|
3113
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3114
|
+
// SECTION: Flag Detection
|
|
3115
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2814
3116
|
setCtfMode(shouldEnable) {
|
|
2815
3117
|
this.data.ctfMode = shouldEnable;
|
|
2816
3118
|
}
|
|
@@ -2826,14 +3128,18 @@ var SharedState = class {
|
|
|
2826
3128
|
getFlags() {
|
|
2827
3129
|
return this.data.flags;
|
|
2828
3130
|
}
|
|
2829
|
-
//
|
|
3131
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3132
|
+
// SECTION: Challenge Analysis (#2: Auto-Prompter)
|
|
3133
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2830
3134
|
setChallengeAnalysis(analysis) {
|
|
2831
3135
|
this.data.challengeAnalysis = analysis;
|
|
2832
3136
|
}
|
|
2833
3137
|
getChallengeAnalysis() {
|
|
2834
3138
|
return this.data.challengeAnalysis;
|
|
2835
3139
|
}
|
|
2836
|
-
//
|
|
3140
|
+
// ═══════════════════════════════════════════════════════════════
|
|
3141
|
+
// SECTION: Time Management
|
|
3142
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2837
3143
|
/** Set a deadline for time-aware strategy (e.g., CTF competition end time) */
|
|
2838
3144
|
setDeadline(deadlineMs) {
|
|
2839
3145
|
this.data.deadlineAt = deadlineMs;
|
|
@@ -11215,6 +11521,62 @@ ${firstLine}`, phase }
|
|
|
11215
11521
|
};
|
|
11216
11522
|
}
|
|
11217
11523
|
|
|
11524
|
+
// src/agents/core-agent/step-handler.ts
|
|
11525
|
+
function emitIterationThink(events, iteration, phase, targetsSize, findingsLength, progress) {
|
|
11526
|
+
emitThink(events, iteration, phase, targetsSize, findingsLength, progress);
|
|
11527
|
+
}
|
|
11528
|
+
function handleResponseContent(response, events, phase, hadReasoningEnd) {
|
|
11529
|
+
if (response.reasoning && !hadReasoningEnd) {
|
|
11530
|
+
emitReasoningStart(events, phase);
|
|
11531
|
+
emitReasoningDelta(events, response.reasoning, phase);
|
|
11532
|
+
emitReasoningEnd(events, phase);
|
|
11533
|
+
}
|
|
11534
|
+
if (response.content?.trim()) {
|
|
11535
|
+
if (!response.reasoning && !hadReasoningEnd) {
|
|
11536
|
+
emitReasoningStart(events, phase);
|
|
11537
|
+
emitReasoningDelta(events, response.content.trim(), phase);
|
|
11538
|
+
emitReasoningEnd(events, phase);
|
|
11539
|
+
} else {
|
|
11540
|
+
events.emit({
|
|
11541
|
+
type: EVENT_TYPES.AI_RESPONSE,
|
|
11542
|
+
timestamp: Date.now(),
|
|
11543
|
+
data: { content: response.content.trim(), phase }
|
|
11544
|
+
});
|
|
11545
|
+
}
|
|
11546
|
+
}
|
|
11547
|
+
}
|
|
11548
|
+
function addAssistantMessage(messages, content) {
|
|
11549
|
+
messages.push({ role: LLM_ROLES.ASSISTANT, content });
|
|
11550
|
+
}
|
|
11551
|
+
function addToolResultsToMessages(messages, results) {
|
|
11552
|
+
for (const res of results) {
|
|
11553
|
+
messages.push({
|
|
11554
|
+
role: LLM_ROLES.USER,
|
|
11555
|
+
content: [{
|
|
11556
|
+
type: LLM_BLOCK_TYPE.TOOL_RESULT,
|
|
11557
|
+
tool_use_id: res.toolCallId,
|
|
11558
|
+
content: res.output,
|
|
11559
|
+
is_error: !!res.error
|
|
11560
|
+
}]
|
|
11561
|
+
});
|
|
11562
|
+
}
|
|
11563
|
+
}
|
|
11564
|
+
async function processToolCallsInStep(toolExecutor, toolCalls, messages, progress) {
|
|
11565
|
+
const results = await toolExecutor.processToolCalls(toolCalls, progress);
|
|
11566
|
+
addToolResultsToMessages(messages, results);
|
|
11567
|
+
return results.length;
|
|
11568
|
+
}
|
|
11569
|
+
async function getLLMStreamResponse(llm, messages, toolSchemas, systemPrompt, events, phase, abortController) {
|
|
11570
|
+
const callbacks = buildStreamCallbacks(events, phase, abortController);
|
|
11571
|
+
const response = await llm.generateResponseStream(
|
|
11572
|
+
messages,
|
|
11573
|
+
toolSchemas,
|
|
11574
|
+
systemPrompt,
|
|
11575
|
+
callbacks
|
|
11576
|
+
);
|
|
11577
|
+
return { response, hadReasoningEnd: callbacks.hadReasoningEnd() };
|
|
11578
|
+
}
|
|
11579
|
+
|
|
11218
11580
|
// src/agents/tool-executor/types.ts
|
|
11219
11581
|
var PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
11220
11582
|
"get_state",
|
|
@@ -11932,6 +12294,82 @@ var ToolExecutor = class {
|
|
|
11932
12294
|
}
|
|
11933
12295
|
};
|
|
11934
12296
|
|
|
12297
|
+
// src/agents/core-agent/tool-processor.ts
|
|
12298
|
+
function createToolExecutor(config) {
|
|
12299
|
+
return new ToolExecutor({
|
|
12300
|
+
state: config.state,
|
|
12301
|
+
events: config.events,
|
|
12302
|
+
toolRegistry: config.toolRegistry,
|
|
12303
|
+
llm: config.llm
|
|
12304
|
+
});
|
|
12305
|
+
}
|
|
12306
|
+
function getTurnToolJournal(toolExecutor) {
|
|
12307
|
+
return toolExecutor?.turnToolJournal ?? [];
|
|
12308
|
+
}
|
|
12309
|
+
function getTurnMemo(toolExecutor) {
|
|
12310
|
+
return toolExecutor?.turnMemo ?? {
|
|
12311
|
+
keyFindings: [],
|
|
12312
|
+
credentials: [],
|
|
12313
|
+
attackVectors: [],
|
|
12314
|
+
failures: [],
|
|
12315
|
+
suspicions: [],
|
|
12316
|
+
attackValue: "LOW",
|
|
12317
|
+
nextSteps: []
|
|
12318
|
+
};
|
|
12319
|
+
}
|
|
12320
|
+
function getTurnReflections(toolExecutor) {
|
|
12321
|
+
return toolExecutor?.turnReflections ?? [];
|
|
12322
|
+
}
|
|
12323
|
+
|
|
12324
|
+
// src/agents/core-agent/run-loop.ts
|
|
12325
|
+
function createProgressTracker() {
|
|
12326
|
+
return {
|
|
12327
|
+
totalToolsExecuted: 0,
|
|
12328
|
+
consecutiveIdleIterations: 0,
|
|
12329
|
+
lastToolExecutedAt: Date.now(),
|
|
12330
|
+
toolErrors: 0,
|
|
12331
|
+
toolSuccesses: 0,
|
|
12332
|
+
blockedCommandPatterns: /* @__PURE__ */ new Map(),
|
|
12333
|
+
totalBlockedCommands: 0
|
|
12334
|
+
};
|
|
12335
|
+
}
|
|
12336
|
+
function createInitialMessages(task) {
|
|
12337
|
+
return [{ role: LLM_ROLES.USER, content: task }];
|
|
12338
|
+
}
|
|
12339
|
+
function checkAbort(abortController, iteration, toolsExecuted) {
|
|
12340
|
+
if (abortController.signal.aborted) {
|
|
12341
|
+
return buildCancelledResult(iteration, toolsExecuted);
|
|
12342
|
+
}
|
|
12343
|
+
return null;
|
|
12344
|
+
}
|
|
12345
|
+
function updateProgressAfterStep(progress, toolsExecuted) {
|
|
12346
|
+
progress.totalToolsExecuted += toolsExecuted;
|
|
12347
|
+
if (toolsExecuted > 0) {
|
|
12348
|
+
progress.consecutiveIdleIterations = 0;
|
|
12349
|
+
progress.lastToolExecutedAt = Date.now();
|
|
12350
|
+
} else {
|
|
12351
|
+
progress.consecutiveIdleIterations++;
|
|
12352
|
+
}
|
|
12353
|
+
}
|
|
12354
|
+
function handleIdleIteration(progress, messages, getPhase, getTargetsSize, getFindingsLength) {
|
|
12355
|
+
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
12356
|
+
progress.consecutiveIdleIterations = 0;
|
|
12357
|
+
const nudge = buildDeadlockNudge(getPhase(), getTargetsSize(), getFindingsLength());
|
|
12358
|
+
messages.push({ role: LLM_ROLES.USER, content: nudge });
|
|
12359
|
+
}
|
|
12360
|
+
}
|
|
12361
|
+
function buildSuccessResult(output, iteration, tools) {
|
|
12362
|
+
return { output, iterations: iteration + 1, toolsExecuted: tools, isCompleted: true };
|
|
12363
|
+
}
|
|
12364
|
+
function buildMaxIterationsResult(maxIterations, tools) {
|
|
12365
|
+
return {
|
|
12366
|
+
output: `Max iterations (${maxIterations}) reached. Tools: ${tools}.`,
|
|
12367
|
+
iterations: maxIterations,
|
|
12368
|
+
toolsExecuted: tools,
|
|
12369
|
+
isCompleted: false
|
|
12370
|
+
};
|
|
12371
|
+
}
|
|
12372
|
+
|
|
11935
12373
|
// src/agents/core-agent/core-agent.ts
|
|
11936
12374
|
var CoreAgent = class {
|
|
11937
12375
|
llm;
|
|
@@ -11950,73 +12388,43 @@ var CoreAgent = class {
|
|
|
11950
12388
|
this.llm = getLLMClient();
|
|
11951
12389
|
this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
|
|
11952
12390
|
}
|
|
11953
|
-
/** Set or update the tool registry (for delayed initialization) */
|
|
11954
12391
|
setToolRegistry(registry) {
|
|
11955
12392
|
this.toolRegistry = registry;
|
|
11956
12393
|
}
|
|
11957
|
-
/** Abort the current execution */
|
|
11958
12394
|
abort() {
|
|
11959
12395
|
this.abortController?.abort();
|
|
11960
12396
|
}
|
|
11961
|
-
/** Get turn tool journal (for MainAgent) */
|
|
11962
12397
|
getTurnToolJournal() {
|
|
11963
|
-
return this.toolExecutor
|
|
12398
|
+
return getTurnToolJournal(this.toolExecutor);
|
|
11964
12399
|
}
|
|
11965
12400
|
getTurnMemo() {
|
|
11966
|
-
return this.toolExecutor
|
|
12401
|
+
return getTurnMemo(this.toolExecutor);
|
|
11967
12402
|
}
|
|
11968
12403
|
getTurnReflections() {
|
|
11969
|
-
return this.toolExecutor
|
|
12404
|
+
return getTurnReflections(this.toolExecutor);
|
|
11970
12405
|
}
|
|
11971
|
-
// ─────────────────────────────────────────────────────────────────
|
|
11972
|
-
// SUBSECTION: Main Run Loop
|
|
11973
|
-
// ─────────────────────────────────────────────────────────────────
|
|
11974
12406
|
/** The core loop: Think → Act → Observe */
|
|
11975
12407
|
async run(task, systemPrompt) {
|
|
11976
12408
|
this.abortController = new AbortController();
|
|
11977
|
-
const messages =
|
|
12409
|
+
const messages = createInitialMessages(task);
|
|
12410
|
+
const progress = createProgressTracker();
|
|
11978
12411
|
let consecutiveLLMErrors = 0;
|
|
11979
|
-
const maxConsecutiveLLMErrors = AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS;
|
|
11980
|
-
const progress = {
|
|
11981
|
-
totalToolsExecuted: 0,
|
|
11982
|
-
consecutiveIdleIterations: 0,
|
|
11983
|
-
lastToolExecutedAt: Date.now(),
|
|
11984
|
-
toolErrors: 0,
|
|
11985
|
-
toolSuccesses: 0,
|
|
11986
|
-
blockedCommandPatterns: /* @__PURE__ */ new Map(),
|
|
11987
|
-
totalBlockedCommands: 0
|
|
11988
|
-
};
|
|
11989
12412
|
for (let iteration = 0; iteration < this.maxIterations; iteration++) {
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
}
|
|
12413
|
+
const cancelledResult = checkAbort(this.abortController, iteration, progress.totalToolsExecuted);
|
|
12414
|
+
if (cancelledResult) return cancelledResult;
|
|
11993
12415
|
try {
|
|
11994
12416
|
const result2 = await this.step(iteration, messages, systemPrompt, progress);
|
|
11995
|
-
progress
|
|
11996
|
-
consecutiveLLMErrors = 0;
|
|
11997
|
-
if (result2.toolsExecuted > 0) {
|
|
11998
|
-
progress.consecutiveIdleIterations = 0;
|
|
11999
|
-
progress.lastToolExecutedAt = Date.now();
|
|
12000
|
-
} else if (!result2.isCompleted) {
|
|
12001
|
-
progress.consecutiveIdleIterations++;
|
|
12002
|
-
}
|
|
12417
|
+
updateProgressAfterStep(progress, result2.toolsExecuted);
|
|
12003
12418
|
if (result2.isCompleted) {
|
|
12004
|
-
return
|
|
12005
|
-
output: result2.output,
|
|
12006
|
-
iterations: iteration + 1,
|
|
12007
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
12008
|
-
isCompleted: true
|
|
12009
|
-
};
|
|
12010
|
-
}
|
|
12011
|
-
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
12012
|
-
progress.consecutiveIdleIterations = 0;
|
|
12013
|
-
const nudge = buildDeadlockNudge(
|
|
12014
|
-
this.state.getPhase(),
|
|
12015
|
-
this.state.getTargets().size,
|
|
12016
|
-
this.state.getFindings().length
|
|
12017
|
-
);
|
|
12018
|
-
messages.push({ role: LLM_ROLES.USER, content: nudge });
|
|
12419
|
+
return buildSuccessResult(result2.output, iteration, progress.totalToolsExecuted);
|
|
12019
12420
|
}
|
|
12421
|
+
handleIdleIteration(
|
|
12422
|
+
progress,
|
|
12423
|
+
messages,
|
|
12424
|
+
() => this.state.getPhase(),
|
|
12425
|
+
() => this.state.getTargets().size,
|
|
12426
|
+
() => this.state.getFindings().length
|
|
12427
|
+
);
|
|
12020
12428
|
} catch (error) {
|
|
12021
12429
|
const action = handleLoopError(
|
|
12022
12430
|
error,
|
|
@@ -12024,35 +12432,20 @@ var CoreAgent = class {
|
|
|
12024
12432
|
progress,
|
|
12025
12433
|
iteration,
|
|
12026
12434
|
consecutiveLLMErrors,
|
|
12027
|
-
|
|
12435
|
+
AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS,
|
|
12028
12436
|
this.abortController,
|
|
12029
12437
|
this.events,
|
|
12030
12438
|
() => this.state.getPhase()
|
|
12031
12439
|
);
|
|
12032
12440
|
if (action.action === "return") return action.result;
|
|
12033
|
-
|
|
12034
|
-
if (error instanceof LLMError && error.errorInfo.type === "rate_limit") {
|
|
12035
|
-
consecutiveLLMErrors++;
|
|
12036
|
-
} else {
|
|
12037
|
-
consecutiveLLMErrors = 0;
|
|
12038
|
-
}
|
|
12039
|
-
continue;
|
|
12040
|
-
}
|
|
12441
|
+
consecutiveLLMErrors = error instanceof LLMError && error.errorInfo.type === "rate_limit" ? consecutiveLLMErrors + 1 : 0;
|
|
12041
12442
|
}
|
|
12042
12443
|
}
|
|
12043
|
-
return
|
|
12044
|
-
output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
|
|
12045
|
-
iterations: this.maxIterations,
|
|
12046
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
12047
|
-
isCompleted: false
|
|
12048
|
-
};
|
|
12444
|
+
return buildMaxIterationsResult(this.maxIterations, progress.totalToolsExecuted);
|
|
12049
12445
|
}
|
|
12050
|
-
// ─────────────────────────────────────────────────────────────────
|
|
12051
|
-
// SUBSECTION: Step Execution
|
|
12052
|
-
// ─────────────────────────────────────────────────────────────────
|
|
12053
12446
|
async step(iteration, messages, systemPrompt, progress) {
|
|
12054
12447
|
const phase = this.state.getPhase();
|
|
12055
|
-
|
|
12448
|
+
emitIterationThink(
|
|
12056
12449
|
this.events,
|
|
12057
12450
|
iteration,
|
|
12058
12451
|
phase,
|
|
@@ -12060,61 +12453,37 @@ var CoreAgent = class {
|
|
|
12060
12453
|
this.state.getFindings().length,
|
|
12061
12454
|
progress
|
|
12062
12455
|
);
|
|
12063
|
-
|
|
12064
|
-
this.toolExecutor = new ToolExecutor({
|
|
12065
|
-
state: this.state,
|
|
12066
|
-
events: this.events,
|
|
12067
|
-
toolRegistry: this.toolRegistry,
|
|
12068
|
-
llm: this.llm
|
|
12069
|
-
});
|
|
12070
|
-
}
|
|
12456
|
+
this.ensureToolExecutor();
|
|
12071
12457
|
this.toolExecutor?.clearTurnState();
|
|
12072
|
-
const
|
|
12073
|
-
|
|
12458
|
+
const { response, hadReasoningEnd } = await getLLMStreamResponse(
|
|
12459
|
+
this.llm,
|
|
12074
12460
|
messages,
|
|
12075
12461
|
this.toolExecutor?.getToolSchemas() ?? [],
|
|
12076
12462
|
systemPrompt,
|
|
12077
|
-
|
|
12463
|
+
this.events,
|
|
12464
|
+
phase,
|
|
12465
|
+
this.abortController
|
|
12078
12466
|
);
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
emitReasoningDelta(this.events, response.reasoning, phase);
|
|
12082
|
-
emitReasoningEnd(this.events, phase);
|
|
12083
|
-
}
|
|
12084
|
-
if (response.content?.trim()) {
|
|
12085
|
-
if (!response.reasoning && !callbacks.hadReasoningEnd()) {
|
|
12086
|
-
emitReasoningStart(this.events, phase);
|
|
12087
|
-
emitReasoningDelta(this.events, response.content.trim(), phase);
|
|
12088
|
-
emitReasoningEnd(this.events, phase);
|
|
12089
|
-
} else {
|
|
12090
|
-
this.events.emit({
|
|
12091
|
-
type: EVENT_TYPES.AI_RESPONSE,
|
|
12092
|
-
timestamp: Date.now(),
|
|
12093
|
-
data: { content: response.content.trim(), phase }
|
|
12094
|
-
});
|
|
12095
|
-
}
|
|
12096
|
-
}
|
|
12097
|
-
messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
|
|
12467
|
+
handleResponseContent(response, this.events, phase, hadReasoningEnd);
|
|
12468
|
+
addAssistantMessage(messages, response.content);
|
|
12098
12469
|
if (!response.toolCalls?.length) {
|
|
12099
12470
|
return { output: response.content, toolsExecuted: 0, isCompleted: false };
|
|
12100
12471
|
}
|
|
12101
|
-
const
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
is_error: !!res.error
|
|
12117
|
-
}]
|
|
12472
|
+
const toolsExecuted = await processToolCallsInStep(
|
|
12473
|
+
this.toolExecutor,
|
|
12474
|
+
response.toolCalls,
|
|
12475
|
+
messages,
|
|
12476
|
+
progress
|
|
12477
|
+
);
|
|
12478
|
+
return { output: "", toolsExecuted, isCompleted: false };
|
|
12479
|
+
}
|
|
12480
|
+
ensureToolExecutor() {
|
|
12481
|
+
if (this.toolRegistry && !this.toolExecutor) {
|
|
12482
|
+
this.toolExecutor = createToolExecutor({
|
|
12483
|
+
state: this.state,
|
|
12484
|
+
events: this.events,
|
|
12485
|
+
toolRegistry: this.toolRegistry,
|
|
12486
|
+
llm: this.llm
|
|
12118
12487
|
});
|
|
12119
12488
|
}
|
|
12120
12489
|
}
|
|
@@ -14075,6 +14444,325 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
14075
14444
|
};
|
|
14076
14445
|
};
|
|
14077
14446
|
|
|
14447
|
+
// src/platform/tui/hooks/commands/index.ts
|
|
14448
|
+
import { useCallback as useCallback3 } from "react";
|
|
14449
|
+
|
|
14450
|
+
// src/platform/tui/hooks/commands/session-commands.ts
|
|
14451
|
+
var createSessionCommands = (ctx) => ({
|
|
14452
|
+
[UI_COMMANDS.HELP]: () => {
|
|
14453
|
+
ctx.addMessage("system", HELP_TEXT);
|
|
14454
|
+
},
|
|
14455
|
+
[UI_COMMANDS.HELP_SHORT]: () => {
|
|
14456
|
+
ctx.addMessage("system", HELP_TEXT);
|
|
14457
|
+
},
|
|
14458
|
+
[UI_COMMANDS.CLEAR]: async () => {
|
|
14459
|
+
if (ctx.isProcessingRef.current) {
|
|
14460
|
+
ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
|
|
14461
|
+
return;
|
|
14462
|
+
}
|
|
14463
|
+
ctx.setMessages([]);
|
|
14464
|
+
const result2 = await ctx.agent.resetSession();
|
|
14465
|
+
if (result2.cleared.length > 0) {
|
|
14466
|
+
ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
14467
|
+
} else {
|
|
14468
|
+
ctx.addMessage("system", "[reset] Session clean");
|
|
14469
|
+
}
|
|
14470
|
+
if (result2.errors.length > 0) {
|
|
14471
|
+
ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
14472
|
+
}
|
|
14473
|
+
ctx.refreshStats();
|
|
14474
|
+
},
|
|
14475
|
+
[UI_COMMANDS.CLEAR_SHORT]: async () => {
|
|
14476
|
+
if (ctx.isProcessingRef.current) {
|
|
14477
|
+
ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
|
|
14478
|
+
return;
|
|
14479
|
+
}
|
|
14480
|
+
ctx.setMessages([]);
|
|
14481
|
+
const result2 = await ctx.agent.resetSession();
|
|
14482
|
+
if (result2.cleared.length > 0) {
|
|
14483
|
+
ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
14484
|
+
} else {
|
|
14485
|
+
ctx.addMessage("system", "[reset] Session clean");
|
|
14486
|
+
}
|
|
14487
|
+
if (result2.errors.length > 0) {
|
|
14488
|
+
ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
14489
|
+
}
|
|
14490
|
+
ctx.refreshStats();
|
|
14491
|
+
},
|
|
14492
|
+
[UI_COMMANDS.EXIT]: () => {
|
|
14493
|
+
ctx.handleExit();
|
|
14494
|
+
},
|
|
14495
|
+
[UI_COMMANDS.QUIT]: () => {
|
|
14496
|
+
ctx.handleExit();
|
|
14497
|
+
},
|
|
14498
|
+
[UI_COMMANDS.EXIT_SHORT]: () => {
|
|
14499
|
+
ctx.handleExit();
|
|
14500
|
+
}
|
|
14501
|
+
});
|
|
14502
|
+
|
|
14503
|
+
// src/platform/tui/hooks/commands/target-commands.ts
|
|
14504
|
+
var createTargetCommands = (ctx) => ({
|
|
14505
|
+
[UI_COMMANDS.TARGET]: (args) => {
|
|
14506
|
+
if (!args[0]) {
|
|
14507
|
+
ctx.addMessage("error", "Usage: /target <ip>");
|
|
14508
|
+
return;
|
|
14509
|
+
}
|
|
14510
|
+
ctx.agent.addTarget(args[0]);
|
|
14511
|
+
ctx.agent.setScope([args[0]]);
|
|
14512
|
+
ctx.addMessage("system", `Target \u2192 ${args[0]}`);
|
|
14513
|
+
},
|
|
14514
|
+
[UI_COMMANDS.TARGET_SHORT]: (args) => {
|
|
14515
|
+
if (!args[0]) {
|
|
14516
|
+
ctx.addMessage("error", "Usage: /target <ip>");
|
|
14517
|
+
return;
|
|
14518
|
+
}
|
|
14519
|
+
ctx.agent.addTarget(args[0]);
|
|
14520
|
+
ctx.agent.setScope([args[0]]);
|
|
14521
|
+
ctx.addMessage("system", `Target \u2192 ${args[0]}`);
|
|
14522
|
+
},
|
|
14523
|
+
[UI_COMMANDS.START]: async (args) => {
|
|
14524
|
+
if (!ctx.agent.getState().getTargets().size) {
|
|
14525
|
+
ctx.addMessage("error", "Set target first: /target <ip>");
|
|
14526
|
+
return;
|
|
14527
|
+
}
|
|
14528
|
+
if (!ctx.autoApproveModeRef.current) {
|
|
14529
|
+
ctx.setAutoApproveMode(true);
|
|
14530
|
+
ctx.agent.setAutoApprove(true);
|
|
14531
|
+
ctx.addMessage("system", "[auto] Autonomous mode enabled");
|
|
14532
|
+
}
|
|
14533
|
+
ctx.addMessage("system", "Starting penetration test...");
|
|
14534
|
+
const targets = Array.from(ctx.agent.getState().getTargets().keys());
|
|
14535
|
+
const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
|
|
14536
|
+
await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
14537
|
+
},
|
|
14538
|
+
[UI_COMMANDS.START_SHORT]: async (args) => {
|
|
14539
|
+
if (!ctx.agent.getState().getTargets().size) {
|
|
14540
|
+
ctx.addMessage("error", "Set target first: /target <ip>");
|
|
14541
|
+
return;
|
|
14542
|
+
}
|
|
14543
|
+
if (!ctx.autoApproveModeRef.current) {
|
|
14544
|
+
ctx.setAutoApproveMode(true);
|
|
14545
|
+
ctx.agent.setAutoApprove(true);
|
|
14546
|
+
ctx.addMessage("system", "[auto] Autonomous mode enabled");
|
|
14547
|
+
}
|
|
14548
|
+
ctx.addMessage("system", "Starting penetration test...");
|
|
14549
|
+
const targets = Array.from(ctx.agent.getState().getTargets().keys());
|
|
14550
|
+
const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
|
|
14551
|
+
await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
14552
|
+
}
|
|
14553
|
+
});
|
|
14554
|
+
|
|
14555
|
+
// src/platform/tui/hooks/commands/formatters.ts
|
|
14556
|
+
var confIcon = (c) => {
|
|
14557
|
+
if (c >= 100) return "\u{1F534}";
|
|
14558
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
|
|
14559
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
|
|
14560
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
|
|
14561
|
+
return "\u26AA";
|
|
14562
|
+
};
|
|
14563
|
+
var confLabel = (c) => {
|
|
14564
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
|
|
14565
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
|
|
14566
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
|
|
14567
|
+
return "speculative";
|
|
14568
|
+
};
|
|
14569
|
+
var formatFindings = (findings) => {
|
|
14570
|
+
if (!findings.length) return "No findings.";
|
|
14571
|
+
const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
|
|
14572
|
+
const findingLines = [];
|
|
14573
|
+
const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14574
|
+
const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14575
|
+
const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
14576
|
+
findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 \u{1F534}\u{1F7E0} confirmed:${nConfirmed} \u{1F7E1} probable:${nProbable} \u{1F7E2}\u26AA possible:${nPossible} \u2500\u2500\u2500`);
|
|
14577
|
+
findingLines.push("");
|
|
14578
|
+
sorted.forEach((f) => {
|
|
14579
|
+
const icon = confIcon(f.confidence);
|
|
14580
|
+
const label = confLabel(f.confidence);
|
|
14581
|
+
const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
|
|
14582
|
+
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
14583
|
+
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
14584
|
+
findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
|
|
14585
|
+
findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
|
|
14586
|
+
if (f.affected.length > 0) {
|
|
14587
|
+
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
14588
|
+
}
|
|
14589
|
+
if (f.description) {
|
|
14590
|
+
findingLines.push(` ${f.description}`);
|
|
14591
|
+
}
|
|
14592
|
+
if (f.evidence.length > 0) {
|
|
14593
|
+
findingLines.push(` Evidence:`);
|
|
14594
|
+
f.evidence.forEach((e) => {
|
|
14595
|
+
findingLines.push(` \u25B8 ${e}`);
|
|
14596
|
+
});
|
|
14597
|
+
}
|
|
14598
|
+
if (f.remediation) {
|
|
14599
|
+
findingLines.push(` Fix: ${f.remediation}`);
|
|
14600
|
+
}
|
|
14601
|
+
findingLines.push("");
|
|
14602
|
+
});
|
|
14603
|
+
return findingLines.join("\n");
|
|
14604
|
+
};
|
|
14605
|
+
|
|
14606
|
+
// src/platform/tui/hooks/commands/display-commands.ts
|
|
14607
|
+
var createDisplayCommands = (ctx) => ({
|
|
14608
|
+
[UI_COMMANDS.FINDINGS]: () => {
|
|
14609
|
+
const findings = ctx.agent.getState().getFindings();
|
|
14610
|
+
ctx.addMessage("system", formatFindings(findings));
|
|
14611
|
+
},
|
|
14612
|
+
[UI_COMMANDS.FINDINGS_SHORT]: () => {
|
|
14613
|
+
const findings = ctx.agent.getState().getFindings();
|
|
14614
|
+
ctx.addMessage("system", formatFindings(findings));
|
|
14615
|
+
},
|
|
14616
|
+
[UI_COMMANDS.ASSETS]: () => {
|
|
14617
|
+
ctx.addMessage("status", formatInlineStatus());
|
|
14618
|
+
},
|
|
14619
|
+
[UI_COMMANDS.ASSETS_SHORT]: () => {
|
|
14620
|
+
ctx.addMessage("status", formatInlineStatus());
|
|
14621
|
+
},
|
|
14622
|
+
[UI_COMMANDS.LOGS]: (args) => {
|
|
14623
|
+
if (!args[0]) {
|
|
14624
|
+
ctx.addMessage("error", "Usage: /logs <process_id>");
|
|
14625
|
+
return;
|
|
14626
|
+
}
|
|
14627
|
+
const procData = getProcessOutput(args[0]);
|
|
14628
|
+
if (!procData) {
|
|
14629
|
+
ctx.addMessage("error", `Asset [${args[0]}] not found.`);
|
|
14630
|
+
return;
|
|
14631
|
+
}
|
|
14632
|
+
ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
|
|
14633
|
+
${procData.stdout || "(no output)"}
|
|
14634
|
+
--- End Log ---`);
|
|
14635
|
+
},
|
|
14636
|
+
[UI_COMMANDS.LOGS_SHORT]: (args) => {
|
|
14637
|
+
if (!args[0]) {
|
|
14638
|
+
ctx.addMessage("error", "Usage: /logs <process_id>");
|
|
14639
|
+
return;
|
|
14640
|
+
}
|
|
14641
|
+
const procData = getProcessOutput(args[0]);
|
|
14642
|
+
if (!procData) {
|
|
14643
|
+
ctx.addMessage("error", `Asset [${args[0]}] not found.`);
|
|
14644
|
+
return;
|
|
14645
|
+
}
|
|
14646
|
+
ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
|
|
14647
|
+
${procData.stdout || "(no output)"}
|
|
14648
|
+
--- End Log ---`);
|
|
14649
|
+
},
|
|
14650
|
+
[UI_COMMANDS.GRAPH]: () => {
|
|
14651
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toASCII());
|
|
14652
|
+
},
|
|
14653
|
+
[UI_COMMANDS.GRAPH_SHORT]: () => {
|
|
14654
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toASCII());
|
|
14655
|
+
},
|
|
14656
|
+
[UI_COMMANDS.PATHS]: () => {
|
|
14657
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
|
|
14658
|
+
},
|
|
14659
|
+
[UI_COMMANDS.PATHS_SHORT]: () => {
|
|
14660
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
|
|
14661
|
+
}
|
|
14662
|
+
});
|
|
14663
|
+
|
|
14664
|
+
// src/platform/tui/hooks/commands/toggle-commands.ts
|
|
14665
|
+
var createToggleCommands = (ctx) => ({
|
|
14666
|
+
[UI_COMMANDS.CTF]: () => {
|
|
14667
|
+
const ctfEnabled = ctx.agent.toggleCtfMode();
|
|
14668
|
+
ctx.addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
|
|
14669
|
+
},
|
|
14670
|
+
[UI_COMMANDS.TOR]: () => {
|
|
14671
|
+
const newTorState = !isTorEnabled();
|
|
14672
|
+
setTorEnabled(newTorState);
|
|
14673
|
+
ctx.addMessage("system", newTorState ? "\u{1F9C5} Tor proxy ON \u2014 target traffic routed through SOCKS5 (proxychains4 / native flags)" : "\u{1F9C5} Tor proxy OFF \u2014 direct connections");
|
|
14674
|
+
},
|
|
14675
|
+
[UI_COMMANDS.AUTO]: () => {
|
|
14676
|
+
ctx.setAutoApproveMode((prev) => {
|
|
14677
|
+
const newVal = !prev;
|
|
14678
|
+
ctx.agent.setAutoApprove(newVal);
|
|
14679
|
+
ctx.addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
|
|
14680
|
+
return newVal;
|
|
14681
|
+
});
|
|
14682
|
+
}
|
|
14683
|
+
});
|
|
14684
|
+
|
|
14685
|
+
// src/platform/tui/hooks/commands/index.ts
|
|
14686
|
+
var useCommands = (props) => {
|
|
14687
|
+
const ctx = {
|
|
14688
|
+
agent: props.agent,
|
|
14689
|
+
addMessage: props.addMessage,
|
|
14690
|
+
setMessages: props.setMessages,
|
|
14691
|
+
executeTask: props.executeTask,
|
|
14692
|
+
refreshStats: props.refreshStats,
|
|
14693
|
+
setAutoApproveMode: props.setAutoApproveMode,
|
|
14694
|
+
handleExit: props.handleExit,
|
|
14695
|
+
isProcessingRef: props.isProcessingRef,
|
|
14696
|
+
autoApproveModeRef: props.autoApproveModeRef
|
|
14697
|
+
};
|
|
14698
|
+
const handlers = {
|
|
14699
|
+
...createSessionCommands(ctx),
|
|
14700
|
+
...createTargetCommands(ctx),
|
|
14701
|
+
...createDisplayCommands(ctx),
|
|
14702
|
+
...createToggleCommands(ctx)
|
|
14703
|
+
};
|
|
14704
|
+
const handleCommand = useCallback3(async (cmd, args) => {
|
|
14705
|
+
const handler = handlers[cmd];
|
|
14706
|
+
if (handler) {
|
|
14707
|
+
await handler(args);
|
|
14708
|
+
} else {
|
|
14709
|
+
ctx.addMessage("error", `Unknown command: /${cmd}`);
|
|
14710
|
+
}
|
|
14711
|
+
}, [handlers, ctx]);
|
|
14712
|
+
return { handleCommand };
|
|
14713
|
+
};
|
|
14714
|
+
|
|
14715
|
+
// src/platform/tui/hooks/useKeyboardShortcuts.ts
|
|
14716
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
14717
|
+
import { useInput } from "ink";
|
|
14718
|
+
var useKeyboardShortcuts = ({
|
|
14719
|
+
addMessage,
|
|
14720
|
+
handleExit,
|
|
14721
|
+
abort,
|
|
14722
|
+
cancelInputRequest,
|
|
14723
|
+
isProcessingRef,
|
|
14724
|
+
inputRequestRef
|
|
14725
|
+
}) => {
|
|
14726
|
+
const ctrlCTimerRef = useRef4(null);
|
|
14727
|
+
const ctrlCPressedRef = useRef4(false);
|
|
14728
|
+
const handleCtrlC = useCallback4(() => {
|
|
14729
|
+
if (ctrlCPressedRef.current) {
|
|
14730
|
+
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14731
|
+
handleExit();
|
|
14732
|
+
return;
|
|
14733
|
+
}
|
|
14734
|
+
ctrlCPressedRef.current = true;
|
|
14735
|
+
addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
|
|
14736
|
+
if (isProcessingRef.current) abort();
|
|
14737
|
+
ctrlCTimerRef.current = setTimeout(() => {
|
|
14738
|
+
ctrlCPressedRef.current = false;
|
|
14739
|
+
ctrlCTimerRef.current = null;
|
|
14740
|
+
}, DISPLAY_LIMITS.EXIT_DELAY);
|
|
14741
|
+
}, [handleExit, addMessage, abort, isProcessingRef]);
|
|
14742
|
+
useEffect3(() => {
|
|
14743
|
+
return () => {
|
|
14744
|
+
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14745
|
+
};
|
|
14746
|
+
}, []);
|
|
14747
|
+
useInput(useCallback4((ch, key) => {
|
|
14748
|
+
if (key.escape) {
|
|
14749
|
+
if (inputRequestRef.current.status === "active") cancelInputRequest();
|
|
14750
|
+
else if (isProcessingRef.current) abort();
|
|
14751
|
+
}
|
|
14752
|
+
if (key.ctrl && ch === "c") handleCtrlC();
|
|
14753
|
+
}, [cancelInputRequest, abort, handleCtrlC, isProcessingRef, inputRequestRef]));
|
|
14754
|
+
useEffect3(() => {
|
|
14755
|
+
const onSignal = () => handleCtrlC();
|
|
14756
|
+
process.on("SIGINT", onSignal);
|
|
14757
|
+
process.on("SIGTERM", onSignal);
|
|
14758
|
+
return () => {
|
|
14759
|
+
process.off("SIGINT", onSignal);
|
|
14760
|
+
process.off("SIGTERM", onSignal);
|
|
14761
|
+
};
|
|
14762
|
+
}, [handleCtrlC]);
|
|
14763
|
+
return { handleCtrlC };
|
|
14764
|
+
};
|
|
14765
|
+
|
|
14078
14766
|
// src/platform/tui/components/MessageList.tsx
|
|
14079
14767
|
import { memo } from "react";
|
|
14080
14768
|
import { Box as Box2, Text as Text2, Static } from "ink";
|
|
@@ -14324,24 +15012,24 @@ var MessageList = memo(({ messages }) => {
|
|
|
14324
15012
|
});
|
|
14325
15013
|
|
|
14326
15014
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
14327
|
-
import { memo as memo3, useEffect as
|
|
15015
|
+
import { memo as memo3, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
14328
15016
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
14329
15017
|
|
|
14330
15018
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
14331
|
-
import { useState as useState3, useEffect as
|
|
15019
|
+
import { useState as useState3, useEffect as useEffect4, memo as memo2 } from "react";
|
|
14332
15020
|
import { Text as Text3 } from "ink";
|
|
14333
15021
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
14334
|
-
var FRAMES = ["\
|
|
14335
|
-
var INTERVAL =
|
|
15022
|
+
var FRAMES = ["\xB7", "\u2736", "\u2737", "\u2738", "\u2739", "\u273A", "\u2739", "\u2738", "\u2737", "\u2736"];
|
|
15023
|
+
var INTERVAL = 100;
|
|
14336
15024
|
var MusicSpinner = memo2(({ color }) => {
|
|
14337
15025
|
const [index, setIndex] = useState3(0);
|
|
14338
|
-
|
|
15026
|
+
useEffect4(() => {
|
|
14339
15027
|
const timer = setInterval(() => {
|
|
14340
15028
|
setIndex((i) => (i + 1) % FRAMES.length);
|
|
14341
15029
|
}, INTERVAL);
|
|
14342
15030
|
return () => clearInterval(timer);
|
|
14343
15031
|
}, []);
|
|
14344
|
-
return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
|
|
15032
|
+
return /* @__PURE__ */ jsx3(Text3, { color: color || "yellow", children: FRAMES[index] });
|
|
14345
15033
|
});
|
|
14346
15034
|
|
|
14347
15035
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
@@ -14356,9 +15044,9 @@ var StatusDisplay = memo3(({
|
|
|
14356
15044
|
return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
|
|
14357
15045
|
};
|
|
14358
15046
|
const [statusElapsed, setStatusElapsed] = useState4(0);
|
|
14359
|
-
const statusTimerRef =
|
|
14360
|
-
const statusStartRef =
|
|
14361
|
-
|
|
15047
|
+
const statusTimerRef = useRef5(null);
|
|
15048
|
+
const statusStartRef = useRef5(Date.now());
|
|
15049
|
+
useEffect5(() => {
|
|
14362
15050
|
if (statusTimerRef.current) clearInterval(statusTimerRef.current);
|
|
14363
15051
|
if (isProcessing && currentStatus) {
|
|
14364
15052
|
statusStartRef.current = Date.now();
|
|
@@ -14419,8 +15107,8 @@ var StatusDisplay = memo3(({
|
|
|
14419
15107
|
});
|
|
14420
15108
|
|
|
14421
15109
|
// src/platform/tui/components/ChatInput.tsx
|
|
14422
|
-
import { useMemo, useCallback as
|
|
14423
|
-
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
15110
|
+
import { useMemo, useCallback as useCallback5, useRef as useRef6, memo as memo4, useState as useState5 } from "react";
|
|
15111
|
+
import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
14424
15112
|
import TextInput from "ink-text-input";
|
|
14425
15113
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
14426
15114
|
var MAX_SUGGESTIONS = 6;
|
|
@@ -14442,18 +15130,18 @@ var ChatInput = memo4(({
|
|
|
14442
15130
|
return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
|
|
14443
15131
|
}, [isSlashMode, partialCmd, hasArgs]);
|
|
14444
15132
|
const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
|
|
14445
|
-
const suggestionsRef =
|
|
15133
|
+
const suggestionsRef = useRef6(suggestions);
|
|
14446
15134
|
suggestionsRef.current = suggestions;
|
|
14447
|
-
const isSlashModeRef =
|
|
15135
|
+
const isSlashModeRef = useRef6(isSlashMode);
|
|
14448
15136
|
isSlashModeRef.current = isSlashMode;
|
|
14449
|
-
const hasArgsRef =
|
|
15137
|
+
const hasArgsRef = useRef6(hasArgs);
|
|
14450
15138
|
hasArgsRef.current = hasArgs;
|
|
14451
|
-
const inputRequestRef =
|
|
15139
|
+
const inputRequestRef = useRef6(inputRequest);
|
|
14452
15140
|
inputRequestRef.current = inputRequest;
|
|
14453
|
-
const onChangeRef =
|
|
15141
|
+
const onChangeRef = useRef6(onChange);
|
|
14454
15142
|
onChangeRef.current = onChange;
|
|
14455
15143
|
const [inputKey, setInputKey] = useState5(0);
|
|
14456
|
-
|
|
15144
|
+
useInput2(useCallback5((_input, key) => {
|
|
14457
15145
|
if (inputRequestRef.current.status === "active") return;
|
|
14458
15146
|
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
14459
15147
|
const best = suggestionsRef.current[0];
|
|
@@ -14614,13 +15302,13 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14614
15302
|
addMessage,
|
|
14615
15303
|
refreshStats
|
|
14616
15304
|
} = useAgent(autoApproveMode, target);
|
|
14617
|
-
const isProcessingRef =
|
|
15305
|
+
const isProcessingRef = useRef7(isProcessing);
|
|
14618
15306
|
isProcessingRef.current = isProcessing;
|
|
14619
|
-
const autoApproveModeRef =
|
|
15307
|
+
const autoApproveModeRef = useRef7(autoApproveMode);
|
|
14620
15308
|
autoApproveModeRef.current = autoApproveMode;
|
|
14621
|
-
const inputRequestRef =
|
|
15309
|
+
const inputRequestRef = useRef7(inputRequest);
|
|
14622
15310
|
inputRequestRef.current = inputRequest;
|
|
14623
|
-
const handleExit =
|
|
15311
|
+
const handleExit = useCallback6(() => {
|
|
14624
15312
|
const ir = inputRequestRef.current;
|
|
14625
15313
|
if (ir.status === "active") {
|
|
14626
15314
|
ir.resolve(null);
|
|
@@ -14631,166 +15319,18 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14631
15319
|
exit();
|
|
14632
15320
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
14633
15321
|
}, [exit, setInputRequest]);
|
|
14634
|
-
const handleCommand =
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
setMessages([]);
|
|
14647
|
-
const result2 = await agent.resetSession();
|
|
14648
|
-
if (result2.cleared.length > 0) {
|
|
14649
|
-
addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
14650
|
-
} else {
|
|
14651
|
-
addMessage("system", "[reset] Session clean");
|
|
14652
|
-
}
|
|
14653
|
-
if (result2.errors.length > 0) {
|
|
14654
|
-
addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
14655
|
-
}
|
|
14656
|
-
refreshStats();
|
|
14657
|
-
break;
|
|
14658
|
-
}
|
|
14659
|
-
case UI_COMMANDS.TARGET:
|
|
14660
|
-
case UI_COMMANDS.TARGET_SHORT:
|
|
14661
|
-
if (!args[0]) {
|
|
14662
|
-
addMessage("error", "Usage: /target <ip>");
|
|
14663
|
-
break;
|
|
14664
|
-
}
|
|
14665
|
-
agent.addTarget(args[0]);
|
|
14666
|
-
agent.setScope([args[0]]);
|
|
14667
|
-
addMessage("system", `Target \u2192 ${args[0]}`);
|
|
14668
|
-
break;
|
|
14669
|
-
case UI_COMMANDS.START:
|
|
14670
|
-
case UI_COMMANDS.START_SHORT:
|
|
14671
|
-
if (!agent.getState().getTargets().size) {
|
|
14672
|
-
addMessage("error", "Set target first: /target <ip>");
|
|
14673
|
-
break;
|
|
14674
|
-
}
|
|
14675
|
-
if (!autoApproveModeRef.current) {
|
|
14676
|
-
setAutoApproveMode(true);
|
|
14677
|
-
agent.setAutoApprove(true);
|
|
14678
|
-
addMessage("system", "[auto] Autonomous mode enabled");
|
|
14679
|
-
}
|
|
14680
|
-
addMessage("system", "Starting penetration test...");
|
|
14681
|
-
const targets = Array.from(agent.getState().getTargets().keys());
|
|
14682
|
-
const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
|
|
14683
|
-
executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
14684
|
-
break;
|
|
14685
|
-
case UI_COMMANDS.FINDINGS:
|
|
14686
|
-
case UI_COMMANDS.FINDINGS_SHORT: {
|
|
14687
|
-
const findings = agent.getState().getFindings();
|
|
14688
|
-
if (!findings.length) {
|
|
14689
|
-
addMessage("system", "No findings.");
|
|
14690
|
-
break;
|
|
14691
|
-
}
|
|
14692
|
-
const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
|
|
14693
|
-
const confIcon = (c) => {
|
|
14694
|
-
if (c >= 100) return "\u{1F534}";
|
|
14695
|
-
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
|
|
14696
|
-
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
|
|
14697
|
-
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
|
|
14698
|
-
return "\u26AA";
|
|
14699
|
-
};
|
|
14700
|
-
const confLabel = (c) => {
|
|
14701
|
-
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
|
|
14702
|
-
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
|
|
14703
|
-
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
|
|
14704
|
-
return "speculative";
|
|
14705
|
-
};
|
|
14706
|
-
const findingLines = [];
|
|
14707
|
-
const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14708
|
-
const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14709
|
-
const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
14710
|
-
findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 \u{1F534}\u{1F7E0} confirmed:${nConfirmed} \u{1F7E1} probable:${nProbable} \u{1F7E2}\u26AA possible:${nPossible} \u2500\u2500\u2500`);
|
|
14711
|
-
findingLines.push("");
|
|
14712
|
-
sorted.forEach((f, i) => {
|
|
14713
|
-
const icon = confIcon(f.confidence);
|
|
14714
|
-
const label = confLabel(f.confidence);
|
|
14715
|
-
const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
|
|
14716
|
-
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
14717
|
-
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
14718
|
-
findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
|
|
14719
|
-
findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
|
|
14720
|
-
if (f.affected.length > 0) {
|
|
14721
|
-
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
14722
|
-
}
|
|
14723
|
-
if (f.description) {
|
|
14724
|
-
findingLines.push(` ${f.description}`);
|
|
14725
|
-
}
|
|
14726
|
-
if (f.evidence.length > 0) {
|
|
14727
|
-
findingLines.push(` Evidence:`);
|
|
14728
|
-
f.evidence.forEach((e) => {
|
|
14729
|
-
findingLines.push(` \u25B8 ${e}`);
|
|
14730
|
-
});
|
|
14731
|
-
}
|
|
14732
|
-
if (f.remediation) {
|
|
14733
|
-
findingLines.push(` Fix: ${f.remediation}`);
|
|
14734
|
-
}
|
|
14735
|
-
findingLines.push("");
|
|
14736
|
-
});
|
|
14737
|
-
addMessage("system", findingLines.join("\n"));
|
|
14738
|
-
break;
|
|
14739
|
-
}
|
|
14740
|
-
case UI_COMMANDS.ASSETS:
|
|
14741
|
-
case UI_COMMANDS.ASSETS_SHORT:
|
|
14742
|
-
addMessage("status", formatInlineStatus());
|
|
14743
|
-
break;
|
|
14744
|
-
case UI_COMMANDS.LOGS:
|
|
14745
|
-
case UI_COMMANDS.LOGS_SHORT:
|
|
14746
|
-
if (!args[0]) {
|
|
14747
|
-
addMessage("error", "Usage: /logs <process_id>");
|
|
14748
|
-
break;
|
|
14749
|
-
}
|
|
14750
|
-
const procData = getProcessOutput(args[0]);
|
|
14751
|
-
if (!procData) {
|
|
14752
|
-
addMessage("error", `Asset [${args[0]}] not found.`);
|
|
14753
|
-
break;
|
|
14754
|
-
}
|
|
14755
|
-
addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
|
|
14756
|
-
${procData.stdout || "(no output)"}
|
|
14757
|
-
--- End Log ---`);
|
|
14758
|
-
break;
|
|
14759
|
-
case UI_COMMANDS.CTF:
|
|
14760
|
-
const ctfEnabled = agent.toggleCtfMode();
|
|
14761
|
-
addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
|
|
14762
|
-
break;
|
|
14763
|
-
case UI_COMMANDS.TOR:
|
|
14764
|
-
const newTorState = !isTorEnabled();
|
|
14765
|
-
setTorEnabled(newTorState);
|
|
14766
|
-
addMessage("system", newTorState ? "\u{1F9C5} Tor proxy ON \u2014 target traffic routed through SOCKS5 (proxychains4 / native flags)" : "\u{1F9C5} Tor proxy OFF \u2014 direct connections");
|
|
14767
|
-
break;
|
|
14768
|
-
case UI_COMMANDS.GRAPH:
|
|
14769
|
-
case UI_COMMANDS.GRAPH_SHORT:
|
|
14770
|
-
addMessage("system", agent.getState().attackGraph.toASCII());
|
|
14771
|
-
break;
|
|
14772
|
-
case UI_COMMANDS.PATHS:
|
|
14773
|
-
case UI_COMMANDS.PATHS_SHORT:
|
|
14774
|
-
addMessage("system", agent.getState().attackGraph.toPathsList());
|
|
14775
|
-
break;
|
|
14776
|
-
case UI_COMMANDS.AUTO:
|
|
14777
|
-
setAutoApproveMode((prev) => {
|
|
14778
|
-
const newVal = !prev;
|
|
14779
|
-
agent.setAutoApprove(newVal);
|
|
14780
|
-
addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
|
|
14781
|
-
return newVal;
|
|
14782
|
-
});
|
|
14783
|
-
break;
|
|
14784
|
-
case UI_COMMANDS.EXIT:
|
|
14785
|
-
case UI_COMMANDS.QUIT:
|
|
14786
|
-
case UI_COMMANDS.EXIT_SHORT:
|
|
14787
|
-
handleExit();
|
|
14788
|
-
break;
|
|
14789
|
-
default:
|
|
14790
|
-
addMessage("error", `Unknown command: /${cmd}`);
|
|
14791
|
-
}
|
|
14792
|
-
}, [agent, addMessage, executeTask, setMessages, handleExit, refreshStats]);
|
|
14793
|
-
const handleSubmit = useCallback4(async (value) => {
|
|
15322
|
+
const { handleCommand } = useCommands({
|
|
15323
|
+
agent,
|
|
15324
|
+
addMessage,
|
|
15325
|
+
setMessages,
|
|
15326
|
+
executeTask,
|
|
15327
|
+
refreshStats,
|
|
15328
|
+
setAutoApproveMode,
|
|
15329
|
+
handleExit,
|
|
15330
|
+
isProcessingRef,
|
|
15331
|
+
autoApproveModeRef
|
|
15332
|
+
});
|
|
15333
|
+
const handleSubmit = useCallback6(async (value) => {
|
|
14794
15334
|
const trimmed = value.trim();
|
|
14795
15335
|
if (!trimmed) return;
|
|
14796
15336
|
setInput("");
|
|
@@ -14805,7 +15345,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14805
15345
|
await executeTask(trimmed);
|
|
14806
15346
|
}
|
|
14807
15347
|
}, [agent, addMessage, executeTask, handleCommand]);
|
|
14808
|
-
const handleSecretSubmit =
|
|
15348
|
+
const handleSecretSubmit = useCallback6((value) => {
|
|
14809
15349
|
const ir = inputRequestRef.current;
|
|
14810
15350
|
if (ir.status !== "active") return;
|
|
14811
15351
|
const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
|
|
@@ -14815,43 +15355,14 @@ ${procData.stdout || "(no output)"}
|
|
|
14815
15355
|
setInputRequest({ status: "inactive" });
|
|
14816
15356
|
setSecretInput("");
|
|
14817
15357
|
}, [addMessage, setInputRequest]);
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
ctrlCPressedRef.current = true;
|
|
14827
|
-
addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
|
|
14828
|
-
if (isProcessingRef.current) abort();
|
|
14829
|
-
ctrlCTimerRef.current = setTimeout(() => {
|
|
14830
|
-
ctrlCPressedRef.current = false;
|
|
14831
|
-
ctrlCTimerRef.current = null;
|
|
14832
|
-
}, 3e3);
|
|
14833
|
-
}, [handleExit, addMessage, abort]);
|
|
14834
|
-
useEffect5(() => {
|
|
14835
|
-
return () => {
|
|
14836
|
-
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14837
|
-
};
|
|
14838
|
-
}, []);
|
|
14839
|
-
useInput2(useCallback4((ch, key) => {
|
|
14840
|
-
if (key.escape) {
|
|
14841
|
-
if (inputRequestRef.current.status === "active") cancelInputRequest();
|
|
14842
|
-
else if (isProcessingRef.current) abort();
|
|
14843
|
-
}
|
|
14844
|
-
if (key.ctrl && ch === "c") handleCtrlC();
|
|
14845
|
-
}, [cancelInputRequest, abort, handleCtrlC]));
|
|
14846
|
-
useEffect5(() => {
|
|
14847
|
-
const onSignal = () => handleCtrlC();
|
|
14848
|
-
process.on("SIGINT", onSignal);
|
|
14849
|
-
process.on("SIGTERM", onSignal);
|
|
14850
|
-
return () => {
|
|
14851
|
-
process.off("SIGINT", onSignal);
|
|
14852
|
-
process.off("SIGTERM", onSignal);
|
|
14853
|
-
};
|
|
14854
|
-
}, [handleCtrlC]);
|
|
15358
|
+
useKeyboardShortcuts({
|
|
15359
|
+
addMessage,
|
|
15360
|
+
handleExit,
|
|
15361
|
+
abort,
|
|
15362
|
+
cancelInputRequest,
|
|
15363
|
+
isProcessingRef,
|
|
15364
|
+
inputRequestRef
|
|
15365
|
+
});
|
|
14855
15366
|
const isSlashMode = input.startsWith("/");
|
|
14856
15367
|
const partialCmd = isSlashMode ? input.slice(1).split(" ")[0] : "";
|
|
14857
15368
|
const hasArgs = isSlashMode && input.includes(" ");
|