@kenhkl/agent-moss 0.17.7 → 0.17.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +417 -42
- package/dist/engine/coordinator.d.ts +42 -0
- package/dist/engine/coordinator.js +143 -18
- package/dist/server.js +417 -42
- package/package.json +1 -1
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
interface AttackChainNode {
|
|
2
|
+
session_id: string;
|
|
3
|
+
seq: number;
|
|
4
|
+
stage: string;
|
|
5
|
+
action_detail: string;
|
|
6
|
+
resources: string[];
|
|
7
|
+
/** 节点在性质中的角色:source/carrier/sink(P2),或阶段名(P1) */
|
|
8
|
+
role?: string;
|
|
9
|
+
/** 是否为违例见证节点(当前动作补全链 / 触发外传) */
|
|
10
|
+
witness?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface AttackChainEdge {
|
|
13
|
+
from: number;
|
|
14
|
+
to: number;
|
|
15
|
+
type: 'next' | 'data';
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
/** 性质表达式 token:token 文本 + 对应节点下标(可为多个)。 */
|
|
19
|
+
interface AttackChainExprToken {
|
|
20
|
+
token: string;
|
|
21
|
+
node_index: number | number[];
|
|
22
|
+
}
|
|
23
|
+
/** 结构化攻击链图(前端 SVG 渲染的数据契约,与 Python 端逐字段对齐)。 */
|
|
24
|
+
interface AttackChainGraph {
|
|
25
|
+
/** 性质元信息(前端展示"违反了哪条性质") */
|
|
26
|
+
property_id?: string;
|
|
27
|
+
property_short?: string;
|
|
28
|
+
property_name?: string;
|
|
29
|
+
/** 性质表达式与节点位置的对应关系 */
|
|
30
|
+
property_expression?: AttackChainExprToken[];
|
|
31
|
+
nodes: AttackChainNode[];
|
|
32
|
+
edges: AttackChainEdge[];
|
|
33
|
+
}
|
|
34
|
+
|
|
1
35
|
/** Security judgment result from a single analysis layer */
|
|
2
36
|
interface SecurityJudgment {
|
|
3
37
|
allowed: boolean;
|
|
@@ -8,6 +42,10 @@ interface SecurityJudgment {
|
|
|
8
42
|
source: string;
|
|
9
43
|
violatedLayers: string[];
|
|
10
44
|
isCredential?: boolean;
|
|
45
|
+
/** 组合式拦截的结构化攻击链(前端可视化用) */
|
|
46
|
+
violationGraph?: AttackChainGraph | null;
|
|
47
|
+
/** 兼容旧调用方:反例路径数组 */
|
|
48
|
+
violationChain?: unknown[];
|
|
11
49
|
}
|
|
12
50
|
|
|
13
51
|
/** 把命令按 shell 的连接符切成若干段,去掉空白段。
|
|
@@ -43,6 +81,7 @@ interface AnalysisInput {
|
|
|
43
81
|
* 由调用方(含未来 agentgate 非侵入式采集)提供。 */
|
|
44
82
|
sessionHistory?: {
|
|
45
83
|
sessionId?: string;
|
|
84
|
+
instanceId?: string;
|
|
46
85
|
actionHistory: {
|
|
47
86
|
actionType: string;
|
|
48
87
|
actionDetail: string;
|
|
@@ -52,12 +91,15 @@ interface AnalysisInput {
|
|
|
52
91
|
aNext: {
|
|
53
92
|
actionType: string;
|
|
54
93
|
actionDetail: string;
|
|
94
|
+
instanceId?: string;
|
|
55
95
|
};
|
|
56
96
|
reason: string;
|
|
57
97
|
osType: string;
|
|
58
98
|
cwd: string;
|
|
59
99
|
/** 调用方 agent 标识(如 'xiaoo' / 'opencode' / 'opendesk')。空 = 通用模式。 */
|
|
60
100
|
agentId: string;
|
|
101
|
+
/** 调用方实例标识(安装级)。仅用于展示层脱敏:同实例节点不隐藏。 */
|
|
102
|
+
instanceId?: string;
|
|
61
103
|
/** 调用方场景标识(如 'bank'),用于加载场景定制规则(客户沙箱的目录访问边界/禁 ssh 等)。
|
|
62
104
|
* 空时兜底环境变量 AGENT_MOSS_SCENARIO(部署级,客户沙箱设一次,所有请求生效)。 */
|
|
63
105
|
scenarioId?: string;
|
|
@@ -8369,7 +8369,7 @@ function markIntegrity(actionDetail) {
|
|
|
8369
8369
|
}
|
|
8370
8370
|
return { integrity: "medium", isLowSource: false };
|
|
8371
8371
|
}
|
|
8372
|
-
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "") {
|
|
8372
|
+
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "", instanceId = "") {
|
|
8373
8373
|
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
8374
8374
|
return {
|
|
8375
8375
|
sessionId,
|
|
@@ -8378,13 +8378,14 @@ function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId =
|
|
|
8378
8378
|
actionDetail,
|
|
8379
8379
|
stage: markStage(actionDetail),
|
|
8380
8380
|
agentId: agentId || void 0,
|
|
8381
|
+
instanceId: instanceId || void 0,
|
|
8381
8382
|
resources: extractResources(actionDetail),
|
|
8382
8383
|
integrity,
|
|
8383
8384
|
isLowSource,
|
|
8384
8385
|
timestamp: timestamp || void 0
|
|
8385
8386
|
};
|
|
8386
8387
|
}
|
|
8387
|
-
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "") {
|
|
8388
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "", instanceId = "") {
|
|
8388
8389
|
const nodes = [];
|
|
8389
8390
|
const edges = [];
|
|
8390
8391
|
const stageFlags = /* @__PURE__ */ new Set();
|
|
@@ -8395,7 +8396,8 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8395
8396
|
actionType: String(a.actionType || ""),
|
|
8396
8397
|
actionDetail: ad,
|
|
8397
8398
|
calledAt: a.calledAt || void 0,
|
|
8398
|
-
agentId: a.agentId || agentId || void 0
|
|
8399
|
+
agentId: a.agentId || agentId || void 0,
|
|
8400
|
+
instanceId: a.instanceId || instanceId || void 0
|
|
8399
8401
|
});
|
|
8400
8402
|
}
|
|
8401
8403
|
if (aNext) {
|
|
@@ -8403,11 +8405,12 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8403
8405
|
actionType: String(aNext.actionType || ""),
|
|
8404
8406
|
actionDetail: String(aNext.actionDetail || ""),
|
|
8405
8407
|
calledAt: aNext.calledAt || void 0,
|
|
8406
|
-
agentId: aNext.agentId || agentId || void 0
|
|
8408
|
+
agentId: aNext.agentId || agentId || void 0,
|
|
8409
|
+
instanceId: aNext.instanceId || instanceId || void 0
|
|
8407
8410
|
});
|
|
8408
8411
|
}
|
|
8409
8412
|
details.forEach((d, seq) => {
|
|
8410
|
-
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "");
|
|
8413
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "", d.instanceId || "");
|
|
8411
8414
|
nodes.push(node);
|
|
8412
8415
|
if (node.stage) stageFlags.add(node.stage);
|
|
8413
8416
|
});
|
|
@@ -8419,12 +8422,18 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8419
8422
|
function buildMultiSessionGraph(sessions, aNext) {
|
|
8420
8423
|
const mg = { sessions: [], crossEdges: [] };
|
|
8421
8424
|
if (!sessions || sessions.length === 0) {
|
|
8422
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
8425
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext?.instanceId || ""));
|
|
8423
8426
|
return mg;
|
|
8424
8427
|
}
|
|
8425
8428
|
for (const s of sessions) {
|
|
8426
8429
|
const sid = String(s.sessionId || "s");
|
|
8427
|
-
mg.sessions.push(buildSessionGraph(
|
|
8430
|
+
mg.sessions.push(buildSessionGraph(
|
|
8431
|
+
s.actionHistory || [],
|
|
8432
|
+
void 0,
|
|
8433
|
+
sid,
|
|
8434
|
+
s.agentId || "",
|
|
8435
|
+
s.instanceId || ""
|
|
8436
|
+
));
|
|
8428
8437
|
}
|
|
8429
8438
|
if (aNext) {
|
|
8430
8439
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
@@ -8435,13 +8444,14 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
8435
8444
|
String(aNext.actionType || ""),
|
|
8436
8445
|
String(aNext.actionDetail || ""),
|
|
8437
8446
|
aNext.calledAt || void 0,
|
|
8438
|
-
aNext.agentId || ""
|
|
8447
|
+
aNext.agentId || "",
|
|
8448
|
+
aNext.instanceId || ""
|
|
8439
8449
|
));
|
|
8440
8450
|
if (last.nodes.length > 1) {
|
|
8441
8451
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
8442
8452
|
}
|
|
8443
8453
|
} else {
|
|
8444
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
8454
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext.instanceId || ""));
|
|
8445
8455
|
}
|
|
8446
8456
|
}
|
|
8447
8457
|
addCrossDataEdges(mg);
|
|
@@ -8483,6 +8493,34 @@ function allNodesFlat(mg) {
|
|
|
8483
8493
|
// src/engine/formal-engine/properties.ts
|
|
8484
8494
|
init_esm_shims();
|
|
8485
8495
|
init_rules_loader();
|
|
8496
|
+
function graphFromPath(nodes, path5, edgeType = "next", propertyMeta, roles, witnessLast = true, propertyExpression) {
|
|
8497
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
8498
|
+
for (const n of nodes) byKey.set(`${n.sessionId}#${n.seq}`, n);
|
|
8499
|
+
const gNodes = [];
|
|
8500
|
+
const gEdges = [];
|
|
8501
|
+
for (const item of path5) {
|
|
8502
|
+
const n = byKey.get(`${item.sessionId}#${item.seq}`);
|
|
8503
|
+
gNodes.push({
|
|
8504
|
+
session_id: item.sessionId,
|
|
8505
|
+
seq: item.seq,
|
|
8506
|
+
stage: n ? n.stage : "",
|
|
8507
|
+
action_detail: n ? n.actionDetail : item.detail,
|
|
8508
|
+
resources: n ? [...n.resources].sort() : [],
|
|
8509
|
+
instance_id: n ? n.instanceId || "" : ""
|
|
8510
|
+
});
|
|
8511
|
+
if (gNodes.length > 1) {
|
|
8512
|
+
gEdges.push({ from: gNodes.length - 2, to: gNodes.length - 1, type: edgeType });
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8515
|
+
(roles || []).forEach((role, i) => {
|
|
8516
|
+
if (i < gNodes.length && role) gNodes[i].role = role;
|
|
8517
|
+
});
|
|
8518
|
+
if (witnessLast && gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
8519
|
+
const out = { nodes: gNodes, edges: gEdges };
|
|
8520
|
+
if (propertyMeta) Object.assign(out, propertyMeta);
|
|
8521
|
+
if (propertyExpression) out.property_expression = propertyExpression;
|
|
8522
|
+
return out;
|
|
8523
|
+
}
|
|
8486
8524
|
function parseTs(ts) {
|
|
8487
8525
|
if (!ts) return null;
|
|
8488
8526
|
const t = Date.parse(String(ts).trim());
|
|
@@ -8543,7 +8581,12 @@ function checkCrossSessionChain(graph) {
|
|
|
8543
8581
|
propertyId: "cross-" + chain.id,
|
|
8544
8582
|
reason: chain.reason,
|
|
8545
8583
|
riskLevel: chain.riskLevel,
|
|
8546
|
-
path: path5
|
|
8584
|
+
path: path5,
|
|
8585
|
+
graph: graphFromPath(nodes, path5, "next", {
|
|
8586
|
+
property_id: "cross-" + chain.id,
|
|
8587
|
+
property_short: "P1",
|
|
8588
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
8589
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
8547
8590
|
});
|
|
8548
8591
|
return violations;
|
|
8549
8592
|
} else {
|
|
@@ -8563,7 +8606,12 @@ function checkCrossSessionChain(graph) {
|
|
|
8563
8606
|
propertyId: "cross-" + chain.id,
|
|
8564
8607
|
reason: chain.reason,
|
|
8565
8608
|
riskLevel: chain.riskLevel,
|
|
8566
|
-
path: path5
|
|
8609
|
+
path: path5,
|
|
8610
|
+
graph: graphFromPath(nodes, path5, "next", {
|
|
8611
|
+
property_id: "cross-" + chain.id,
|
|
8612
|
+
property_short: "P1",
|
|
8613
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
8614
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
8567
8615
|
});
|
|
8568
8616
|
}
|
|
8569
8617
|
}
|
|
@@ -8578,10 +8626,15 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8578
8626
|
for (let i = 0; i < nodes.length; i++) {
|
|
8579
8627
|
const n = nodes[i];
|
|
8580
8628
|
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
8581
|
-
tainted.set(i,
|
|
8629
|
+
tainted.set(i, {
|
|
8630
|
+
source: `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`,
|
|
8631
|
+
parent: null
|
|
8632
|
+
});
|
|
8582
8633
|
}
|
|
8583
8634
|
}
|
|
8584
8635
|
if (tainted.size === 0) return violations;
|
|
8636
|
+
const taintEdges = /* @__PURE__ */ new Map();
|
|
8637
|
+
const edgeKey = (p, c) => `${p}:${c}`;
|
|
8585
8638
|
const edges = allEdges(graph);
|
|
8586
8639
|
let changed = true;
|
|
8587
8640
|
while (changed) {
|
|
@@ -8592,7 +8645,8 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8592
8645
|
const dstRes = nodes[e.dst].resources;
|
|
8593
8646
|
for (const r of srcRes) {
|
|
8594
8647
|
if (dstRes.has(r)) {
|
|
8595
|
-
tainted.set(e.dst, tainted.get(e.src));
|
|
8648
|
+
tainted.set(e.dst, { source: tainted.get(e.src).source, parent: e.src });
|
|
8649
|
+
taintEdges.set(edgeKey(e.src, e.dst), r);
|
|
8596
8650
|
changed = true;
|
|
8597
8651
|
break;
|
|
8598
8652
|
}
|
|
@@ -8600,16 +8654,76 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8600
8654
|
}
|
|
8601
8655
|
}
|
|
8602
8656
|
}
|
|
8657
|
+
const tracePath = (idx) => {
|
|
8658
|
+
const chain = [idx];
|
|
8659
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
8660
|
+
while (true) {
|
|
8661
|
+
const parent = tainted.get(chain[chain.length - 1])?.parent;
|
|
8662
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
8663
|
+
chain.push(parent);
|
|
8664
|
+
seen.add(parent);
|
|
8665
|
+
}
|
|
8666
|
+
return chain.reverse();
|
|
8667
|
+
};
|
|
8668
|
+
const taintGraph = (chain) => {
|
|
8669
|
+
const gNodes = [];
|
|
8670
|
+
const gEdges = [];
|
|
8671
|
+
chain.forEach((ni, k) => {
|
|
8672
|
+
const n = nodes[ni];
|
|
8673
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
8674
|
+
gNodes.push({
|
|
8675
|
+
session_id: n.sessionId,
|
|
8676
|
+
seq: n.seq,
|
|
8677
|
+
stage: n.stage,
|
|
8678
|
+
action_detail: n.actionDetail,
|
|
8679
|
+
resources: [...n.resources].sort(),
|
|
8680
|
+
role,
|
|
8681
|
+
instance_id: n.instanceId || ""
|
|
8682
|
+
});
|
|
8683
|
+
if (k > 0) {
|
|
8684
|
+
const prev = chain[k - 1];
|
|
8685
|
+
gEdges.push({
|
|
8686
|
+
from: k - 1,
|
|
8687
|
+
to: k,
|
|
8688
|
+
type: "data",
|
|
8689
|
+
label: taintEdges.get(edgeKey(prev, ni)) || ""
|
|
8690
|
+
});
|
|
8691
|
+
}
|
|
8692
|
+
});
|
|
8693
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
8694
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
8695
|
+
if (chain.length > 2) {
|
|
8696
|
+
expr.push({
|
|
8697
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
8698
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
8699
|
+
});
|
|
8700
|
+
}
|
|
8701
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
8702
|
+
return {
|
|
8703
|
+
property_id: "sensitive-data-flow",
|
|
8704
|
+
property_short: "P2",
|
|
8705
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
8706
|
+
property_expression: expr,
|
|
8707
|
+
nodes: gNodes,
|
|
8708
|
+
edges: gEdges
|
|
8709
|
+
};
|
|
8710
|
+
};
|
|
8603
8711
|
const witness = graph.witnessIdx;
|
|
8604
8712
|
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
8605
8713
|
for (const i of candidates) {
|
|
8606
8714
|
const n = nodes[i];
|
|
8607
8715
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
8716
|
+
const chain = tracePath(i);
|
|
8608
8717
|
violations.push({
|
|
8609
8718
|
propertyId: "sensitive-data-flow",
|
|
8610
|
-
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
8719
|
+
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i).source} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
8611
8720
|
riskLevel: "critical",
|
|
8612
|
-
path:
|
|
8721
|
+
path: chain.map((j) => ({
|
|
8722
|
+
sessionId: nodes[j].sessionId,
|
|
8723
|
+
seq: nodes[j].seq,
|
|
8724
|
+
detail: nodes[j].actionDetail.slice(0, 60)
|
|
8725
|
+
})),
|
|
8726
|
+
graph: taintGraph(chain)
|
|
8613
8727
|
});
|
|
8614
8728
|
if (witness !== void 0) return violations;
|
|
8615
8729
|
}
|
|
@@ -9332,12 +9446,21 @@ async function runAnalysis(input) {
|
|
|
9332
9446
|
formalSessions.push({
|
|
9333
9447
|
sessionId: s.sessionId || "s",
|
|
9334
9448
|
...s.agentId ? { agentId: s.agentId } : {},
|
|
9449
|
+
...s.instanceId ? { instanceId: s.instanceId } : {},
|
|
9335
9450
|
actionHistory: s.actionHistory || []
|
|
9336
9451
|
});
|
|
9337
9452
|
}
|
|
9338
9453
|
}
|
|
9339
|
-
formalSessions.push({
|
|
9340
|
-
|
|
9454
|
+
formalSessions.push({
|
|
9455
|
+
sessionId: "current",
|
|
9456
|
+
agentId: input.agentId,
|
|
9457
|
+
instanceId: input.instanceId || "",
|
|
9458
|
+
actionHistory: input.actionHistory
|
|
9459
|
+
});
|
|
9460
|
+
const formalResult = formalEngine.verify(formalSessions, {
|
|
9461
|
+
...input.aNext,
|
|
9462
|
+
instanceId: input.instanceId || ""
|
|
9463
|
+
});
|
|
9341
9464
|
if (!formalResult.verified) {
|
|
9342
9465
|
const v = formalResult.violations[0];
|
|
9343
9466
|
if (isFormalObserveMode()) {
|
|
@@ -9353,7 +9476,9 @@ async function runAnalysis(input) {
|
|
|
9353
9476
|
riskType: "formal_verification",
|
|
9354
9477
|
confidence: 90,
|
|
9355
9478
|
source: "formal_engine",
|
|
9356
|
-
violatedLayers: ["L2.2"]
|
|
9479
|
+
violatedLayers: ["L2.2"],
|
|
9480
|
+
violationGraph: v?.graph ?? null,
|
|
9481
|
+
violationChain: (v?.path ?? []).map((p) => [p.sessionId, p.seq, p.detail])
|
|
9357
9482
|
};
|
|
9358
9483
|
}
|
|
9359
9484
|
}
|