@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
package/dist/cli.js
CHANGED
|
@@ -1246,18 +1246,43 @@ function hardenExistingPaths() {
|
|
|
1246
1246
|
function chainDetailMode() {
|
|
1247
1247
|
return (env("AGENT_MOSS_CHAIN_DETAIL", "redacted") || "redacted").toLowerCase();
|
|
1248
1248
|
}
|
|
1249
|
-
function redactChain(chain, currentSessionId) {
|
|
1249
|
+
function redactChain(chain, currentInstanceId = "", currentSessionId = "") {
|
|
1250
1250
|
if (!chain || chain.length === 0) return [];
|
|
1251
1251
|
if (chainDetailMode() === "full") return [...chain];
|
|
1252
1252
|
return chain.map((step) => {
|
|
1253
1253
|
if (Array.isArray(step) && step.length >= 3) {
|
|
1254
1254
|
const [sid, seq, , ts] = step;
|
|
1255
|
-
|
|
1255
|
+
const inst = step.length > 5 ? String(step[5] ?? "") : "";
|
|
1256
|
+
const sameInstance = !!(inst && currentInstanceId && inst === String(currentInstanceId));
|
|
1257
|
+
const sameSessionFallback = !inst && !!currentSessionId && String(sid) === String(currentSessionId);
|
|
1258
|
+
if (sameInstance || sameSessionFallback) return [...step];
|
|
1256
1259
|
return ["<other-session>", seq, "<\u5DF2\u9690\u85CF>", ts ?? ""];
|
|
1257
1260
|
}
|
|
1258
1261
|
return step;
|
|
1259
1262
|
});
|
|
1260
1263
|
}
|
|
1264
|
+
function redactGraph(graph, currentInstanceId = "", currentSessionId = "") {
|
|
1265
|
+
if (!graph) return null;
|
|
1266
|
+
if (chainDetailMode() === "full") return graph;
|
|
1267
|
+
const nodes = (graph.nodes || []).map((n) => {
|
|
1268
|
+
const m = { ...n };
|
|
1269
|
+
const inst = String(m.instance_id ?? "") || "";
|
|
1270
|
+
const sid = String(m.session_id ?? "") || "";
|
|
1271
|
+
const sameInstance = !!(inst && currentInstanceId && inst === String(currentInstanceId));
|
|
1272
|
+
const sameSessionFallback = !inst && !!currentSessionId && sid === String(currentSessionId);
|
|
1273
|
+
if (!(sameInstance || sameSessionFallback)) {
|
|
1274
|
+
m.session_id = "<other-session>";
|
|
1275
|
+
m.action_detail = "<\u5DF2\u9690\u85CF>";
|
|
1276
|
+
m.resources = [];
|
|
1277
|
+
}
|
|
1278
|
+
return m;
|
|
1279
|
+
});
|
|
1280
|
+
const out = { nodes, edges: graph.edges || [] };
|
|
1281
|
+
for (const key of ["property_id", "property_short", "property_name", "property_expression"]) {
|
|
1282
|
+
if (graph[key] !== void 0) out[key] = graph[key];
|
|
1283
|
+
}
|
|
1284
|
+
return out;
|
|
1285
|
+
}
|
|
1261
1286
|
function sessionSweepThreshold() {
|
|
1262
1287
|
return envInt("AGENT_MOSS_SESSION_SWEEP_THRESHOLD", 50);
|
|
1263
1288
|
}
|
|
@@ -1419,6 +1444,8 @@ var init_models = __esm({
|
|
|
1419
1444
|
session_id: z.string(),
|
|
1420
1445
|
/** 来源 agent(AgentGate 逐会话带上):记录在链路径里供告警/取证,不参与链匹配。 */
|
|
1421
1446
|
agent_id: z.string().optional(),
|
|
1447
|
+
/** 来源实例:仅用于展示层脱敏(同实例不隐藏),不参与链匹配。 */
|
|
1448
|
+
instance_id: z.string().optional(),
|
|
1422
1449
|
action_history: z.array(ActionItemSchema).default([])
|
|
1423
1450
|
})).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
|
|
1424
1451
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
@@ -1426,6 +1453,7 @@ var init_models = __esm({
|
|
|
1426
1453
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
1427
1454
|
cwd: z.string().default("").describe("Current working directory"),
|
|
1428
1455
|
agent_id: z.string().default("").describe("Caller agent identifier, e.g. 'xiaoo' / 'opencode' / 'opendesk'. Empty = generic mode (no agent-specific rules)."),
|
|
1456
|
+
instance_id: z.string().default("").describe("Agent installation/instance identifier. Used only for display-layer redaction: nodes from the same instance are returned unredacted. Does not affect detection."),
|
|
1429
1457
|
metadata: z.record(z.unknown()).optional().describe("Extended metadata")
|
|
1430
1458
|
});
|
|
1431
1459
|
AnalyzeResponseSchema = z.object({
|
|
@@ -1439,7 +1467,44 @@ var init_models = __esm({
|
|
|
1439
1467
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
1440
1468
|
analysis_duration_ms: z.number().default(0),
|
|
1441
1469
|
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
1442
|
-
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
1470
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([]),
|
|
1471
|
+
// 结构化攻击链图(前端可视化):节点 + 边(next 时序 / data 数据流)
|
|
1472
|
+
violation_graph: z.object({
|
|
1473
|
+
// 性质元信息(前端展示"违反了哪条性质")
|
|
1474
|
+
property_id: z.string().optional(),
|
|
1475
|
+
property_short: z.string().optional(),
|
|
1476
|
+
property_name: z.string().optional(),
|
|
1477
|
+
// 性质表达式 token → 节点位置(前端展示"表达式对应哪个节点")
|
|
1478
|
+
property_expression: z.array(
|
|
1479
|
+
z.object({
|
|
1480
|
+
token: z.string(),
|
|
1481
|
+
node_index: z.union([z.number(), z.array(z.number())])
|
|
1482
|
+
})
|
|
1483
|
+
).optional(),
|
|
1484
|
+
nodes: z.array(
|
|
1485
|
+
z.object({
|
|
1486
|
+
session_id: z.string().default(""),
|
|
1487
|
+
seq: z.number().default(0),
|
|
1488
|
+
stage: z.string().default(""),
|
|
1489
|
+
action_detail: z.string().default(""),
|
|
1490
|
+
resources: z.array(z.string()).default([]),
|
|
1491
|
+
// 节点在性质中的角色:source/carrier/sink(P2)或阶段名(P1)
|
|
1492
|
+
role: z.string().optional(),
|
|
1493
|
+
// 是否为违例见证节点(当前动作补全链 / 触发外传)
|
|
1494
|
+
witness: z.boolean().optional(),
|
|
1495
|
+
// 来源实例(仅展示层脱敏:同实例不隐藏)
|
|
1496
|
+
instance_id: z.string().optional()
|
|
1497
|
+
})
|
|
1498
|
+
),
|
|
1499
|
+
edges: z.array(
|
|
1500
|
+
z.object({
|
|
1501
|
+
from: z.number(),
|
|
1502
|
+
to: z.number(),
|
|
1503
|
+
type: z.enum(["next", "data"]).default("next"),
|
|
1504
|
+
label: z.string().optional()
|
|
1505
|
+
})
|
|
1506
|
+
)
|
|
1507
|
+
}).nullable().default(null)
|
|
1443
1508
|
});
|
|
1444
1509
|
HealthResponseSchema = z.object({
|
|
1445
1510
|
status: z.string().default("healthy"),
|
|
@@ -9387,7 +9452,7 @@ function markIntegrity(actionDetail) {
|
|
|
9387
9452
|
}
|
|
9388
9453
|
return { integrity: "medium", isLowSource: false };
|
|
9389
9454
|
}
|
|
9390
|
-
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "") {
|
|
9455
|
+
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "", instanceId = "") {
|
|
9391
9456
|
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
9392
9457
|
return {
|
|
9393
9458
|
sessionId,
|
|
@@ -9396,13 +9461,14 @@ function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId =
|
|
|
9396
9461
|
actionDetail,
|
|
9397
9462
|
stage: markStage(actionDetail),
|
|
9398
9463
|
agentId: agentId || void 0,
|
|
9464
|
+
instanceId: instanceId || void 0,
|
|
9399
9465
|
resources: extractResources(actionDetail),
|
|
9400
9466
|
integrity,
|
|
9401
9467
|
isLowSource,
|
|
9402
9468
|
timestamp: timestamp || void 0
|
|
9403
9469
|
};
|
|
9404
9470
|
}
|
|
9405
|
-
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "") {
|
|
9471
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "", instanceId = "") {
|
|
9406
9472
|
const nodes = [];
|
|
9407
9473
|
const edges = [];
|
|
9408
9474
|
const stageFlags = /* @__PURE__ */ new Set();
|
|
@@ -9413,7 +9479,8 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
9413
9479
|
actionType: String(a.actionType || ""),
|
|
9414
9480
|
actionDetail: ad,
|
|
9415
9481
|
calledAt: a.calledAt || void 0,
|
|
9416
|
-
agentId: a.agentId || agentId || void 0
|
|
9482
|
+
agentId: a.agentId || agentId || void 0,
|
|
9483
|
+
instanceId: a.instanceId || instanceId || void 0
|
|
9417
9484
|
});
|
|
9418
9485
|
}
|
|
9419
9486
|
if (aNext) {
|
|
@@ -9421,11 +9488,12 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
9421
9488
|
actionType: String(aNext.actionType || ""),
|
|
9422
9489
|
actionDetail: String(aNext.actionDetail || ""),
|
|
9423
9490
|
calledAt: aNext.calledAt || void 0,
|
|
9424
|
-
agentId: aNext.agentId || agentId || void 0
|
|
9491
|
+
agentId: aNext.agentId || agentId || void 0,
|
|
9492
|
+
instanceId: aNext.instanceId || instanceId || void 0
|
|
9425
9493
|
});
|
|
9426
9494
|
}
|
|
9427
9495
|
details.forEach((d, seq) => {
|
|
9428
|
-
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "");
|
|
9496
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "", d.instanceId || "");
|
|
9429
9497
|
nodes.push(node);
|
|
9430
9498
|
if (node.stage) stageFlags.add(node.stage);
|
|
9431
9499
|
});
|
|
@@ -9437,12 +9505,18 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
9437
9505
|
function buildMultiSessionGraph(sessions, aNext) {
|
|
9438
9506
|
const mg = { sessions: [], crossEdges: [] };
|
|
9439
9507
|
if (!sessions || sessions.length === 0) {
|
|
9440
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
9508
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext?.instanceId || ""));
|
|
9441
9509
|
return mg;
|
|
9442
9510
|
}
|
|
9443
9511
|
for (const s of sessions) {
|
|
9444
9512
|
const sid = String(s.sessionId || "s");
|
|
9445
|
-
mg.sessions.push(buildSessionGraph(
|
|
9513
|
+
mg.sessions.push(buildSessionGraph(
|
|
9514
|
+
s.actionHistory || [],
|
|
9515
|
+
void 0,
|
|
9516
|
+
sid,
|
|
9517
|
+
s.agentId || "",
|
|
9518
|
+
s.instanceId || ""
|
|
9519
|
+
));
|
|
9446
9520
|
}
|
|
9447
9521
|
if (aNext) {
|
|
9448
9522
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
@@ -9453,13 +9527,14 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
9453
9527
|
String(aNext.actionType || ""),
|
|
9454
9528
|
String(aNext.actionDetail || ""),
|
|
9455
9529
|
aNext.calledAt || void 0,
|
|
9456
|
-
aNext.agentId || ""
|
|
9530
|
+
aNext.agentId || "",
|
|
9531
|
+
aNext.instanceId || ""
|
|
9457
9532
|
));
|
|
9458
9533
|
if (last.nodes.length > 1) {
|
|
9459
9534
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
9460
9535
|
}
|
|
9461
9536
|
} else {
|
|
9462
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
9537
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext.instanceId || ""));
|
|
9463
9538
|
}
|
|
9464
9539
|
}
|
|
9465
9540
|
addCrossDataEdges(mg);
|
|
@@ -9511,6 +9586,34 @@ var init_builder = __esm({
|
|
|
9511
9586
|
});
|
|
9512
9587
|
|
|
9513
9588
|
// src/engine/formal-engine/properties.ts
|
|
9589
|
+
function graphFromPath(nodes, path6, edgeType = "next", propertyMeta, roles, witnessLast = true, propertyExpression) {
|
|
9590
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9591
|
+
for (const n of nodes) byKey.set(`${n.sessionId}#${n.seq}`, n);
|
|
9592
|
+
const gNodes = [];
|
|
9593
|
+
const gEdges = [];
|
|
9594
|
+
for (const item of path6) {
|
|
9595
|
+
const n = byKey.get(`${item.sessionId}#${item.seq}`);
|
|
9596
|
+
gNodes.push({
|
|
9597
|
+
session_id: item.sessionId,
|
|
9598
|
+
seq: item.seq,
|
|
9599
|
+
stage: n ? n.stage : "",
|
|
9600
|
+
action_detail: n ? n.actionDetail : item.detail,
|
|
9601
|
+
resources: n ? [...n.resources].sort() : [],
|
|
9602
|
+
instance_id: n ? n.instanceId || "" : ""
|
|
9603
|
+
});
|
|
9604
|
+
if (gNodes.length > 1) {
|
|
9605
|
+
gEdges.push({ from: gNodes.length - 2, to: gNodes.length - 1, type: edgeType });
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
(roles || []).forEach((role, i) => {
|
|
9609
|
+
if (i < gNodes.length && role) gNodes[i].role = role;
|
|
9610
|
+
});
|
|
9611
|
+
if (witnessLast && gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
9612
|
+
const out = { nodes: gNodes, edges: gEdges };
|
|
9613
|
+
if (propertyMeta) Object.assign(out, propertyMeta);
|
|
9614
|
+
if (propertyExpression) out.property_expression = propertyExpression;
|
|
9615
|
+
return out;
|
|
9616
|
+
}
|
|
9514
9617
|
function parseTs(ts) {
|
|
9515
9618
|
if (!ts) return null;
|
|
9516
9619
|
const t = Date.parse(String(ts).trim());
|
|
@@ -9568,7 +9671,12 @@ function checkCrossSessionChain(graph) {
|
|
|
9568
9671
|
propertyId: "cross-" + chain.id,
|
|
9569
9672
|
reason: chain.reason,
|
|
9570
9673
|
riskLevel: chain.riskLevel,
|
|
9571
|
-
path: path6
|
|
9674
|
+
path: path6,
|
|
9675
|
+
graph: graphFromPath(nodes, path6, "next", {
|
|
9676
|
+
property_id: "cross-" + chain.id,
|
|
9677
|
+
property_short: "P1",
|
|
9678
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
9679
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
9572
9680
|
});
|
|
9573
9681
|
return violations;
|
|
9574
9682
|
} else {
|
|
@@ -9588,7 +9696,12 @@ function checkCrossSessionChain(graph) {
|
|
|
9588
9696
|
propertyId: "cross-" + chain.id,
|
|
9589
9697
|
reason: chain.reason,
|
|
9590
9698
|
riskLevel: chain.riskLevel,
|
|
9591
|
-
path: path6
|
|
9699
|
+
path: path6,
|
|
9700
|
+
graph: graphFromPath(nodes, path6, "next", {
|
|
9701
|
+
property_id: "cross-" + chain.id,
|
|
9702
|
+
property_short: "P1",
|
|
9703
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
9704
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
9592
9705
|
});
|
|
9593
9706
|
}
|
|
9594
9707
|
}
|
|
@@ -9603,10 +9716,15 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9603
9716
|
for (let i = 0; i < nodes.length; i++) {
|
|
9604
9717
|
const n = nodes[i];
|
|
9605
9718
|
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
9606
|
-
tainted.set(i,
|
|
9719
|
+
tainted.set(i, {
|
|
9720
|
+
source: `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`,
|
|
9721
|
+
parent: null
|
|
9722
|
+
});
|
|
9607
9723
|
}
|
|
9608
9724
|
}
|
|
9609
9725
|
if (tainted.size === 0) return violations;
|
|
9726
|
+
const taintEdges = /* @__PURE__ */ new Map();
|
|
9727
|
+
const edgeKey = (p, c) => `${p}:${c}`;
|
|
9610
9728
|
const edges = allEdges(graph);
|
|
9611
9729
|
let changed = true;
|
|
9612
9730
|
while (changed) {
|
|
@@ -9617,7 +9735,8 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9617
9735
|
const dstRes = nodes[e.dst].resources;
|
|
9618
9736
|
for (const r of srcRes) {
|
|
9619
9737
|
if (dstRes.has(r)) {
|
|
9620
|
-
tainted.set(e.dst, tainted.get(e.src));
|
|
9738
|
+
tainted.set(e.dst, { source: tainted.get(e.src).source, parent: e.src });
|
|
9739
|
+
taintEdges.set(edgeKey(e.src, e.dst), r);
|
|
9621
9740
|
changed = true;
|
|
9622
9741
|
break;
|
|
9623
9742
|
}
|
|
@@ -9625,16 +9744,76 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9625
9744
|
}
|
|
9626
9745
|
}
|
|
9627
9746
|
}
|
|
9747
|
+
const tracePath = (idx) => {
|
|
9748
|
+
const chain = [idx];
|
|
9749
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
9750
|
+
while (true) {
|
|
9751
|
+
const parent = tainted.get(chain[chain.length - 1])?.parent;
|
|
9752
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
9753
|
+
chain.push(parent);
|
|
9754
|
+
seen.add(parent);
|
|
9755
|
+
}
|
|
9756
|
+
return chain.reverse();
|
|
9757
|
+
};
|
|
9758
|
+
const taintGraph = (chain) => {
|
|
9759
|
+
const gNodes = [];
|
|
9760
|
+
const gEdges = [];
|
|
9761
|
+
chain.forEach((ni, k) => {
|
|
9762
|
+
const n = nodes[ni];
|
|
9763
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
9764
|
+
gNodes.push({
|
|
9765
|
+
session_id: n.sessionId,
|
|
9766
|
+
seq: n.seq,
|
|
9767
|
+
stage: n.stage,
|
|
9768
|
+
action_detail: n.actionDetail,
|
|
9769
|
+
resources: [...n.resources].sort(),
|
|
9770
|
+
role,
|
|
9771
|
+
instance_id: n.instanceId || ""
|
|
9772
|
+
});
|
|
9773
|
+
if (k > 0) {
|
|
9774
|
+
const prev = chain[k - 1];
|
|
9775
|
+
gEdges.push({
|
|
9776
|
+
from: k - 1,
|
|
9777
|
+
to: k,
|
|
9778
|
+
type: "data",
|
|
9779
|
+
label: taintEdges.get(edgeKey(prev, ni)) || ""
|
|
9780
|
+
});
|
|
9781
|
+
}
|
|
9782
|
+
});
|
|
9783
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
9784
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
9785
|
+
if (chain.length > 2) {
|
|
9786
|
+
expr.push({
|
|
9787
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
9788
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
9789
|
+
});
|
|
9790
|
+
}
|
|
9791
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
9792
|
+
return {
|
|
9793
|
+
property_id: "sensitive-data-flow",
|
|
9794
|
+
property_short: "P2",
|
|
9795
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
9796
|
+
property_expression: expr,
|
|
9797
|
+
nodes: gNodes,
|
|
9798
|
+
edges: gEdges
|
|
9799
|
+
};
|
|
9800
|
+
};
|
|
9628
9801
|
const witness = graph.witnessIdx;
|
|
9629
9802
|
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
9630
9803
|
for (const i of candidates) {
|
|
9631
9804
|
const n = nodes[i];
|
|
9632
9805
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
9806
|
+
const chain = tracePath(i);
|
|
9633
9807
|
violations.push({
|
|
9634
9808
|
propertyId: "sensitive-data-flow",
|
|
9635
|
-
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`,
|
|
9809
|
+
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`,
|
|
9636
9810
|
riskLevel: "critical",
|
|
9637
|
-
path:
|
|
9811
|
+
path: chain.map((j) => ({
|
|
9812
|
+
sessionId: nodes[j].sessionId,
|
|
9813
|
+
seq: nodes[j].seq,
|
|
9814
|
+
detail: nodes[j].actionDetail.slice(0, 60)
|
|
9815
|
+
})),
|
|
9816
|
+
graph: taintGraph(chain)
|
|
9638
9817
|
});
|
|
9639
9818
|
if (witness !== void 0) return violations;
|
|
9640
9819
|
}
|
|
@@ -10176,6 +10355,12 @@ var init_stateful = __esm({
|
|
|
10176
10355
|
nodes_ = [];
|
|
10177
10356
|
ptr = {};
|
|
10178
10357
|
tainted = /* @__PURE__ */ new Set();
|
|
10358
|
+
/** taint 传播路径:nodeIdx -> 使该节点染毒的前驱 nodeIdx(CRED 源为 null) */
|
|
10359
|
+
taintParent = /* @__PURE__ */ new Map();
|
|
10360
|
+
/** "parent:child" -> 触发传播的共享资源(图上的 data 边标签) */
|
|
10361
|
+
taintEdgeLabel = /* @__PURE__ */ new Map();
|
|
10362
|
+
/** 资源 -> 首次染毒该资源的节点 seq */
|
|
10363
|
+
taintOrigin = /* @__PURE__ */ new Map();
|
|
10179
10364
|
/** taint 反索引:资源路径 → 已染毒(O(1) 命中)。 */
|
|
10180
10365
|
taintedResources = /* @__PURE__ */ new Set();
|
|
10181
10366
|
aliveResources = {};
|
|
@@ -10226,7 +10411,7 @@ var init_stateful = __esm({
|
|
|
10226
10411
|
for (const s of sessions || []) {
|
|
10227
10412
|
const sid = String(s.sessionId || "s");
|
|
10228
10413
|
for (const a of s.actionHistory || []) {
|
|
10229
|
-
const node = this.appendNode(a, sid);
|
|
10414
|
+
const node = this.appendNode(a, sid, false, s.agentId || "", s.instanceId || "");
|
|
10230
10415
|
if (!node) {
|
|
10231
10416
|
continue;
|
|
10232
10417
|
}
|
|
@@ -10240,8 +10425,8 @@ var init_stateful = __esm({
|
|
|
10240
10425
|
}
|
|
10241
10426
|
/** 增量推进:把当前动作追加到时间线 → 链指针推进 → 判定。 */
|
|
10242
10427
|
advance(args) {
|
|
10243
|
-
const { aNext, sessionId = "current", isError = false, agentId = "" } = args;
|
|
10244
|
-
const node = this.appendNode(aNext, sessionId, isError, agentId);
|
|
10428
|
+
const { aNext, sessionId = "current", isError = false, agentId = "", instanceId = "" } = args;
|
|
10429
|
+
const node = this.appendNode(aNext, sessionId, isError, agentId, instanceId);
|
|
10245
10430
|
if (!node) {
|
|
10246
10431
|
return { seq: this.seqNext, node: null, matched: null, pendingPrefixes: this.pendingPrefixes, exfil: [], shouldBlock: false };
|
|
10247
10432
|
}
|
|
@@ -10261,7 +10446,7 @@ var init_stateful = __esm({
|
|
|
10261
10446
|
}
|
|
10262
10447
|
};
|
|
10263
10448
|
}
|
|
10264
|
-
appendNode(action, sessionId, isError = false, agentId = "") {
|
|
10449
|
+
appendNode(action, sessionId, isError = false, agentId = "", instanceId = "") {
|
|
10265
10450
|
const ad = String(action.actionDetail || "");
|
|
10266
10451
|
const at = String(action.actionType || "");
|
|
10267
10452
|
if (!ad) return null;
|
|
@@ -10279,6 +10464,7 @@ var init_stateful = __esm({
|
|
|
10279
10464
|
instances: extractInstances(ad, stage),
|
|
10280
10465
|
isError,
|
|
10281
10466
|
agentId: String(action.agentId || agentId || ""),
|
|
10467
|
+
instanceId: String(action.instanceId || instanceId || ""),
|
|
10282
10468
|
lifecycle: lifecycleFor(ad, stage, isError),
|
|
10283
10469
|
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
10284
10470
|
};
|
|
@@ -10386,7 +10572,8 @@ var init_stateful = __esm({
|
|
|
10386
10572
|
stages,
|
|
10387
10573
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
10388
10574
|
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
10389
|
-
path: this.chainPath(stages)
|
|
10575
|
+
path: this.chainPath(stages),
|
|
10576
|
+
graph: this.buildChainGraph(stages, cid)
|
|
10390
10577
|
};
|
|
10391
10578
|
}
|
|
10392
10579
|
}
|
|
@@ -10421,7 +10608,8 @@ var init_stateful = __esm({
|
|
|
10421
10608
|
seq: n.seq,
|
|
10422
10609
|
stage: n.stage,
|
|
10423
10610
|
timestamp: n.timestamp,
|
|
10424
|
-
agentId: n.agentId
|
|
10611
|
+
agentId: n.agentId,
|
|
10612
|
+
instanceId: n.instanceId
|
|
10425
10613
|
});
|
|
10426
10614
|
const idx = stages.indexOf(n.stage);
|
|
10427
10615
|
if (idx + 1 < stages.length) {
|
|
@@ -10433,21 +10621,70 @@ var init_stateful = __esm({
|
|
|
10433
10621
|
}
|
|
10434
10622
|
return path6;
|
|
10435
10623
|
}
|
|
10624
|
+
/** 把链路径转成前端可画的结构化图(相邻节点 next 边)。 */
|
|
10625
|
+
buildChainGraph(stages, chainId) {
|
|
10626
|
+
const path6 = this.chainPath(stages);
|
|
10627
|
+
const gNodes = path6.map((p, i) => {
|
|
10628
|
+
const n = this.nodes_.find((x) => x.sessionId === p.sessionId && x.seq === p.seq);
|
|
10629
|
+
return {
|
|
10630
|
+
session_id: p.sessionId,
|
|
10631
|
+
seq: p.seq,
|
|
10632
|
+
stage: p.stage,
|
|
10633
|
+
action_detail: n ? n.actionDetail : "",
|
|
10634
|
+
resources: n ? [...n.resources].sort() : [],
|
|
10635
|
+
role: stages[i] || "",
|
|
10636
|
+
instance_id: p.instanceId || ""
|
|
10637
|
+
};
|
|
10638
|
+
});
|
|
10639
|
+
const gEdges = [];
|
|
10640
|
+
for (let i = 1; i < gNodes.length; i++) {
|
|
10641
|
+
gEdges.push({ from: i - 1, to: i, type: "next" });
|
|
10642
|
+
}
|
|
10643
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
10644
|
+
return {
|
|
10645
|
+
property_id: "cross-" + chainId,
|
|
10646
|
+
property_short: "P1",
|
|
10647
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE",
|
|
10648
|
+
property_expression: stages.map((s, i) => ({ token: s, node_index: i })),
|
|
10649
|
+
nodes: gNodes,
|
|
10650
|
+
edges: gEdges
|
|
10651
|
+
};
|
|
10652
|
+
}
|
|
10436
10653
|
// ---------------- 敏感数据 taint(P2) ----------------
|
|
10437
10654
|
advanceTaint(node) {
|
|
10438
10655
|
if (node.lifecycle === "dead") return;
|
|
10439
10656
|
const idx = node.seq;
|
|
10440
10657
|
const taintedRes = /* @__PURE__ */ new Set();
|
|
10658
|
+
let parent = null;
|
|
10659
|
+
let label = "";
|
|
10660
|
+
let foundParent = false;
|
|
10441
10661
|
for (const r of node.resources) {
|
|
10442
|
-
if (this.resourceTainted(r))
|
|
10662
|
+
if (this.resourceTainted(r)) {
|
|
10663
|
+
taintedRes.add(r);
|
|
10664
|
+
if (!foundParent) {
|
|
10665
|
+
const origin = this.taintOrigin.get(r);
|
|
10666
|
+
if (origin !== void 0) {
|
|
10667
|
+
parent = origin;
|
|
10668
|
+
label = r;
|
|
10669
|
+
foundParent = true;
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
10443
10673
|
}
|
|
10444
10674
|
if (node.stage === "CRED" && node.resources.some((r) => CRED_FILE_RE2.test(r))) {
|
|
10445
10675
|
this.tainted.add(idx);
|
|
10676
|
+
parent = null;
|
|
10677
|
+
label = "";
|
|
10446
10678
|
for (const r of node.resources) taintedRes.add(r);
|
|
10447
10679
|
}
|
|
10448
10680
|
if (taintedRes.size > 0) {
|
|
10449
10681
|
this.tainted.add(idx);
|
|
10450
|
-
|
|
10682
|
+
this.taintParent.set(idx, parent);
|
|
10683
|
+
if (parent !== null) this.taintEdgeLabel.set(`${parent}:${idx}`, label);
|
|
10684
|
+
for (const r of node.resources) {
|
|
10685
|
+
this.taintedResources.add(r);
|
|
10686
|
+
if (!this.taintOrigin.has(r)) this.taintOrigin.set(r, idx);
|
|
10687
|
+
}
|
|
10451
10688
|
}
|
|
10452
10689
|
}
|
|
10453
10690
|
resourceTainted(resource) {
|
|
@@ -10457,16 +10694,85 @@ var init_stateful = __esm({
|
|
|
10457
10694
|
checkExfilAfterTaint(node) {
|
|
10458
10695
|
if (!this.tainted.has(node.seq)) return [];
|
|
10459
10696
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
10697
|
+
const chain = this.traceTaintChain(node.seq);
|
|
10460
10698
|
return [{
|
|
10461
10699
|
property_id: "sensitive-data-flow",
|
|
10462
|
-
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
10700
|
+
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node, chain)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
10463
10701
|
risk_level: "critical",
|
|
10464
|
-
path:
|
|
10702
|
+
path: chain.map((j) => {
|
|
10703
|
+
const n = this.nodes_[j];
|
|
10704
|
+
return [n.sessionId, n.seq, n.actionDetail.slice(0, 60), n.timestamp, null, n.instanceId];
|
|
10705
|
+
}),
|
|
10706
|
+
graph: this.taintGraph(chain)
|
|
10465
10707
|
}];
|
|
10466
10708
|
}
|
|
10467
10709
|
return [];
|
|
10468
10710
|
}
|
|
10469
|
-
|
|
10711
|
+
/** 沿 taint parent 回溯出 CRED 源 → 当前节点的完整路径。 */
|
|
10712
|
+
traceTaintChain(idx) {
|
|
10713
|
+
const chain = [idx];
|
|
10714
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
10715
|
+
while (true) {
|
|
10716
|
+
const parent = this.taintParent.get(chain[chain.length - 1]);
|
|
10717
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
10718
|
+
chain.push(parent);
|
|
10719
|
+
seen.add(parent);
|
|
10720
|
+
}
|
|
10721
|
+
return chain.reverse();
|
|
10722
|
+
}
|
|
10723
|
+
/** 把 taint 路径转成前端可画的结构化图(data 边带共享资源标签)。 */
|
|
10724
|
+
taintGraph(chain) {
|
|
10725
|
+
const gNodes = [];
|
|
10726
|
+
const gEdges = [];
|
|
10727
|
+
chain.forEach((ni, k) => {
|
|
10728
|
+
const n = this.nodes_[ni];
|
|
10729
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
10730
|
+
gNodes.push({
|
|
10731
|
+
session_id: n.sessionId,
|
|
10732
|
+
seq: n.seq,
|
|
10733
|
+
stage: n.stage,
|
|
10734
|
+
action_detail: n.actionDetail,
|
|
10735
|
+
resources: [...n.resources].sort(),
|
|
10736
|
+
role,
|
|
10737
|
+
instance_id: n.instanceId
|
|
10738
|
+
});
|
|
10739
|
+
if (k > 0) {
|
|
10740
|
+
const prev = chain[k - 1];
|
|
10741
|
+
gEdges.push({
|
|
10742
|
+
from: k - 1,
|
|
10743
|
+
to: k,
|
|
10744
|
+
type: "data",
|
|
10745
|
+
label: this.taintEdgeLabel.get(`${prev}:${ni}`) || ""
|
|
10746
|
+
});
|
|
10747
|
+
}
|
|
10748
|
+
});
|
|
10749
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
10750
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
10751
|
+
if (chain.length > 2) {
|
|
10752
|
+
expr.push({
|
|
10753
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
10754
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
10755
|
+
});
|
|
10756
|
+
}
|
|
10757
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
10758
|
+
return {
|
|
10759
|
+
property_id: "sensitive-data-flow",
|
|
10760
|
+
property_short: "P2",
|
|
10761
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
10762
|
+
property_expression: expr,
|
|
10763
|
+
nodes: gNodes,
|
|
10764
|
+
edges: gEdges
|
|
10765
|
+
};
|
|
10766
|
+
}
|
|
10767
|
+
taintSource(node, chain = []) {
|
|
10768
|
+
if (chain.length > 0) {
|
|
10769
|
+
const src = this.nodes_[chain[0]];
|
|
10770
|
+
if (src) {
|
|
10771
|
+
for (const r of node.resources) {
|
|
10772
|
+
if (src.resources.includes(r)) return `${src.sessionId}#${src.seq} \u8BBF\u95EE ${r}`;
|
|
10773
|
+
}
|
|
10774
|
+
}
|
|
10775
|
+
}
|
|
10470
10776
|
const sorted = [...this.tainted].sort((a, b) => a - b);
|
|
10471
10777
|
for (const i of sorted) {
|
|
10472
10778
|
if (i === node.seq) continue;
|
|
@@ -10485,6 +10791,9 @@ var init_stateful = __esm({
|
|
|
10485
10791
|
nodes: this.nodes_.map((n) => ({ ...n, resources: [...n.resources] })),
|
|
10486
10792
|
ptrs: { ...this.ptr },
|
|
10487
10793
|
tainted: [...this.tainted].sort((a, b) => a - b),
|
|
10794
|
+
// taint 传播路径(前端攻击链图);边标签不入快照,重启后可从资源交集重建
|
|
10795
|
+
taint_parent: Object.fromEntries([...this.taintParent.entries()].map(([k, v]) => [String(k), v])),
|
|
10796
|
+
taint_origin: Object.fromEntries([...this.taintOrigin.entries()]),
|
|
10488
10797
|
alive: { ...this.aliveResources },
|
|
10489
10798
|
seq_next: this.seqNext
|
|
10490
10799
|
};
|
|
@@ -10497,6 +10806,8 @@ var init_stateful = __esm({
|
|
|
10497
10806
|
seq: Number(raw.seq || 0),
|
|
10498
10807
|
sessionId: String(raw.sessionId || ""),
|
|
10499
10808
|
agentId: String(raw.agentId || ""),
|
|
10809
|
+
instanceId: String(raw.instanceId || ""),
|
|
10810
|
+
// 旧快照无此字段 → 空
|
|
10500
10811
|
actionType: String(raw.actionType || ""),
|
|
10501
10812
|
actionDetail: String(raw.actionDetail || ""),
|
|
10502
10813
|
stage: String(raw.stage || ""),
|
|
@@ -10512,15 +10823,53 @@ var init_stateful = __esm({
|
|
|
10512
10823
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
10513
10824
|
this.ptr = Object.fromEntries(Object.entries(snapshot.ptrs || {}).map(([k, v]) => [k, Number(v)]));
|
|
10514
10825
|
this.tainted = new Set((snapshot.tainted || []).map(Number));
|
|
10826
|
+
this.taintParent = new Map(
|
|
10827
|
+
Object.entries(snapshot.taint_parent || {}).map(
|
|
10828
|
+
([k, v]) => [Number(k), v === null || v === void 0 ? null : Number(v)]
|
|
10829
|
+
)
|
|
10830
|
+
);
|
|
10831
|
+
this.taintOrigin = new Map(
|
|
10832
|
+
Object.entries(snapshot.taint_origin || {}).map(
|
|
10833
|
+
([k, v]) => [String(k), Number(v)]
|
|
10834
|
+
)
|
|
10835
|
+
);
|
|
10836
|
+
this.taintEdgeLabel = /* @__PURE__ */ new Map();
|
|
10515
10837
|
this.taintedResources = /* @__PURE__ */ new Set();
|
|
10516
10838
|
for (const i of this.tainted) {
|
|
10517
10839
|
const n = this.nodes_[i];
|
|
10518
10840
|
if (n) for (const r of n.resources) this.taintedResources.add(r);
|
|
10519
10841
|
}
|
|
10842
|
+
if (this.tainted.size > 0 && this.taintParent.size === 0) this.rebuildTaintParent();
|
|
10843
|
+
if (this.taintOrigin.size === 0) {
|
|
10844
|
+
for (const i of [...this.tainted].sort((a, b) => a - b)) {
|
|
10845
|
+
const n = this.nodes_[i];
|
|
10846
|
+
if (!n) continue;
|
|
10847
|
+
for (const r of n.resources) {
|
|
10848
|
+
if (!this.taintOrigin.has(r)) this.taintOrigin.set(r, i);
|
|
10849
|
+
}
|
|
10850
|
+
}
|
|
10851
|
+
}
|
|
10520
10852
|
this.aliveResources = Object.fromEntries(
|
|
10521
10853
|
Object.entries(snapshot.alive || {}).map(([k, v]) => [k, Number(v)])
|
|
10522
10854
|
);
|
|
10523
10855
|
}
|
|
10856
|
+
/** 旧快照无 parent 信息时,按 seq 顺序找最近的资源相交染毒前驱。 */
|
|
10857
|
+
rebuildTaintParent() {
|
|
10858
|
+
for (const i of [...this.tainted].sort((a, b) => a - b)) {
|
|
10859
|
+
const n = this.nodes_[i];
|
|
10860
|
+
if (!n) continue;
|
|
10861
|
+
let parent = null;
|
|
10862
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
10863
|
+
if (!this.tainted.has(j)) continue;
|
|
10864
|
+
const m = this.nodes_[j];
|
|
10865
|
+
if (m && m.resources.some((r) => n.resources.includes(r))) {
|
|
10866
|
+
parent = j;
|
|
10867
|
+
break;
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
10870
|
+
this.taintParent.set(i, parent);
|
|
10871
|
+
}
|
|
10872
|
+
}
|
|
10524
10873
|
};
|
|
10525
10874
|
}
|
|
10526
10875
|
});
|
|
@@ -10603,9 +10952,9 @@ var init_chain_state = __esm({
|
|
|
10603
10952
|
delete this.data[agentId];
|
|
10604
10953
|
this._scheduleWrite();
|
|
10605
10954
|
}
|
|
10606
|
-
applyAction(agentId, aNext, sessionId, isError = false, sourceAgent = "") {
|
|
10955
|
+
applyAction(agentId, aNext, sessionId, isError = false, sourceAgent = "", instanceId = "") {
|
|
10607
10956
|
const t = this.load(agentId) || new StatefulChainTracker(agentId);
|
|
10608
|
-
const r = t.advance({ aNext, sessionId, isError, agentId: sourceAgent });
|
|
10957
|
+
const r = t.advance({ aNext, sessionId, isError, agentId: sourceAgent, instanceId });
|
|
10609
10958
|
this.save(t);
|
|
10610
10959
|
return r;
|
|
10611
10960
|
}
|
|
@@ -10923,12 +11272,21 @@ async function runAnalysis(input) {
|
|
|
10923
11272
|
formalSessions.push({
|
|
10924
11273
|
sessionId: s.sessionId || "s",
|
|
10925
11274
|
...s.agentId ? { agentId: s.agentId } : {},
|
|
11275
|
+
...s.instanceId ? { instanceId: s.instanceId } : {},
|
|
10926
11276
|
actionHistory: s.actionHistory || []
|
|
10927
11277
|
});
|
|
10928
11278
|
}
|
|
10929
11279
|
}
|
|
10930
|
-
formalSessions.push({
|
|
10931
|
-
|
|
11280
|
+
formalSessions.push({
|
|
11281
|
+
sessionId: "current",
|
|
11282
|
+
agentId: input.agentId,
|
|
11283
|
+
instanceId: input.instanceId || "",
|
|
11284
|
+
actionHistory: input.actionHistory
|
|
11285
|
+
});
|
|
11286
|
+
const formalResult = formalEngine.verify(formalSessions, {
|
|
11287
|
+
...input.aNext,
|
|
11288
|
+
instanceId: input.instanceId || ""
|
|
11289
|
+
});
|
|
10932
11290
|
if (!formalResult.verified) {
|
|
10933
11291
|
const v = formalResult.violations[0];
|
|
10934
11292
|
if (isFormalObserveMode()) {
|
|
@@ -10944,7 +11302,9 @@ async function runAnalysis(input) {
|
|
|
10944
11302
|
riskType: "formal_verification",
|
|
10945
11303
|
confidence: 90,
|
|
10946
11304
|
source: "formal_engine",
|
|
10947
|
-
violatedLayers: ["L2.2"]
|
|
11305
|
+
violatedLayers: ["L2.2"],
|
|
11306
|
+
violationGraph: v?.graph ?? null,
|
|
11307
|
+
violationChain: (v?.path ?? []).map((p) => [p.sessionId, p.seq, p.detail])
|
|
10948
11308
|
};
|
|
10949
11309
|
}
|
|
10950
11310
|
}
|
|
@@ -11489,7 +11849,9 @@ async function analyzeInner(input, mossLog) {
|
|
|
11489
11849
|
violated_layers: judgment.violatedLayers || [],
|
|
11490
11850
|
policy: "",
|
|
11491
11851
|
violated_policy: `[${judgment.riskType}] ${judgment.reason}`,
|
|
11492
|
-
confidence: judgment.confidence
|
|
11852
|
+
confidence: judgment.confidence,
|
|
11853
|
+
violation_graph: judgment.violationGraph ?? null,
|
|
11854
|
+
violation_chain: judgment.violationChain ?? []
|
|
11493
11855
|
};
|
|
11494
11856
|
mossLog.logResult("Deny", denied.reason);
|
|
11495
11857
|
return denied;
|
|
@@ -12008,6 +12370,9 @@ async function analyzeRoute(c) {
|
|
|
12008
12370
|
`[AgentMoss] \u26A0\uFE0F \u8DE8\u4F1A\u8BDD\u6279\u91CF\u8BBF\u95EE: key=${_sweepKey} \u7A97\u53E3\u5185 ${_sweepN} \u4E2A\u4E0D\u540C session\uFF08\u9608\u503C ${sessionSweepThreshold()}\uFF09`
|
|
12009
12371
|
);
|
|
12010
12372
|
}
|
|
12373
|
+
const instanceId = String(
|
|
12374
|
+
req.instance_id || req.metadata?.instance_id || ""
|
|
12375
|
+
);
|
|
12011
12376
|
const result = await analyze({
|
|
12012
12377
|
sessionId: req.session_id,
|
|
12013
12378
|
promptSession: req.prompt_session,
|
|
@@ -12023,8 +12388,9 @@ async function analyzeRoute(c) {
|
|
|
12023
12388
|
})),
|
|
12024
12389
|
sessionHistory: (req.session_history || []).map((s) => ({
|
|
12025
12390
|
sessionId: s.session_id,
|
|
12026
|
-
// 来源 agent
|
|
12391
|
+
// 来源 agent/实例(AgentGate 逐会话带上,用于链路径取证/展示脱敏;不参与链匹配)
|
|
12027
12392
|
...s.agent_id ? { agentId: s.agent_id } : {},
|
|
12393
|
+
...s.instance_id ? { instanceId: s.instance_id } : {},
|
|
12028
12394
|
actionHistory: (s.action_history || []).map((h) => ({
|
|
12029
12395
|
actionType: h.action_type,
|
|
12030
12396
|
actionDetail: h.action_detail,
|
|
@@ -12048,11 +12414,13 @@ async function analyzeRoute(c) {
|
|
|
12048
12414
|
})),
|
|
12049
12415
|
aNext: {
|
|
12050
12416
|
actionType: req.a_next.action_type,
|
|
12051
|
-
actionDetail: req.a_next.action_detail
|
|
12417
|
+
actionDetail: req.a_next.action_detail,
|
|
12418
|
+
instanceId
|
|
12052
12419
|
},
|
|
12053
12420
|
reason: req.reason,
|
|
12054
12421
|
osType,
|
|
12055
12422
|
cwd: req.cwd,
|
|
12423
|
+
instanceId,
|
|
12056
12424
|
// agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
|
|
12057
12425
|
// 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
|
|
12058
12426
|
agentId,
|
|
@@ -12088,8 +12456,9 @@ async function analyzeRoute(c) {
|
|
|
12088
12456
|
{ actionType: req.a_next.action_type, actionDetail: req.a_next.action_detail },
|
|
12089
12457
|
req.session_id,
|
|
12090
12458
|
Boolean(meta.is_error),
|
|
12091
|
-
agentId
|
|
12459
|
+
agentId,
|
|
12092
12460
|
// 来源:真实 agent(桶 key 可能是 __system__)
|
|
12461
|
+
instanceId
|
|
12093
12462
|
);
|
|
12094
12463
|
const judgment = incMatchedToDeny(inc);
|
|
12095
12464
|
if (judgment) {
|
|
@@ -12110,7 +12479,8 @@ async function analyzeRoute(c) {
|
|
|
12110
12479
|
confidence: 90,
|
|
12111
12480
|
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
12112
12481
|
// 跨会话节点脱敏(时间线按 agent_id 共享,原样返回等于泄露别人的命令原文)
|
|
12113
|
-
violation_chain: redactChain(judgment.violation_chain, req.session_id)
|
|
12482
|
+
violation_chain: redactChain(judgment.violation_chain, instanceId, req.session_id),
|
|
12483
|
+
violation_graph: redactGraph(judgment.violation_graph, instanceId, req.session_id)
|
|
12114
12484
|
})
|
|
12115
12485
|
);
|
|
12116
12486
|
}
|
|
@@ -12130,7 +12500,9 @@ async function analyzeRoute(c) {
|
|
|
12130
12500
|
policy: result.policy,
|
|
12131
12501
|
violated_policy: result.violated_policy,
|
|
12132
12502
|
confidence: result.confidence,
|
|
12133
|
-
analysis_duration_ms: Math.round(durationMs * 10) / 10
|
|
12503
|
+
analysis_duration_ms: Math.round(durationMs * 10) / 10,
|
|
12504
|
+
violation_chain: result.violation_chain ?? [],
|
|
12505
|
+
violation_graph: redactGraph(result.violation_graph, instanceId, req.session_id)
|
|
12134
12506
|
})
|
|
12135
12507
|
);
|
|
12136
12508
|
} catch (err) {
|
|
@@ -12160,7 +12532,8 @@ function incMatchedToDeny(inc) {
|
|
|
12160
12532
|
property_id: "cross-" + inc.matched.chainId,
|
|
12161
12533
|
reason: inc.matched.reason,
|
|
12162
12534
|
risk_level: inc.matched.riskLevel,
|
|
12163
|
-
violation_chain: chain
|
|
12535
|
+
violation_chain: chain,
|
|
12536
|
+
violation_graph: inc.matched.graph ?? null
|
|
12164
12537
|
};
|
|
12165
12538
|
}
|
|
12166
12539
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
@@ -12168,7 +12541,9 @@ function incMatchedToDeny(inc) {
|
|
|
12168
12541
|
property_id: "sensitive-data-flow",
|
|
12169
12542
|
reason: inc.exfil[0].reason,
|
|
12170
12543
|
risk_level: inc.exfil[0].risk_level,
|
|
12171
|
-
|
|
12544
|
+
// P2 的 path 现在包含 CRED 源 → 中间载体 → 外传点的完整链
|
|
12545
|
+
violation_chain: inc.exfil[0].path ?? [],
|
|
12546
|
+
violation_graph: inc.exfil[0].graph ?? null
|
|
12172
12547
|
};
|
|
12173
12548
|
}
|
|
12174
12549
|
return null;
|