@kenhkl/agent-moss 0.17.6 → 0.17.8
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 +398 -42
- package/dist/engine/coordinator.d.ts +38 -0
- package/dist/engine/coordinator.js +129 -17
- package/dist/server.js +398 -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 的连接符切成若干段,去掉空白段。
|
|
@@ -2249,7 +2249,7 @@ function _scenarioRulesToDefaults() {
|
|
|
2249
2249
|
if (!existsSync5(scenDir)) return out;
|
|
2250
2250
|
let entries;
|
|
2251
2251
|
try {
|
|
2252
|
-
entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2252
|
+
entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("__")).map((d) => d.name);
|
|
2253
2253
|
} catch {
|
|
2254
2254
|
return out;
|
|
2255
2255
|
}
|
|
@@ -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) {
|
|
8372
|
+
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "") {
|
|
8373
8373
|
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
8374
8374
|
return {
|
|
8375
8375
|
sessionId,
|
|
@@ -8377,13 +8377,14 @@ function initNode(sessionId, seq, actionType, actionDetail, timestamp) {
|
|
|
8377
8377
|
actionType,
|
|
8378
8378
|
actionDetail,
|
|
8379
8379
|
stage: markStage(actionDetail),
|
|
8380
|
+
agentId: agentId || void 0,
|
|
8380
8381
|
resources: extractResources(actionDetail),
|
|
8381
8382
|
integrity,
|
|
8382
8383
|
isLowSource,
|
|
8383
8384
|
timestamp: timestamp || void 0
|
|
8384
8385
|
};
|
|
8385
8386
|
}
|
|
8386
|
-
function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
8387
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "") {
|
|
8387
8388
|
const nodes = [];
|
|
8388
8389
|
const edges = [];
|
|
8389
8390
|
const stageFlags = /* @__PURE__ */ new Set();
|
|
@@ -8393,18 +8394,20 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
|
8393
8394
|
if (ad) details.push({
|
|
8394
8395
|
actionType: String(a.actionType || ""),
|
|
8395
8396
|
actionDetail: ad,
|
|
8396
|
-
calledAt: a.calledAt || void 0
|
|
8397
|
+
calledAt: a.calledAt || void 0,
|
|
8398
|
+
agentId: a.agentId || agentId || void 0
|
|
8397
8399
|
});
|
|
8398
8400
|
}
|
|
8399
8401
|
if (aNext) {
|
|
8400
8402
|
details.push({
|
|
8401
8403
|
actionType: String(aNext.actionType || ""),
|
|
8402
8404
|
actionDetail: String(aNext.actionDetail || ""),
|
|
8403
|
-
calledAt: aNext.calledAt || void 0
|
|
8405
|
+
calledAt: aNext.calledAt || void 0,
|
|
8406
|
+
agentId: aNext.agentId || agentId || void 0
|
|
8404
8407
|
});
|
|
8405
8408
|
}
|
|
8406
8409
|
details.forEach((d, seq) => {
|
|
8407
|
-
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt);
|
|
8410
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "");
|
|
8408
8411
|
nodes.push(node);
|
|
8409
8412
|
if (node.stage) stageFlags.add(node.stage);
|
|
8410
8413
|
});
|
|
@@ -8421,7 +8424,7 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
8421
8424
|
}
|
|
8422
8425
|
for (const s of sessions) {
|
|
8423
8426
|
const sid = String(s.sessionId || "s");
|
|
8424
|
-
mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid));
|
|
8427
|
+
mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid, s.agentId || ""));
|
|
8425
8428
|
}
|
|
8426
8429
|
if (aNext) {
|
|
8427
8430
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
@@ -8431,7 +8434,8 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
8431
8434
|
last.nodes.length,
|
|
8432
8435
|
String(aNext.actionType || ""),
|
|
8433
8436
|
String(aNext.actionDetail || ""),
|
|
8434
|
-
aNext.calledAt || void 0
|
|
8437
|
+
aNext.calledAt || void 0,
|
|
8438
|
+
aNext.agentId || ""
|
|
8435
8439
|
));
|
|
8436
8440
|
if (last.nodes.length > 1) {
|
|
8437
8441
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
@@ -8479,6 +8483,33 @@ function allNodesFlat(mg) {
|
|
|
8479
8483
|
// src/engine/formal-engine/properties.ts
|
|
8480
8484
|
init_esm_shims();
|
|
8481
8485
|
init_rules_loader();
|
|
8486
|
+
function graphFromPath(nodes, path5, edgeType = "next", propertyMeta, roles, witnessLast = true, propertyExpression) {
|
|
8487
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
8488
|
+
for (const n of nodes) byKey.set(`${n.sessionId}#${n.seq}`, n);
|
|
8489
|
+
const gNodes = [];
|
|
8490
|
+
const gEdges = [];
|
|
8491
|
+
for (const item of path5) {
|
|
8492
|
+
const n = byKey.get(`${item.sessionId}#${item.seq}`);
|
|
8493
|
+
gNodes.push({
|
|
8494
|
+
session_id: item.sessionId,
|
|
8495
|
+
seq: item.seq,
|
|
8496
|
+
stage: n ? n.stage : "",
|
|
8497
|
+
action_detail: n ? n.actionDetail : item.detail,
|
|
8498
|
+
resources: n ? [...n.resources].sort() : []
|
|
8499
|
+
});
|
|
8500
|
+
if (gNodes.length > 1) {
|
|
8501
|
+
gEdges.push({ from: gNodes.length - 2, to: gNodes.length - 1, type: edgeType });
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
(roles || []).forEach((role, i) => {
|
|
8505
|
+
if (i < gNodes.length && role) gNodes[i].role = role;
|
|
8506
|
+
});
|
|
8507
|
+
if (witnessLast && gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
8508
|
+
const out = { nodes: gNodes, edges: gEdges };
|
|
8509
|
+
if (propertyMeta) Object.assign(out, propertyMeta);
|
|
8510
|
+
if (propertyExpression) out.property_expression = propertyExpression;
|
|
8511
|
+
return out;
|
|
8512
|
+
}
|
|
8482
8513
|
function parseTs(ts) {
|
|
8483
8514
|
if (!ts) return null;
|
|
8484
8515
|
const t = Date.parse(String(ts).trim());
|
|
@@ -8539,7 +8570,12 @@ function checkCrossSessionChain(graph) {
|
|
|
8539
8570
|
propertyId: "cross-" + chain.id,
|
|
8540
8571
|
reason: chain.reason,
|
|
8541
8572
|
riskLevel: chain.riskLevel,
|
|
8542
|
-
path: path5
|
|
8573
|
+
path: path5,
|
|
8574
|
+
graph: graphFromPath(nodes, path5, "next", {
|
|
8575
|
+
property_id: "cross-" + chain.id,
|
|
8576
|
+
property_short: "P1",
|
|
8577
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
8578
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
8543
8579
|
});
|
|
8544
8580
|
return violations;
|
|
8545
8581
|
} else {
|
|
@@ -8559,7 +8595,12 @@ function checkCrossSessionChain(graph) {
|
|
|
8559
8595
|
propertyId: "cross-" + chain.id,
|
|
8560
8596
|
reason: chain.reason,
|
|
8561
8597
|
riskLevel: chain.riskLevel,
|
|
8562
|
-
path: path5
|
|
8598
|
+
path: path5,
|
|
8599
|
+
graph: graphFromPath(nodes, path5, "next", {
|
|
8600
|
+
property_id: "cross-" + chain.id,
|
|
8601
|
+
property_short: "P1",
|
|
8602
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
8603
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
8563
8604
|
});
|
|
8564
8605
|
}
|
|
8565
8606
|
}
|
|
@@ -8574,10 +8615,15 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8574
8615
|
for (let i = 0; i < nodes.length; i++) {
|
|
8575
8616
|
const n = nodes[i];
|
|
8576
8617
|
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
8577
|
-
tainted.set(i,
|
|
8618
|
+
tainted.set(i, {
|
|
8619
|
+
source: `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`,
|
|
8620
|
+
parent: null
|
|
8621
|
+
});
|
|
8578
8622
|
}
|
|
8579
8623
|
}
|
|
8580
8624
|
if (tainted.size === 0) return violations;
|
|
8625
|
+
const taintEdges = /* @__PURE__ */ new Map();
|
|
8626
|
+
const edgeKey = (p, c) => `${p}:${c}`;
|
|
8581
8627
|
const edges = allEdges(graph);
|
|
8582
8628
|
let changed = true;
|
|
8583
8629
|
while (changed) {
|
|
@@ -8588,7 +8634,8 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8588
8634
|
const dstRes = nodes[e.dst].resources;
|
|
8589
8635
|
for (const r of srcRes) {
|
|
8590
8636
|
if (dstRes.has(r)) {
|
|
8591
|
-
tainted.set(e.dst, tainted.get(e.src));
|
|
8637
|
+
tainted.set(e.dst, { source: tainted.get(e.src).source, parent: e.src });
|
|
8638
|
+
taintEdges.set(edgeKey(e.src, e.dst), r);
|
|
8592
8639
|
changed = true;
|
|
8593
8640
|
break;
|
|
8594
8641
|
}
|
|
@@ -8596,16 +8643,75 @@ function checkSensitiveDataFlow(graph) {
|
|
|
8596
8643
|
}
|
|
8597
8644
|
}
|
|
8598
8645
|
}
|
|
8646
|
+
const tracePath = (idx) => {
|
|
8647
|
+
const chain = [idx];
|
|
8648
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
8649
|
+
while (true) {
|
|
8650
|
+
const parent = tainted.get(chain[chain.length - 1])?.parent;
|
|
8651
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
8652
|
+
chain.push(parent);
|
|
8653
|
+
seen.add(parent);
|
|
8654
|
+
}
|
|
8655
|
+
return chain.reverse();
|
|
8656
|
+
};
|
|
8657
|
+
const taintGraph = (chain) => {
|
|
8658
|
+
const gNodes = [];
|
|
8659
|
+
const gEdges = [];
|
|
8660
|
+
chain.forEach((ni, k) => {
|
|
8661
|
+
const n = nodes[ni];
|
|
8662
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
8663
|
+
gNodes.push({
|
|
8664
|
+
session_id: n.sessionId,
|
|
8665
|
+
seq: n.seq,
|
|
8666
|
+
stage: n.stage,
|
|
8667
|
+
action_detail: n.actionDetail,
|
|
8668
|
+
resources: [...n.resources].sort(),
|
|
8669
|
+
role
|
|
8670
|
+
});
|
|
8671
|
+
if (k > 0) {
|
|
8672
|
+
const prev = chain[k - 1];
|
|
8673
|
+
gEdges.push({
|
|
8674
|
+
from: k - 1,
|
|
8675
|
+
to: k,
|
|
8676
|
+
type: "data",
|
|
8677
|
+
label: taintEdges.get(edgeKey(prev, ni)) || ""
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
});
|
|
8681
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
8682
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
8683
|
+
if (chain.length > 2) {
|
|
8684
|
+
expr.push({
|
|
8685
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
8686
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
8687
|
+
});
|
|
8688
|
+
}
|
|
8689
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
8690
|
+
return {
|
|
8691
|
+
property_id: "sensitive-data-flow",
|
|
8692
|
+
property_short: "P2",
|
|
8693
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
8694
|
+
property_expression: expr,
|
|
8695
|
+
nodes: gNodes,
|
|
8696
|
+
edges: gEdges
|
|
8697
|
+
};
|
|
8698
|
+
};
|
|
8599
8699
|
const witness = graph.witnessIdx;
|
|
8600
8700
|
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
8601
8701
|
for (const i of candidates) {
|
|
8602
8702
|
const n = nodes[i];
|
|
8603
8703
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
8704
|
+
const chain = tracePath(i);
|
|
8604
8705
|
violations.push({
|
|
8605
8706
|
propertyId: "sensitive-data-flow",
|
|
8606
|
-
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`,
|
|
8707
|
+
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`,
|
|
8607
8708
|
riskLevel: "critical",
|
|
8608
|
-
path:
|
|
8709
|
+
path: chain.map((j) => ({
|
|
8710
|
+
sessionId: nodes[j].sessionId,
|
|
8711
|
+
seq: nodes[j].seq,
|
|
8712
|
+
detail: nodes[j].actionDetail.slice(0, 60)
|
|
8713
|
+
})),
|
|
8714
|
+
graph: taintGraph(chain)
|
|
8609
8715
|
});
|
|
8610
8716
|
if (witness !== void 0) return violations;
|
|
8611
8717
|
}
|
|
@@ -9325,10 +9431,14 @@ async function runAnalysis(input) {
|
|
|
9325
9431
|
const formalSessions = [];
|
|
9326
9432
|
if (input.sessionHistory) {
|
|
9327
9433
|
for (const s of input.sessionHistory) {
|
|
9328
|
-
formalSessions.push({
|
|
9434
|
+
formalSessions.push({
|
|
9435
|
+
sessionId: s.sessionId || "s",
|
|
9436
|
+
...s.agentId ? { agentId: s.agentId } : {},
|
|
9437
|
+
actionHistory: s.actionHistory || []
|
|
9438
|
+
});
|
|
9329
9439
|
}
|
|
9330
9440
|
}
|
|
9331
|
-
formalSessions.push({ sessionId: "current", actionHistory: input.actionHistory });
|
|
9441
|
+
formalSessions.push({ sessionId: "current", agentId: input.agentId, actionHistory: input.actionHistory });
|
|
9332
9442
|
const formalResult = formalEngine.verify(formalSessions, input.aNext);
|
|
9333
9443
|
if (!formalResult.verified) {
|
|
9334
9444
|
const v = formalResult.violations[0];
|
|
@@ -9345,7 +9455,9 @@ async function runAnalysis(input) {
|
|
|
9345
9455
|
riskType: "formal_verification",
|
|
9346
9456
|
confidence: 90,
|
|
9347
9457
|
source: "formal_engine",
|
|
9348
|
-
violatedLayers: ["L2.2"]
|
|
9458
|
+
violatedLayers: ["L2.2"],
|
|
9459
|
+
violationGraph: v?.graph ?? null,
|
|
9460
|
+
violationChain: (v?.path ?? []).map((p) => [p.sessionId, p.seq, p.detail])
|
|
9349
9461
|
};
|
|
9350
9462
|
}
|
|
9351
9463
|
}
|