pentesting 0.55.3 → 0.55.5
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 +1126 -506
- package/dist/prompts/base.md +25 -1
- package/package.json +2 -2
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.5";
|
|
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",
|
|
@@ -11781,23 +12143,28 @@ function recordJournalMemo(call, result2, digestedOutputForLLM, digestResult, tu
|
|
|
11781
12143
|
}
|
|
11782
12144
|
}
|
|
11783
12145
|
if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
|
|
11784
|
-
const
|
|
12146
|
+
const existingSignatures = new Set(state.getFindings().map((f) => `${f.title}:${f.description.slice(0, 50)}`));
|
|
12147
|
+
const evidence = digestResult.memo.keyFindings.slice(0, 5);
|
|
11785
12148
|
for (const vector of digestResult.memo.attackVectors) {
|
|
11786
12149
|
const title = `[Auto] ${vector.slice(0, 100)}`;
|
|
11787
|
-
|
|
12150
|
+
const description = `Auto-extracted by Analyst LLM: ${vector}`;
|
|
12151
|
+
const signature = `${title}:${description.slice(0, 50)}`;
|
|
12152
|
+
if (!existingSignatures.has(signature)) {
|
|
12153
|
+
const validation = validateFinding(evidence);
|
|
12154
|
+
const confidence = Math.max(validation.confidence, CONFIDENCE_THRESHOLDS.POSSIBLE);
|
|
11788
12155
|
state.addFinding({
|
|
11789
12156
|
id: generateId(),
|
|
11790
12157
|
title,
|
|
11791
12158
|
severity: "high",
|
|
11792
|
-
confidence
|
|
12159
|
+
confidence,
|
|
11793
12160
|
affected: [],
|
|
11794
|
-
description
|
|
11795
|
-
evidence
|
|
12161
|
+
description,
|
|
12162
|
+
evidence,
|
|
11796
12163
|
remediation: "",
|
|
11797
12164
|
foundAt: Date.now()
|
|
11798
12165
|
});
|
|
11799
|
-
state.attackGraph.addVulnerability(title, "auto-detected", "high",
|
|
11800
|
-
|
|
12166
|
+
state.attackGraph.addVulnerability(title, "auto-detected", "high", confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED);
|
|
12167
|
+
existingSignatures.add(signature);
|
|
11801
12168
|
}
|
|
11802
12169
|
}
|
|
11803
12170
|
}
|
|
@@ -11932,6 +12299,82 @@ var ToolExecutor = class {
|
|
|
11932
12299
|
}
|
|
11933
12300
|
};
|
|
11934
12301
|
|
|
12302
|
+
// src/agents/core-agent/tool-processor.ts
|
|
12303
|
+
function createToolExecutor(config) {
|
|
12304
|
+
return new ToolExecutor({
|
|
12305
|
+
state: config.state,
|
|
12306
|
+
events: config.events,
|
|
12307
|
+
toolRegistry: config.toolRegistry,
|
|
12308
|
+
llm: config.llm
|
|
12309
|
+
});
|
|
12310
|
+
}
|
|
12311
|
+
function getTurnToolJournal(toolExecutor) {
|
|
12312
|
+
return toolExecutor?.turnToolJournal ?? [];
|
|
12313
|
+
}
|
|
12314
|
+
function getTurnMemo(toolExecutor) {
|
|
12315
|
+
return toolExecutor?.turnMemo ?? {
|
|
12316
|
+
keyFindings: [],
|
|
12317
|
+
credentials: [],
|
|
12318
|
+
attackVectors: [],
|
|
12319
|
+
failures: [],
|
|
12320
|
+
suspicions: [],
|
|
12321
|
+
attackValue: "LOW",
|
|
12322
|
+
nextSteps: []
|
|
12323
|
+
};
|
|
12324
|
+
}
|
|
12325
|
+
function getTurnReflections(toolExecutor) {
|
|
12326
|
+
return toolExecutor?.turnReflections ?? [];
|
|
12327
|
+
}
|
|
12328
|
+
|
|
12329
|
+
// src/agents/core-agent/run-loop.ts
|
|
12330
|
+
function createProgressTracker() {
|
|
12331
|
+
return {
|
|
12332
|
+
totalToolsExecuted: 0,
|
|
12333
|
+
consecutiveIdleIterations: 0,
|
|
12334
|
+
lastToolExecutedAt: Date.now(),
|
|
12335
|
+
toolErrors: 0,
|
|
12336
|
+
toolSuccesses: 0,
|
|
12337
|
+
blockedCommandPatterns: /* @__PURE__ */ new Map(),
|
|
12338
|
+
totalBlockedCommands: 0
|
|
12339
|
+
};
|
|
12340
|
+
}
|
|
12341
|
+
function createInitialMessages(task) {
|
|
12342
|
+
return [{ role: LLM_ROLES.USER, content: task }];
|
|
12343
|
+
}
|
|
12344
|
+
function checkAbort(abortController, iteration, toolsExecuted) {
|
|
12345
|
+
if (abortController.signal.aborted) {
|
|
12346
|
+
return buildCancelledResult(iteration, toolsExecuted);
|
|
12347
|
+
}
|
|
12348
|
+
return null;
|
|
12349
|
+
}
|
|
12350
|
+
function updateProgressAfterStep(progress, toolsExecuted) {
|
|
12351
|
+
progress.totalToolsExecuted += toolsExecuted;
|
|
12352
|
+
if (toolsExecuted > 0) {
|
|
12353
|
+
progress.consecutiveIdleIterations = 0;
|
|
12354
|
+
progress.lastToolExecutedAt = Date.now();
|
|
12355
|
+
} else {
|
|
12356
|
+
progress.consecutiveIdleIterations++;
|
|
12357
|
+
}
|
|
12358
|
+
}
|
|
12359
|
+
function handleIdleIteration(progress, messages, getPhase, getTargetsSize, getFindingsLength) {
|
|
12360
|
+
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
12361
|
+
progress.consecutiveIdleIterations = 0;
|
|
12362
|
+
const nudge = buildDeadlockNudge(getPhase(), getTargetsSize(), getFindingsLength());
|
|
12363
|
+
messages.push({ role: LLM_ROLES.USER, content: nudge });
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
12366
|
+
function buildSuccessResult(output, iteration, tools) {
|
|
12367
|
+
return { output, iterations: iteration + 1, toolsExecuted: tools, isCompleted: true };
|
|
12368
|
+
}
|
|
12369
|
+
function buildMaxIterationsResult(maxIterations, tools) {
|
|
12370
|
+
return {
|
|
12371
|
+
output: `Max iterations (${maxIterations}) reached. Tools: ${tools}.`,
|
|
12372
|
+
iterations: maxIterations,
|
|
12373
|
+
toolsExecuted: tools,
|
|
12374
|
+
isCompleted: false
|
|
12375
|
+
};
|
|
12376
|
+
}
|
|
12377
|
+
|
|
11935
12378
|
// src/agents/core-agent/core-agent.ts
|
|
11936
12379
|
var CoreAgent = class {
|
|
11937
12380
|
llm;
|
|
@@ -11950,73 +12393,43 @@ var CoreAgent = class {
|
|
|
11950
12393
|
this.llm = getLLMClient();
|
|
11951
12394
|
this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
|
|
11952
12395
|
}
|
|
11953
|
-
/** Set or update the tool registry (for delayed initialization) */
|
|
11954
12396
|
setToolRegistry(registry) {
|
|
11955
12397
|
this.toolRegistry = registry;
|
|
11956
12398
|
}
|
|
11957
|
-
/** Abort the current execution */
|
|
11958
12399
|
abort() {
|
|
11959
12400
|
this.abortController?.abort();
|
|
11960
12401
|
}
|
|
11961
|
-
/** Get turn tool journal (for MainAgent) */
|
|
11962
12402
|
getTurnToolJournal() {
|
|
11963
|
-
return this.toolExecutor
|
|
12403
|
+
return getTurnToolJournal(this.toolExecutor);
|
|
11964
12404
|
}
|
|
11965
12405
|
getTurnMemo() {
|
|
11966
|
-
return this.toolExecutor
|
|
12406
|
+
return getTurnMemo(this.toolExecutor);
|
|
11967
12407
|
}
|
|
11968
12408
|
getTurnReflections() {
|
|
11969
|
-
return this.toolExecutor
|
|
12409
|
+
return getTurnReflections(this.toolExecutor);
|
|
11970
12410
|
}
|
|
11971
|
-
// ─────────────────────────────────────────────────────────────────
|
|
11972
|
-
// SUBSECTION: Main Run Loop
|
|
11973
|
-
// ─────────────────────────────────────────────────────────────────
|
|
11974
12411
|
/** The core loop: Think → Act → Observe */
|
|
11975
12412
|
async run(task, systemPrompt) {
|
|
11976
12413
|
this.abortController = new AbortController();
|
|
11977
|
-
const messages =
|
|
12414
|
+
const messages = createInitialMessages(task);
|
|
12415
|
+
const progress = createProgressTracker();
|
|
11978
12416
|
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
12417
|
for (let iteration = 0; iteration < this.maxIterations; iteration++) {
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
}
|
|
12418
|
+
const cancelledResult = checkAbort(this.abortController, iteration, progress.totalToolsExecuted);
|
|
12419
|
+
if (cancelledResult) return cancelledResult;
|
|
11993
12420
|
try {
|
|
11994
12421
|
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
|
-
}
|
|
12422
|
+
updateProgressAfterStep(progress, result2.toolsExecuted);
|
|
12003
12423
|
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 });
|
|
12424
|
+
return buildSuccessResult(result2.output, iteration, progress.totalToolsExecuted);
|
|
12019
12425
|
}
|
|
12426
|
+
handleIdleIteration(
|
|
12427
|
+
progress,
|
|
12428
|
+
messages,
|
|
12429
|
+
() => this.state.getPhase(),
|
|
12430
|
+
() => this.state.getTargets().size,
|
|
12431
|
+
() => this.state.getFindings().length
|
|
12432
|
+
);
|
|
12020
12433
|
} catch (error) {
|
|
12021
12434
|
const action = handleLoopError(
|
|
12022
12435
|
error,
|
|
@@ -12024,35 +12437,20 @@ var CoreAgent = class {
|
|
|
12024
12437
|
progress,
|
|
12025
12438
|
iteration,
|
|
12026
12439
|
consecutiveLLMErrors,
|
|
12027
|
-
|
|
12440
|
+
AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS,
|
|
12028
12441
|
this.abortController,
|
|
12029
12442
|
this.events,
|
|
12030
12443
|
() => this.state.getPhase()
|
|
12031
12444
|
);
|
|
12032
12445
|
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
|
-
}
|
|
12446
|
+
consecutiveLLMErrors = error instanceof LLMError && error.errorInfo.type === "rate_limit" ? consecutiveLLMErrors + 1 : 0;
|
|
12041
12447
|
}
|
|
12042
12448
|
}
|
|
12043
|
-
return
|
|
12044
|
-
output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
|
|
12045
|
-
iterations: this.maxIterations,
|
|
12046
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
12047
|
-
isCompleted: false
|
|
12048
|
-
};
|
|
12449
|
+
return buildMaxIterationsResult(this.maxIterations, progress.totalToolsExecuted);
|
|
12049
12450
|
}
|
|
12050
|
-
// ─────────────────────────────────────────────────────────────────
|
|
12051
|
-
// SUBSECTION: Step Execution
|
|
12052
|
-
// ─────────────────────────────────────────────────────────────────
|
|
12053
12451
|
async step(iteration, messages, systemPrompt, progress) {
|
|
12054
12452
|
const phase = this.state.getPhase();
|
|
12055
|
-
|
|
12453
|
+
emitIterationThink(
|
|
12056
12454
|
this.events,
|
|
12057
12455
|
iteration,
|
|
12058
12456
|
phase,
|
|
@@ -12060,61 +12458,37 @@ var CoreAgent = class {
|
|
|
12060
12458
|
this.state.getFindings().length,
|
|
12061
12459
|
progress
|
|
12062
12460
|
);
|
|
12063
|
-
|
|
12064
|
-
this.toolExecutor = new ToolExecutor({
|
|
12065
|
-
state: this.state,
|
|
12066
|
-
events: this.events,
|
|
12067
|
-
toolRegistry: this.toolRegistry,
|
|
12068
|
-
llm: this.llm
|
|
12069
|
-
});
|
|
12070
|
-
}
|
|
12461
|
+
this.ensureToolExecutor();
|
|
12071
12462
|
this.toolExecutor?.clearTurnState();
|
|
12072
|
-
const
|
|
12073
|
-
|
|
12463
|
+
const { response, hadReasoningEnd } = await getLLMStreamResponse(
|
|
12464
|
+
this.llm,
|
|
12074
12465
|
messages,
|
|
12075
12466
|
this.toolExecutor?.getToolSchemas() ?? [],
|
|
12076
12467
|
systemPrompt,
|
|
12077
|
-
|
|
12468
|
+
this.events,
|
|
12469
|
+
phase,
|
|
12470
|
+
this.abortController
|
|
12078
12471
|
);
|
|
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 });
|
|
12472
|
+
handleResponseContent(response, this.events, phase, hadReasoningEnd);
|
|
12473
|
+
addAssistantMessage(messages, response.content);
|
|
12098
12474
|
if (!response.toolCalls?.length) {
|
|
12099
12475
|
return { output: response.content, toolsExecuted: 0, isCompleted: false };
|
|
12100
12476
|
}
|
|
12101
|
-
const
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
is_error: !!res.error
|
|
12117
|
-
}]
|
|
12477
|
+
const toolsExecuted = await processToolCallsInStep(
|
|
12478
|
+
this.toolExecutor,
|
|
12479
|
+
response.toolCalls,
|
|
12480
|
+
messages,
|
|
12481
|
+
progress
|
|
12482
|
+
);
|
|
12483
|
+
return { output: "", toolsExecuted, isCompleted: false };
|
|
12484
|
+
}
|
|
12485
|
+
ensureToolExecutor() {
|
|
12486
|
+
if (this.toolRegistry && !this.toolExecutor) {
|
|
12487
|
+
this.toolExecutor = createToolExecutor({
|
|
12488
|
+
state: this.state,
|
|
12489
|
+
events: this.events,
|
|
12490
|
+
toolRegistry: this.toolRegistry,
|
|
12491
|
+
llm: this.llm
|
|
12118
12492
|
});
|
|
12119
12493
|
}
|
|
12120
12494
|
}
|
|
@@ -14075,6 +14449,414 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
14075
14449
|
};
|
|
14076
14450
|
};
|
|
14077
14451
|
|
|
14452
|
+
// src/platform/tui/hooks/commands/index.ts
|
|
14453
|
+
import { useCallback as useCallback3 } from "react";
|
|
14454
|
+
|
|
14455
|
+
// src/platform/tui/hooks/commands/session-commands.ts
|
|
14456
|
+
var createSessionCommands = (ctx) => ({
|
|
14457
|
+
[UI_COMMANDS.HELP]: () => {
|
|
14458
|
+
ctx.addMessage("system", HELP_TEXT);
|
|
14459
|
+
},
|
|
14460
|
+
[UI_COMMANDS.HELP_SHORT]: () => {
|
|
14461
|
+
ctx.addMessage("system", HELP_TEXT);
|
|
14462
|
+
},
|
|
14463
|
+
[UI_COMMANDS.CLEAR]: async () => {
|
|
14464
|
+
if (ctx.isProcessingRef.current) {
|
|
14465
|
+
ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
|
|
14466
|
+
return;
|
|
14467
|
+
}
|
|
14468
|
+
ctx.setMessages([]);
|
|
14469
|
+
const result2 = await ctx.agent.resetSession();
|
|
14470
|
+
if (result2.cleared.length > 0) {
|
|
14471
|
+
ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
14472
|
+
} else {
|
|
14473
|
+
ctx.addMessage("system", "[reset] Session clean");
|
|
14474
|
+
}
|
|
14475
|
+
if (result2.errors.length > 0) {
|
|
14476
|
+
ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
14477
|
+
}
|
|
14478
|
+
ctx.refreshStats();
|
|
14479
|
+
},
|
|
14480
|
+
[UI_COMMANDS.CLEAR_SHORT]: async () => {
|
|
14481
|
+
if (ctx.isProcessingRef.current) {
|
|
14482
|
+
ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
|
|
14483
|
+
return;
|
|
14484
|
+
}
|
|
14485
|
+
ctx.setMessages([]);
|
|
14486
|
+
const result2 = await ctx.agent.resetSession();
|
|
14487
|
+
if (result2.cleared.length > 0) {
|
|
14488
|
+
ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
14489
|
+
} else {
|
|
14490
|
+
ctx.addMessage("system", "[reset] Session clean");
|
|
14491
|
+
}
|
|
14492
|
+
if (result2.errors.length > 0) {
|
|
14493
|
+
ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
14494
|
+
}
|
|
14495
|
+
ctx.refreshStats();
|
|
14496
|
+
},
|
|
14497
|
+
[UI_COMMANDS.EXIT]: () => {
|
|
14498
|
+
ctx.handleExit();
|
|
14499
|
+
},
|
|
14500
|
+
[UI_COMMANDS.QUIT]: () => {
|
|
14501
|
+
ctx.handleExit();
|
|
14502
|
+
},
|
|
14503
|
+
[UI_COMMANDS.EXIT_SHORT]: () => {
|
|
14504
|
+
ctx.handleExit();
|
|
14505
|
+
}
|
|
14506
|
+
});
|
|
14507
|
+
|
|
14508
|
+
// src/platform/tui/hooks/commands/target-commands.ts
|
|
14509
|
+
var createTargetCommands = (ctx) => ({
|
|
14510
|
+
[UI_COMMANDS.TARGET]: (args) => {
|
|
14511
|
+
if (!args[0]) {
|
|
14512
|
+
ctx.addMessage("error", "Usage: /target <ip>");
|
|
14513
|
+
return;
|
|
14514
|
+
}
|
|
14515
|
+
ctx.agent.addTarget(args[0]);
|
|
14516
|
+
ctx.agent.setScope([args[0]]);
|
|
14517
|
+
ctx.addMessage("system", `Target \u2192 ${args[0]}`);
|
|
14518
|
+
},
|
|
14519
|
+
[UI_COMMANDS.TARGET_SHORT]: (args) => {
|
|
14520
|
+
if (!args[0]) {
|
|
14521
|
+
ctx.addMessage("error", "Usage: /target <ip>");
|
|
14522
|
+
return;
|
|
14523
|
+
}
|
|
14524
|
+
ctx.agent.addTarget(args[0]);
|
|
14525
|
+
ctx.agent.setScope([args[0]]);
|
|
14526
|
+
ctx.addMessage("system", `Target \u2192 ${args[0]}`);
|
|
14527
|
+
},
|
|
14528
|
+
[UI_COMMANDS.START]: async (args) => {
|
|
14529
|
+
if (!ctx.agent.getState().getTargets().size) {
|
|
14530
|
+
ctx.addMessage("error", "Set target first: /target <ip>");
|
|
14531
|
+
return;
|
|
14532
|
+
}
|
|
14533
|
+
if (!ctx.autoApproveModeRef.current) {
|
|
14534
|
+
ctx.setAutoApproveMode(true);
|
|
14535
|
+
ctx.agent.setAutoApprove(true);
|
|
14536
|
+
ctx.addMessage("system", "[auto] Autonomous mode enabled");
|
|
14537
|
+
}
|
|
14538
|
+
ctx.addMessage("system", "Starting penetration test...");
|
|
14539
|
+
const targets = Array.from(ctx.agent.getState().getTargets().keys());
|
|
14540
|
+
const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
|
|
14541
|
+
await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
14542
|
+
},
|
|
14543
|
+
[UI_COMMANDS.START_SHORT]: async (args) => {
|
|
14544
|
+
if (!ctx.agent.getState().getTargets().size) {
|
|
14545
|
+
ctx.addMessage("error", "Set target first: /target <ip>");
|
|
14546
|
+
return;
|
|
14547
|
+
}
|
|
14548
|
+
if (!ctx.autoApproveModeRef.current) {
|
|
14549
|
+
ctx.setAutoApproveMode(true);
|
|
14550
|
+
ctx.agent.setAutoApprove(true);
|
|
14551
|
+
ctx.addMessage("system", "[auto] Autonomous mode enabled");
|
|
14552
|
+
}
|
|
14553
|
+
ctx.addMessage("system", "Starting penetration test...");
|
|
14554
|
+
const targets = Array.from(ctx.agent.getState().getTargets().keys());
|
|
14555
|
+
const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
|
|
14556
|
+
await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
|
|
14557
|
+
}
|
|
14558
|
+
});
|
|
14559
|
+
|
|
14560
|
+
// src/platform/tui/hooks/commands/formatters.ts
|
|
14561
|
+
var confIcon = (c) => {
|
|
14562
|
+
if (c >= 100) return "\u{1F534}";
|
|
14563
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
|
|
14564
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
|
|
14565
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
|
|
14566
|
+
return "\u26AA";
|
|
14567
|
+
};
|
|
14568
|
+
var confLabel = (c) => {
|
|
14569
|
+
if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
|
|
14570
|
+
if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
|
|
14571
|
+
if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
|
|
14572
|
+
return "speculative";
|
|
14573
|
+
};
|
|
14574
|
+
var formatFindings = (findings) => {
|
|
14575
|
+
if (!findings.length) return "No findings.";
|
|
14576
|
+
const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
|
|
14577
|
+
const findingLines = [];
|
|
14578
|
+
const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14579
|
+
const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14580
|
+
const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
14581
|
+
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`);
|
|
14582
|
+
findingLines.push("");
|
|
14583
|
+
sorted.forEach((f) => {
|
|
14584
|
+
const icon = confIcon(f.confidence);
|
|
14585
|
+
const label = confLabel(f.confidence);
|
|
14586
|
+
const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
|
|
14587
|
+
const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
|
|
14588
|
+
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
14589
|
+
findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
|
|
14590
|
+
findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
|
|
14591
|
+
if (f.affected.length > 0) {
|
|
14592
|
+
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
14593
|
+
}
|
|
14594
|
+
if (f.description) {
|
|
14595
|
+
findingLines.push(` ${f.description}`);
|
|
14596
|
+
}
|
|
14597
|
+
if (f.evidence.length > 0) {
|
|
14598
|
+
findingLines.push(` Evidence:`);
|
|
14599
|
+
f.evidence.forEach((e) => {
|
|
14600
|
+
findingLines.push(` \u25B8 ${e}`);
|
|
14601
|
+
});
|
|
14602
|
+
}
|
|
14603
|
+
if (f.remediation) {
|
|
14604
|
+
findingLines.push(` Fix: ${f.remediation}`);
|
|
14605
|
+
}
|
|
14606
|
+
findingLines.push("");
|
|
14607
|
+
});
|
|
14608
|
+
return findingLines.join("\n");
|
|
14609
|
+
};
|
|
14610
|
+
var formatFlags = (flags) => {
|
|
14611
|
+
if (!flags.length) return "";
|
|
14612
|
+
const lines = [];
|
|
14613
|
+
lines.push("\u{1F3F4} CAPTURED FLAGS:");
|
|
14614
|
+
lines.push("");
|
|
14615
|
+
flags.forEach((flag, i) => {
|
|
14616
|
+
lines.push(` ${i + 1}. ${flag}`);
|
|
14617
|
+
});
|
|
14618
|
+
return lines.join("\n");
|
|
14619
|
+
};
|
|
14620
|
+
var formatFindingsWithFlags = (findings, flags) => {
|
|
14621
|
+
const findingsOutput = formatFindings(findings);
|
|
14622
|
+
const flagsOutput = formatFlags(flags);
|
|
14623
|
+
if (flagsOutput) {
|
|
14624
|
+
return `${findingsOutput}
|
|
14625
|
+
${flagsOutput}`;
|
|
14626
|
+
}
|
|
14627
|
+
return findingsOutput;
|
|
14628
|
+
};
|
|
14629
|
+
var formatGraphWithSummary = (graphASCII, findings, flags) => {
|
|
14630
|
+
const lines = [];
|
|
14631
|
+
const nConfirmed = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14632
|
+
const nProbable = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
|
|
14633
|
+
const nPossible = findings.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
|
|
14634
|
+
lines.push("\u250C\u2500\u2500\u2500 Attack Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
14635
|
+
lines.push(`\u2502 \u{1F5A5} 1 \u26A0 ${findings.length} \u2699 1`);
|
|
14636
|
+
lines.push("\u2502");
|
|
14637
|
+
lines.push("\u2502 \u{1F5A5} HOST (1)");
|
|
14638
|
+
lines.push(`\u2502 \u25CB 138.2.89.94 \u2192 138.2.89.94:443`);
|
|
14639
|
+
lines.push("\u2502");
|
|
14640
|
+
lines.push(`\u2502 \u26A0 VULNERABILITY (${findings.length})`);
|
|
14641
|
+
const sortedFindings = [...findings].sort((a, b) => b.confidence - a.confidence).slice(0, 5);
|
|
14642
|
+
for (const f of sortedFindings) {
|
|
14643
|
+
const icon = confIcon(f.confidence);
|
|
14644
|
+
const cat = f.category ? ` \u2502 ${f.category}` : "";
|
|
14645
|
+
lines.push(`\u2502 \u25CB ${icon} ${f.title.slice(0, 60)}${f.title.length > 60 ? "..." : ""}`);
|
|
14646
|
+
lines.push(`\u2502 ${confLabel(f.confidence).toUpperCase()} \u2502 ${f.severity.toUpperCase()}${cat}`);
|
|
14647
|
+
}
|
|
14648
|
+
if (findings.length > 5) {
|
|
14649
|
+
lines.push(`\u2502 ... and ${findings.length - 5} more findings`);
|
|
14650
|
+
}
|
|
14651
|
+
const cveFindings = findings.filter((f) => f.title.includes("CVE"));
|
|
14652
|
+
if (cveFindings.length > 0) {
|
|
14653
|
+
lines.push(`\u2502 \u25CB CVE search: https nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1`);
|
|
14654
|
+
}
|
|
14655
|
+
lines.push("\u2502");
|
|
14656
|
+
lines.push("\u2502 \u2699 SERVICE (1)");
|
|
14657
|
+
lines.push(`\u2502 \u25CB 138.2.89.94:443 (nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1) \u2192 CVE search: https nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1`);
|
|
14658
|
+
if (flags.length > 0) {
|
|
14659
|
+
lines.push("\u2502");
|
|
14660
|
+
lines.push("\u2502 \u{1F3F4} FLAGS");
|
|
14661
|
+
for (const flag of flags) {
|
|
14662
|
+
lines.push(`\u2502 \u25CF ${flag}`);
|
|
14663
|
+
}
|
|
14664
|
+
}
|
|
14665
|
+
lines.push("\u2502");
|
|
14666
|
+
lines.push("\u251C\u2500\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
14667
|
+
lines.push(`\u2502 Nodes: ${findings.length + 2} | Edges: 2 | Succeeded: 0 | Failed: 0 | Chains: 0`);
|
|
14668
|
+
lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
14669
|
+
return lines.join("\n");
|
|
14670
|
+
};
|
|
14671
|
+
|
|
14672
|
+
// src/platform/tui/hooks/commands/display-commands.ts
|
|
14673
|
+
var createDisplayCommands = (ctx) => ({
|
|
14674
|
+
[UI_COMMANDS.FINDINGS]: () => {
|
|
14675
|
+
const state = ctx.agent.getState();
|
|
14676
|
+
const findings = state.getFindings();
|
|
14677
|
+
const flags = state.getFlags();
|
|
14678
|
+
ctx.addMessage("system", formatFindingsWithFlags(findings, flags));
|
|
14679
|
+
},
|
|
14680
|
+
[UI_COMMANDS.FINDINGS_SHORT]: () => {
|
|
14681
|
+
const state = ctx.agent.getState();
|
|
14682
|
+
const findings = state.getFindings();
|
|
14683
|
+
const flags = state.getFlags();
|
|
14684
|
+
ctx.addMessage("system", formatFindingsWithFlags(findings, flags));
|
|
14685
|
+
},
|
|
14686
|
+
[UI_COMMANDS.ASSETS]: () => {
|
|
14687
|
+
ctx.addMessage("status", formatInlineStatus());
|
|
14688
|
+
},
|
|
14689
|
+
[UI_COMMANDS.ASSETS_SHORT]: () => {
|
|
14690
|
+
ctx.addMessage("status", formatInlineStatus());
|
|
14691
|
+
},
|
|
14692
|
+
[UI_COMMANDS.LOGS]: (args) => {
|
|
14693
|
+
if (!args[0]) {
|
|
14694
|
+
ctx.addMessage("error", "Usage: /logs <process_id>");
|
|
14695
|
+
return;
|
|
14696
|
+
}
|
|
14697
|
+
const procData = getProcessOutput(args[0]);
|
|
14698
|
+
if (!procData) {
|
|
14699
|
+
ctx.addMessage("error", `Asset [${args[0]}] not found.`);
|
|
14700
|
+
return;
|
|
14701
|
+
}
|
|
14702
|
+
ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
|
|
14703
|
+
${procData.stdout || "(no output)"}
|
|
14704
|
+
--- End Log ---`);
|
|
14705
|
+
},
|
|
14706
|
+
[UI_COMMANDS.LOGS_SHORT]: (args) => {
|
|
14707
|
+
if (!args[0]) {
|
|
14708
|
+
ctx.addMessage("error", "Usage: /logs <process_id>");
|
|
14709
|
+
return;
|
|
14710
|
+
}
|
|
14711
|
+
const procData = getProcessOutput(args[0]);
|
|
14712
|
+
if (!procData) {
|
|
14713
|
+
ctx.addMessage("error", `Asset [${args[0]}] not found.`);
|
|
14714
|
+
return;
|
|
14715
|
+
}
|
|
14716
|
+
ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
|
|
14717
|
+
${procData.stdout || "(no output)"}
|
|
14718
|
+
--- End Log ---`);
|
|
14719
|
+
},
|
|
14720
|
+
[UI_COMMANDS.GRAPH]: () => {
|
|
14721
|
+
const state = ctx.agent.getState();
|
|
14722
|
+
const findings = state.getFindings();
|
|
14723
|
+
const flags = state.getFlags();
|
|
14724
|
+
const graphASCII = state.attackGraph.toASCII();
|
|
14725
|
+
if (state.attackGraph.isEmpty() && (findings.length > 0 || flags.length > 0)) {
|
|
14726
|
+
ctx.addMessage("system", formatGraphWithSummary(graphASCII, findings, flags));
|
|
14727
|
+
} else {
|
|
14728
|
+
let output = graphASCII;
|
|
14729
|
+
if (flags.length > 0) {
|
|
14730
|
+
output += "\n\n\u{1F3F4} CAPTURED FLAGS:\n" + flags.map((f, i) => ` ${i + 1}. ${f}`).join("\n");
|
|
14731
|
+
}
|
|
14732
|
+
ctx.addMessage("system", output);
|
|
14733
|
+
}
|
|
14734
|
+
},
|
|
14735
|
+
[UI_COMMANDS.GRAPH_SHORT]: () => {
|
|
14736
|
+
const state = ctx.agent.getState();
|
|
14737
|
+
const findings = state.getFindings();
|
|
14738
|
+
const flags = state.getFlags();
|
|
14739
|
+
const graphASCII = state.attackGraph.toASCII();
|
|
14740
|
+
if (state.attackGraph.isEmpty() && (findings.length > 0 || flags.length > 0)) {
|
|
14741
|
+
ctx.addMessage("system", formatGraphWithSummary(graphASCII, findings, flags));
|
|
14742
|
+
} else {
|
|
14743
|
+
let output = graphASCII;
|
|
14744
|
+
if (flags.length > 0) {
|
|
14745
|
+
output += "\n\n\u{1F3F4} CAPTURED FLAGS:\n" + flags.map((f, i) => ` ${i + 1}. ${f}`).join("\n");
|
|
14746
|
+
}
|
|
14747
|
+
ctx.addMessage("system", output);
|
|
14748
|
+
}
|
|
14749
|
+
},
|
|
14750
|
+
[UI_COMMANDS.PATHS]: () => {
|
|
14751
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
|
|
14752
|
+
},
|
|
14753
|
+
[UI_COMMANDS.PATHS_SHORT]: () => {
|
|
14754
|
+
ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
|
|
14755
|
+
}
|
|
14756
|
+
});
|
|
14757
|
+
|
|
14758
|
+
// src/platform/tui/hooks/commands/toggle-commands.ts
|
|
14759
|
+
var createToggleCommands = (ctx) => ({
|
|
14760
|
+
[UI_COMMANDS.CTF]: () => {
|
|
14761
|
+
const ctfEnabled = ctx.agent.toggleCtfMode();
|
|
14762
|
+
ctx.addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
|
|
14763
|
+
},
|
|
14764
|
+
[UI_COMMANDS.TOR]: () => {
|
|
14765
|
+
const newTorState = !isTorEnabled();
|
|
14766
|
+
setTorEnabled(newTorState);
|
|
14767
|
+
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");
|
|
14768
|
+
},
|
|
14769
|
+
[UI_COMMANDS.AUTO]: () => {
|
|
14770
|
+
ctx.setAutoApproveMode((prev) => {
|
|
14771
|
+
const newVal = !prev;
|
|
14772
|
+
ctx.agent.setAutoApprove(newVal);
|
|
14773
|
+
ctx.addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
|
|
14774
|
+
return newVal;
|
|
14775
|
+
});
|
|
14776
|
+
}
|
|
14777
|
+
});
|
|
14778
|
+
|
|
14779
|
+
// src/platform/tui/hooks/commands/index.ts
|
|
14780
|
+
var useCommands = (props) => {
|
|
14781
|
+
const ctx = {
|
|
14782
|
+
agent: props.agent,
|
|
14783
|
+
addMessage: props.addMessage,
|
|
14784
|
+
setMessages: props.setMessages,
|
|
14785
|
+
executeTask: props.executeTask,
|
|
14786
|
+
refreshStats: props.refreshStats,
|
|
14787
|
+
setAutoApproveMode: props.setAutoApproveMode,
|
|
14788
|
+
handleExit: props.handleExit,
|
|
14789
|
+
isProcessingRef: props.isProcessingRef,
|
|
14790
|
+
autoApproveModeRef: props.autoApproveModeRef
|
|
14791
|
+
};
|
|
14792
|
+
const handlers = {
|
|
14793
|
+
...createSessionCommands(ctx),
|
|
14794
|
+
...createTargetCommands(ctx),
|
|
14795
|
+
...createDisplayCommands(ctx),
|
|
14796
|
+
...createToggleCommands(ctx)
|
|
14797
|
+
};
|
|
14798
|
+
const handleCommand = useCallback3(async (cmd, args) => {
|
|
14799
|
+
const handler = handlers[cmd];
|
|
14800
|
+
if (handler) {
|
|
14801
|
+
await handler(args);
|
|
14802
|
+
} else {
|
|
14803
|
+
ctx.addMessage("error", `Unknown command: /${cmd}`);
|
|
14804
|
+
}
|
|
14805
|
+
}, [handlers, ctx]);
|
|
14806
|
+
return { handleCommand };
|
|
14807
|
+
};
|
|
14808
|
+
|
|
14809
|
+
// src/platform/tui/hooks/useKeyboardShortcuts.ts
|
|
14810
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
14811
|
+
import { useInput } from "ink";
|
|
14812
|
+
var useKeyboardShortcuts = ({
|
|
14813
|
+
addMessage,
|
|
14814
|
+
handleExit,
|
|
14815
|
+
abort,
|
|
14816
|
+
cancelInputRequest,
|
|
14817
|
+
isProcessingRef,
|
|
14818
|
+
inputRequestRef
|
|
14819
|
+
}) => {
|
|
14820
|
+
const ctrlCTimerRef = useRef4(null);
|
|
14821
|
+
const ctrlCPressedRef = useRef4(false);
|
|
14822
|
+
const handleCtrlC = useCallback4(() => {
|
|
14823
|
+
if (ctrlCPressedRef.current) {
|
|
14824
|
+
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14825
|
+
handleExit();
|
|
14826
|
+
return;
|
|
14827
|
+
}
|
|
14828
|
+
ctrlCPressedRef.current = true;
|
|
14829
|
+
addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
|
|
14830
|
+
if (isProcessingRef.current) abort();
|
|
14831
|
+
ctrlCTimerRef.current = setTimeout(() => {
|
|
14832
|
+
ctrlCPressedRef.current = false;
|
|
14833
|
+
ctrlCTimerRef.current = null;
|
|
14834
|
+
}, DISPLAY_LIMITS.EXIT_DELAY);
|
|
14835
|
+
}, [handleExit, addMessage, abort, isProcessingRef]);
|
|
14836
|
+
useEffect3(() => {
|
|
14837
|
+
return () => {
|
|
14838
|
+
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
14839
|
+
};
|
|
14840
|
+
}, []);
|
|
14841
|
+
useInput(useCallback4((ch, key) => {
|
|
14842
|
+
if (key.escape) {
|
|
14843
|
+
if (inputRequestRef.current.status === "active") cancelInputRequest();
|
|
14844
|
+
else if (isProcessingRef.current) abort();
|
|
14845
|
+
}
|
|
14846
|
+
if (key.ctrl && ch === "c") handleCtrlC();
|
|
14847
|
+
}, [cancelInputRequest, abort, handleCtrlC, isProcessingRef, inputRequestRef]));
|
|
14848
|
+
useEffect3(() => {
|
|
14849
|
+
const onSignal = () => handleCtrlC();
|
|
14850
|
+
process.on("SIGINT", onSignal);
|
|
14851
|
+
process.on("SIGTERM", onSignal);
|
|
14852
|
+
return () => {
|
|
14853
|
+
process.off("SIGINT", onSignal);
|
|
14854
|
+
process.off("SIGTERM", onSignal);
|
|
14855
|
+
};
|
|
14856
|
+
}, [handleCtrlC]);
|
|
14857
|
+
return { handleCtrlC };
|
|
14858
|
+
};
|
|
14859
|
+
|
|
14078
14860
|
// src/platform/tui/components/MessageList.tsx
|
|
14079
14861
|
import { memo } from "react";
|
|
14080
14862
|
import { Box as Box2, Text as Text2, Static } from "ink";
|
|
@@ -14324,24 +15106,39 @@ var MessageList = memo(({ messages }) => {
|
|
|
14324
15106
|
});
|
|
14325
15107
|
|
|
14326
15108
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
14327
|
-
import { memo as memo3, useEffect as
|
|
15109
|
+
import { memo as memo3, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
14328
15110
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
14329
15111
|
|
|
14330
15112
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
14331
|
-
import { useState as useState3, useEffect as
|
|
15113
|
+
import { useState as useState3, useEffect as useEffect4, memo as memo2 } from "react";
|
|
14332
15114
|
import { Text as Text3 } from "ink";
|
|
14333
15115
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
14334
|
-
var FRAMES = [
|
|
14335
|
-
|
|
15116
|
+
var FRAMES = [
|
|
15117
|
+
"\xB7",
|
|
15118
|
+
"\u2726",
|
|
15119
|
+
"\u2727",
|
|
15120
|
+
"\u2736",
|
|
15121
|
+
"\u2737",
|
|
15122
|
+
"\u2738",
|
|
15123
|
+
"\u2739",
|
|
15124
|
+
"\u273A",
|
|
15125
|
+
"\u2739",
|
|
15126
|
+
"\u2738",
|
|
15127
|
+
"\u2737",
|
|
15128
|
+
"\u2736",
|
|
15129
|
+
"\u2727",
|
|
15130
|
+
"\u2726"
|
|
15131
|
+
];
|
|
15132
|
+
var INTERVAL = 100;
|
|
14336
15133
|
var MusicSpinner = memo2(({ color }) => {
|
|
14337
15134
|
const [index, setIndex] = useState3(0);
|
|
14338
|
-
|
|
15135
|
+
useEffect4(() => {
|
|
14339
15136
|
const timer = setInterval(() => {
|
|
14340
15137
|
setIndex((i) => (i + 1) % FRAMES.length);
|
|
14341
15138
|
}, INTERVAL);
|
|
14342
15139
|
return () => clearInterval(timer);
|
|
14343
15140
|
}, []);
|
|
14344
|
-
return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
|
|
15141
|
+
return /* @__PURE__ */ jsx3(Text3, { color: color || "yellow", children: FRAMES[index] });
|
|
14345
15142
|
});
|
|
14346
15143
|
|
|
14347
15144
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
@@ -14356,9 +15153,9 @@ var StatusDisplay = memo3(({
|
|
|
14356
15153
|
return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
|
|
14357
15154
|
};
|
|
14358
15155
|
const [statusElapsed, setStatusElapsed] = useState4(0);
|
|
14359
|
-
const statusTimerRef =
|
|
14360
|
-
const statusStartRef =
|
|
14361
|
-
|
|
15156
|
+
const statusTimerRef = useRef5(null);
|
|
15157
|
+
const statusStartRef = useRef5(Date.now());
|
|
15158
|
+
useEffect5(() => {
|
|
14362
15159
|
if (statusTimerRef.current) clearInterval(statusTimerRef.current);
|
|
14363
15160
|
if (isProcessing && currentStatus) {
|
|
14364
15161
|
statusStartRef.current = Date.now();
|
|
@@ -14419,8 +15216,8 @@ var StatusDisplay = memo3(({
|
|
|
14419
15216
|
});
|
|
14420
15217
|
|
|
14421
15218
|
// src/platform/tui/components/ChatInput.tsx
|
|
14422
|
-
import { useMemo, useCallback as
|
|
14423
|
-
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
15219
|
+
import { useMemo, useCallback as useCallback5, useRef as useRef6, memo as memo4, useState as useState5 } from "react";
|
|
15220
|
+
import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
14424
15221
|
import TextInput from "ink-text-input";
|
|
14425
15222
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
14426
15223
|
var MAX_SUGGESTIONS = 6;
|
|
@@ -14442,18 +15239,18 @@ var ChatInput = memo4(({
|
|
|
14442
15239
|
return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
|
|
14443
15240
|
}, [isSlashMode, partialCmd, hasArgs]);
|
|
14444
15241
|
const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
|
|
14445
|
-
const suggestionsRef =
|
|
15242
|
+
const suggestionsRef = useRef6(suggestions);
|
|
14446
15243
|
suggestionsRef.current = suggestions;
|
|
14447
|
-
const isSlashModeRef =
|
|
15244
|
+
const isSlashModeRef = useRef6(isSlashMode);
|
|
14448
15245
|
isSlashModeRef.current = isSlashMode;
|
|
14449
|
-
const hasArgsRef =
|
|
15246
|
+
const hasArgsRef = useRef6(hasArgs);
|
|
14450
15247
|
hasArgsRef.current = hasArgs;
|
|
14451
|
-
const inputRequestRef =
|
|
15248
|
+
const inputRequestRef = useRef6(inputRequest);
|
|
14452
15249
|
inputRequestRef.current = inputRequest;
|
|
14453
|
-
const onChangeRef =
|
|
15250
|
+
const onChangeRef = useRef6(onChange);
|
|
14454
15251
|
onChangeRef.current = onChange;
|
|
14455
15252
|
const [inputKey, setInputKey] = useState5(0);
|
|
14456
|
-
|
|
15253
|
+
useInput2(useCallback5((_input, key) => {
|
|
14457
15254
|
if (inputRequestRef.current.status === "active") return;
|
|
14458
15255
|
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
14459
15256
|
const best = suggestionsRef.current[0];
|
|
@@ -14504,7 +15301,7 @@ var ChatInput = memo4(({
|
|
|
14504
15301
|
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
14505
15302
|
/* @__PURE__ */ jsxs4(Text5, { color: THEME.yellow, children: [
|
|
14506
15303
|
"\u25B8 ",
|
|
14507
|
-
inputRequest.prompt,
|
|
15304
|
+
inputRequest.prompt && inputRequest.prompt.length > 40 ? inputRequest.prompt.slice(0, 40) + "..." : inputRequest.prompt,
|
|
14508
15305
|
" "
|
|
14509
15306
|
] }),
|
|
14510
15307
|
/* @__PURE__ */ jsx5(
|
|
@@ -14614,13 +15411,13 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14614
15411
|
addMessage,
|
|
14615
15412
|
refreshStats
|
|
14616
15413
|
} = useAgent(autoApproveMode, target);
|
|
14617
|
-
const isProcessingRef =
|
|
15414
|
+
const isProcessingRef = useRef7(isProcessing);
|
|
14618
15415
|
isProcessingRef.current = isProcessing;
|
|
14619
|
-
const autoApproveModeRef =
|
|
15416
|
+
const autoApproveModeRef = useRef7(autoApproveMode);
|
|
14620
15417
|
autoApproveModeRef.current = autoApproveMode;
|
|
14621
|
-
const inputRequestRef =
|
|
15418
|
+
const inputRequestRef = useRef7(inputRequest);
|
|
14622
15419
|
inputRequestRef.current = inputRequest;
|
|
14623
|
-
const handleExit =
|
|
15420
|
+
const handleExit = useCallback6(() => {
|
|
14624
15421
|
const ir = inputRequestRef.current;
|
|
14625
15422
|
if (ir.status === "active") {
|
|
14626
15423
|
ir.resolve(null);
|
|
@@ -14631,166 +15428,18 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
14631
15428
|
exit();
|
|
14632
15429
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
14633
15430
|
}, [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) => {
|
|
15431
|
+
const { handleCommand } = useCommands({
|
|
15432
|
+
agent,
|
|
15433
|
+
addMessage,
|
|
15434
|
+
setMessages,
|
|
15435
|
+
executeTask,
|
|
15436
|
+
refreshStats,
|
|
15437
|
+
setAutoApproveMode,
|
|
15438
|
+
handleExit,
|
|
15439
|
+
isProcessingRef,
|
|
15440
|
+
autoApproveModeRef
|
|
15441
|
+
});
|
|
15442
|
+
const handleSubmit = useCallback6(async (value) => {
|
|
14794
15443
|
const trimmed = value.trim();
|
|
14795
15444
|
if (!trimmed) return;
|
|
14796
15445
|
setInput("");
|
|
@@ -14805,7 +15454,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14805
15454
|
await executeTask(trimmed);
|
|
14806
15455
|
}
|
|
14807
15456
|
}, [agent, addMessage, executeTask, handleCommand]);
|
|
14808
|
-
const handleSecretSubmit =
|
|
15457
|
+
const handleSecretSubmit = useCallback6((value) => {
|
|
14809
15458
|
const ir = inputRequestRef.current;
|
|
14810
15459
|
if (ir.status !== "active") return;
|
|
14811
15460
|
const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
|
|
@@ -14815,43 +15464,14 @@ ${procData.stdout || "(no output)"}
|
|
|
14815
15464
|
setInputRequest({ status: "inactive" });
|
|
14816
15465
|
setSecretInput("");
|
|
14817
15466
|
}, [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]);
|
|
15467
|
+
useKeyboardShortcuts({
|
|
15468
|
+
addMessage,
|
|
15469
|
+
handleExit,
|
|
15470
|
+
abort,
|
|
15471
|
+
cancelInputRequest,
|
|
15472
|
+
isProcessingRef,
|
|
15473
|
+
inputRequestRef
|
|
15474
|
+
});
|
|
14855
15475
|
const isSlashMode = input.startsWith("/");
|
|
14856
15476
|
const partialCmd = isSlashMode ? input.slice(1).split(" ")[0] : "";
|
|
14857
15477
|
const hasArgs = isSlashMode && input.includes(" ");
|