pentesting 0.73.7 → 0.73.9
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 +17 -185
- package/dist/{agent-tool-EZF2ILH6.js → agent-tool-S6IMN7AQ.js} +3 -3
- package/dist/{chunk-FJ7PENUK.js → chunk-6M7LXEMY.js} +1527 -3
- package/dist/{chunk-UIYY4RLA.js → chunk-DDLHDNOM.js} +84 -990
- package/dist/{chunk-KAUE3MSR.js → chunk-S5ZMXFHR.js} +18 -0
- package/dist/main.js +623 -37
- package/dist/{persistence-7LJFJXK5.js → persistence-VW7NUTP4.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
|
@@ -13,13 +13,14 @@ import {
|
|
|
13
13
|
getAllProcesses,
|
|
14
14
|
getBackgroundProcessesMap,
|
|
15
15
|
getLimits,
|
|
16
|
+
getOptionalRuntimeSection,
|
|
16
17
|
getProcess,
|
|
17
18
|
getProcessEventLog,
|
|
18
19
|
getRequiredRuntimeSection,
|
|
19
20
|
getRuntimeSectionOr,
|
|
20
21
|
logEvent,
|
|
21
22
|
setProcess
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-S5ZMXFHR.js";
|
|
23
24
|
|
|
24
25
|
// src/shared/constants/time/conversions.ts
|
|
25
26
|
var MS_PER_MINUTE = 6e4;
|
|
@@ -347,7 +348,7 @@ var INPUT_PROMPT_PATTERNS = [
|
|
|
347
348
|
];
|
|
348
349
|
|
|
349
350
|
// src/shared/constants/agent.ts
|
|
350
|
-
var APP_VERSION = "0.73.
|
|
351
|
+
var APP_VERSION = "0.73.9";
|
|
351
352
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
352
353
|
var LLM_ROLES = {
|
|
353
354
|
SYSTEM: "system",
|
|
@@ -841,6 +842,8 @@ var UI_COMMANDS = {
|
|
|
841
842
|
START_SHORT: "s",
|
|
842
843
|
FINDINGS: "findings",
|
|
843
844
|
FINDINGS_SHORT: "f",
|
|
845
|
+
REPORT: "report",
|
|
846
|
+
REPORT_SHORT: "r",
|
|
844
847
|
ASSETS: "assets",
|
|
845
848
|
ASSETS_SHORT: "a",
|
|
846
849
|
LOGS: "logs",
|
|
@@ -2798,7 +2801,7 @@ async function cleanupAllProcesses() {
|
|
|
2798
2801
|
cleanupDone = false;
|
|
2799
2802
|
return;
|
|
2800
2803
|
}
|
|
2801
|
-
const { getBackgroundProcessesMap: getBackgroundProcessesMap2 } = await import("./process-registry-
|
|
2804
|
+
const { getBackgroundProcessesMap: getBackgroundProcessesMap2 } = await import("./process-registry-4Y3HB4YQ.js");
|
|
2802
2805
|
const backgroundProcesses = getBackgroundProcessesMap2();
|
|
2803
2806
|
terminateAllNatively(backgroundProcesses, "SIGTERM");
|
|
2804
2807
|
await new Promise((r) => setTimeout(r, SYSTEM_LIMITS.CLEANUP_BATCH_WAIT_MS));
|
|
@@ -3383,6 +3386,28 @@ function matchesServiceProfile(serviceProfile, services) {
|
|
|
3383
3386
|
// src/shared/utils/agent-memory/session-snapshot.ts
|
|
3384
3387
|
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
3385
3388
|
import { join as join3 } from "path";
|
|
3389
|
+
|
|
3390
|
+
// src/agents/memory-rollout-config.ts
|
|
3391
|
+
var DEFAULT_BRANCH_MEMORY_ROLLOUT = {
|
|
3392
|
+
main_prompt: true,
|
|
3393
|
+
strategist: true,
|
|
3394
|
+
delegated_prompt: true,
|
|
3395
|
+
resume_hints: true
|
|
3396
|
+
};
|
|
3397
|
+
function getMemoryRolloutConfig() {
|
|
3398
|
+
return getOptionalRuntimeSection("memory_rollout") ?? {};
|
|
3399
|
+
}
|
|
3400
|
+
function getBranchMemoryRollout() {
|
|
3401
|
+
return {
|
|
3402
|
+
...DEFAULT_BRANCH_MEMORY_ROLLOUT,
|
|
3403
|
+
...getMemoryRolloutConfig().branch_memory ?? {}
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
function isBranchMemoryEnabled(target) {
|
|
3407
|
+
return getBranchMemoryRollout()[target];
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// src/shared/utils/agent-memory/session-snapshot.ts
|
|
3386
3411
|
var SNAPSHOT_FILE = join3(WORKSPACE.MEMORY, SPECIAL_FILES.SESSION_SNAPSHOT);
|
|
3387
3412
|
function saveSessionSnapshot(snapshot) {
|
|
3388
3413
|
try {
|
|
@@ -3413,6 +3438,10 @@ function snapshotToPrompt() {
|
|
|
3413
3438
|
"NEXT PRIORITIES (resume here):",
|
|
3414
3439
|
...snap.next.map((n, i) => ` ${i + 1}. ${n}`)
|
|
3415
3440
|
];
|
|
3441
|
+
if (isBranchMemoryEnabled("resume_hints") && snap.resumeHints && snap.resumeHints.length > 0) {
|
|
3442
|
+
lines.push("", "RESUME HINTS:");
|
|
3443
|
+
snap.resumeHints.forEach((hint) => lines.push(` \u21AA ${hint}`));
|
|
3444
|
+
}
|
|
3416
3445
|
if (snap.credentials.length > 0) {
|
|
3417
3446
|
lines.push("", "USABLE CREDENTIALS:");
|
|
3418
3447
|
snap.credentials.forEach((c) => lines.push(` \u{1F511} ${c}`));
|
|
@@ -3595,6 +3624,1491 @@ var PersistentMemory = class {
|
|
|
3595
3624
|
}
|
|
3596
3625
|
};
|
|
3597
3626
|
|
|
3627
|
+
// src/shared/utils/attack-graph/types.ts
|
|
3628
|
+
var GRAPH_STATUS = {
|
|
3629
|
+
NEEDS_SEARCH: "needs_search",
|
|
3630
|
+
POTENTIAL: "potential"
|
|
3631
|
+
};
|
|
3632
|
+
var GRAPH_LIMITS = {
|
|
3633
|
+
/** Maximum recommended attack chains to return */
|
|
3634
|
+
MAX_CHAINS: 10,
|
|
3635
|
+
/** Maximum chains to show in prompt */
|
|
3636
|
+
PROMPT_CHAINS: 5,
|
|
3637
|
+
/** Maximum unexploited vulns to show in prompt */
|
|
3638
|
+
PROMPT_VULNS: 5,
|
|
3639
|
+
/** Maximum failed paths to show in prompt */
|
|
3640
|
+
PROMPT_FAILED: 5,
|
|
3641
|
+
/** Maximum DFS depth for chain discovery */
|
|
3642
|
+
MAX_CHAIN_DEPTH: 6,
|
|
3643
|
+
/** Maximum nodes to display in ASCII graph */
|
|
3644
|
+
ASCII_MAX_NODES: 30
|
|
3645
|
+
};
|
|
3646
|
+
var NODE_TYPE = {
|
|
3647
|
+
HOST: "host",
|
|
3648
|
+
SERVICE: "service",
|
|
3649
|
+
CREDENTIAL: "credential",
|
|
3650
|
+
VULNERABILITY: "vulnerability",
|
|
3651
|
+
ACCESS: "access",
|
|
3652
|
+
LOOT: "loot",
|
|
3653
|
+
OSINT: "osint",
|
|
3654
|
+
// ─── Active Directory Node Types (2차 — Hard/Insane support) ───
|
|
3655
|
+
/** AD domain object (e.g. DOMAIN.LOCAL) */
|
|
3656
|
+
DOMAIN: "domain",
|
|
3657
|
+
/** AD user or computer account */
|
|
3658
|
+
USER_ACCOUNT: "user-account",
|
|
3659
|
+
/** Group Policy Object — may grant code exec if writable */
|
|
3660
|
+
GPO: "gpo",
|
|
3661
|
+
/** ACL entry — WriteDACL/GenericAll/etc. on another object */
|
|
3662
|
+
ACL: "acl",
|
|
3663
|
+
/** ADCS certificate template — ESC1-13 attack surface */
|
|
3664
|
+
CERTIFICATE_TEMPLATE: "certificate-template"
|
|
3665
|
+
};
|
|
3666
|
+
var NODE_STATUS = {
|
|
3667
|
+
DISCOVERED: "discovered",
|
|
3668
|
+
ATTEMPTED: "attempted",
|
|
3669
|
+
SUCCEEDED: "succeeded",
|
|
3670
|
+
FAILED: "failed"
|
|
3671
|
+
};
|
|
3672
|
+
var EDGE_STATUS = {
|
|
3673
|
+
UNTESTED: "untested",
|
|
3674
|
+
TESTING: "testing",
|
|
3675
|
+
SUCCEEDED: "succeeded",
|
|
3676
|
+
FAILED: "failed"
|
|
3677
|
+
};
|
|
3678
|
+
var IMPACT_WEIGHT = {
|
|
3679
|
+
critical: 4,
|
|
3680
|
+
high: 3,
|
|
3681
|
+
medium: 2,
|
|
3682
|
+
low: 1
|
|
3683
|
+
};
|
|
3684
|
+
|
|
3685
|
+
// src/shared/utils/attack-graph/node.ts
|
|
3686
|
+
function generateNodeId(type, label) {
|
|
3687
|
+
return `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
|
|
3688
|
+
}
|
|
3689
|
+
function createNode(type, label, data = {}, status = NODE_STATUS.DISCOVERED) {
|
|
3690
|
+
const id = generateNodeId(type, label);
|
|
3691
|
+
return {
|
|
3692
|
+
id,
|
|
3693
|
+
type,
|
|
3694
|
+
label,
|
|
3695
|
+
data,
|
|
3696
|
+
status,
|
|
3697
|
+
discoveredAt: Date.now()
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
function canMarkAttempted(node) {
|
|
3701
|
+
return node.status === NODE_STATUS.DISCOVERED;
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// src/shared/utils/attack-graph/edge.ts
|
|
3705
|
+
function createEdge(from, to, relation, confidence = 0.5, status = EDGE_STATUS.UNTESTED) {
|
|
3706
|
+
return {
|
|
3707
|
+
from,
|
|
3708
|
+
to,
|
|
3709
|
+
relation,
|
|
3710
|
+
confidence,
|
|
3711
|
+
status
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
function edgeExists(edges, fromId, toId, relation) {
|
|
3715
|
+
return edges.some(
|
|
3716
|
+
(e) => e.from === fromId && e.to === toId && e.relation === relation
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
function getIncomingEdges(edges, nodeId) {
|
|
3720
|
+
return edges.filter((e) => e.to === nodeId);
|
|
3721
|
+
}
|
|
3722
|
+
function markEdgeSucceeded(edge) {
|
|
3723
|
+
return {
|
|
3724
|
+
...edge,
|
|
3725
|
+
status: EDGE_STATUS.SUCCEEDED
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
function markEdgeFailed(edge, reason) {
|
|
3729
|
+
return {
|
|
3730
|
+
...edge,
|
|
3731
|
+
status: EDGE_STATUS.FAILED,
|
|
3732
|
+
failReason: reason
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
function isTestableEdge(edge) {
|
|
3736
|
+
return edge.status === EDGE_STATUS.UNTESTED || edge.status === EDGE_STATUS.TESTING;
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
// src/shared/utils/attack-graph/factories/host-service.ts
|
|
3740
|
+
function createHostNode(ip, hostname) {
|
|
3741
|
+
return createNode(NODE_TYPE.HOST, ip, { ip, hostname });
|
|
3742
|
+
}
|
|
3743
|
+
function createServiceNode(host, port, service, version) {
|
|
3744
|
+
const hostId = generateNodeId(NODE_TYPE.HOST, host);
|
|
3745
|
+
const node = createNode(
|
|
3746
|
+
NODE_TYPE.SERVICE,
|
|
3747
|
+
`${host}:${port}`,
|
|
3748
|
+
{ host, port, service, version }
|
|
3749
|
+
);
|
|
3750
|
+
const hostEdge = createEdge(hostId, node.id, "has_service", 0.95);
|
|
3751
|
+
return { node, hostEdge };
|
|
3752
|
+
}
|
|
3753
|
+
function findTargetServices(nodes, target) {
|
|
3754
|
+
const results = [];
|
|
3755
|
+
for (const node of nodes.values()) {
|
|
3756
|
+
if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
|
|
3757
|
+
results.push(node);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
return results;
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
// src/shared/utils/attack-graph/factories/credential-exploit.ts
|
|
3764
|
+
var AUTH_SERVICES = [
|
|
3765
|
+
"ssh",
|
|
3766
|
+
"ftp",
|
|
3767
|
+
"rdp",
|
|
3768
|
+
"smb",
|
|
3769
|
+
"http",
|
|
3770
|
+
"mysql",
|
|
3771
|
+
"postgresql",
|
|
3772
|
+
"mssql",
|
|
3773
|
+
"winrm",
|
|
3774
|
+
"vnc",
|
|
3775
|
+
"telnet"
|
|
3776
|
+
];
|
|
3777
|
+
var HIGH_PRIVILEGE_LEVELS = ["root", "admin", "SYSTEM", "Administrator"];
|
|
3778
|
+
function createCredentialNode(username, password, source) {
|
|
3779
|
+
return createNode(
|
|
3780
|
+
NODE_TYPE.CREDENTIAL,
|
|
3781
|
+
`${username}:***`,
|
|
3782
|
+
{ username, password, source }
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
function createCredentialSprayEdges(credId, nodes) {
|
|
3786
|
+
const edges = [];
|
|
3787
|
+
for (const [id, node] of nodes) {
|
|
3788
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
3789
|
+
const svc = String(node.data.service || "");
|
|
3790
|
+
if (AUTH_SERVICES.some((s) => svc.includes(s))) {
|
|
3791
|
+
edges.push(createEdge(credId, id, "can_try_on", 0.6));
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
return edges;
|
|
3796
|
+
}
|
|
3797
|
+
function createVulnerabilityNode(title, target, severity, hasExploit = false) {
|
|
3798
|
+
return createNode(
|
|
3799
|
+
NODE_TYPE.VULNERABILITY,
|
|
3800
|
+
title,
|
|
3801
|
+
{ target, severity, hasExploit }
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
function createVersionSearchNode(service, version) {
|
|
3805
|
+
return createNode(
|
|
3806
|
+
NODE_TYPE.VULNERABILITY,
|
|
3807
|
+
`CVE search: ${service} ${version}`,
|
|
3808
|
+
{ service, version, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
function createAccessNode(host, level, via) {
|
|
3812
|
+
return createNode(
|
|
3813
|
+
NODE_TYPE.ACCESS,
|
|
3814
|
+
`${level}@${host}`,
|
|
3815
|
+
{ host, level, via }
|
|
3816
|
+
);
|
|
3817
|
+
}
|
|
3818
|
+
function createPotentialAccessNode(exploitTitle) {
|
|
3819
|
+
return createNode(
|
|
3820
|
+
NODE_TYPE.ACCESS,
|
|
3821
|
+
`shell via ${exploitTitle}`,
|
|
3822
|
+
{ via: exploitTitle, status: GRAPH_STATUS.POTENTIAL }
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
function isHighPrivilege(level) {
|
|
3826
|
+
return HIGH_PRIVILEGE_LEVELS.includes(level);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
// src/shared/utils/attack-graph/factories/osint-loot.ts
|
|
3830
|
+
function createLootNode(host) {
|
|
3831
|
+
return createNode(
|
|
3832
|
+
NODE_TYPE.LOOT,
|
|
3833
|
+
`flags on ${host}`,
|
|
3834
|
+
{ host, status: GRAPH_STATUS.NEEDS_SEARCH }
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3837
|
+
function createOSINTNode(category, detail, data = {}) {
|
|
3838
|
+
return createNode(
|
|
3839
|
+
NODE_TYPE.OSINT,
|
|
3840
|
+
`${category}: ${detail}`,
|
|
3841
|
+
{ category, detail, ...data }
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
function findOSINTRelatedNodes(nodes, detail) {
|
|
3845
|
+
const results = [];
|
|
3846
|
+
for (const [id, node] of nodes) {
|
|
3847
|
+
if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
|
|
3848
|
+
const hostIp = String(node.data.ip || node.data.host || "");
|
|
3849
|
+
const hostname = String(node.data.hostname || "");
|
|
3850
|
+
if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
|
|
3851
|
+
results.push({ id, node });
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
return results;
|
|
3856
|
+
}
|
|
3857
|
+
function formatFailedPath(from, to, reason) {
|
|
3858
|
+
return `${from} \u2192 ${to}${reason ? ` (${reason})` : ""}`;
|
|
3859
|
+
}
|
|
3860
|
+
function formatFailedNode(label, reason) {
|
|
3861
|
+
return `${label}${reason ? ` (${reason})` : ""}`;
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
// src/shared/utils/attack-graph/chain-discovery.ts
|
|
3865
|
+
var IMPACT_WEIGHT_MAP = {
|
|
3866
|
+
[NODE_TYPE.LOOT]: SEVERITIES.CRITICAL,
|
|
3867
|
+
[NODE_TYPE.DOMAIN]: SEVERITIES.CRITICAL,
|
|
3868
|
+
[NODE_TYPE.CERTIFICATE_TEMPLATE]: SEVERITIES.HIGH
|
|
3869
|
+
};
|
|
3870
|
+
function recommendChains(nodes, edges) {
|
|
3871
|
+
const chains = [];
|
|
3872
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3873
|
+
const goalTypes = [
|
|
3874
|
+
NODE_TYPE.ACCESS,
|
|
3875
|
+
NODE_TYPE.LOOT,
|
|
3876
|
+
NODE_TYPE.DOMAIN,
|
|
3877
|
+
NODE_TYPE.CERTIFICATE_TEMPLATE
|
|
3878
|
+
];
|
|
3879
|
+
const entryNodeTypes = [
|
|
3880
|
+
NODE_TYPE.HOST,
|
|
3881
|
+
NODE_TYPE.SERVICE,
|
|
3882
|
+
NODE_TYPE.CREDENTIAL,
|
|
3883
|
+
NODE_TYPE.OSINT
|
|
3884
|
+
];
|
|
3885
|
+
const entryNodes = Array.from(nodes.values()).filter(
|
|
3886
|
+
(n) => entryNodeTypes.includes(n.type)
|
|
3887
|
+
);
|
|
3888
|
+
const ctx = { nodes, edges, goalTypes, results: chains };
|
|
3889
|
+
for (const entry of entryNodes) {
|
|
3890
|
+
visited.clear();
|
|
3891
|
+
dfsChains(entry.id, [], [], visited, ctx);
|
|
3892
|
+
}
|
|
3893
|
+
return prioritizeChains(chains);
|
|
3894
|
+
}
|
|
3895
|
+
function prioritizeChains(chains) {
|
|
3896
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3897
|
+
const unique = chains.filter((c) => {
|
|
3898
|
+
if (seen.has(c.description)) return false;
|
|
3899
|
+
seen.add(c.description);
|
|
3900
|
+
return true;
|
|
3901
|
+
});
|
|
3902
|
+
unique.sort(
|
|
3903
|
+
(a, b) => b.probability * (IMPACT_WEIGHT[b.impact] || 1) - a.probability * (IMPACT_WEIGHT[a.impact] || 1)
|
|
3904
|
+
);
|
|
3905
|
+
return unique.slice(0, GRAPH_LIMITS.MAX_CHAINS);
|
|
3906
|
+
}
|
|
3907
|
+
function dfsChains(nodeId, path4, pathEdges, visited, ctx) {
|
|
3908
|
+
const { nodes, edges, goalTypes, results } = ctx;
|
|
3909
|
+
const node = nodes.get(nodeId);
|
|
3910
|
+
if (!node || visited.has(nodeId) || path4.length >= GRAPH_LIMITS.MAX_CHAIN_DEPTH || node.status === NODE_STATUS.FAILED) {
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
visited.add(nodeId);
|
|
3914
|
+
path4.push(node);
|
|
3915
|
+
if (isGoalReached(node, goalTypes, path4)) {
|
|
3916
|
+
results.push(buildChain(path4, pathEdges, node));
|
|
3917
|
+
}
|
|
3918
|
+
const outEdges = edges.filter((e) => e.from === nodeId && e.status !== EDGE_STATUS.FAILED);
|
|
3919
|
+
for (const edge of outEdges) {
|
|
3920
|
+
dfsChains(edge.to, path4, [...pathEdges, edge], visited, ctx);
|
|
3921
|
+
}
|
|
3922
|
+
path4.pop();
|
|
3923
|
+
visited.delete(nodeId);
|
|
3924
|
+
}
|
|
3925
|
+
function isGoalReached(node, goalTypes, path4) {
|
|
3926
|
+
return goalTypes.includes(node.type) && node.status !== NODE_STATUS.SUCCEEDED && path4.length > 1;
|
|
3927
|
+
}
|
|
3928
|
+
function buildChain(path4, edges, targetNode) {
|
|
3929
|
+
return {
|
|
3930
|
+
steps: [...path4],
|
|
3931
|
+
edges: [...edges],
|
|
3932
|
+
description: path4.map((n) => n.label).join(" \u2192 "),
|
|
3933
|
+
probability: edges.reduce((acc, e) => acc * e.confidence, 1),
|
|
3934
|
+
impact: estimateImpact(targetNode),
|
|
3935
|
+
length: path4.length
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
function estimateImpact(node) {
|
|
3939
|
+
if (IMPACT_WEIGHT_MAP[node.type]) return IMPACT_WEIGHT_MAP[node.type];
|
|
3940
|
+
if (node.type === NODE_TYPE.ACCESS) {
|
|
3941
|
+
const level = String(node.data.level || "");
|
|
3942
|
+
return ["root", "SYSTEM", "Administrator", "admin"].includes(level) ? SEVERITIES.CRITICAL : SEVERITIES.HIGH;
|
|
3943
|
+
}
|
|
3944
|
+
if (node.type === NODE_TYPE.VULNERABILITY) {
|
|
3945
|
+
const sev = String(node.data.severity || "").toLowerCase();
|
|
3946
|
+
if (sev === "critical") return SEVERITIES.CRITICAL;
|
|
3947
|
+
if (sev === "high") return SEVERITIES.HIGH;
|
|
3948
|
+
if (sev === "medium") return SEVERITIES.MEDIUM;
|
|
3949
|
+
}
|
|
3950
|
+
return SEVERITIES.LOW;
|
|
3951
|
+
}
|
|
3952
|
+
function extractSuccessfulChain(nodes, edges) {
|
|
3953
|
+
const succeededNodes = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
3954
|
+
if (succeededNodes.length < 2) return null;
|
|
3955
|
+
const succeededEdges = edges.filter((e) => e.status === EDGE_STATUS.SUCCEEDED);
|
|
3956
|
+
const { chainLabels, toolsUsed } = traceSucceededPath(succeededNodes, succeededEdges, nodes);
|
|
3957
|
+
if (chainLabels.length < 2) return null;
|
|
3958
|
+
const serviceProfile = succeededNodes.length > 0 ? succeededNodes.map((n) => abstractifyLabel(n.label)).filter(Boolean).join(" + ") : "unknown";
|
|
3959
|
+
return {
|
|
3960
|
+
serviceProfile,
|
|
3961
|
+
chainSummary: chainLabels.join(" \u2192 "),
|
|
3962
|
+
toolsUsed: [...new Set(toolsUsed.filter(Boolean))]
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
function traceSucceededPath(succeededNodes, succeededEdges, nodes) {
|
|
3966
|
+
const labels = [];
|
|
3967
|
+
const tools = [];
|
|
3968
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3969
|
+
const incomingTargets = new Set(succeededEdges.map((e) => e.to));
|
|
3970
|
+
const entryNodes = succeededNodes.filter((n) => !incomingTargets.has(n.id));
|
|
3971
|
+
const queue = entryNodes.length > 0 ? [entryNodes[0]] : [succeededNodes[0]];
|
|
3972
|
+
while (queue.length > 0) {
|
|
3973
|
+
const current = queue.shift();
|
|
3974
|
+
if (visited.has(current.id)) continue;
|
|
3975
|
+
visited.add(current.id);
|
|
3976
|
+
labels.push(abstractifyLabel(current.label));
|
|
3977
|
+
if (current.technique) tools.push(current.technique.split(" ")[0]);
|
|
3978
|
+
const nextEdges = succeededEdges.filter((e) => e.from === current.id);
|
|
3979
|
+
for (const edge of nextEdges) {
|
|
3980
|
+
const nextNode = nodes.get(edge.to);
|
|
3981
|
+
if (nextNode?.status === NODE_STATUS.SUCCEEDED) {
|
|
3982
|
+
if (edge.action) tools.push(edge.action.split(" ")[0]);
|
|
3983
|
+
queue.push(nextNode);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
return { chainLabels: labels, toolsUsed: tools };
|
|
3988
|
+
}
|
|
3989
|
+
function abstractifyLabel(label) {
|
|
3990
|
+
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, " ");
|
|
3991
|
+
return result || label;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// src/shared/utils/attack-graph/prompt-formatter.ts
|
|
3995
|
+
function formatGraphForPrompt(nodes, edges, failedPaths) {
|
|
3996
|
+
if (nodes.size === 0) return "";
|
|
3997
|
+
const lines = ["<attack-graph>"];
|
|
3998
|
+
const nodesByType = {};
|
|
3999
|
+
const nodesByStatus = {};
|
|
4000
|
+
for (const node of nodes.values()) {
|
|
4001
|
+
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
4002
|
+
nodesByStatus[node.status] = (nodesByStatus[node.status] || 0) + 1;
|
|
4003
|
+
}
|
|
4004
|
+
lines.push(`Nodes: ${Object.entries(nodesByType).map(([t, c]) => `${c} ${t}s`).join(", ")}`);
|
|
4005
|
+
lines.push(`Status: ${Object.entries(nodesByStatus).map(([s, c]) => `${c} ${s}`).join(", ")}`);
|
|
4006
|
+
lines.push(`Edges: ${edges.length} (${edges.filter((e) => e.status === EDGE_STATUS.FAILED).length} failed)`);
|
|
4007
|
+
const chains = recommendChains(nodes, edges);
|
|
4008
|
+
if (chains.length > 0) {
|
|
4009
|
+
lines.push("");
|
|
4010
|
+
lines.push("RECOMMENDED ATTACK PATHS (try these):");
|
|
4011
|
+
for (let i = 0; i < Math.min(GRAPH_LIMITS.PROMPT_CHAINS, chains.length); i++) {
|
|
4012
|
+
const c = chains[i];
|
|
4013
|
+
const prob = (c.probability * 100).toFixed(0);
|
|
4014
|
+
lines.push(` PATH ${i + 1} [${c.impact.toUpperCase()} | ${prob}%]: ${c.description}`);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
if (failedPaths.length > 0) {
|
|
4018
|
+
lines.push("");
|
|
4019
|
+
lines.push("FAILED PATHS (DO NOT RETRY):");
|
|
4020
|
+
for (const fp of failedPaths.slice(-GRAPH_LIMITS.PROMPT_FAILED)) {
|
|
4021
|
+
lines.push(` \u2717 ${fp}`);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
const unexploitedVulns = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.VULNERABILITY && n.status !== NODE_STATUS.SUCCEEDED && n.status !== NODE_STATUS.FAILED);
|
|
4025
|
+
if (unexploitedVulns.length > 0) {
|
|
4026
|
+
lines.push("");
|
|
4027
|
+
lines.push(`UNEXPLOITED VULNERABILITIES (${unexploitedVulns.length}):`);
|
|
4028
|
+
for (const v of unexploitedVulns.slice(0, GRAPH_LIMITS.PROMPT_VULNS)) {
|
|
4029
|
+
const sev = v.data.severity || "unknown";
|
|
4030
|
+
const status = v.status === NODE_STATUS.ATTEMPTED ? " \u23F3 testing" : "";
|
|
4031
|
+
lines.push(` - ${v.label} (${sev})${status}`);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
const accessNodes = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.ACCESS && n.status === NODE_STATUS.SUCCEEDED);
|
|
4035
|
+
if (accessNodes.length > 0) {
|
|
4036
|
+
lines.push("");
|
|
4037
|
+
lines.push("ACHIEVED ACCESS:");
|
|
4038
|
+
for (const a of accessNodes) {
|
|
4039
|
+
lines.push(` \u2713 ${a.label}`);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
const credNodes = Array.from(nodes.values()).filter((n) => n.type === NODE_TYPE.CREDENTIAL);
|
|
4043
|
+
if (credNodes.length > 0) {
|
|
4044
|
+
lines.push("");
|
|
4045
|
+
lines.push(`CREDENTIALS (${credNodes.length} \u2014 try on all services):`);
|
|
4046
|
+
for (const c of credNodes) {
|
|
4047
|
+
const source = c.data.source || "unknown";
|
|
4048
|
+
const sprayEdges = edges.filter((e) => e.from === c.id && e.relation === "can_try_on");
|
|
4049
|
+
const testedCount = sprayEdges.filter((e) => e.status !== EDGE_STATUS.UNTESTED).length;
|
|
4050
|
+
lines.push(` \u{1F511} ${c.label} (from: ${source}) [sprayed: ${testedCount}/${sprayEdges.length}]`);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
lines.push("</attack-graph>");
|
|
4054
|
+
return lines.join("\n");
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
// src/shared/utils/attack-graph/formatters/icons.ts
|
|
4058
|
+
var TYPE_ICONS = {
|
|
4059
|
+
[NODE_TYPE.HOST]: "\u{1F5A5}",
|
|
4060
|
+
[NODE_TYPE.SERVICE]: "\u2699",
|
|
4061
|
+
[NODE_TYPE.VULNERABILITY]: "\u26A0",
|
|
4062
|
+
[NODE_TYPE.CREDENTIAL]: "\u{1F511}",
|
|
4063
|
+
[NODE_TYPE.ACCESS]: "\u{1F513}",
|
|
4064
|
+
[NODE_TYPE.LOOT]: "\u{1F3F4}",
|
|
4065
|
+
[NODE_TYPE.OSINT]: "\u{1F50D}",
|
|
4066
|
+
// AD node icons (2차 — Hard/Insane support)
|
|
4067
|
+
[NODE_TYPE.DOMAIN]: "\u{1F3F0}",
|
|
4068
|
+
[NODE_TYPE.USER_ACCOUNT]: "\u{1F464}",
|
|
4069
|
+
[NODE_TYPE.GPO]: "\u{1F4CB}",
|
|
4070
|
+
[NODE_TYPE.ACL]: "\u{1F510}",
|
|
4071
|
+
[NODE_TYPE.CERTIFICATE_TEMPLATE]: "\u{1F4DC}"
|
|
4072
|
+
};
|
|
4073
|
+
var STATUS_ICONS = {
|
|
4074
|
+
[NODE_STATUS.DISCOVERED]: "\u25CB",
|
|
4075
|
+
[NODE_STATUS.ATTEMPTED]: "\u25D0",
|
|
4076
|
+
[NODE_STATUS.SUCCEEDED]: "\u25CF",
|
|
4077
|
+
[NODE_STATUS.FAILED]: "\u2717"
|
|
4078
|
+
};
|
|
4079
|
+
|
|
4080
|
+
// src/shared/utils/attack-graph/formatters/stats.ts
|
|
4081
|
+
function getGraphStats(nodes, edges) {
|
|
4082
|
+
const succeeded = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED).length;
|
|
4083
|
+
const failed = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.FAILED).length;
|
|
4084
|
+
return {
|
|
4085
|
+
nodes: nodes.size,
|
|
4086
|
+
edges: edges.length,
|
|
4087
|
+
succeeded,
|
|
4088
|
+
failed,
|
|
4089
|
+
chains: recommendChains(nodes, edges).length
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
// src/shared/utils/attack-graph/formatters/graph-ascii.ts
|
|
4094
|
+
function formatGraphAsASCII(nodes, edges) {
|
|
4095
|
+
if (nodes.size === 0) return "(Empty attack graph \u2014 no discoveries yet)";
|
|
4096
|
+
const lines = [];
|
|
4097
|
+
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");
|
|
4098
|
+
const groups = {};
|
|
4099
|
+
for (const node of nodes.values()) {
|
|
4100
|
+
if (!groups[node.type]) groups[node.type] = [];
|
|
4101
|
+
groups[node.type].push(node);
|
|
4102
|
+
}
|
|
4103
|
+
const typeSummary = Object.entries(groups).map(([t, ns]) => `${TYPE_ICONS[t] || "\xB7"} ${ns.length}`).join(" ");
|
|
4104
|
+
lines.push(`\u2502 ${typeSummary}`);
|
|
4105
|
+
for (const [type, typeNodes] of Object.entries(groups)) {
|
|
4106
|
+
const icon = TYPE_ICONS[type] || "\xB7";
|
|
4107
|
+
lines.push(`\u2502`);
|
|
4108
|
+
lines.push(`\u2502 ${icon} ${type.toUpperCase()} (${typeNodes.length})`);
|
|
4109
|
+
for (const node of typeNodes) {
|
|
4110
|
+
const sIcon = STATUS_ICONS[node.status] || "?";
|
|
4111
|
+
const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
|
|
4112
|
+
let detail = "";
|
|
4113
|
+
if (type === NODE_TYPE.CREDENTIAL) {
|
|
4114
|
+
const source = node.data.source ? ` (from: ${node.data.source})` : "";
|
|
4115
|
+
detail = source;
|
|
4116
|
+
} else if (type === NODE_TYPE.VULNERABILITY) {
|
|
4117
|
+
const sev = node.data.severity ? ` [${String(node.data.severity).toUpperCase()}]` : "";
|
|
4118
|
+
const exploit = node.data.hasExploit ? " [EXPLOIT]" : "";
|
|
4119
|
+
const target = node.data.target ? ` \u2192 ${node.data.target}` : "";
|
|
4120
|
+
detail = `${sev}${exploit}${target}`;
|
|
4121
|
+
} else if (type === NODE_TYPE.ACCESS) {
|
|
4122
|
+
const via = node.data.via ? ` via ${node.data.via}` : "";
|
|
4123
|
+
detail = via;
|
|
4124
|
+
} else if (type === NODE_TYPE.SERVICE) {
|
|
4125
|
+
const ver = node.data.version ? ` (${node.data.version})` : "";
|
|
4126
|
+
detail = ver;
|
|
4127
|
+
}
|
|
4128
|
+
const outEdges = edges.filter((e) => e.from === node.id);
|
|
4129
|
+
const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
|
|
4130
|
+
const target = nodes.get(e.to);
|
|
4131
|
+
const eName = target ? target.label : e.to;
|
|
4132
|
+
const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
|
|
4133
|
+
return `${eName}${eStatus}`;
|
|
4134
|
+
}).join(", ")}` : "";
|
|
4135
|
+
lines.push(`\u2502 ${sIcon} ${node.label}${detail}${fail}${edgeStr}`);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
const succeededNodes = Array.from(nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
4139
|
+
if (succeededNodes.length > 0) {
|
|
4140
|
+
lines.push(`\u2502`);
|
|
4141
|
+
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`);
|
|
4142
|
+
for (const n of succeededNodes) {
|
|
4143
|
+
const via = n.data.via ? ` via ${n.data.via}` : "";
|
|
4144
|
+
lines.push(`\u2502 \u25CF ${n.label}${via}`);
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
const stats = getGraphStats(nodes, edges);
|
|
4148
|
+
lines.push(`\u2502`);
|
|
4149
|
+
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`);
|
|
4150
|
+
lines.push(`\u2502 Nodes: ${stats.nodes} | Edges: ${stats.edges} | Succeeded: ${stats.succeeded} | Failed: ${stats.failed} | Chains: ${stats.chains}`);
|
|
4151
|
+
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`);
|
|
4152
|
+
return lines.join("\n");
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
// src/shared/utils/attack-graph/formatters/paths-list.ts
|
|
4156
|
+
function formatPathsList(nodes, edges, failedPaths) {
|
|
4157
|
+
const chains = recommendChains(nodes, edges);
|
|
4158
|
+
if (chains.length === 0) {
|
|
4159
|
+
if (nodes.size === 0) return "No attack graph data yet. Start reconnaissance first.";
|
|
4160
|
+
return "No viable attack paths found. All paths may be exhausted or failed.";
|
|
4161
|
+
}
|
|
4162
|
+
const lines = [];
|
|
4163
|
+
lines.push(`\u2500\u2500\u2500 ${chains.length} Attack Paths (sorted by priority) \u2500\u2500\u2500`);
|
|
4164
|
+
lines.push("");
|
|
4165
|
+
for (let i = 0; i < chains.length; i++) {
|
|
4166
|
+
const c = chains[i];
|
|
4167
|
+
const prob = (c.probability * 100).toFixed(0);
|
|
4168
|
+
const impactColors = {
|
|
4169
|
+
[SEVERITIES.CRITICAL]: "\u{1F534}",
|
|
4170
|
+
[SEVERITIES.HIGH]: "\u{1F7E0}",
|
|
4171
|
+
[SEVERITIES.MEDIUM]: "\u{1F7E1}",
|
|
4172
|
+
[SEVERITIES.LOW]: "\u{1F7E2}"
|
|
4173
|
+
};
|
|
4174
|
+
const impactIcon = impactColors[c.impact] || "\u26AA";
|
|
4175
|
+
lines.push(`${impactIcon} PATH ${i + 1} [${c.impact.toUpperCase()} | prob: ${prob}% | steps: ${c.length}]`);
|
|
4176
|
+
lines.push(` ${c.description}`);
|
|
4177
|
+
for (const edge of c.edges) {
|
|
4178
|
+
const statusTag = edge.status === EDGE_STATUS.UNTESTED ? " ?" : edge.status === EDGE_STATUS.TESTING ? " \u23F3" : edge.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : " \u2717";
|
|
4179
|
+
lines.push(` ${statusTag} ${edge.from.split(":").pop()} \u2014[${edge.relation}]\u2192 ${edge.to.split(":").pop()}`);
|
|
4180
|
+
}
|
|
4181
|
+
lines.push("");
|
|
4182
|
+
}
|
|
4183
|
+
if (failedPaths.length > 0) {
|
|
4184
|
+
lines.push(`\u2500\u2500\u2500 Failed Paths (${failedPaths.length}) \u2500\u2500\u2500`);
|
|
4185
|
+
for (const fp of failedPaths) {
|
|
4186
|
+
lines.push(` \u2717 ${fp}`);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
return lines.join("\n");
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
// src/shared/utils/attack-graph/operations/infrastructure.ts
|
|
4193
|
+
function registerHost(graph, ip, hostname) {
|
|
4194
|
+
const node = createHostNode(ip, hostname);
|
|
4195
|
+
if (!graph.nodes.has(node.id)) {
|
|
4196
|
+
graph.nodes.set(node.id, node);
|
|
4197
|
+
}
|
|
4198
|
+
return node.id;
|
|
4199
|
+
}
|
|
4200
|
+
function registerService(graph, host, port, service, version) {
|
|
4201
|
+
const hostId = registerHost(graph, host);
|
|
4202
|
+
const { node, hostEdge } = createServiceNode(host, port, service, version);
|
|
4203
|
+
if (!graph.nodes.has(node.id)) {
|
|
4204
|
+
graph.nodes.set(node.id, node);
|
|
4205
|
+
}
|
|
4206
|
+
if (hostEdge && !edgeExists(graph.edges, hostEdge.from, hostEdge.to, hostEdge.relation)) {
|
|
4207
|
+
graph.edges.push(hostEdge);
|
|
4208
|
+
}
|
|
4209
|
+
if (version) {
|
|
4210
|
+
const vulnNode = createVersionSearchNode(service, version);
|
|
4211
|
+
if (!graph.nodes.has(vulnNode.id)) {
|
|
4212
|
+
graph.nodes.set(vulnNode.id, vulnNode);
|
|
4213
|
+
}
|
|
4214
|
+
graph.addEdge(node.id, vulnNode.id, "may_have", 0.3);
|
|
4215
|
+
}
|
|
4216
|
+
return node.id;
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
// src/shared/utils/attack-graph/operations/security.ts
|
|
4220
|
+
function registerCredential(graph, username, password, source) {
|
|
4221
|
+
const node = createCredentialNode(username, password, source);
|
|
4222
|
+
if (!graph.nodes.has(node.id)) {
|
|
4223
|
+
graph.nodes.set(node.id, node);
|
|
4224
|
+
}
|
|
4225
|
+
const sprayEdges = createCredentialSprayEdges(node.id, graph.nodes);
|
|
4226
|
+
for (const edge of sprayEdges) {
|
|
4227
|
+
if (!edgeExists(graph.edges, edge.from, edge.to, edge.relation)) {
|
|
4228
|
+
graph.edges.push(edge);
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
return node.id;
|
|
4232
|
+
}
|
|
4233
|
+
function registerVulnerability(graph, title, target, severity, hasExploit = false) {
|
|
4234
|
+
const vulnNode = createVulnerabilityNode(title, target, severity, hasExploit);
|
|
4235
|
+
if (!graph.nodes.has(vulnNode.id)) {
|
|
4236
|
+
graph.nodes.set(vulnNode.id, vulnNode);
|
|
4237
|
+
}
|
|
4238
|
+
const targetServices = findTargetServices(graph.nodes, target);
|
|
4239
|
+
for (const svc of targetServices) {
|
|
4240
|
+
graph.addEdge(svc.id, vulnNode.id, "has_vulnerability", 0.8);
|
|
4241
|
+
}
|
|
4242
|
+
if (hasExploit) {
|
|
4243
|
+
const accessNode = createPotentialAccessNode(title);
|
|
4244
|
+
if (!graph.nodes.has(accessNode.id)) {
|
|
4245
|
+
graph.nodes.set(accessNode.id, accessNode);
|
|
4246
|
+
}
|
|
4247
|
+
graph.addEdge(vulnNode.id, accessNode.id, "leads_to", 0.7);
|
|
4248
|
+
}
|
|
4249
|
+
return vulnNode.id;
|
|
4250
|
+
}
|
|
4251
|
+
function registerAccess(graph, host, level, via) {
|
|
4252
|
+
const accessNode = createAccessNode(host, level, via);
|
|
4253
|
+
if (!graph.nodes.has(accessNode.id)) {
|
|
4254
|
+
graph.nodes.set(accessNode.id, accessNode);
|
|
4255
|
+
}
|
|
4256
|
+
const node = graph.nodes.get(accessNode.id);
|
|
4257
|
+
if (node) {
|
|
4258
|
+
node.status = NODE_STATUS.SUCCEEDED;
|
|
4259
|
+
node.resolvedAt = Date.now();
|
|
4260
|
+
const incomingEdges = getIncomingEdges(graph.edges, accessNode.id);
|
|
4261
|
+
for (const edge of incomingEdges) {
|
|
4262
|
+
Object.assign(edge, markEdgeSucceeded(edge));
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
if (isHighPrivilege(level)) {
|
|
4266
|
+
const lootNode = createLootNode(host);
|
|
4267
|
+
if (!graph.nodes.has(lootNode.id)) {
|
|
4268
|
+
graph.nodes.set(lootNode.id, lootNode);
|
|
4269
|
+
}
|
|
4270
|
+
graph.addEdge(accessNode.id, lootNode.id, "can_access", 0.9);
|
|
4271
|
+
}
|
|
4272
|
+
return accessNode.id;
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
// src/shared/utils/attack-graph/operations/discovery.ts
|
|
4276
|
+
function registerOSINT(graph, category, detail, data = {}) {
|
|
4277
|
+
const osintNode = createOSINTNode(category, detail, data);
|
|
4278
|
+
if (!graph.nodes.has(osintNode.id)) {
|
|
4279
|
+
graph.nodes.set(osintNode.id, osintNode);
|
|
4280
|
+
}
|
|
4281
|
+
const relatedNodes = findOSINTRelatedNodes(graph.nodes, detail);
|
|
4282
|
+
for (const { id } of relatedNodes) {
|
|
4283
|
+
graph.addEdge(osintNode.id, id, "relates_to", 0.5);
|
|
4284
|
+
}
|
|
4285
|
+
return osintNode.id;
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// src/shared/utils/attack-graph/status-management.ts
|
|
4289
|
+
function markNodeAttemptedInGraph(nodes, nodeId) {
|
|
4290
|
+
const node = nodes.get(nodeId);
|
|
4291
|
+
if (node && canMarkAttempted(node)) {
|
|
4292
|
+
node.status = NODE_STATUS.ATTEMPTED;
|
|
4293
|
+
node.attemptedAt = Date.now();
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
function markNodeSucceededInGraph(nodes, edges, nodeId) {
|
|
4297
|
+
const node = nodes.get(nodeId);
|
|
4298
|
+
if (node) {
|
|
4299
|
+
node.status = NODE_STATUS.SUCCEEDED;
|
|
4300
|
+
node.resolvedAt = Date.now();
|
|
4301
|
+
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
4302
|
+
for (const edge of incomingEdges) {
|
|
4303
|
+
if (edge.status === EDGE_STATUS.TESTING) {
|
|
4304
|
+
Object.assign(edge, markEdgeSucceeded(edge));
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function markNodeFailedInGraph(nodes, edges, nodeId, reason, callbacks) {
|
|
4310
|
+
const node = nodes.get(nodeId);
|
|
4311
|
+
if (node) {
|
|
4312
|
+
node.status = NODE_STATUS.FAILED;
|
|
4313
|
+
node.resolvedAt = Date.now();
|
|
4314
|
+
node.failReason = reason;
|
|
4315
|
+
callbacks.onFailedPath(formatFailedNode(node.label, reason));
|
|
4316
|
+
const incomingEdges = getIncomingEdges(edges, nodeId);
|
|
4317
|
+
for (const edge of incomingEdges) {
|
|
4318
|
+
if (isTestableEdge(edge)) {
|
|
4319
|
+
Object.assign(edge, markEdgeFailed(edge, reason));
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
function markEdgeTestingInList(edges, fromId, toId) {
|
|
4325
|
+
for (const edge of edges) {
|
|
4326
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
4327
|
+
edge.status = EDGE_STATUS.TESTING;
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
function markEdgeFailedInList(edges, fromId, toId, reason, callbacks) {
|
|
4332
|
+
for (const edge of edges) {
|
|
4333
|
+
if (edge.from === fromId && edge.to === toId) {
|
|
4334
|
+
edge.status = EDGE_STATUS.FAILED;
|
|
4335
|
+
edge.failReason = reason;
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
callbacks.onFailedPath(formatFailedPath(fromId, toId, reason));
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
// src/shared/utils/attack-graph/store.ts
|
|
4342
|
+
var AttackGraphStore = class {
|
|
4343
|
+
nodes = /* @__PURE__ */ new Map();
|
|
4344
|
+
edges = [];
|
|
4345
|
+
failedPaths = [];
|
|
4346
|
+
/** Reset the store to an empty state. */
|
|
4347
|
+
reset() {
|
|
4348
|
+
this.nodes.clear();
|
|
4349
|
+
this.edges = [];
|
|
4350
|
+
this.failedPaths = [];
|
|
4351
|
+
}
|
|
4352
|
+
/**
|
|
4353
|
+
* Set/update a node with IMP-1 cap.
|
|
4354
|
+
* Evicts oldest 'discovered' node when limit reached.
|
|
4355
|
+
*/
|
|
4356
|
+
setNode(id, node) {
|
|
4357
|
+
if (!this.nodes.has(id) && this.nodes.size >= AGENT_LIMITS.MAX_ATTACK_NODES) {
|
|
4358
|
+
const oldestDiscovered = Array.from(this.nodes.entries()).find(([, n]) => n.status === "discovered");
|
|
4359
|
+
if (oldestDiscovered) {
|
|
4360
|
+
this.nodes.delete(oldestDiscovered[0]);
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
this.nodes.set(id, node);
|
|
4364
|
+
}
|
|
4365
|
+
/** Get node by ID. */
|
|
4366
|
+
getNode(id) {
|
|
4367
|
+
return this.nodes.get(id);
|
|
4368
|
+
}
|
|
4369
|
+
/** Check if node exists. */
|
|
4370
|
+
hasNode(id) {
|
|
4371
|
+
return this.nodes.has(id);
|
|
4372
|
+
}
|
|
4373
|
+
/**
|
|
4374
|
+
* Add an edge with IMP-15 deduplication and IMP-1 cap.
|
|
4375
|
+
* Deduplication key: from + to + relation.
|
|
4376
|
+
*/
|
|
4377
|
+
addEdge(edge) {
|
|
4378
|
+
const isDuplicate = this.edges.some(
|
|
4379
|
+
(e) => e.from === edge.from && e.to === edge.to && e.relation === edge.relation
|
|
4380
|
+
);
|
|
4381
|
+
if (isDuplicate) return;
|
|
4382
|
+
this.edges.push(edge);
|
|
4383
|
+
if (this.edges.length > AGENT_LIMITS.MAX_ATTACK_EDGES) {
|
|
4384
|
+
const prunableIdx = this.edges.findIndex(
|
|
4385
|
+
(e) => e.status === "failed" || e.status === "untested"
|
|
4386
|
+
);
|
|
4387
|
+
if (prunableIdx >= 0) {
|
|
4388
|
+
this.edges.splice(prunableIdx, 1);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Record a failed path with IMP-1 cap.
|
|
4394
|
+
* Keeps only the most recent MAX_FAILED_PATHS entries.
|
|
4395
|
+
*/
|
|
4396
|
+
recordFailedPath(path4) {
|
|
4397
|
+
this.failedPaths.push(path4);
|
|
4398
|
+
if (this.failedPaths.length > AGENT_LIMITS.MAX_FAILED_PATHS) {
|
|
4399
|
+
this.failedPaths = this.failedPaths.slice(-AGENT_LIMITS.MAX_FAILED_PATHS);
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
/** Get internal nodes map reference (for operations). */
|
|
4403
|
+
getNodesMap() {
|
|
4404
|
+
return this.nodes;
|
|
4405
|
+
}
|
|
4406
|
+
/** Get internal edges list reference (for operations). */
|
|
4407
|
+
getEdgesList() {
|
|
4408
|
+
return this.edges;
|
|
4409
|
+
}
|
|
4410
|
+
/** Get failed paths list. */
|
|
4411
|
+
getFailedPaths() {
|
|
4412
|
+
return this.failedPaths;
|
|
4413
|
+
}
|
|
4414
|
+
/** Get all nodes as array. */
|
|
4415
|
+
getAllNodes() {
|
|
4416
|
+
return Array.from(this.nodes.values());
|
|
4417
|
+
}
|
|
4418
|
+
/** Get all edges as array. */
|
|
4419
|
+
getAllEdges() {
|
|
4420
|
+
return [...this.edges];
|
|
4421
|
+
}
|
|
4422
|
+
};
|
|
4423
|
+
|
|
4424
|
+
// src/shared/utils/attack-graph/core.ts
|
|
4425
|
+
var AttackGraph = class {
|
|
4426
|
+
store = new AttackGraphStore();
|
|
4427
|
+
// ─── Core Graph Operations ──────────────────────────────────
|
|
4428
|
+
/** Reset the graph to an empty state. */
|
|
4429
|
+
reset() {
|
|
4430
|
+
this.store.reset();
|
|
4431
|
+
}
|
|
4432
|
+
/**
|
|
4433
|
+
* Add a node to the attack graph.
|
|
4434
|
+
*/
|
|
4435
|
+
addNode(type, label, data = {}) {
|
|
4436
|
+
const node = createNode(type, label, data);
|
|
4437
|
+
if (!this.store.hasNode(node.id)) {
|
|
4438
|
+
this.store.setNode(node.id, node);
|
|
4439
|
+
}
|
|
4440
|
+
return node.id;
|
|
4441
|
+
}
|
|
4442
|
+
/**
|
|
4443
|
+
* Add an edge (relationship) between two nodes.
|
|
4444
|
+
*/
|
|
4445
|
+
addEdge(fromId, toId, relation, confidence = 0.5) {
|
|
4446
|
+
if (!edgeExists(this.store.getEdgesList(), fromId, toId, relation)) {
|
|
4447
|
+
this.store.addEdge(createEdge(fromId, toId, relation, confidence));
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
/**
|
|
4451
|
+
* Get node by ID.
|
|
4452
|
+
*/
|
|
4453
|
+
getNode(nodeId) {
|
|
4454
|
+
return this.store.getNode(nodeId);
|
|
4455
|
+
}
|
|
4456
|
+
// ─── Status Management ──────────────────────────────────────
|
|
4457
|
+
/**
|
|
4458
|
+
* Mark a node as being attempted (in-progress attack).
|
|
4459
|
+
*/
|
|
4460
|
+
markAttempted(nodeId) {
|
|
4461
|
+
markNodeAttemptedInGraph(this.store.getNodesMap(), nodeId);
|
|
4462
|
+
}
|
|
4463
|
+
/**
|
|
4464
|
+
* Mark a node as successfully exploited/achieved.
|
|
4465
|
+
*/
|
|
4466
|
+
markSucceeded(nodeId) {
|
|
4467
|
+
markNodeSucceededInGraph(this.store.getNodesMap(), this.store.getEdgesList(), nodeId);
|
|
4468
|
+
}
|
|
4469
|
+
/**
|
|
4470
|
+
* Mark a node as failed (attack didn't work).
|
|
4471
|
+
*/
|
|
4472
|
+
markFailed(nodeId, reason) {
|
|
4473
|
+
markNodeFailedInGraph(this.store.getNodesMap(), this.store.getEdgesList(), nodeId, reason, {
|
|
4474
|
+
onFailedPath: (path4) => this.store.recordFailedPath(path4)
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
/**
|
|
4478
|
+
* Mark an edge as being tested.
|
|
4479
|
+
*/
|
|
4480
|
+
markEdgeTesting(fromId, toId) {
|
|
4481
|
+
markEdgeTestingInList(this.store.getEdgesList(), fromId, toId);
|
|
4482
|
+
}
|
|
4483
|
+
/**
|
|
4484
|
+
* Mark an edge as failed.
|
|
4485
|
+
*/
|
|
4486
|
+
markEdgeFailed(fromId, toId, reason) {
|
|
4487
|
+
markEdgeFailedInList(this.store.getEdgesList(), fromId, toId, reason, {
|
|
4488
|
+
onFailedPath: (path4) => this.store.recordFailedPath(path4)
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
// ─── Domain-Specific Registration ───────────────────────────
|
|
4492
|
+
/** Internal graph container for domain operations */
|
|
4493
|
+
get graphContainer() {
|
|
4494
|
+
return {
|
|
4495
|
+
nodes: this.store.getNodesMap(),
|
|
4496
|
+
edges: this.store.getEdgesList(),
|
|
4497
|
+
addEdge: this.addEdge.bind(this),
|
|
4498
|
+
markSucceeded: this.markSucceeded.bind(this)
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
/**
|
|
4502
|
+
* Record a host discovery.
|
|
4503
|
+
*/
|
|
4504
|
+
addHost(ip, hostname) {
|
|
4505
|
+
return registerHost(this.graphContainer, ip, hostname);
|
|
4506
|
+
}
|
|
4507
|
+
/**
|
|
4508
|
+
* Record a service discovery and auto-create edges.
|
|
4509
|
+
*/
|
|
4510
|
+
addService(host, port, service, version) {
|
|
4511
|
+
return registerService(this.graphContainer, host, port, service, version);
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Record a credential discovery and create spray edges.
|
|
4515
|
+
*/
|
|
4516
|
+
addCredential(username, password, source) {
|
|
4517
|
+
return registerCredential(this.graphContainer, username, password, source);
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Record a vulnerability finding.
|
|
4521
|
+
*/
|
|
4522
|
+
addVulnerability(title, target, severity, hasExploit = false) {
|
|
4523
|
+
return registerVulnerability(this.graphContainer, title, target, severity, hasExploit);
|
|
4524
|
+
}
|
|
4525
|
+
/**
|
|
4526
|
+
* Record gained access.
|
|
4527
|
+
*/
|
|
4528
|
+
addAccess(host, level, via) {
|
|
4529
|
+
return registerAccess(this.graphContainer, host, level, via);
|
|
4530
|
+
}
|
|
4531
|
+
/**
|
|
4532
|
+
* Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
|
|
4533
|
+
*/
|
|
4534
|
+
addOSINT(category, detail, data = {}) {
|
|
4535
|
+
return registerOSINT(this.graphContainer, category, detail, data);
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* Record an Active Directory object (domain, user-account, GPO, ACL, certificate-template).
|
|
4539
|
+
*
|
|
4540
|
+
* WHY: AD attacks chain through complex object relationships (ACL → user → cert → DA).
|
|
4541
|
+
* Tracking these as graph nodes lets the Strategist see multi-hop AD paths.
|
|
4542
|
+
*
|
|
4543
|
+
* type: 'domain' | 'user-account' | 'gpo' | 'acl' | 'certificate-template'
|
|
4544
|
+
* label: human-readable name (e.g. "CORP.LOCAL", "svc_sql", "ESC1-vulnerable")
|
|
4545
|
+
* data: freeform metadata (members, rights, target, confidence, etc.)
|
|
4546
|
+
*/
|
|
4547
|
+
addDomainObject(type, label, data = {}) {
|
|
4548
|
+
return this.addNode(type, label, { adObject: true, ...data });
|
|
4549
|
+
}
|
|
4550
|
+
// ─── Chain Discovery (DFS) ──────────────────────────────────
|
|
4551
|
+
/**
|
|
4552
|
+
* Find all viable attack paths using DFS.
|
|
4553
|
+
*/
|
|
4554
|
+
recommendChains() {
|
|
4555
|
+
return recommendChains(this.store.getNodesMap(), this.store.getEdgesList());
|
|
4556
|
+
}
|
|
4557
|
+
// ─── Prompt Generation ──────────────────────────────────────
|
|
4558
|
+
/**
|
|
4559
|
+
* Format attack graph status for prompt injection.
|
|
4560
|
+
*/
|
|
4561
|
+
toPrompt() {
|
|
4562
|
+
return formatGraphForPrompt(this.store.getNodesMap(), this.store.getEdgesList(), this.store.getFailedPaths());
|
|
4563
|
+
}
|
|
4564
|
+
// ─── TUI Visualization ──────────────────────────────────────
|
|
4565
|
+
/**
|
|
4566
|
+
* Generate ASCII visualization for TUI /graph command.
|
|
4567
|
+
*/
|
|
4568
|
+
toASCII() {
|
|
4569
|
+
return formatGraphAsASCII(this.store.getNodesMap(), this.store.getEdgesList());
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* Generate path listing for TUI /paths command.
|
|
4573
|
+
*/
|
|
4574
|
+
toPathsList() {
|
|
4575
|
+
return formatPathsList(this.store.getNodesMap(), this.store.getEdgesList(), this.store.getFailedPaths());
|
|
4576
|
+
}
|
|
4577
|
+
// ─── Stats ──────────────────────────────────────────────────
|
|
4578
|
+
/**
|
|
4579
|
+
* Get graph stats for metrics display.
|
|
4580
|
+
*/
|
|
4581
|
+
getStats() {
|
|
4582
|
+
return getGraphStats(this.store.getNodesMap(), this.store.getEdgesList());
|
|
4583
|
+
}
|
|
4584
|
+
/**
|
|
4585
|
+
* Get all nodes.
|
|
4586
|
+
*/
|
|
4587
|
+
getAllNodes() {
|
|
4588
|
+
return this.store.getAllNodes();
|
|
4589
|
+
}
|
|
4590
|
+
/**
|
|
4591
|
+
* Get all edges.
|
|
4592
|
+
*/
|
|
4593
|
+
getAllEdges() {
|
|
4594
|
+
return this.store.getAllEdges();
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Get failed paths list.
|
|
4598
|
+
*/
|
|
4599
|
+
getFailedPaths() {
|
|
4600
|
+
return this.store.getFailedPaths();
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Check if the graph has any data.
|
|
4604
|
+
*/
|
|
4605
|
+
isEmpty() {
|
|
4606
|
+
return this.store.getNodesMap().size === 0;
|
|
4607
|
+
}
|
|
4608
|
+
};
|
|
4609
|
+
|
|
4610
|
+
// src/shared/utils/agent-memory/branch-index.ts
|
|
4611
|
+
var IPV4_HOST_PATTERN = /\b(\d{1,3}(?:\.\d{1,3}){3})(?::(\d+))?\b/;
|
|
4612
|
+
var DEFAULT_BRANCH_CONFIDENCE = 25;
|
|
4613
|
+
var MEMORY_FAILURE_CONFIDENCE = 40;
|
|
4614
|
+
var GRAPH_RECOMMENDATION_CONFIDENCE_MULTIPLIER = 100;
|
|
4615
|
+
var ACCESS_CONFIDENCE = 100;
|
|
4616
|
+
function buildMemoryBranchIndex(state3) {
|
|
4617
|
+
const builder = new MemoryBranchIndexBuilder(state3);
|
|
4618
|
+
return builder.build();
|
|
4619
|
+
}
|
|
4620
|
+
var MemoryBranchIndexBuilder = class {
|
|
4621
|
+
constructor(state3) {
|
|
4622
|
+
this.state = state3;
|
|
4623
|
+
this.buildServiceLookup();
|
|
4624
|
+
}
|
|
4625
|
+
branches = /* @__PURE__ */ new Map();
|
|
4626
|
+
serviceLookup = /* @__PURE__ */ new Map();
|
|
4627
|
+
build() {
|
|
4628
|
+
this.addTargetBranches();
|
|
4629
|
+
this.addFindingBranches();
|
|
4630
|
+
this.addWorkingMemoryBranches();
|
|
4631
|
+
this.addDelegatedBranches();
|
|
4632
|
+
this.addAttackGraphBranches();
|
|
4633
|
+
this.addAttackGraphRecommendations();
|
|
4634
|
+
return {
|
|
4635
|
+
branches: Array.from(this.branches.values()).map((branch) => this.finalizeBranch(branch)).sort((a, b) => b.touchedAt - a.touchedAt)
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
buildServiceLookup() {
|
|
4639
|
+
for (const target of this.state.getAllTargets()) {
|
|
4640
|
+
for (const port of target.ports) {
|
|
4641
|
+
this.serviceLookup.set(this.serviceLookupKey(target.ip, port.port), port.service);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
addTargetBranches() {
|
|
4646
|
+
for (const target of this.state.getAllTargets()) {
|
|
4647
|
+
if (target.ports.length === 0) {
|
|
4648
|
+
this.touchBranch({ host: target.ip }, "canonical_state", DEFAULT_BRANCH_CONFIDENCE);
|
|
4649
|
+
continue;
|
|
4650
|
+
}
|
|
4651
|
+
for (const port of target.ports) {
|
|
4652
|
+
this.touchBranch({
|
|
4653
|
+
host: target.ip,
|
|
4654
|
+
service: port.service,
|
|
4655
|
+
port: port.port
|
|
4656
|
+
}, "canonical_state", DEFAULT_BRANCH_CONFIDENCE);
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
addFindingBranches() {
|
|
4661
|
+
for (const finding of this.state.getFindings()) {
|
|
4662
|
+
const affectedTargets = finding.affected.map((affected) => this.parseBranchTarget(affected)).filter((target) => target !== null);
|
|
4663
|
+
if (affectedTargets.length === 0) {
|
|
4664
|
+
continue;
|
|
4665
|
+
}
|
|
4666
|
+
for (const target of affectedTargets) {
|
|
4667
|
+
const branch = this.touchBranch({
|
|
4668
|
+
host: target.host,
|
|
4669
|
+
service: target.service ?? this.lookupService(target.host, target.port),
|
|
4670
|
+
port: target.port,
|
|
4671
|
+
vectorType: "vuln",
|
|
4672
|
+
vectorId: finding.title
|
|
4673
|
+
}, "canonical_state", finding.confidence, finding.foundAt);
|
|
4674
|
+
this.pushUnique(branch.findings, finding.title);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
addWorkingMemoryBranches() {
|
|
4679
|
+
for (const entry of this.state.workingMemory.getEntries()) {
|
|
4680
|
+
if (entry.fingerprint) {
|
|
4681
|
+
this.addFailureEntry(entry, entry.fingerprint);
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
addFailureEntry(entry, fingerprint) {
|
|
4686
|
+
const rawCommand = typeof entry.context.command === "string" ? entry.context.command : "";
|
|
4687
|
+
const target = this.parseBranchTarget(rawCommand) ?? this.parseBranchTarget(fingerprint.target);
|
|
4688
|
+
if (!target) {
|
|
4689
|
+
return;
|
|
4690
|
+
}
|
|
4691
|
+
const resolvedPort = target.port ?? this.lookupPort(target.host, target.service);
|
|
4692
|
+
const resolvedService = target.service ?? this.lookupService(target.host, resolvedPort);
|
|
4693
|
+
const branch = this.touchBranch({
|
|
4694
|
+
host: target.host,
|
|
4695
|
+
service: resolvedService,
|
|
4696
|
+
port: resolvedPort
|
|
4697
|
+
}, "memory", MEMORY_FAILURE_CONFIDENCE, entry.timestamp);
|
|
4698
|
+
this.pushUnique(branch.recentAttempts, entry.content);
|
|
4699
|
+
this.pushUnique(branch.recentFailures, entry.content);
|
|
4700
|
+
if (fingerprint.hypothesizedReason) {
|
|
4701
|
+
this.pushUnique(branch.blockers, fingerprint.hypothesizedReason);
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
addDelegatedBranches() {
|
|
4705
|
+
const activeExecution = this.state.getActiveDelegatedExecution();
|
|
4706
|
+
if (activeExecution?.target) {
|
|
4707
|
+
this.addDelegatedBranch(activeExecution.target, activeExecution.task, activeExecution.updatedAt);
|
|
4708
|
+
}
|
|
4709
|
+
for (const task of this.state.getDelegatedTasks()) {
|
|
4710
|
+
if (!task.target) {
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4713
|
+
const branch = this.addDelegatedBranch(task.target, task.summary || task.task, task.updatedAt);
|
|
4714
|
+
this.pushUnique(branch.nextCandidates, task.suggestedNext);
|
|
4715
|
+
if (task.resumeHint) {
|
|
4716
|
+
this.pushUnique(branch.blockers, task.resumeHint);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
addDelegatedBranch(targetValue, detail, touchedAt) {
|
|
4721
|
+
const target = this.parseBranchTarget(targetValue) ?? { host: targetValue };
|
|
4722
|
+
const branch = this.touchBranch({
|
|
4723
|
+
host: target.host,
|
|
4724
|
+
service: target.service ?? this.lookupService(target.host, target.port),
|
|
4725
|
+
port: target.port
|
|
4726
|
+
}, "delegated", DEFAULT_BRANCH_CONFIDENCE, touchedAt);
|
|
4727
|
+
this.pushUnique(branch.nextCandidates, detail);
|
|
4728
|
+
return branch;
|
|
4729
|
+
}
|
|
4730
|
+
addAttackGraphBranches() {
|
|
4731
|
+
const nodes = this.state.attackGraph.getAllNodes();
|
|
4732
|
+
const edges = this.state.attackGraph.getAllEdges();
|
|
4733
|
+
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
|
4734
|
+
for (const node of nodes) {
|
|
4735
|
+
this.addAttackNode(node);
|
|
4736
|
+
}
|
|
4737
|
+
for (const edge of edges) {
|
|
4738
|
+
this.addCredentialSprayEdge(edge, nodesById);
|
|
4739
|
+
}
|
|
4740
|
+
for (const failedPath of this.state.attackGraph.getFailedPaths()) {
|
|
4741
|
+
const target = this.parseBranchTarget(failedPath);
|
|
4742
|
+
if (!target) {
|
|
4743
|
+
continue;
|
|
4744
|
+
}
|
|
4745
|
+
const branch = this.touchBranch({
|
|
4746
|
+
host: target.host,
|
|
4747
|
+
service: target.service ?? this.lookupService(target.host, target.port),
|
|
4748
|
+
port: target.port
|
|
4749
|
+
}, "graph", MEMORY_FAILURE_CONFIDENCE);
|
|
4750
|
+
this.pushUnique(branch.blockers, failedPath);
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
addAttackNode(node) {
|
|
4754
|
+
if (node.type === NODE_TYPE.SERVICE) {
|
|
4755
|
+
const branch = this.touchBranch({
|
|
4756
|
+
host: this.readString(node.data.host) || node.label,
|
|
4757
|
+
service: this.readString(node.data.service),
|
|
4758
|
+
port: this.readNumber(node.data.port)
|
|
4759
|
+
}, "graph", DEFAULT_BRANCH_CONFIDENCE, node.discoveredAt);
|
|
4760
|
+
this.pushUnique(branch.nextCandidates, `Service discovered: ${node.label}`);
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
if (node.type === NODE_TYPE.VULNERABILITY) {
|
|
4764
|
+
const target = this.parseBranchTarget(this.readString(node.data.target) || "");
|
|
4765
|
+
if (!target) {
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
const branch = this.touchBranch({
|
|
4769
|
+
host: target.host,
|
|
4770
|
+
service: target.service ?? this.lookupService(target.host, target.port),
|
|
4771
|
+
port: target.port,
|
|
4772
|
+
vectorType: "vuln",
|
|
4773
|
+
vectorId: node.label
|
|
4774
|
+
}, "graph", DEFAULT_BRANCH_CONFIDENCE, node.discoveredAt);
|
|
4775
|
+
this.pushUnique(branch.findings, node.label);
|
|
4776
|
+
return;
|
|
4777
|
+
}
|
|
4778
|
+
if (node.type === NODE_TYPE.ACCESS) {
|
|
4779
|
+
const host = this.readString(node.data.host);
|
|
4780
|
+
const level = this.readString(node.data.level) || node.label;
|
|
4781
|
+
if (!host) {
|
|
4782
|
+
return;
|
|
4783
|
+
}
|
|
4784
|
+
const branch = this.touchBranch({
|
|
4785
|
+
host,
|
|
4786
|
+
vectorType: "shell",
|
|
4787
|
+
vectorId: level
|
|
4788
|
+
}, "graph", ACCESS_CONFIDENCE, node.resolvedAt ?? node.discoveredAt);
|
|
4789
|
+
this.pushUnique(branch.accessState, node.label);
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
addCredentialSprayEdge(edge, nodesById) {
|
|
4794
|
+
if (edge.relation !== "can_try_on") {
|
|
4795
|
+
return;
|
|
4796
|
+
}
|
|
4797
|
+
const credentialNode = nodesById.get(edge.from);
|
|
4798
|
+
const serviceNode = nodesById.get(edge.to);
|
|
4799
|
+
if (!credentialNode || !serviceNode || serviceNode.type !== NODE_TYPE.SERVICE) {
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
const host = this.readString(serviceNode.data.host);
|
|
4803
|
+
if (!host) {
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
const branch = this.touchBranch({
|
|
4807
|
+
host,
|
|
4808
|
+
service: this.readString(serviceNode.data.service),
|
|
4809
|
+
port: this.readNumber(serviceNode.data.port)
|
|
4810
|
+
}, "graph", DEFAULT_BRANCH_CONFIDENCE, serviceNode.discoveredAt);
|
|
4811
|
+
this.pushUnique(branch.credentials, credentialNode.label);
|
|
4812
|
+
}
|
|
4813
|
+
addAttackGraphRecommendations() {
|
|
4814
|
+
const chains = this.state.attackGraph.recommendChains();
|
|
4815
|
+
for (const chain of chains.slice(0, 3)) {
|
|
4816
|
+
const branchKey = this.getBranchKeyFromChain(chain);
|
|
4817
|
+
if (!branchKey) {
|
|
4818
|
+
continue;
|
|
4819
|
+
}
|
|
4820
|
+
const branch = this.touchBranch(
|
|
4821
|
+
branchKey,
|
|
4822
|
+
"graph",
|
|
4823
|
+
Math.round(chain.probability * GRAPH_RECOMMENDATION_CONFIDENCE_MULTIPLIER)
|
|
4824
|
+
);
|
|
4825
|
+
this.pushUnique(branch.nextCandidates, `Recommended path: ${chain.description}`);
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
getBranchKeyFromChain(chain) {
|
|
4829
|
+
for (const node of chain.steps) {
|
|
4830
|
+
if (node.type !== NODE_TYPE.SERVICE) {
|
|
4831
|
+
continue;
|
|
4832
|
+
}
|
|
4833
|
+
const host = this.readString(node.data.host);
|
|
4834
|
+
if (!host) {
|
|
4835
|
+
continue;
|
|
4836
|
+
}
|
|
4837
|
+
return {
|
|
4838
|
+
host,
|
|
4839
|
+
service: this.readString(node.data.service),
|
|
4840
|
+
port: this.readNumber(node.data.port)
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
return null;
|
|
4844
|
+
}
|
|
4845
|
+
touchBranch(key, source, confidence, touchedAt = Date.now()) {
|
|
4846
|
+
const branchKey = this.serializeBranchKey(key);
|
|
4847
|
+
const existing = this.branches.get(branchKey);
|
|
4848
|
+
if (existing) {
|
|
4849
|
+
existing.confidence = Math.max(existing.confidence, confidence);
|
|
4850
|
+
existing.touchedAt = Math.max(existing.touchedAt, touchedAt);
|
|
4851
|
+
this.pushUniqueSource(existing.sources, source);
|
|
4852
|
+
return existing;
|
|
4853
|
+
}
|
|
4854
|
+
const created = {
|
|
4855
|
+
key,
|
|
4856
|
+
status: "new",
|
|
4857
|
+
findings: [],
|
|
4858
|
+
credentials: [],
|
|
4859
|
+
recentAttempts: [],
|
|
4860
|
+
recentFailures: [],
|
|
4861
|
+
successfulActions: [],
|
|
4862
|
+
blockers: [],
|
|
4863
|
+
accessState: [],
|
|
4864
|
+
nextCandidates: [],
|
|
4865
|
+
confidence,
|
|
4866
|
+
touchedAt,
|
|
4867
|
+
sources: [source]
|
|
4868
|
+
};
|
|
4869
|
+
this.branches.set(branchKey, created);
|
|
4870
|
+
return created;
|
|
4871
|
+
}
|
|
4872
|
+
finalizeBranch(branch) {
|
|
4873
|
+
return {
|
|
4874
|
+
...branch,
|
|
4875
|
+
status: this.resolveStatus(branch),
|
|
4876
|
+
findings: [...branch.findings],
|
|
4877
|
+
credentials: [...branch.credentials],
|
|
4878
|
+
recentAttempts: [...branch.recentAttempts],
|
|
4879
|
+
recentFailures: [...branch.recentFailures],
|
|
4880
|
+
successfulActions: [...branch.successfulActions],
|
|
4881
|
+
blockers: [...branch.blockers],
|
|
4882
|
+
accessState: [...branch.accessState],
|
|
4883
|
+
nextCandidates: [...branch.nextCandidates],
|
|
4884
|
+
sources: [...branch.sources]
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
resolveStatus(branch) {
|
|
4888
|
+
if (branch.accessState.length > 0) {
|
|
4889
|
+
return "succeeded";
|
|
4890
|
+
}
|
|
4891
|
+
if (branch.blockers.length > 0 && branch.nextCandidates.length === 0) {
|
|
4892
|
+
return "blocked";
|
|
4893
|
+
}
|
|
4894
|
+
if (branch.findings.length > 0 || branch.nextCandidates.length > 0 || branch.credentials.length > 0) {
|
|
4895
|
+
return "promising";
|
|
4896
|
+
}
|
|
4897
|
+
if (branch.recentAttempts.length > 0 || branch.recentFailures.length > 0) {
|
|
4898
|
+
return "enumerating";
|
|
4899
|
+
}
|
|
4900
|
+
return "new";
|
|
4901
|
+
}
|
|
4902
|
+
lookupService(host, port) {
|
|
4903
|
+
if (!port) {
|
|
4904
|
+
return void 0;
|
|
4905
|
+
}
|
|
4906
|
+
return this.serviceLookup.get(this.serviceLookupKey(host, port));
|
|
4907
|
+
}
|
|
4908
|
+
lookupPort(host, service) {
|
|
4909
|
+
if (!service) {
|
|
4910
|
+
return void 0;
|
|
4911
|
+
}
|
|
4912
|
+
const target = this.state.getTarget(host);
|
|
4913
|
+
const match = target?.ports.find((port) => port.service === service);
|
|
4914
|
+
return match?.port;
|
|
4915
|
+
}
|
|
4916
|
+
parseBranchTarget(raw) {
|
|
4917
|
+
if (!raw) {
|
|
4918
|
+
return null;
|
|
4919
|
+
}
|
|
4920
|
+
const fromUrl = this.parseUrlTarget(raw);
|
|
4921
|
+
if (fromUrl) {
|
|
4922
|
+
return fromUrl;
|
|
4923
|
+
}
|
|
4924
|
+
const match = raw.match(IPV4_HOST_PATTERN);
|
|
4925
|
+
if (!match) {
|
|
4926
|
+
return null;
|
|
4927
|
+
}
|
|
4928
|
+
return {
|
|
4929
|
+
host: match[1],
|
|
4930
|
+
port: match[2] ? Number(match[2]) : void 0
|
|
4931
|
+
};
|
|
4932
|
+
}
|
|
4933
|
+
parseUrlTarget(raw) {
|
|
4934
|
+
if (!raw.includes("://")) {
|
|
4935
|
+
return null;
|
|
4936
|
+
}
|
|
4937
|
+
try {
|
|
4938
|
+
const match = raw.match(/\b[a-z][a-z0-9+.-]*:\/\/\S+/i);
|
|
4939
|
+
if (!match) {
|
|
4940
|
+
return null;
|
|
4941
|
+
}
|
|
4942
|
+
const parsed = new URL(match[0]);
|
|
4943
|
+
return {
|
|
4944
|
+
host: parsed.hostname,
|
|
4945
|
+
port: parsed.port ? Number(parsed.port) : void 0,
|
|
4946
|
+
service: parsed.protocol.replace(":", "")
|
|
4947
|
+
};
|
|
4948
|
+
} catch {
|
|
4949
|
+
return null;
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
readNumber(value) {
|
|
4953
|
+
return typeof value === "number" ? value : void 0;
|
|
4954
|
+
}
|
|
4955
|
+
readString(value) {
|
|
4956
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
4957
|
+
}
|
|
4958
|
+
serializeBranchKey(key) {
|
|
4959
|
+
return [
|
|
4960
|
+
key.host,
|
|
4961
|
+
key.service ?? "",
|
|
4962
|
+
key.port ?? "",
|
|
4963
|
+
key.vectorType ?? "",
|
|
4964
|
+
key.vectorId ?? ""
|
|
4965
|
+
].join("|");
|
|
4966
|
+
}
|
|
4967
|
+
serviceLookupKey(host, port) {
|
|
4968
|
+
return `${host}:${port}`;
|
|
4969
|
+
}
|
|
4970
|
+
pushUnique(target, value) {
|
|
4971
|
+
if (!value || target.includes(value)) {
|
|
4972
|
+
return;
|
|
4973
|
+
}
|
|
4974
|
+
target.push(value);
|
|
4975
|
+
}
|
|
4976
|
+
pushUniqueSource(target, value) {
|
|
4977
|
+
if (!value || target.includes(value)) {
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
target.push(value);
|
|
4981
|
+
}
|
|
4982
|
+
};
|
|
4983
|
+
|
|
4984
|
+
// src/shared/utils/agent-memory/branch-summary.ts
|
|
4985
|
+
var DEFAULT_RANKED_BRANCH_LIMIT = 5;
|
|
4986
|
+
var DEFAULT_BLOCKED_BRANCH_LIMIT = 3;
|
|
4987
|
+
var DEFAULT_RESUME_HINT_LIMIT = 3;
|
|
4988
|
+
var MAX_BRANCH_DETAILS = 2;
|
|
4989
|
+
function rankMemoryBranches(index, limit = DEFAULT_RANKED_BRANCH_LIMIT) {
|
|
4990
|
+
return [...index.branches].sort(compareMemoryBranch).slice(0, Math.max(0, Math.floor(limit)));
|
|
4991
|
+
}
|
|
4992
|
+
function buildCampaignMemorySummary(index, rankedLimit = DEFAULT_RANKED_BRANCH_LIMIT, blockedLimit = DEFAULT_BLOCKED_BRANCH_LIMIT) {
|
|
4993
|
+
if (index.branches.length === 0) {
|
|
4994
|
+
return "";
|
|
4995
|
+
}
|
|
4996
|
+
const ranked = rankMemoryBranches(index, rankedLimit);
|
|
4997
|
+
const blocked = index.branches.filter((branch) => branch.status === "blocked").sort(compareMemoryBranch).slice(0, Math.max(0, Math.floor(blockedLimit)));
|
|
4998
|
+
const sections = [
|
|
4999
|
+
"<campaign-memory>",
|
|
5000
|
+
"<ranked-branches>",
|
|
5001
|
+
...ranked.map((branch, indexValue) => formatRankedBranch(indexValue + 1, branch)),
|
|
5002
|
+
"</ranked-branches>"
|
|
5003
|
+
];
|
|
5004
|
+
if (blocked.length > 0) {
|
|
5005
|
+
sections.push("<blocked-branches>");
|
|
5006
|
+
sections.push(...blocked.map((branch) => formatBlockedBranch(branch)));
|
|
5007
|
+
sections.push("</blocked-branches>");
|
|
5008
|
+
}
|
|
5009
|
+
sections.push("</campaign-memory>");
|
|
5010
|
+
return sections.join("\n");
|
|
5011
|
+
}
|
|
5012
|
+
function buildMicroBranchSummary(branch, activationReason, signals = []) {
|
|
5013
|
+
const lines = [
|
|
5014
|
+
"<delegated-branch-memory>",
|
|
5015
|
+
`target: ${formatBranchTarget(branch)}`,
|
|
5016
|
+
`status: ${branch.status}`,
|
|
5017
|
+
`confidence: ${branch.confidence}`,
|
|
5018
|
+
`activation_reason: ${activationReason}`,
|
|
5019
|
+
signals.length > 0 ? `signals: ${signals.join(", ")}` : "",
|
|
5020
|
+
formatList("findings", branch.findings),
|
|
5021
|
+
formatList("blockers", branch.blockers),
|
|
5022
|
+
formatList("next", branch.nextCandidates),
|
|
5023
|
+
formatList("access", branch.accessState),
|
|
5024
|
+
formatList("recent_failures", branch.recentFailures),
|
|
5025
|
+
"</delegated-branch-memory>"
|
|
5026
|
+
].filter(Boolean);
|
|
5027
|
+
return lines.join("\n");
|
|
5028
|
+
}
|
|
5029
|
+
function buildSnapshotResumeHints(index, limit = DEFAULT_RESUME_HINT_LIMIT) {
|
|
5030
|
+
return rankMemoryBranches(index, limit).map(buildResumeHintLine).filter(Boolean);
|
|
5031
|
+
}
|
|
5032
|
+
function formatRankedBranch(rank, branch) {
|
|
5033
|
+
const details = [
|
|
5034
|
+
`#${rank} ${formatBranchTarget(branch)} | status=${branch.status} | confidence=${branch.confidence}`,
|
|
5035
|
+
formatList("findings", branch.findings),
|
|
5036
|
+
formatList("next", branch.nextCandidates),
|
|
5037
|
+
formatList("credentials", branch.credentials),
|
|
5038
|
+
formatList("access", branch.accessState),
|
|
5039
|
+
formatList("recent_failures", branch.recentFailures)
|
|
5040
|
+
].filter(Boolean);
|
|
5041
|
+
return details.join("\n");
|
|
5042
|
+
}
|
|
5043
|
+
function formatBlockedBranch(branch) {
|
|
5044
|
+
const blockers = formatList("blockers", branch.blockers);
|
|
5045
|
+
const next = formatList("next", branch.nextCandidates);
|
|
5046
|
+
const failures = formatList("recent_failures", branch.recentFailures);
|
|
5047
|
+
return [
|
|
5048
|
+
`${formatBranchTarget(branch)} | confidence=${branch.confidence}`,
|
|
5049
|
+
blockers,
|
|
5050
|
+
next,
|
|
5051
|
+
failures
|
|
5052
|
+
].filter(Boolean).join("\n");
|
|
5053
|
+
}
|
|
5054
|
+
function formatBranchTarget(branch) {
|
|
5055
|
+
const host = branch.key.host;
|
|
5056
|
+
const port = branch.key.port ? `:${branch.key.port}` : "";
|
|
5057
|
+
const service = branch.key.service ? ` (${branch.key.service})` : "";
|
|
5058
|
+
const vector = branch.key.vectorId ? ` [${branch.key.vectorId}]` : "";
|
|
5059
|
+
return `${host}${port}${service}${vector}`;
|
|
5060
|
+
}
|
|
5061
|
+
function formatList(label, values) {
|
|
5062
|
+
if (values.length === 0) {
|
|
5063
|
+
return "";
|
|
5064
|
+
}
|
|
5065
|
+
const visible = values.slice(0, MAX_BRANCH_DETAILS).join(" | ");
|
|
5066
|
+
const more = values.length > MAX_BRANCH_DETAILS ? ` (+${values.length - MAX_BRANCH_DETAILS} more)` : "";
|
|
5067
|
+
return `${label}: ${visible}${more}`;
|
|
5068
|
+
}
|
|
5069
|
+
function buildResumeHintLine(branch) {
|
|
5070
|
+
const target = formatBranchTarget(branch);
|
|
5071
|
+
const next = branch.nextCandidates[0];
|
|
5072
|
+
const blocker = branch.blockers[0];
|
|
5073
|
+
const finding = branch.findings[0];
|
|
5074
|
+
const hints = [
|
|
5075
|
+
finding ? `finding=${finding}` : "",
|
|
5076
|
+
next ? `next=${next}` : "",
|
|
5077
|
+
blocker ? `blocker=${blocker}` : "",
|
|
5078
|
+
branch.accessState[0] ? `access=${branch.accessState[0]}` : ""
|
|
5079
|
+
].filter(Boolean);
|
|
5080
|
+
if (hints.length === 0) {
|
|
5081
|
+
return `${target} | status=${branch.status} | confidence=${branch.confidence}`;
|
|
5082
|
+
}
|
|
5083
|
+
return `${target} | ${hints.join(" | ")}`;
|
|
5084
|
+
}
|
|
5085
|
+
function compareMemoryBranch(left, right) {
|
|
5086
|
+
const statusDelta = getStatusRank(right.status) - getStatusRank(left.status);
|
|
5087
|
+
if (statusDelta !== 0) {
|
|
5088
|
+
return statusDelta;
|
|
5089
|
+
}
|
|
5090
|
+
if (right.confidence !== left.confidence) {
|
|
5091
|
+
return right.confidence - left.confidence;
|
|
5092
|
+
}
|
|
5093
|
+
return right.touchedAt - left.touchedAt;
|
|
5094
|
+
}
|
|
5095
|
+
function getStatusRank(status) {
|
|
5096
|
+
switch (status) {
|
|
5097
|
+
case "succeeded":
|
|
5098
|
+
return 5;
|
|
5099
|
+
case "promising":
|
|
5100
|
+
return 4;
|
|
5101
|
+
case "enumerating":
|
|
5102
|
+
return 3;
|
|
5103
|
+
case "new":
|
|
5104
|
+
return 2;
|
|
5105
|
+
case "blocked":
|
|
5106
|
+
return 1;
|
|
5107
|
+
case "abandoned":
|
|
5108
|
+
return 0;
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
|
|
3598
5112
|
// src/shared/utils/agent-memory/dynamic/extraction.ts
|
|
3599
5113
|
var KNOWN_TECHS = [
|
|
3600
5114
|
"apache",
|
|
@@ -4186,13 +5700,23 @@ export {
|
|
|
4186
5700
|
EVENT_TYPES,
|
|
4187
5701
|
COMMAND_EVENT_TYPES,
|
|
4188
5702
|
UI_COMMANDS,
|
|
5703
|
+
NODE_TYPE,
|
|
5704
|
+
NODE_STATUS,
|
|
5705
|
+
EDGE_STATUS,
|
|
5706
|
+
extractSuccessfulChain,
|
|
5707
|
+
AttackGraph,
|
|
4189
5708
|
generateId,
|
|
4190
5709
|
generatePrefixedId,
|
|
4191
5710
|
WorkingMemory,
|
|
4192
5711
|
EpisodicMemory,
|
|
5712
|
+
isBranchMemoryEnabled,
|
|
4193
5713
|
saveSessionSnapshot,
|
|
4194
5714
|
snapshotToPrompt,
|
|
4195
5715
|
PersistentMemory,
|
|
5716
|
+
buildMemoryBranchIndex,
|
|
5717
|
+
buildCampaignMemorySummary,
|
|
5718
|
+
buildMicroBranchSummary,
|
|
5719
|
+
buildSnapshotResumeHints,
|
|
4196
5720
|
DynamicTechniqueLibrary,
|
|
4197
5721
|
createSessionRuntime,
|
|
4198
5722
|
setActiveSessionRuntime,
|