pentesting 0.73.8 → 0.73.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -7
- package/dist/{agent-tool-Y6CG5KRL.js → agent-tool-K3ETJX2V.js} +3 -3
- package/dist/{chunk-DN7INJ7C.js → chunk-OP3YVCKB.js} +84 -990
- package/dist/{chunk-KAUE3MSR.js → chunk-S5ZMXFHR.js} +18 -0
- package/dist/{chunk-26G2YIOA.js → chunk-TFYJWIQF.js} +1527 -3
- package/dist/main.js +623 -37
- package/dist/{persistence-SNX7ZQU5.js → persistence-TSBV5F6R.js} +2 -2
- package/dist/{process-registry-7XV46TDC.js → process-registry-4Y3HB4YQ.js} +1 -1
- package/dist/prompts/llm/input-processor-system.md +9 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
APPROVAL_STATUSES,
|
|
5
5
|
ATTACK_TACTICS,
|
|
6
6
|
ATTACK_VALUE_RANK,
|
|
7
|
+
AttackGraph,
|
|
7
8
|
CATEGORY_APPROVAL,
|
|
8
9
|
CONFIDENCE_THRESHOLDS,
|
|
9
10
|
DANGER_LEVELS,
|
|
@@ -41,6 +42,9 @@ import {
|
|
|
41
42
|
WORKSPACE,
|
|
42
43
|
WORK_DIR,
|
|
43
44
|
WorkingMemory,
|
|
45
|
+
buildCampaignMemorySummary,
|
|
46
|
+
buildMemoryBranchIndex,
|
|
47
|
+
buildSnapshotResumeHints,
|
|
44
48
|
cleanupAllProcesses,
|
|
45
49
|
createTempFile,
|
|
46
50
|
debugLog,
|
|
@@ -52,6 +56,7 @@ import {
|
|
|
52
56
|
getProcessOutput,
|
|
53
57
|
getTorBrowserArgs,
|
|
54
58
|
getUsedPorts,
|
|
59
|
+
isBranchMemoryEnabled,
|
|
55
60
|
listBackgroundProcesses,
|
|
56
61
|
llmNodeCooldownPolicy,
|
|
57
62
|
llmNodeOutputParsing,
|
|
@@ -64,7 +69,7 @@ import {
|
|
|
64
69
|
startBackgroundProcess,
|
|
65
70
|
stopBackgroundProcess,
|
|
66
71
|
writeFileContent
|
|
67
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-TFYJWIQF.js";
|
|
68
73
|
import {
|
|
69
74
|
DETECTION_PATTERNS,
|
|
70
75
|
HEALTH_CONFIG,
|
|
@@ -78,7 +83,7 @@ import {
|
|
|
78
83
|
__require,
|
|
79
84
|
getProcessEventLog,
|
|
80
85
|
logEvent
|
|
81
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-S5ZMXFHR.js";
|
|
82
87
|
|
|
83
88
|
// src/shared/utils/config/env.ts
|
|
84
89
|
var ENV_KEYS = {
|
|
@@ -2458,7 +2463,9 @@ function validateFinding(evidence) {
|
|
|
2458
2463
|
}
|
|
2459
2464
|
|
|
2460
2465
|
// src/agents/tool-executor/result-handler/journal.ts
|
|
2466
|
+
var TOOL_SIGNAL_OUTPUT_LIMIT = 180;
|
|
2461
2467
|
function recordJournalMemo(call, result, contextOutputForLLM, digestResult, turnState, state) {
|
|
2468
|
+
state.lastToolSignal = buildLatestToolSignal(call, result, digestResult);
|
|
2462
2469
|
turnState.toolJournal.push({
|
|
2463
2470
|
name: call.name,
|
|
2464
2471
|
inputSummary: JSON.stringify(call.input),
|
|
@@ -2534,6 +2541,30 @@ function recordJournalMemo(call, result, contextOutputForLLM, digestResult, turn
|
|
|
2534
2541
|
state.setPhase(PHASES.VULN_ANALYSIS);
|
|
2535
2542
|
}
|
|
2536
2543
|
}
|
|
2544
|
+
function buildLatestToolSignal(call, result, digestResult) {
|
|
2545
|
+
const input = JSON.stringify(call.input);
|
|
2546
|
+
const outputPreview = summarizeToolSignalField(result.output, TOOL_SIGNAL_OUTPUT_LIMIT);
|
|
2547
|
+
const keyFindings = digestResult?.memo?.keyFindings?.slice(0, 2).join("; ") ?? "";
|
|
2548
|
+
const nextSteps = digestResult?.memo?.nextSteps?.slice(0, 2).join("; ") ?? "";
|
|
2549
|
+
return [
|
|
2550
|
+
`tool=${call.name}`,
|
|
2551
|
+
`success=${result.success ? "true" : "false"}`,
|
|
2552
|
+
input ? `input=${input}` : "",
|
|
2553
|
+
outputPreview ? `output=${outputPreview}` : "",
|
|
2554
|
+
keyFindings ? `findings=${keyFindings}` : "",
|
|
2555
|
+
nextSteps ? `next=${nextSteps}` : ""
|
|
2556
|
+
].filter(Boolean).join(" | ");
|
|
2557
|
+
}
|
|
2558
|
+
function summarizeToolSignalField(value, limit) {
|
|
2559
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
2560
|
+
if (!normalized) {
|
|
2561
|
+
return "";
|
|
2562
|
+
}
|
|
2563
|
+
if (normalized.length <= limit) {
|
|
2564
|
+
return normalized;
|
|
2565
|
+
}
|
|
2566
|
+
return `${normalized.slice(0, limit - 3)}...`;
|
|
2567
|
+
}
|
|
2537
2568
|
|
|
2538
2569
|
// src/agents/tool-executor/parallel-executor.ts
|
|
2539
2570
|
async function processToolCalls(toolCalls, deps, progress) {
|
|
@@ -6701,989 +6732,6 @@ function createTriager(llm) {
|
|
|
6701
6732
|
return new Triager(llm);
|
|
6702
6733
|
}
|
|
6703
6734
|
|
|
6704
|
-
// src/shared/utils/attack-graph/types.ts
|
|
6705
|
-
var GRAPH_STATUS = {
|
|
6706
|
-
NEEDS_SEARCH: "needs_search",
|
|
6707
|
-
POTENTIAL: "potential"
|
|
6708
|
-
};
|
|
6709
|
-
var GRAPH_LIMITS = {
|
|
6710
|
-
/** Maximum recommended attack chains to return */
|
|
6711
|
-
MAX_CHAINS: 10,
|
|
6712
|
-
/** Maximum chains to show in prompt */
|
|
6713
|
-
PROMPT_CHAINS: 5,
|
|
6714
|
-
/** Maximum unexploited vulns to show in prompt */
|
|
6715
|
-
PROMPT_VULNS: 5,
|
|
6716
|
-
/** Maximum failed paths to show in prompt */
|
|
6717
|
-
PROMPT_FAILED: 5,
|
|
6718
|
-
/** Maximum DFS depth for chain discovery */
|
|
6719
|
-
MAX_CHAIN_DEPTH: 6,
|
|
6720
|
-
/** Maximum nodes to display in ASCII graph */
|
|
6721
|
-
ASCII_MAX_NODES: 30
|
|
6722
|
-
};
|
|
6723
|
-
var NODE_TYPE = {
|
|
6724
|
-
HOST: "host",
|
|
6725
|
-
SERVICE: "service",
|
|
6726
|
-
CREDENTIAL: "credential",
|
|
6727
|
-
VULNERABILITY: "vulnerability",
|
|
6728
|
-
ACCESS: "access",
|
|
6729
|
-
LOOT: "loot",
|
|
6730
|
-
OSINT: "osint",
|
|
6731
|
-
// ─── Active Directory Node Types (2차 — Hard/Insane support) ───
|
|
6732
|
-
/** AD domain object (e.g. DOMAIN.LOCAL) */
|
|
6733
|
-
DOMAIN: "domain",
|
|
6734
|
-
/** AD user or computer account */
|
|
6735
|
-
USER_ACCOUNT: "user-account",
|
|
6736
|
-
/** Group Policy Object — may grant code exec if writable */
|
|
6737
|
-
GPO: "gpo",
|
|
6738
|
-
/** ACL entry — WriteDACL/GenericAll/etc. on another object */
|
|
6739
|
-
ACL: "acl",
|
|
6740
|
-
/** ADCS certificate template — ESC1-13 attack surface */
|
|
6741
|
-
CERTIFICATE_TEMPLATE: "certificate-template"
|
|
6742
|
-
};
|
|
6743
|
-
var NODE_STATUS = {
|
|
6744
|
-
DISCOVERED: "discovered",
|
|
6745
|
-
ATTEMPTED: "attempted",
|
|
6746
|
-
SUCCEEDED: "succeeded",
|
|
6747
|
-
FAILED: "failed"
|
|
6748
|
-
};
|
|
6749
|
-
var EDGE_STATUS = {
|
|
6750
|
-
UNTESTED: "untested",
|
|
6751
|
-
TESTING: "testing",
|
|
6752
|
-
SUCCEEDED: "succeeded",
|
|
6753
|
-
FAILED: "failed"
|
|
6754
|
-
};
|
|
6755
|
-
var IMPACT_WEIGHT = {
|
|
6756
|
-
critical: 4,
|
|
6757
|
-
high: 3,
|
|
6758
|
-
medium: 2,
|
|
6759
|
-
low: 1
|
|
6760
|
-
};
|
|
6761
|
-
|
|
6762
|
-
// src/shared/utils/attack-graph/node.ts
|
|
6763
|
-
function generateNodeId(type, label) {
|
|
6764
|
-
return `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
|
|
6765
|
-
}
|
|
6766
|
-
function createNode(type, label, data = {}, status = NODE_STATUS.DISCOVERED) {
|
|
6767
|
-
const id = generateNodeId(type, label);
|
|
6768
|
-
return {
|
|
6769
|
-
id,
|
|
6770
|
-
type,
|
|
6771
|
-
label,
|
|
6772
|
-
data,
|
|
6773
|
-
status,
|
|
6774
|
-
discoveredAt: Date.now()
|
|
6775
|
-
};
|
|
6776
|
-
}
|
|
6777
|
-
function canMarkAttempted(node) {
|
|
6778
|
-
return node.status === NODE_STATUS.DISCOVERED;
|
|
6779
|
-
}
|
|
6780
|
-
|
|
6781
|
-
// src/shared/utils/attack-graph/edge.ts
|
|
6782
|
-
function createEdge(from, to, relation, confidence = 0.5, status = EDGE_STATUS.UNTESTED) {
|
|
6783
|
-
return {
|
|
6784
|
-
from,
|
|
6785
|
-
to,
|
|
6786
|
-
relation,
|
|
6787
|
-
confidence,
|
|
6788
|
-
status
|
|
6789
|
-
};
|
|
6790
|
-
}
|
|
6791
|
-
function edgeExists(edges, fromId, toId, relation) {
|
|
6792
|
-
return edges.some(
|
|
6793
|
-
(e) => e.from === fromId && e.to === toId && e.relation === relation
|
|
6794
|
-
);
|
|
6795
|
-
}
|
|
6796
|
-
function getIncomingEdges(edges, nodeId) {
|
|
6797
|
-
return edges.filter((e) => e.to === nodeId);
|
|
6798
|
-
}
|
|
6799
|
-
function markEdgeSucceeded(edge) {
|
|
6800
|
-
return {
|
|
6801
|
-
...edge,
|
|
6802
|
-
status: EDGE_STATUS.SUCCEEDED
|
|
6803
|
-
};
|
|
6804
|
-
}
|
|
6805
|
-
function markEdgeFailed(edge, reason) {
|
|
6806
|
-
return {
|
|
6807
|
-
...edge,
|
|
6808
|
-
status: EDGE_STATUS.FAILED,
|
|
6809
|
-
failReason: reason
|
|
6810
|
-
};
|
|
6811
|
-
}
|
|
6812
|
-
function isTestableEdge(edge) {
|
|
6813
|
-
return edge.status === EDGE_STATUS.UNTESTED || edge.status === EDGE_STATUS.TESTING;
|
|
6814
|
-
}
|
|
6815
|
-
|
|
6816
|
-
// src/shared/utils/attack-graph/factories/host-service.ts
|
|
6817
|
-
function createHostNode(ip, hostname) {
|
|
6818
|
-
return createNode(NODE_TYPE.HOST, ip, { ip, hostname });
|
|
6819
|
-
}
|
|
6820
|
-
function createServiceNode(host, port, service, version) {
|
|
6821
|
-
const hostId = generateNodeId(NODE_TYPE.HOST, host);
|
|
6822
|
-
const node = createNode(
|
|
6823
|
-
NODE_TYPE.SERVICE,
|
|
6824
|
-
`${host}:${port}`,
|
|
6825
|
-
{ host, port, service, version }
|
|
6826
|
-
);
|
|
6827
|
-
const hostEdge = createEdge(hostId, node.id, "has_service", 0.95);
|
|
6828
|
-
return { node, hostEdge };
|
|
6829
|
-
}
|
|
6830
|
-
function findTargetServices(nodes, target) {
|
|
6831
|
-
const results = [];
|
|
6832
|
-
for (const node of nodes.values()) {
|
|
6833
|
-
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
6834
|
-
results.push(node);
|
|
6835
|
-
}
|
|
6836
|
-
}
|
|
6837
|
-
return results;
|
|
6838
|
-
}
|
|
6839
|
-
|
|
6840
|
-
// src/shared/utils/attack-graph/factories/credential-exploit.ts
|
|
6841
|
-
var AUTH_SERVICES = [
|
|
6842
|
-
"ssh",
|
|
6843
|
-
"ftp",
|
|
6844
|
-
"rdp",
|
|
6845
|
-
"smb",
|
|
6846
|
-
"http",
|
|
6847
|
-
"mysql",
|
|
6848
|
-
"postgresql",
|
|
6849
|
-
"mssql",
|
|
6850
|
-
"winrm",
|
|
6851
|
-
"vnc",
|
|
6852
|
-
"telnet"
|
|
6853
|
-
];
|
|
6854
|
-
var HIGH_PRIVILEGE_LEVELS = ["root", "admin", "SYSTEM", "Administrator"];
|
|
6855
|
-
function createCredentialNode(username, password, source) {
|
|
6856
|
-
return createNode(
|
|
6857
|
-
NODE_TYPE.CREDENTIAL,
|
|
6858
|
-
`${username}:***`,
|
|
6859
|
-
{ username, password, source }
|
|
6860
|
-
);
|
|
6861
|
-
}
|
|
6862
|
-
function createCredentialSprayEdges(credId, nodes) {
|
|
6863
|
-
const edges = [];
|
|
6864
|
-
for (const [id, node] of nodes) {
|
|
6865
|
-
if (node.type === NODE_TYPE.SERVICE) {
|
|
6866
|
-
const svc = String(node.data.service || "");
|
|
6867
|
-
if (AUTH_SERVICES.some((s) => svc.includes(s))) {
|
|
6868
|
-
edges.push(createEdge(credId, id, "can_try_on", 0.6));
|
|
6869
|
-
}
|
|
6870
|
-
}
|
|
6871
|
-
}
|
|
6872
|
-
return edges;
|
|
6873
|
-
}
|
|
6874
|
-
function createVulnerabilityNode(title, target, severity, hasExploit = false) {
|
|
6875
|
-
return createNode(
|
|
6876
|
-
NODE_TYPE.VULNERABILITY,
|
|
6877
|
-
title,
|
|
6878
|
-
{ target, severity, hasExploit }
|
|
6879
|
-
);
|
|
6880
|
-
}
|
|
6881
|
-
function createVersionSearchNode(service, version) {
|
|
6882
|
-
return createNode(
|
|
6883
|
-
NODE_TYPE.VULNERABILITY,
|
|
6884
|
-
`CVE search: ${service} ${version}`,
|
|
6885
|
-
{ service, version, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
6886
|
-
);
|
|
6887
|
-
}
|
|
6888
|
-
function createAccessNode(host, level, via) {
|
|
6889
|
-
return createNode(
|
|
6890
|
-
NODE_TYPE.ACCESS,
|
|
6891
|
-
`${level}@${host}`,
|
|
6892
|
-
{ host, level, via }
|
|
6893
|
-
);
|
|
6894
|
-
}
|
|
6895
|
-
function createPotentialAccessNode(exploitTitle) {
|
|
6896
|
-
return createNode(
|
|
6897
|
-
NODE_TYPE.ACCESS,
|
|
6898
|
-
`shell via ${exploitTitle}`,
|
|
6899
|
-
{ via: exploitTitle, status: GRAPH_STATUS.POTENTIAL }
|
|
6900
|
-
);
|
|
6901
|
-
}
|
|
6902
|
-
function isHighPrivilege(level) {
|
|
6903
|
-
return HIGH_PRIVILEGE_LEVELS.includes(level);
|
|
6904
|
-
}
|
|
6905
|
-
|
|
6906
|
-
// src/shared/utils/attack-graph/factories/osint-loot.ts
|
|
6907
|
-
function createLootNode(host) {
|
|
6908
|
-
return createNode(
|
|
6909
|
-
NODE_TYPE.LOOT,
|
|
6910
|
-
`flags on ${host}`,
|
|
6911
|
-
{ host, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
6912
|
-
);
|
|
6913
|
-
}
|
|
6914
|
-
function createOSINTNode(category, detail, data = {}) {
|
|
6915
|
-
return createNode(
|
|
6916
|
-
NODE_TYPE.OSINT,
|
|
6917
|
-
`${category}: ${detail}`,
|
|
6918
|
-
{ category, detail, ...data }
|
|
6919
|
-
);
|
|
6920
|
-
}
|
|
6921
|
-
function findOSINTRelatedNodes(nodes, detail) {
|
|
6922
|
-
const results = [];
|
|
6923
|
-
for (const [id, node] of nodes) {
|
|
6924
|
-
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
6925
|
-
const hostIp = String(node.data.ip || node.data.host || "");
|
|
6926
|
-
const hostname = String(node.data.hostname || "");
|
|
6927
|
-
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
6928
|
-
results.push({ id, node });
|
|
6929
|
-
}
|
|
6930
|
-
}
|
|
6931
|
-
}
|
|
6932
|
-
return results;
|
|
6933
|
-
}
|
|
6934
|
-
function formatFailedPath(from, to, reason) {
|
|
6935
|
-
return `${from} \u2192 ${to}${reason ? ` (${reason})` : ""}`;
|
|
6936
|
-
}
|
|
6937
|
-
function formatFailedNode(label, reason) {
|
|
6938
|
-
return `${label}${reason ? ` (${reason})` : ""}`;
|
|
6939
|
-
}
|
|
6940
|
-
|
|
6941
|
-
// src/shared/utils/attack-graph/chain-discovery.ts
|
|
6942
|
-
var IMPACT_WEIGHT_MAP = {
|
|
6943
|
-
[NODE_TYPE.LOOT]: SEVERITIES.CRITICAL,
|
|
6944
|
-
[NODE_TYPE.DOMAIN]: SEVERITIES.CRITICAL,
|
|
6945
|
-
[NODE_TYPE.CERTIFICATE_TEMPLATE]: SEVERITIES.HIGH
|
|
6946
|
-
};
|
|
6947
|
-
function recommendChains(nodes, edges) {
|
|
6948
|
-
const chains = [];
|
|
6949
|
-
const visited = /* @__PURE__ */ new Set();
|
|
6950
|
-
const goalTypes = [
|
|
6951
|
-
NODE_TYPE.ACCESS,
|
|
6952
|
-
NODE_TYPE.LOOT,
|
|
6953
|
-
NODE_TYPE.DOMAIN,
|
|
6954
|
-
NODE_TYPE.CERTIFICATE_TEMPLATE
|
|
6955
|
-
];
|
|
6956
|
-
const entryNodeTypes = [
|
|
6957
|
-
NODE_TYPE.HOST,
|
|
6958
|
-
NODE_TYPE.SERVICE,
|
|
6959
|
-
NODE_TYPE.CREDENTIAL,
|
|
6960
|
-
NODE_TYPE.OSINT
|
|
6961
|
-
];
|
|
6962
|
-
const entryNodes = Array.from(nodes.values()).filter(
|
|
6963
|
-
(n) => entryNodeTypes.includes(n.type)
|
|
6964
|
-
);
|
|
6965
|
-
const ctx = { nodes, edges, goalTypes, results: chains };
|
|
6966
|
-
for (const entry of entryNodes) {
|
|
6967
|
-
visited.clear();
|
|
6968
|
-
dfsChains(entry.id, [], [], visited, ctx);
|
|
6969
|
-
}
|
|
6970
|
-
return prioritizeChains(chains);
|
|
6971
|
-
}
|
|
6972
|
-
function prioritizeChains(chains) {
|
|
6973
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6974
|
-
const unique = chains.filter((c) => {
|
|
6975
|
-
if (seen.has(c.description)) return false;
|
|
6976
|
-
seen.add(c.description);
|
|
6977
|
-
return true;
|
|
6978
|
-
});
|
|
6979
|
-
unique.sort(
|
|
6980
|
-
(a, b) => b.probability * (IMPACT_WEIGHT[b.impact] || 1) - a.probability * (IMPACT_WEIGHT[a.impact] || 1)
|
|
6981
|
-
);
|
|
6982
|
-
return unique.slice(0, GRAPH_LIMITS.MAX_CHAINS);
|
|
6983
|
-
}
|
|
6984
|
-
function dfsChains(nodeId, path2, pathEdges, visited, ctx) {
|
|
6985
|
-
const { nodes, edges, goalTypes, results } = ctx;
|
|
6986
|
-
const node = nodes.get(nodeId);
|
|
6987
|
-
if (!node || visited.has(nodeId) || path2.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH || node.status === NODE_STATUS.FAILED) {
|
|
6988
|
-
return;
|
|
6989
|
-
}
|
|
6990
|
-
visited.add(nodeId);
|
|
6991
|
-
path2.push(node);
|
|
6992
|
-
if (isGoalReached(node, goalTypes, path2)) {
|
|
6993
|
-
results.push(buildChain(path2, pathEdges, node));
|
|
6994
|
-
}
|
|
6995
|
-
const outEdges = edges.filter((e) => e.from === nodeId && e.status !== EDGE_STATUS.FAILED);
|
|
6996
|
-
for (const edge of outEdges) {
|
|
6997
|
-
dfsChains(edge.to, path2, [...pathEdges, edge], visited, ctx);
|
|
6998
|
-
}
|
|
6999
|
-
path2.pop();
|
|
7000
|
-
visited.delete(nodeId);
|
|
7001
|
-
}
|
|
7002
|
-
function isGoalReached(node, goalTypes, path2) {
|
|
7003
|
-
return goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path2.length > 1;
|
|
7004
|
-
}
|
|
7005
|
-
function buildChain(path2, edges, targetNode) {
|
|
7006
|
-
return {
|
|
7007
|
-
steps: [...path2],
|
|
7008
|
-
edges: [...edges],
|
|
7009
|
-
description: path2.map((n) => n.label).join(" \u2192 "),
|
|
7010
|
-
probability: edges.reduce((acc, e) => acc * e.confidence, 1),
|
|
7011
|
-
impact: estimateImpact(targetNode),
|
|
7012
|
-
length: path2.length
|
|
7013
|
-
};
|
|
7014
|
-
}
|
|
7015
|
-
function estimateImpact(node) {
|
|
7016
|
-
if (IMPACT_WEIGHT_MAP[node.type]) return IMPACT_WEIGHT_MAP[node.type];
|
|
7017
|
-
if (node.type === NODE_TYPE.ACCESS) {
|
|
7018
|
-
const level = String(node.data.level || "");
|
|
7019
|
-
return ["root", "SYSTEM", "Administrator", "admin"].includes(level) ? SEVERITIES.CRITICAL : SEVERITIES.HIGH;
|
|
7020
|
-
}
|
|
7021
|
-
if (node.type === NODE_TYPE.VULNERABILITY) {
|
|
7022
|
-
const sev = String(node.data.severity || "").toLowerCase();
|
|
7023
|
-
if (sev === "critical") return SEVERITIES.CRITICAL;
|
|
7024
|
-
if (sev === "high") return SEVERITIES.HIGH;
|
|
7025
|
-
if (sev === "medium") return SEVERITIES.MEDIUM;
|
|
7026
|
-
}
|
|
7027
|
-
return SEVERITIES.LOW;
|
|
7028
|
-
}
|
|
7029
|
-
function extractSuccessfulChain(nodes, edges) {
|
|
7030
|
-
const succeededNodes = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
7031
|
-
if (succeededNodes.length < 2) return null;
|
|
7032
|
-
const succeededEdges = edges.filter((e) => e.status === EDGE_STATUS.SUCCEEDED);
|
|
7033
|
-
const { chainLabels, toolsUsed } = traceSucceededPath(succeededNodes, succeededEdges, nodes);
|
|
7034
|
-
if (chainLabels.length < 2) return null;
|
|
7035
|
-
const serviceProfile = succeededNodes.length > 0 ? succeededNodes.map((n) => abstractifyLabel(n.label)).filter(Boolean).join(" + ") : "unknown";
|
|
7036
|
-
return {
|
|
7037
|
-
serviceProfile,
|
|
7038
|
-
chainSummary: chainLabels.join(" \u2192 "),
|
|
7039
|
-
toolsUsed: [...new Set(toolsUsed.filter(Boolean))]
|
|
7040
|
-
};
|
|
7041
|
-
}
|
|
7042
|
-
function traceSucceededPath(succeededNodes, succeededEdges, nodes) {
|
|
7043
|
-
const labels = [];
|
|
7044
|
-
const tools = [];
|
|
7045
|
-
const visited = /* @__PURE__ */ new Set();
|
|
7046
|
-
const incomingTargets = new Set(succeededEdges.map((e) => e.to));
|
|
7047
|
-
const entryNodes = succeededNodes.filter((n) => !incomingTargets.has(n.id));
|
|
7048
|
-
const queue = entryNodes.length > 0 ? [entryNodes[0]] : [succeededNodes[0]];
|
|
7049
|
-
while (queue.length > 0) {
|
|
7050
|
-
const current = queue.shift();
|
|
7051
|
-
if (visited.has(current.id)) continue;
|
|
7052
|
-
visited.add(current.id);
|
|
7053
|
-
labels.push(abstractifyLabel(current.label));
|
|
7054
|
-
if (current.technique) tools.push(current.technique.split(" ")[0]);
|
|
7055
|
-
const nextEdges = succeededEdges.filter((e) => e.from === current.id);
|
|
7056
|
-
for (const edge of nextEdges) {
|
|
7057
|
-
const nextNode = nodes.get(edge.to);
|
|
7058
|
-
if (nextNode?.status === NODE_STATUS.SUCCEEDED) {
|
|
7059
|
-
if (edge.action) tools.push(edge.action.split(" ")[0]);
|
|
7060
|
-
queue.push(nextNode);
|
|
7061
|
-
}
|
|
7062
|
-
}
|
|
7063
|
-
}
|
|
7064
|
-
return { chainLabels: labels, toolsUsed: tools };
|
|
7065
|
-
}
|
|
7066
|
-
function abstractifyLabel(label) {
|
|
7067
|
-
let result = label.replace(/^(host|service|vulnerability|access|loot|osint|credential|cve_search):/i, "").replace(/^https?:\/\/|^ftp:\/\//gi, "").replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?\b/g, "").replace(/:\d{2,5}\b/g, "").replace(/\b[0-9a-f]{16,}\b/gi, "").replace(/(\d+\.\d+)\.\d+/g, "$1.x").trim().replace(/\s+/g, " ");
|
|
7068
|
-
return result || label;
|
|
7069
|
-
}
|
|
7070
|
-
|
|
7071
|
-
// src/shared/utils/attack-graph/prompt-formatter.ts
|
|
7072
|
-
function formatGraphForPrompt(nodes, edges, failedPaths) {
|
|
7073
|
-
if (nodes.size === 0) return "";
|
|
7074
|
-
const lines = ["<attack-graph>"];
|
|
7075
|
-
const nodesByType = {};
|
|
7076
|
-
const nodesByStatus = {};
|
|
7077
|
-
for (const node of nodes.values()) {
|
|
7078
|
-
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
7079
|
-
nodesByStatus[node.status] = (nodesByStatus[node.status] || 0) + 1;
|
|
7080
|
-
}
|
|
7081
|
-
lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
|
|
7082
|
-
lines.push(`Status: ${Object.entries(nodesByStatus).map(([s, c]) => `${c} ${s}`).join(", ")}`);
|
|
7083
|
-
lines.push(`Edges: ${edges.length} (${edges.filter((e) => e.status === EDGE_STATUS.FAILED).length} failed)`);
|
|
7084
|
-
const chains = recommendChains(nodes, edges);
|
|
7085
|
-
if (chains.length > 0) {
|
|
7086
|
-
lines.push("");
|
|
7087
|
-
lines.push("RECOMMENDED ATTACK PATHS (try these):");
|
|
7088
|
-
for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
|
|
7089
|
-
const c = chains[i];
|
|
7090
|
-
const prob = (c.probability * 100).toFixed(0);
|
|
7091
|
-
lines.push(` PATH ${i + 1} [${c.impact.toUpperCase()} | ${prob}%]: ${c.description}`);
|
|
7092
|
-
}
|
|
7093
|
-
}
|
|
7094
|
-
if (failedPaths.length > 0) {
|
|
7095
|
-
lines.push("");
|
|
7096
|
-
lines.push("FAILED PATHS (DO NOT RETRY):");
|
|
7097
|
-
for (const fp of failedPaths.slice(-GRAPH_LIMITS.PROMPT_FAILED)) {
|
|
7098
|
-
lines.push(` \u2717 ${fp}`);
|
|
7099
|
-
}
|
|
7100
|
-
}
|
|
7101
|
-
const unexploitedVulns = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.VULNERABILITY && n.status !== NODE_STATUS.SUCCEEDED && n.status !== NODE_STATUS.FAILED);
|
|
7102
|
-
if (unexploitedVulns.length > 0) {
|
|
7103
|
-
lines.push("");
|
|
7104
|
-
lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
|
|
7105
|
-
for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
|
|
7106
|
-
const sev = v.data.severity || "unknown";
|
|
7107
|
-
const status = v.status === NODE_STATUS.ATTEMPTED ? " \u23F3 testing" : "";
|
|
7108
|
-
lines.push(` - ${v.label} (${sev})${status}`);
|
|
7109
|
-
}
|
|
7110
|
-
}
|
|
7111
|
-
const accessNodes = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.ACCESS && n.status === NODE_STATUS.SUCCEEDED);
|
|
7112
|
-
if (accessNodes.length > 0) {
|
|
7113
|
-
lines.push("");
|
|
7114
|
-
lines.push("ACHIEVED ACCESS:");
|
|
7115
|
-
for (const a of accessNodes) {
|
|
7116
|
-
lines.push(` \u2713 ${a.label}`);
|
|
7117
|
-
}
|
|
7118
|
-
}
|
|
7119
|
-
const credNodes = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.CREDENTIAL);
|
|
7120
|
-
if (credNodes.length > 0) {
|
|
7121
|
-
lines.push("");
|
|
7122
|
-
lines.push(`CREDENTIALS (${credNodes.length} \u2014 try on all services):`);
|
|
7123
|
-
for (const c of credNodes) {
|
|
7124
|
-
const source = c.data.source || "unknown";
|
|
7125
|
-
const sprayEdges = edges.filter((e) => e.from === c.id && e.relation === "can_try_on");
|
|
7126
|
-
const testedCount = sprayEdges.filter((e) => e.status !== EDGE_STATUS.UNTESTED).length;
|
|
7127
|
-
lines.push(` \u{1F511} ${c.label} (from: ${source}) [sprayed: ${testedCount}/${sprayEdges.length}]`);
|
|
7128
|
-
}
|
|
7129
|
-
}
|
|
7130
|
-
lines.push("</attack-graph>");
|
|
7131
|
-
return lines.join("\n");
|
|
7132
|
-
}
|
|
7133
|
-
|
|
7134
|
-
// src/shared/utils/attack-graph/formatters/icons.ts
|
|
7135
|
-
var TYPE_ICONS = {
|
|
7136
|
-
[NODE_TYPE.HOST]: "\u{1F5A5}",
|
|
7137
|
-
[NODE_TYPE.SERVICE]: "\u2699",
|
|
7138
|
-
[NODE_TYPE.VULNERABILITY]: "\u26A0",
|
|
7139
|
-
[NODE_TYPE.CREDENTIAL]: "\u{1F511}",
|
|
7140
|
-
[NODE_TYPE.ACCESS]: "\u{1F513}",
|
|
7141
|
-
[NODE_TYPE.LOOT]: "\u{1F3F4}",
|
|
7142
|
-
[NODE_TYPE.OSINT]: "\u{1F50D}",
|
|
7143
|
-
// AD node icons (2차 — Hard/Insane support)
|
|
7144
|
-
[NODE_TYPE.DOMAIN]: "\u{1F3F0}",
|
|
7145
|
-
[NODE_TYPE.USER_ACCOUNT]: "\u{1F464}",
|
|
7146
|
-
[NODE_TYPE.GPO]: "\u{1F4CB}",
|
|
7147
|
-
[NODE_TYPE.ACL]: "\u{1F510}",
|
|
7148
|
-
[NODE_TYPE.CERTIFICATE_TEMPLATE]: "\u{1F4DC}"
|
|
7149
|
-
};
|
|
7150
|
-
var STATUS_ICONS2 = {
|
|
7151
|
-
[NODE_STATUS.DISCOVERED]: "\u25CB",
|
|
7152
|
-
[NODE_STATUS.ATTEMPTED]: "\u25D0",
|
|
7153
|
-
[NODE_STATUS.SUCCEEDED]: "\u25CF",
|
|
7154
|
-
[NODE_STATUS.FAILED]: "\u2717"
|
|
7155
|
-
};
|
|
7156
|
-
|
|
7157
|
-
// src/shared/utils/attack-graph/formatters/stats.ts
|
|
7158
|
-
function getGraphStats(nodes, edges) {
|
|
7159
|
-
const succeeded = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED).length;
|
|
7160
|
-
const failed = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.FAILED).length;
|
|
7161
|
-
return {
|
|
7162
|
-
nodes: nodes.size,
|
|
7163
|
-
edges: edges.length,
|
|
7164
|
-
succeeded,
|
|
7165
|
-
failed,
|
|
7166
|
-
chains: recommendChains(nodes, edges).length
|
|
7167
|
-
};
|
|
7168
|
-
}
|
|
7169
|
-
|
|
7170
|
-
// src/shared/utils/attack-graph/formatters/graph-ascii.ts
|
|
7171
|
-
function formatGraphAsASCII(nodes, edges) {
|
|
7172
|
-
if (nodes.size === 0) return "(Empty attack graph \u2014 no discoveries yet)";
|
|
7173
|
-
const lines = [];
|
|
7174
|
-
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");
|
|
7175
|
-
const groups = {};
|
|
7176
|
-
for (const node of nodes.values()) {
|
|
7177
|
-
if (!groups[node.type]) groups[node.type] = [];
|
|
7178
|
-
groups[node.type].push(node);
|
|
7179
|
-
}
|
|
7180
|
-
const typeSummary = Object.entries(groups).map(([t, ns]) => `${TYPE_ICONS[t] || "\xB7"} ${ns.length}`).join(" ");
|
|
7181
|
-
lines.push(`\u2502 ${typeSummary}`);
|
|
7182
|
-
for (const [type, typeNodes] of Object.entries(groups)) {
|
|
7183
|
-
const icon = TYPE_ICONS[type] || "\xB7";
|
|
7184
|
-
lines.push(`\u2502`);
|
|
7185
|
-
lines.push(`\u2502 ${icon} ${type.toUpperCase()} (${typeNodes.length})`);
|
|
7186
|
-
for (const node of typeNodes) {
|
|
7187
|
-
const sIcon = STATUS_ICONS2[node.status] || "?";
|
|
7188
|
-
const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
|
|
7189
|
-
let detail = "";
|
|
7190
|
-
if (type === NODE_TYPE.CREDENTIAL) {
|
|
7191
|
-
const source = node.data.source ? ` (from: ${node.data.source})` : "";
|
|
7192
|
-
detail = source;
|
|
7193
|
-
} else if (type === NODE_TYPE.VULNERABILITY) {
|
|
7194
|
-
const sev = node.data.severity ? ` [${String(node.data.severity).toUpperCase()}]` : "";
|
|
7195
|
-
const exploit = node.data.hasExploit ? " [EXPLOIT]" : "";
|
|
7196
|
-
const target = node.data.target ? ` \u2192 ${node.data.target}` : "";
|
|
7197
|
-
detail = `${sev}${exploit}${target}`;
|
|
7198
|
-
} else if (type === NODE_TYPE.ACCESS) {
|
|
7199
|
-
const via = node.data.via ? ` via ${node.data.via}` : "";
|
|
7200
|
-
detail = via;
|
|
7201
|
-
} else if (type === NODE_TYPE.SERVICE) {
|
|
7202
|
-
const ver = node.data.version ? ` (${node.data.version})` : "";
|
|
7203
|
-
detail = ver;
|
|
7204
|
-
}
|
|
7205
|
-
const outEdges = edges.filter((e) => e.from === node.id);
|
|
7206
|
-
const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
|
|
7207
|
-
const target = nodes.get(e.to);
|
|
7208
|
-
const eName = target ? target.label : e.to;
|
|
7209
|
-
const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
|
|
7210
|
-
return `${eName}${eStatus}`;
|
|
7211
|
-
}).join(", ")}` : "";
|
|
7212
|
-
lines.push(`\u2502 ${sIcon} ${node.label}${detail}${fail}${edgeStr}`);
|
|
7213
|
-
}
|
|
7214
|
-
}
|
|
7215
|
-
const succeededNodes = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
7216
|
-
if (succeededNodes.length > 0) {
|
|
7217
|
-
lines.push(`\u2502`);
|
|
7218
|
-
lines.push(`\u251C\u2500\u2500\u2500 Exploited \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`);
|
|
7219
|
-
for (const n of succeededNodes) {
|
|
7220
|
-
const via = n.data.via ? ` via ${n.data.via}` : "";
|
|
7221
|
-
lines.push(`\u2502 \u25CF ${n.label}${via}`);
|
|
7222
|
-
}
|
|
7223
|
-
}
|
|
7224
|
-
const stats = getGraphStats(nodes, edges);
|
|
7225
|
-
lines.push(`\u2502`);
|
|
7226
|
-
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`);
|
|
7227
|
-
lines.push(`\u2502 Nodes: ${stats.nodes} | Edges: ${stats.edges} | Succeeded: ${stats.succeeded} | Failed: ${stats.failed} | Chains: ${stats.chains}`);
|
|
7228
|
-
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`);
|
|
7229
|
-
return lines.join("\n");
|
|
7230
|
-
}
|
|
7231
|
-
|
|
7232
|
-
// src/shared/utils/attack-graph/formatters/paths-list.ts
|
|
7233
|
-
function formatPathsList(nodes, edges, failedPaths) {
|
|
7234
|
-
const chains = recommendChains(nodes, edges);
|
|
7235
|
-
if (chains.length === 0) {
|
|
7236
|
-
if (nodes.size === 0) return "No attack graph data yet. Start reconnaissance first.";
|
|
7237
|
-
return "No viable attack paths found. All paths may be exhausted or failed.";
|
|
7238
|
-
}
|
|
7239
|
-
const lines = [];
|
|
7240
|
-
lines.push(`\u2500\u2500\u2500 ${chains.length} Attack Paths (sorted by priority) \u2500\u2500\u2500`);
|
|
7241
|
-
lines.push("");
|
|
7242
|
-
for (let i = 0; i < chains.length; i++) {
|
|
7243
|
-
const c = chains[i];
|
|
7244
|
-
const prob = (c.probability * 100).toFixed(0);
|
|
7245
|
-
const impactColors = {
|
|
7246
|
-
[SEVERITIES.CRITICAL]: "\u{1F534}",
|
|
7247
|
-
[SEVERITIES.HIGH]: "\u{1F7E0}",
|
|
7248
|
-
[SEVERITIES.MEDIUM]: "\u{1F7E1}",
|
|
7249
|
-
[SEVERITIES.LOW]: "\u{1F7E2}"
|
|
7250
|
-
};
|
|
7251
|
-
const impactIcon = impactColors[c.impact] || "\u26AA";
|
|
7252
|
-
lines.push(`${impactIcon} PATH ${i + 1} [${c.impact.toUpperCase()} | prob: ${prob}% | steps: ${c.length}]`);
|
|
7253
|
-
lines.push(` ${c.description}`);
|
|
7254
|
-
for (const edge of c.edges) {
|
|
7255
|
-
const statusTag = edge.status === EDGE_STATUS.UNTESTED ? " ?" : edge.status === EDGE_STATUS.TESTING ? " \u23F3" : edge.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : " \u2717";
|
|
7256
|
-
lines.push(` ${statusTag} ${edge.from.split(":").pop()} \u2014[${edge.relation}]\u2192 ${edge.to.split(":").pop()}`);
|
|
7257
|
-
}
|
|
7258
|
-
lines.push("");
|
|
7259
|
-
}
|
|
7260
|
-
if (failedPaths.length > 0) {
|
|
7261
|
-
lines.push(`\u2500\u2500\u2500 Failed Paths (${failedPaths.length}) \u2500\u2500\u2500`);
|
|
7262
|
-
for (const fp of failedPaths) {
|
|
7263
|
-
lines.push(` \u2717 ${fp}`);
|
|
7264
|
-
}
|
|
7265
|
-
}
|
|
7266
|
-
return lines.join("\n");
|
|
7267
|
-
}
|
|
7268
|
-
|
|
7269
|
-
// src/shared/utils/attack-graph/operations/infrastructure.ts
|
|
7270
|
-
function registerHost(graph, ip, hostname) {
|
|
7271
|
-
const node = createHostNode(ip, hostname);
|
|
7272
|
-
if (!graph.nodes.has(node.id)) {
|
|
7273
|
-
graph.nodes.set(node.id, node);
|
|
7274
|
-
}
|
|
7275
|
-
return node.id;
|
|
7276
|
-
}
|
|
7277
|
-
function registerService(graph, host, port, service, version) {
|
|
7278
|
-
const hostId = registerHost(graph, host);
|
|
7279
|
-
const { node, hostEdge } = createServiceNode(host, port, service, version);
|
|
7280
|
-
if (!graph.nodes.has(node.id)) {
|
|
7281
|
-
graph.nodes.set(node.id, node);
|
|
7282
|
-
}
|
|
7283
|
-
if (hostEdge && !edgeExists(graph.edges, hostEdge.from, hostEdge.to, hostEdge.relation)) {
|
|
7284
|
-
graph.edges.push(hostEdge);
|
|
7285
|
-
}
|
|
7286
|
-
if (version) {
|
|
7287
|
-
const vulnNode = createVersionSearchNode(service, version);
|
|
7288
|
-
if (!graph.nodes.has(vulnNode.id)) {
|
|
7289
|
-
graph.nodes.set(vulnNode.id, vulnNode);
|
|
7290
|
-
}
|
|
7291
|
-
graph.addEdge(node.id, vulnNode.id, "may_have", 0.3);
|
|
7292
|
-
}
|
|
7293
|
-
return node.id;
|
|
7294
|
-
}
|
|
7295
|
-
|
|
7296
|
-
// src/shared/utils/attack-graph/operations/security.ts
|
|
7297
|
-
function registerCredential(graph, username, password, source) {
|
|
7298
|
-
const node = createCredentialNode(username, password, source);
|
|
7299
|
-
if (!graph.nodes.has(node.id)) {
|
|
7300
|
-
graph.nodes.set(node.id, node);
|
|
7301
|
-
}
|
|
7302
|
-
const sprayEdges = createCredentialSprayEdges(node.id, graph.nodes);
|
|
7303
|
-
for (const edge of sprayEdges) {
|
|
7304
|
-
if (!edgeExists(graph.edges, edge.from, edge.to, edge.relation)) {
|
|
7305
|
-
graph.edges.push(edge);
|
|
7306
|
-
}
|
|
7307
|
-
}
|
|
7308
|
-
return node.id;
|
|
7309
|
-
}
|
|
7310
|
-
function registerVulnerability(graph, title, target, severity, hasExploit = false) {
|
|
7311
|
-
const vulnNode = createVulnerabilityNode(title, target, severity, hasExploit);
|
|
7312
|
-
if (!graph.nodes.has(vulnNode.id)) {
|
|
7313
|
-
graph.nodes.set(vulnNode.id, vulnNode);
|
|
7314
|
-
}
|
|
7315
|
-
const targetServices = findTargetServices(graph.nodes, target);
|
|
7316
|
-
for (const svc of targetServices) {
|
|
7317
|
-
graph.addEdge(svc.id, vulnNode.id, "has_vulnerability", 0.8);
|
|
7318
|
-
}
|
|
7319
|
-
if (hasExploit) {
|
|
7320
|
-
const accessNode = createPotentialAccessNode(title);
|
|
7321
|
-
if (!graph.nodes.has(accessNode.id)) {
|
|
7322
|
-
graph.nodes.set(accessNode.id, accessNode);
|
|
7323
|
-
}
|
|
7324
|
-
graph.addEdge(vulnNode.id, accessNode.id, "leads_to", 0.7);
|
|
7325
|
-
}
|
|
7326
|
-
return vulnNode.id;
|
|
7327
|
-
}
|
|
7328
|
-
function registerAccess(graph, host, level, via) {
|
|
7329
|
-
const accessNode = createAccessNode(host, level, via);
|
|
7330
|
-
if (!graph.nodes.has(accessNode.id)) {
|
|
7331
|
-
graph.nodes.set(accessNode.id, accessNode);
|
|
7332
|
-
}
|
|
7333
|
-
const node = graph.nodes.get(accessNode.id);
|
|
7334
|
-
if (node) {
|
|
7335
|
-
node.status = NODE_STATUS.SUCCEEDED;
|
|
7336
|
-
node.resolvedAt = Date.now();
|
|
7337
|
-
const incomingEdges = getIncomingEdges(graph.edges, accessNode.id);
|
|
7338
|
-
for (const edge of incomingEdges) {
|
|
7339
|
-
Object.assign(edge, markEdgeSucceeded(edge));
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
if (isHighPrivilege(level)) {
|
|
7343
|
-
const lootNode = createLootNode(host);
|
|
7344
|
-
if (!graph.nodes.has(lootNode.id)) {
|
|
7345
|
-
graph.nodes.set(lootNode.id, lootNode);
|
|
7346
|
-
}
|
|
7347
|
-
graph.addEdge(accessNode.id, lootNode.id, "can_access", 0.9);
|
|
7348
|
-
}
|
|
7349
|
-
return accessNode.id;
|
|
7350
|
-
}
|
|
7351
|
-
|
|
7352
|
-
// src/shared/utils/attack-graph/operations/discovery.ts
|
|
7353
|
-
function registerOSINT(graph, category, detail, data = {}) {
|
|
7354
|
-
const osintNode = createOSINTNode(category, detail, data);
|
|
7355
|
-
if (!graph.nodes.has(osintNode.id)) {
|
|
7356
|
-
graph.nodes.set(osintNode.id, osintNode);
|
|
7357
|
-
}
|
|
7358
|
-
const relatedNodes = findOSINTRelatedNodes(graph.nodes, detail);
|
|
7359
|
-
for (const { id } of relatedNodes) {
|
|
7360
|
-
graph.addEdge(osintNode.id, id, "relates_to", 0.5);
|
|
7361
|
-
}
|
|
7362
|
-
return osintNode.id;
|
|
7363
|
-
}
|
|
7364
|
-
|
|
7365
|
-
// src/shared/utils/attack-graph/status-management.ts
|
|
7366
|
-
function markNodeAttemptedInGraph(nodes, nodeId) {
|
|
7367
|
-
const node = nodes.get(nodeId);
|
|
7368
|
-
if (node && canMarkAttempted(node)) {
|
|
7369
|
-
node.status = NODE_STATUS.ATTEMPTED;
|
|
7370
|
-
node.attemptedAt = Date.now();
|
|
7371
|
-
}
|
|
7372
|
-
}
|
|
7373
|
-
function markNodeSucceededInGraph(nodes, edges, nodeId) {
|
|
7374
|
-
const node = nodes.get(nodeId);
|
|
7375
|
-
if (node) {
|
|
7376
|
-
node.status = NODE_STATUS.SUCCEEDED;
|
|
7377
|
-
node.resolvedAt = Date.now();
|
|
7378
|
-
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
7379
|
-
for (const edge of incomingEdges) {
|
|
7380
|
-
if (edge.status === EDGE_STATUS.TESTING) {
|
|
7381
|
-
Object.assign(edge, markEdgeSucceeded(edge));
|
|
7382
|
-
}
|
|
7383
|
-
}
|
|
7384
|
-
}
|
|
7385
|
-
}
|
|
7386
|
-
function markNodeFailedInGraph(nodes, edges, nodeId, reason, callbacks) {
|
|
7387
|
-
const node = nodes.get(nodeId);
|
|
7388
|
-
if (node) {
|
|
7389
|
-
node.status = NODE_STATUS.FAILED;
|
|
7390
|
-
node.resolvedAt = Date.now();
|
|
7391
|
-
node.failReason = reason;
|
|
7392
|
-
callbacks.onFailedPath(formatFailedNode(node.label, reason));
|
|
7393
|
-
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
7394
|
-
for (const edge of incomingEdges) {
|
|
7395
|
-
if (isTestableEdge(edge)) {
|
|
7396
|
-
Object.assign(edge, markEdgeFailed(edge, reason));
|
|
7397
|
-
}
|
|
7398
|
-
}
|
|
7399
|
-
}
|
|
7400
|
-
}
|
|
7401
|
-
function markEdgeTestingInList(edges, fromId, toId) {
|
|
7402
|
-
for (const edge of edges) {
|
|
7403
|
-
if (edge.from === fromId && edge.to === toId) {
|
|
7404
|
-
edge.status = EDGE_STATUS.TESTING;
|
|
7405
|
-
}
|
|
7406
|
-
}
|
|
7407
|
-
}
|
|
7408
|
-
function markEdgeFailedInList(edges, fromId, toId, reason, callbacks) {
|
|
7409
|
-
for (const edge of edges) {
|
|
7410
|
-
if (edge.from === fromId && edge.to === toId) {
|
|
7411
|
-
edge.status = EDGE_STATUS.FAILED;
|
|
7412
|
-
edge.failReason = reason;
|
|
7413
|
-
}
|
|
7414
|
-
}
|
|
7415
|
-
callbacks.onFailedPath(formatFailedPath(fromId, toId, reason));
|
|
7416
|
-
}
|
|
7417
|
-
|
|
7418
|
-
// src/shared/utils/attack-graph/store.ts
|
|
7419
|
-
var AttackGraphStore = class {
|
|
7420
|
-
nodes = /* @__PURE__ */ new Map();
|
|
7421
|
-
edges = [];
|
|
7422
|
-
failedPaths = [];
|
|
7423
|
-
/** Reset the store to an empty state. */
|
|
7424
|
-
reset() {
|
|
7425
|
-
this.nodes.clear();
|
|
7426
|
-
this.edges = [];
|
|
7427
|
-
this.failedPaths = [];
|
|
7428
|
-
}
|
|
7429
|
-
/**
|
|
7430
|
-
* Set/update a node with IMP-1 cap.
|
|
7431
|
-
* Evicts oldest 'discovered' node when limit reached.
|
|
7432
|
-
*/
|
|
7433
|
-
setNode(id, node) {
|
|
7434
|
-
if (!this.nodes.has(id) && this.nodes.size >= AGENT_LIMITS.MAX_ATTACK_NODES) {
|
|
7435
|
-
const oldestDiscovered = Array.from(this.nodes.entries()).find(([, n]) => n.status === "discovered");
|
|
7436
|
-
if (oldestDiscovered) {
|
|
7437
|
-
this.nodes.delete(oldestDiscovered[0]);
|
|
7438
|
-
}
|
|
7439
|
-
}
|
|
7440
|
-
this.nodes.set(id, node);
|
|
7441
|
-
}
|
|
7442
|
-
/** Get node by ID. */
|
|
7443
|
-
getNode(id) {
|
|
7444
|
-
return this.nodes.get(id);
|
|
7445
|
-
}
|
|
7446
|
-
/** Check if node exists. */
|
|
7447
|
-
hasNode(id) {
|
|
7448
|
-
return this.nodes.has(id);
|
|
7449
|
-
}
|
|
7450
|
-
/**
|
|
7451
|
-
* Add an edge with IMP-15 deduplication and IMP-1 cap.
|
|
7452
|
-
* Deduplication key: from + to + relation.
|
|
7453
|
-
*/
|
|
7454
|
-
addEdge(edge) {
|
|
7455
|
-
const isDuplicate = this.edges.some(
|
|
7456
|
-
(e) => e.from === edge.from && e.to === edge.to && e.relation === edge.relation
|
|
7457
|
-
);
|
|
7458
|
-
if (isDuplicate) return;
|
|
7459
|
-
this.edges.push(edge);
|
|
7460
|
-
if (this.edges.length > AGENT_LIMITS.MAX_ATTACK_EDGES) {
|
|
7461
|
-
const prunableIdx = this.edges.findIndex(
|
|
7462
|
-
(e) => e.status === "failed" || e.status === "untested"
|
|
7463
|
-
);
|
|
7464
|
-
if (prunableIdx >= 0) {
|
|
7465
|
-
this.edges.splice(prunableIdx, 1);
|
|
7466
|
-
}
|
|
7467
|
-
}
|
|
7468
|
-
}
|
|
7469
|
-
/**
|
|
7470
|
-
* Record a failed path with IMP-1 cap.
|
|
7471
|
-
* Keeps only the most recent MAX_FAILED_PATHS entries.
|
|
7472
|
-
*/
|
|
7473
|
-
recordFailedPath(path2) {
|
|
7474
|
-
this.failedPaths.push(path2);
|
|
7475
|
-
if (this.failedPaths.length > AGENT_LIMITS.MAX_FAILED_PATHS) {
|
|
7476
|
-
this.failedPaths = this.failedPaths.slice(-AGENT_LIMITS.MAX_FAILED_PATHS);
|
|
7477
|
-
}
|
|
7478
|
-
}
|
|
7479
|
-
/** Get internal nodes map reference (for operations). */
|
|
7480
|
-
getNodesMap() {
|
|
7481
|
-
return this.nodes;
|
|
7482
|
-
}
|
|
7483
|
-
/** Get internal edges list reference (for operations). */
|
|
7484
|
-
getEdgesList() {
|
|
7485
|
-
return this.edges;
|
|
7486
|
-
}
|
|
7487
|
-
/** Get failed paths list. */
|
|
7488
|
-
getFailedPaths() {
|
|
7489
|
-
return this.failedPaths;
|
|
7490
|
-
}
|
|
7491
|
-
/** Get all nodes as array. */
|
|
7492
|
-
getAllNodes() {
|
|
7493
|
-
return Array.from(this.nodes.values());
|
|
7494
|
-
}
|
|
7495
|
-
/** Get all edges as array. */
|
|
7496
|
-
getAllEdges() {
|
|
7497
|
-
return [...this.edges];
|
|
7498
|
-
}
|
|
7499
|
-
};
|
|
7500
|
-
|
|
7501
|
-
// src/shared/utils/attack-graph/core.ts
|
|
7502
|
-
var AttackGraph = class {
|
|
7503
|
-
store = new AttackGraphStore();
|
|
7504
|
-
// ─── Core Graph Operations ──────────────────────────────────
|
|
7505
|
-
/** Reset the graph to an empty state. */
|
|
7506
|
-
reset() {
|
|
7507
|
-
this.store.reset();
|
|
7508
|
-
}
|
|
7509
|
-
/**
|
|
7510
|
-
* Add a node to the attack graph.
|
|
7511
|
-
*/
|
|
7512
|
-
addNode(type, label, data = {}) {
|
|
7513
|
-
const node = createNode(type, label, data);
|
|
7514
|
-
if (!this.store.hasNode(node.id)) {
|
|
7515
|
-
this.store.setNode(node.id, node);
|
|
7516
|
-
}
|
|
7517
|
-
return node.id;
|
|
7518
|
-
}
|
|
7519
|
-
/**
|
|
7520
|
-
* Add an edge (relationship) between two nodes.
|
|
7521
|
-
*/
|
|
7522
|
-
addEdge(fromId, toId, relation, confidence = 0.5) {
|
|
7523
|
-
if (!edgeExists(this.store.getEdgesList(), fromId, toId, relation)) {
|
|
7524
|
-
this.store.addEdge(createEdge(fromId, toId, relation, confidence));
|
|
7525
|
-
}
|
|
7526
|
-
}
|
|
7527
|
-
/**
|
|
7528
|
-
* Get node by ID.
|
|
7529
|
-
*/
|
|
7530
|
-
getNode(nodeId) {
|
|
7531
|
-
return this.store.getNode(nodeId);
|
|
7532
|
-
}
|
|
7533
|
-
// ─── Status Management ──────────────────────────────────────
|
|
7534
|
-
/**
|
|
7535
|
-
* Mark a node as being attempted (in-progress attack).
|
|
7536
|
-
*/
|
|
7537
|
-
markAttempted(nodeId) {
|
|
7538
|
-
markNodeAttemptedInGraph(this.store.getNodesMap(), nodeId);
|
|
7539
|
-
}
|
|
7540
|
-
/**
|
|
7541
|
-
* Mark a node as successfully exploited/achieved.
|
|
7542
|
-
*/
|
|
7543
|
-
markSucceeded(nodeId) {
|
|
7544
|
-
markNodeSucceededInGraph(this.store.getNodesMap(), this.store.getEdgesList(), nodeId);
|
|
7545
|
-
}
|
|
7546
|
-
/**
|
|
7547
|
-
* Mark a node as failed (attack didn't work).
|
|
7548
|
-
*/
|
|
7549
|
-
markFailed(nodeId, reason) {
|
|
7550
|
-
markNodeFailedInGraph(this.store.getNodesMap(), this.store.getEdgesList(), nodeId, reason, {
|
|
7551
|
-
onFailedPath: (path2) => this.store.recordFailedPath(path2)
|
|
7552
|
-
});
|
|
7553
|
-
}
|
|
7554
|
-
/**
|
|
7555
|
-
* Mark an edge as being tested.
|
|
7556
|
-
*/
|
|
7557
|
-
markEdgeTesting(fromId, toId) {
|
|
7558
|
-
markEdgeTestingInList(this.store.getEdgesList(), fromId, toId);
|
|
7559
|
-
}
|
|
7560
|
-
/**
|
|
7561
|
-
* Mark an edge as failed.
|
|
7562
|
-
*/
|
|
7563
|
-
markEdgeFailed(fromId, toId, reason) {
|
|
7564
|
-
markEdgeFailedInList(this.store.getEdgesList(), fromId, toId, reason, {
|
|
7565
|
-
onFailedPath: (path2) => this.store.recordFailedPath(path2)
|
|
7566
|
-
});
|
|
7567
|
-
}
|
|
7568
|
-
// ─── Domain-Specific Registration ───────────────────────────
|
|
7569
|
-
/** Internal graph container for domain operations */
|
|
7570
|
-
get graphContainer() {
|
|
7571
|
-
return {
|
|
7572
|
-
nodes: this.store.getNodesMap(),
|
|
7573
|
-
edges: this.store.getEdgesList(),
|
|
7574
|
-
addEdge: this.addEdge.bind(this),
|
|
7575
|
-
markSucceeded: this.markSucceeded.bind(this)
|
|
7576
|
-
};
|
|
7577
|
-
}
|
|
7578
|
-
/**
|
|
7579
|
-
* Record a host discovery.
|
|
7580
|
-
*/
|
|
7581
|
-
addHost(ip, hostname) {
|
|
7582
|
-
return registerHost(this.graphContainer, ip, hostname);
|
|
7583
|
-
}
|
|
7584
|
-
/**
|
|
7585
|
-
* Record a service discovery and auto-create edges.
|
|
7586
|
-
*/
|
|
7587
|
-
addService(host, port, service, version) {
|
|
7588
|
-
return registerService(this.graphContainer, host, port, service, version);
|
|
7589
|
-
}
|
|
7590
|
-
/**
|
|
7591
|
-
* Record a credential discovery and create spray edges.
|
|
7592
|
-
*/
|
|
7593
|
-
addCredential(username, password, source) {
|
|
7594
|
-
return registerCredential(this.graphContainer, username, password, source);
|
|
7595
|
-
}
|
|
7596
|
-
/**
|
|
7597
|
-
* Record a vulnerability finding.
|
|
7598
|
-
*/
|
|
7599
|
-
addVulnerability(title, target, severity, hasExploit = false) {
|
|
7600
|
-
return registerVulnerability(this.graphContainer, title, target, severity, hasExploit);
|
|
7601
|
-
}
|
|
7602
|
-
/**
|
|
7603
|
-
* Record gained access.
|
|
7604
|
-
*/
|
|
7605
|
-
addAccess(host, level, via) {
|
|
7606
|
-
return registerAccess(this.graphContainer, host, level, via);
|
|
7607
|
-
}
|
|
7608
|
-
/**
|
|
7609
|
-
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
7610
|
-
*/
|
|
7611
|
-
addOSINT(category, detail, data = {}) {
|
|
7612
|
-
return registerOSINT(this.graphContainer, category, detail, data);
|
|
7613
|
-
}
|
|
7614
|
-
/**
|
|
7615
|
-
* Record an Active Directory object (domain, user-account, GPO, ACL, certificate-template).
|
|
7616
|
-
*
|
|
7617
|
-
* WHY: AD attacks chain through complex object relationships (ACL → user → cert → DA).
|
|
7618
|
-
* Tracking these as graph nodes lets the Strategist see multi-hop AD paths.
|
|
7619
|
-
*
|
|
7620
|
-
* type: 'domain' | 'user-account' | 'gpo' | 'acl' | 'certificate-template'
|
|
7621
|
-
* label: human-readable name (e.g. "CORP.LOCAL", "svc_sql", "ESC1-vulnerable")
|
|
7622
|
-
* data: freeform metadata (members, rights, target, confidence, etc.)
|
|
7623
|
-
*/
|
|
7624
|
-
addDomainObject(type, label, data = {}) {
|
|
7625
|
-
return this.addNode(type, label, { adObject: true, ...data });
|
|
7626
|
-
}
|
|
7627
|
-
// ─── Chain Discovery (DFS) ──────────────────────────────────
|
|
7628
|
-
/**
|
|
7629
|
-
* Find all viable attack paths using DFS.
|
|
7630
|
-
*/
|
|
7631
|
-
recommendChains() {
|
|
7632
|
-
return recommendChains(this.store.getNodesMap(), this.store.getEdgesList());
|
|
7633
|
-
}
|
|
7634
|
-
// ─── Prompt Generation ──────────────────────────────────────
|
|
7635
|
-
/**
|
|
7636
|
-
* Format attack graph status for prompt injection.
|
|
7637
|
-
*/
|
|
7638
|
-
toPrompt() {
|
|
7639
|
-
return formatGraphForPrompt(this.store.getNodesMap(), this.store.getEdgesList(), this.store.getFailedPaths());
|
|
7640
|
-
}
|
|
7641
|
-
// ─── TUI Visualization ──────────────────────────────────────
|
|
7642
|
-
/**
|
|
7643
|
-
* Generate ASCII visualization for TUI /graph command.
|
|
7644
|
-
*/
|
|
7645
|
-
toASCII() {
|
|
7646
|
-
return formatGraphAsASCII(this.store.getNodesMap(), this.store.getEdgesList());
|
|
7647
|
-
}
|
|
7648
|
-
/**
|
|
7649
|
-
* Generate path listing for TUI /paths command.
|
|
7650
|
-
*/
|
|
7651
|
-
toPathsList() {
|
|
7652
|
-
return formatPathsList(this.store.getNodesMap(), this.store.getEdgesList(), this.store.getFailedPaths());
|
|
7653
|
-
}
|
|
7654
|
-
// ─── Stats ──────────────────────────────────────────────────
|
|
7655
|
-
/**
|
|
7656
|
-
* Get graph stats for metrics display.
|
|
7657
|
-
*/
|
|
7658
|
-
getStats() {
|
|
7659
|
-
return getGraphStats(this.store.getNodesMap(), this.store.getEdgesList());
|
|
7660
|
-
}
|
|
7661
|
-
/**
|
|
7662
|
-
* Get all nodes.
|
|
7663
|
-
*/
|
|
7664
|
-
getAllNodes() {
|
|
7665
|
-
return this.store.getAllNodes();
|
|
7666
|
-
}
|
|
7667
|
-
/**
|
|
7668
|
-
* Get all edges.
|
|
7669
|
-
*/
|
|
7670
|
-
getAllEdges() {
|
|
7671
|
-
return this.store.getAllEdges();
|
|
7672
|
-
}
|
|
7673
|
-
/**
|
|
7674
|
-
* Get failed paths list.
|
|
7675
|
-
*/
|
|
7676
|
-
getFailedPaths() {
|
|
7677
|
-
return this.store.getFailedPaths();
|
|
7678
|
-
}
|
|
7679
|
-
/**
|
|
7680
|
-
* Check if the graph has any data.
|
|
7681
|
-
*/
|
|
7682
|
-
isEmpty() {
|
|
7683
|
-
return this.store.getNodesMap().size === 0;
|
|
7684
|
-
}
|
|
7685
|
-
};
|
|
7686
|
-
|
|
7687
6735
|
// src/engine/state/models/engagement.ts
|
|
7688
6736
|
function safeList4(values) {
|
|
7689
6737
|
return Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
|
|
@@ -8255,6 +7303,12 @@ var SharedState = class {
|
|
|
8255
7303
|
* Injected into prompt as <triage-memo> for the main LLM.
|
|
8256
7304
|
*/
|
|
8257
7305
|
lastTriageMemo = "";
|
|
7306
|
+
/**
|
|
7307
|
+
* Compact last tool-result signal for branch activation.
|
|
7308
|
+
* WHY: Raw tool output is too large and not persisted into next prompt build.
|
|
7309
|
+
* This stores only a narrow routing hint derived from the latest tool call.
|
|
7310
|
+
*/
|
|
7311
|
+
lastToolSignal = "";
|
|
8258
7312
|
/**
|
|
8259
7313
|
* Artifacts for CTF / Non-Network tasks (e.g., .bin files, source code).
|
|
8260
7314
|
*/
|
|
@@ -8294,6 +7348,7 @@ var SharedState = class {
|
|
|
8294
7348
|
this.activeDelegatedExecution = null;
|
|
8295
7349
|
this.lastReflection = "";
|
|
8296
7350
|
this.lastTriageMemo = "";
|
|
7351
|
+
this.lastToolSignal = "";
|
|
8297
7352
|
}
|
|
8298
7353
|
// Delegation to MissionState
|
|
8299
7354
|
setMissionSummary(summary) {
|
|
@@ -8531,6 +7586,8 @@ function buildStrategistPrompt(input) {
|
|
|
8531
7586
|
if (policyDocument) sections.push("", "## Policy Memory", policyDocument);
|
|
8532
7587
|
const graph = state.attackGraph.toPrompt();
|
|
8533
7588
|
if (graph) sections.push("", "## Attack Graph", graph);
|
|
7589
|
+
const campaignMemory = isBranchMemoryEnabled("strategist") ? buildCampaignMemorySummary(buildMemoryBranchIndex(state)) : "";
|
|
7590
|
+
if (campaignMemory) sections.push("", "## Ranked Branches", campaignMemory);
|
|
8534
7591
|
const timeline = state.episodicMemory.toPrompt();
|
|
8535
7592
|
if (timeline) sections.push("", "## Recent Actions", timeline);
|
|
8536
7593
|
const techniques = state.dynamicTechniques.toPrompt();
|
|
@@ -8689,6 +7746,39 @@ function extractTag(content, tag) {
|
|
|
8689
7746
|
function parseBoolean(value) {
|
|
8690
7747
|
return value.trim().toLowerCase() === "true";
|
|
8691
7748
|
}
|
|
7749
|
+
function parseNumber(value) {
|
|
7750
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
7751
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7752
|
+
}
|
|
7753
|
+
function buildDefaultTurnPolicy(shouldForwardToMain, directResponse) {
|
|
7754
|
+
if (!shouldForwardToMain) {
|
|
7755
|
+
return {
|
|
7756
|
+
classification: directResponse.trim() ? "direct_answer" : "policy_update_only",
|
|
7757
|
+
executionMode: "none",
|
|
7758
|
+
turnBudget: 0,
|
|
7759
|
+
continueCondition: "until_first_result",
|
|
7760
|
+
stopReasonTarget: "answer_ready"
|
|
7761
|
+
};
|
|
7762
|
+
}
|
|
7763
|
+
return void 0;
|
|
7764
|
+
}
|
|
7765
|
+
function parseTurnPolicy(response, shouldForwardToMain, directResponse) {
|
|
7766
|
+
const classification = extractTag(response, "input_classification");
|
|
7767
|
+
const executionMode = extractTag(response, "execution_mode");
|
|
7768
|
+
const turnBudget = extractTag(response, "turn_budget");
|
|
7769
|
+
const continueCondition = extractTag(response, "continue_condition");
|
|
7770
|
+
const stopReasonTarget = extractTag(response, "stop_reason_target");
|
|
7771
|
+
if (!classification || !executionMode || !turnBudget || !continueCondition || !stopReasonTarget) {
|
|
7772
|
+
return buildDefaultTurnPolicy(shouldForwardToMain, directResponse);
|
|
7773
|
+
}
|
|
7774
|
+
return {
|
|
7775
|
+
classification,
|
|
7776
|
+
executionMode,
|
|
7777
|
+
turnBudget: Math.max(0, parseNumber(turnBudget)),
|
|
7778
|
+
continueCondition,
|
|
7779
|
+
stopReasonTarget
|
|
7780
|
+
};
|
|
7781
|
+
}
|
|
8692
7782
|
var InputProcessor = class extends AuxiliaryLLMBase {
|
|
8693
7783
|
constructor(llm) {
|
|
8694
7784
|
super(llm, {
|
|
@@ -8718,6 +7808,7 @@ var InputProcessor = class extends AuxiliaryLLMBase {
|
|
|
8718
7808
|
const policyDocument = extractTag(response, "policy_document_markdown");
|
|
8719
7809
|
const policyUpdateSummary = extractTag(response, "policy_update_summary");
|
|
8720
7810
|
const insightSummary = extractTag(response, "insight_summary");
|
|
7811
|
+
const turnPolicy = parseTurnPolicy(response, shouldForwardToMain, directResponse);
|
|
8721
7812
|
return {
|
|
8722
7813
|
shouldForwardToMain,
|
|
8723
7814
|
forwardedInput,
|
|
@@ -8726,6 +7817,7 @@ var InputProcessor = class extends AuxiliaryLLMBase {
|
|
|
8726
7817
|
policyDocument,
|
|
8727
7818
|
policyUpdateSummary,
|
|
8728
7819
|
insightSummary,
|
|
7820
|
+
turnPolicy,
|
|
8729
7821
|
success: true
|
|
8730
7822
|
};
|
|
8731
7823
|
}
|
|
@@ -8738,6 +7830,7 @@ var InputProcessor = class extends AuxiliaryLLMBase {
|
|
|
8738
7830
|
policyDocument: "",
|
|
8739
7831
|
policyUpdateSummary: "",
|
|
8740
7832
|
insightSummary: "",
|
|
7833
|
+
turnPolicy: void 0,
|
|
8741
7834
|
success: false
|
|
8742
7835
|
};
|
|
8743
7836
|
}
|
|
@@ -8811,11 +7904,13 @@ async function executeUpdatePhase(p, state, events) {
|
|
|
8811
7904
|
const primaryTarget = targets[0]?.ip ?? "unknown";
|
|
8812
7905
|
const loot = state.getLoot();
|
|
8813
7906
|
const usableCreds = loot.filter((l) => USABLE_CREDENTIAL_LOOT_TYPES.has(l.type)).map((l) => l.detail).slice(0, 10);
|
|
7907
|
+
const resumeHints = isBranchMemoryEnabled("resume_hints") ? buildSnapshotResumeHints(buildMemoryBranchIndex(state)) : [];
|
|
8814
7908
|
saveSessionSnapshot({
|
|
8815
7909
|
target: primaryTarget,
|
|
8816
7910
|
phase: newPhase,
|
|
8817
7911
|
achieved: [`Phase transition: ${oldPhase} \u2192 ${newPhase}. Reason: ${reason}`],
|
|
8818
7912
|
next: [`Continue from ${newPhase} phase on ${primaryTarget}`],
|
|
7913
|
+
resumeHints,
|
|
8819
7914
|
credentials: usableCreds,
|
|
8820
7915
|
savedAt: Date.now()
|
|
8821
7916
|
});
|
|
@@ -8838,11 +7933,13 @@ Reason: ${reason}
|
|
|
8838
7933
|
|
|
8839
7934
|
// src/domains/engagement/handlers/snapshot.ts
|
|
8840
7935
|
async function executeSaveSessionSnapshot(p, state) {
|
|
7936
|
+
const resumeHints = isBranchMemoryEnabled("resume_hints") ? buildSnapshotResumeHints(buildMemoryBranchIndex(state)) : [];
|
|
8841
7937
|
const snapshot = {
|
|
8842
7938
|
target: p.target,
|
|
8843
7939
|
phase: state.getPhase(),
|
|
8844
7940
|
achieved: p.achieved || [],
|
|
8845
7941
|
next: p.next || [],
|
|
7942
|
+
resumeHints,
|
|
8846
7943
|
credentials: p.credentials || [],
|
|
8847
7944
|
notes: p.notes,
|
|
8848
7945
|
savedAt: Date.now()
|
|
@@ -8855,6 +7952,7 @@ Target: ${snapshot.target}
|
|
|
8855
7952
|
Phase: ${snapshot.phase}
|
|
8856
7953
|
Achieved: ${snapshot.achieved.length} items
|
|
8857
7954
|
Next: ${snapshot.next.length} priorities
|
|
7955
|
+
Resume hints: ${snapshot.resumeHints?.length ?? 0}
|
|
8858
7956
|
Creds: ${snapshot.credentials.length}`
|
|
8859
7957
|
};
|
|
8860
7958
|
}
|
|
@@ -11956,7 +11054,7 @@ After completion: record key loot/findings from the sub-agent output to canonica
|
|
|
11956
11054
|
rootTaskId: activeExecution.rootTaskId
|
|
11957
11055
|
}
|
|
11958
11056
|
});
|
|
11959
|
-
const { AgentTool } = await import("./agent-tool-
|
|
11057
|
+
const { AgentTool } = await import("./agent-tool-K3ETJX2V.js");
|
|
11960
11058
|
const executor = new AgentTool(state, events, scopeGuard, approvalGate);
|
|
11961
11059
|
try {
|
|
11962
11060
|
const result = await executor.execute(input);
|
|
@@ -12239,10 +11337,6 @@ export {
|
|
|
12239
11337
|
DEFAULT_MODEL,
|
|
12240
11338
|
getApiKey,
|
|
12241
11339
|
getModel,
|
|
12242
|
-
NODE_TYPE,
|
|
12243
|
-
NODE_STATUS,
|
|
12244
|
-
EDGE_STATUS,
|
|
12245
|
-
extractSuccessfulChain,
|
|
12246
11340
|
getTimeAdaptiveStrategy,
|
|
12247
11341
|
SharedState,
|
|
12248
11342
|
HealthMonitor,
|