@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.
@@ -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, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
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: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
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({ sessionId: s.sessionId || "s", actionHistory: s.actionHistory || [] });
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
  }