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.
@@ -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-26G2YIOA.js";
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-KAUE3MSR.js";
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-Y6CG5KRL.js");
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,