@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.
@@ -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(s.actionHistory || [], void 0, sid, s.agentId || ""));
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, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
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: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
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({ sessionId: "current", agentId: input.agentId, actionHistory: input.actionHistory });
9340
- const formalResult = formalEngine.verify(formalSessions, input.aNext);
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
  }