@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/server.js
CHANGED
|
@@ -1092,18 +1092,43 @@ function hardenExistingPaths() {
|
|
|
1092
1092
|
function chainDetailMode() {
|
|
1093
1093
|
return (env("AGENT_MOSS_CHAIN_DETAIL", "redacted") || "redacted").toLowerCase();
|
|
1094
1094
|
}
|
|
1095
|
-
function redactChain(chain, currentSessionId) {
|
|
1095
|
+
function redactChain(chain, currentInstanceId = "", currentSessionId = "") {
|
|
1096
1096
|
if (!chain || chain.length === 0) return [];
|
|
1097
1097
|
if (chainDetailMode() === "full") return [...chain];
|
|
1098
1098
|
return chain.map((step) => {
|
|
1099
1099
|
if (Array.isArray(step) && step.length >= 3) {
|
|
1100
1100
|
const [sid, seq, , ts] = step;
|
|
1101
|
-
|
|
1101
|
+
const inst = step.length > 5 ? String(step[5] ?? "") : "";
|
|
1102
|
+
const sameInstance = !!(inst && currentInstanceId && inst === String(currentInstanceId));
|
|
1103
|
+
const sameSessionFallback = !inst && !!currentSessionId && String(sid) === String(currentSessionId);
|
|
1104
|
+
if (sameInstance || sameSessionFallback) return [...step];
|
|
1102
1105
|
return ["<other-session>", seq, "<\u5DF2\u9690\u85CF>", ts ?? ""];
|
|
1103
1106
|
}
|
|
1104
1107
|
return step;
|
|
1105
1108
|
});
|
|
1106
1109
|
}
|
|
1110
|
+
function redactGraph(graph, currentInstanceId = "", currentSessionId = "") {
|
|
1111
|
+
if (!graph) return null;
|
|
1112
|
+
if (chainDetailMode() === "full") return graph;
|
|
1113
|
+
const nodes = (graph.nodes || []).map((n) => {
|
|
1114
|
+
const m = { ...n };
|
|
1115
|
+
const inst = String(m.instance_id ?? "") || "";
|
|
1116
|
+
const sid = String(m.session_id ?? "") || "";
|
|
1117
|
+
const sameInstance = !!(inst && currentInstanceId && inst === String(currentInstanceId));
|
|
1118
|
+
const sameSessionFallback = !inst && !!currentSessionId && sid === String(currentSessionId);
|
|
1119
|
+
if (!(sameInstance || sameSessionFallback)) {
|
|
1120
|
+
m.session_id = "<other-session>";
|
|
1121
|
+
m.action_detail = "<\u5DF2\u9690\u85CF>";
|
|
1122
|
+
m.resources = [];
|
|
1123
|
+
}
|
|
1124
|
+
return m;
|
|
1125
|
+
});
|
|
1126
|
+
const out = { nodes, edges: graph.edges || [] };
|
|
1127
|
+
for (const key of ["property_id", "property_short", "property_name", "property_expression"]) {
|
|
1128
|
+
if (graph[key] !== void 0) out[key] = graph[key];
|
|
1129
|
+
}
|
|
1130
|
+
return out;
|
|
1131
|
+
}
|
|
1107
1132
|
function sessionSweepThreshold() {
|
|
1108
1133
|
return envInt("AGENT_MOSS_SESSION_SWEEP_THRESHOLD", 50);
|
|
1109
1134
|
}
|
|
@@ -8870,7 +8895,7 @@ function markIntegrity(actionDetail) {
|
|
|
8870
8895
|
}
|
|
8871
8896
|
return { integrity: "medium", isLowSource: false };
|
|
8872
8897
|
}
|
|
8873
|
-
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "") {
|
|
8898
|
+
function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId = "", instanceId = "") {
|
|
8874
8899
|
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
8875
8900
|
return {
|
|
8876
8901
|
sessionId,
|
|
@@ -8879,13 +8904,14 @@ function initNode(sessionId, seq, actionType, actionDetail, timestamp, agentId =
|
|
|
8879
8904
|
actionDetail,
|
|
8880
8905
|
stage: markStage(actionDetail),
|
|
8881
8906
|
agentId: agentId || void 0,
|
|
8907
|
+
instanceId: instanceId || void 0,
|
|
8882
8908
|
resources: extractResources(actionDetail),
|
|
8883
8909
|
integrity,
|
|
8884
8910
|
isLowSource,
|
|
8885
8911
|
timestamp: timestamp || void 0
|
|
8886
8912
|
};
|
|
8887
8913
|
}
|
|
8888
|
-
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "") {
|
|
8914
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId = "", instanceId = "") {
|
|
8889
8915
|
const nodes = [];
|
|
8890
8916
|
const edges = [];
|
|
8891
8917
|
const stageFlags = /* @__PURE__ */ new Set();
|
|
@@ -8896,7 +8922,8 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8896
8922
|
actionType: String(a.actionType || ""),
|
|
8897
8923
|
actionDetail: ad,
|
|
8898
8924
|
calledAt: a.calledAt || void 0,
|
|
8899
|
-
agentId: a.agentId || agentId || void 0
|
|
8925
|
+
agentId: a.agentId || agentId || void 0,
|
|
8926
|
+
instanceId: a.instanceId || instanceId || void 0
|
|
8900
8927
|
});
|
|
8901
8928
|
}
|
|
8902
8929
|
if (aNext) {
|
|
@@ -8904,11 +8931,12 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8904
8931
|
actionType: String(aNext.actionType || ""),
|
|
8905
8932
|
actionDetail: String(aNext.actionDetail || ""),
|
|
8906
8933
|
calledAt: aNext.calledAt || void 0,
|
|
8907
|
-
agentId: aNext.agentId || agentId || void 0
|
|
8934
|
+
agentId: aNext.agentId || agentId || void 0,
|
|
8935
|
+
instanceId: aNext.instanceId || instanceId || void 0
|
|
8908
8936
|
});
|
|
8909
8937
|
}
|
|
8910
8938
|
details.forEach((d, seq) => {
|
|
8911
|
-
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "");
|
|
8939
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt, d.agentId || "", d.instanceId || "");
|
|
8912
8940
|
nodes.push(node);
|
|
8913
8941
|
if (node.stage) stageFlags.add(node.stage);
|
|
8914
8942
|
});
|
|
@@ -8920,12 +8948,18 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current", agentId
|
|
|
8920
8948
|
function buildMultiSessionGraph(sessions, aNext) {
|
|
8921
8949
|
const mg = { sessions: [], crossEdges: [] };
|
|
8922
8950
|
if (!sessions || sessions.length === 0) {
|
|
8923
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
8951
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext?.instanceId || ""));
|
|
8924
8952
|
return mg;
|
|
8925
8953
|
}
|
|
8926
8954
|
for (const s of sessions) {
|
|
8927
8955
|
const sid = String(s.sessionId || "s");
|
|
8928
|
-
mg.sessions.push(buildSessionGraph(
|
|
8956
|
+
mg.sessions.push(buildSessionGraph(
|
|
8957
|
+
s.actionHistory || [],
|
|
8958
|
+
void 0,
|
|
8959
|
+
sid,
|
|
8960
|
+
s.agentId || "",
|
|
8961
|
+
s.instanceId || ""
|
|
8962
|
+
));
|
|
8929
8963
|
}
|
|
8930
8964
|
if (aNext) {
|
|
8931
8965
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
@@ -8936,13 +8970,14 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
8936
8970
|
String(aNext.actionType || ""),
|
|
8937
8971
|
String(aNext.actionDetail || ""),
|
|
8938
8972
|
aNext.calledAt || void 0,
|
|
8939
|
-
aNext.agentId || ""
|
|
8973
|
+
aNext.agentId || "",
|
|
8974
|
+
aNext.instanceId || ""
|
|
8940
8975
|
));
|
|
8941
8976
|
if (last.nodes.length > 1) {
|
|
8942
8977
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
8943
8978
|
}
|
|
8944
8979
|
} else {
|
|
8945
|
-
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
8980
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current", "", aNext.instanceId || ""));
|
|
8946
8981
|
}
|
|
8947
8982
|
}
|
|
8948
8983
|
addCrossDataEdges(mg);
|
|
@@ -8994,6 +9029,34 @@ var init_builder = __esm({
|
|
|
8994
9029
|
});
|
|
8995
9030
|
|
|
8996
9031
|
// src/engine/formal-engine/properties.ts
|
|
9032
|
+
function graphFromPath(nodes, path6, edgeType = "next", propertyMeta, roles, witnessLast = true, propertyExpression) {
|
|
9033
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9034
|
+
for (const n of nodes) byKey.set(`${n.sessionId}#${n.seq}`, n);
|
|
9035
|
+
const gNodes = [];
|
|
9036
|
+
const gEdges = [];
|
|
9037
|
+
for (const item of path6) {
|
|
9038
|
+
const n = byKey.get(`${item.sessionId}#${item.seq}`);
|
|
9039
|
+
gNodes.push({
|
|
9040
|
+
session_id: item.sessionId,
|
|
9041
|
+
seq: item.seq,
|
|
9042
|
+
stage: n ? n.stage : "",
|
|
9043
|
+
action_detail: n ? n.actionDetail : item.detail,
|
|
9044
|
+
resources: n ? [...n.resources].sort() : [],
|
|
9045
|
+
instance_id: n ? n.instanceId || "" : ""
|
|
9046
|
+
});
|
|
9047
|
+
if (gNodes.length > 1) {
|
|
9048
|
+
gEdges.push({ from: gNodes.length - 2, to: gNodes.length - 1, type: edgeType });
|
|
9049
|
+
}
|
|
9050
|
+
}
|
|
9051
|
+
(roles || []).forEach((role, i) => {
|
|
9052
|
+
if (i < gNodes.length && role) gNodes[i].role = role;
|
|
9053
|
+
});
|
|
9054
|
+
if (witnessLast && gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
9055
|
+
const out = { nodes: gNodes, edges: gEdges };
|
|
9056
|
+
if (propertyMeta) Object.assign(out, propertyMeta);
|
|
9057
|
+
if (propertyExpression) out.property_expression = propertyExpression;
|
|
9058
|
+
return out;
|
|
9059
|
+
}
|
|
8997
9060
|
function parseTs(ts) {
|
|
8998
9061
|
if (!ts) return null;
|
|
8999
9062
|
const t = Date.parse(String(ts).trim());
|
|
@@ -9051,7 +9114,12 @@ function checkCrossSessionChain(graph) {
|
|
|
9051
9114
|
propertyId: "cross-" + chain.id,
|
|
9052
9115
|
reason: chain.reason,
|
|
9053
9116
|
riskLevel: chain.riskLevel,
|
|
9054
|
-
path: path6
|
|
9117
|
+
path: path6,
|
|
9118
|
+
graph: graphFromPath(nodes, path6, "next", {
|
|
9119
|
+
property_id: "cross-" + chain.id,
|
|
9120
|
+
property_short: "P1",
|
|
9121
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
9122
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
9055
9123
|
});
|
|
9056
9124
|
return violations;
|
|
9057
9125
|
} else {
|
|
@@ -9071,7 +9139,12 @@ function checkCrossSessionChain(graph) {
|
|
|
9071
9139
|
propertyId: "cross-" + chain.id,
|
|
9072
9140
|
reason: chain.reason,
|
|
9073
9141
|
riskLevel: chain.riskLevel,
|
|
9074
|
-
path: path6
|
|
9142
|
+
path: path6,
|
|
9143
|
+
graph: graphFromPath(nodes, path6, "next", {
|
|
9144
|
+
property_id: "cross-" + chain.id,
|
|
9145
|
+
property_short: "P1",
|
|
9146
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE"
|
|
9147
|
+
}, stages, true, stages.map((s, i) => ({ token: s, node_index: i })))
|
|
9075
9148
|
});
|
|
9076
9149
|
}
|
|
9077
9150
|
}
|
|
@@ -9086,10 +9159,15 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9086
9159
|
for (let i = 0; i < nodes.length; i++) {
|
|
9087
9160
|
const n = nodes[i];
|
|
9088
9161
|
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
9089
|
-
tainted.set(i,
|
|
9162
|
+
tainted.set(i, {
|
|
9163
|
+
source: `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`,
|
|
9164
|
+
parent: null
|
|
9165
|
+
});
|
|
9090
9166
|
}
|
|
9091
9167
|
}
|
|
9092
9168
|
if (tainted.size === 0) return violations;
|
|
9169
|
+
const taintEdges = /* @__PURE__ */ new Map();
|
|
9170
|
+
const edgeKey = (p, c) => `${p}:${c}`;
|
|
9093
9171
|
const edges = allEdges(graph);
|
|
9094
9172
|
let changed = true;
|
|
9095
9173
|
while (changed) {
|
|
@@ -9100,7 +9178,8 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9100
9178
|
const dstRes = nodes[e.dst].resources;
|
|
9101
9179
|
for (const r of srcRes) {
|
|
9102
9180
|
if (dstRes.has(r)) {
|
|
9103
|
-
tainted.set(e.dst, tainted.get(e.src));
|
|
9181
|
+
tainted.set(e.dst, { source: tainted.get(e.src).source, parent: e.src });
|
|
9182
|
+
taintEdges.set(edgeKey(e.src, e.dst), r);
|
|
9104
9183
|
changed = true;
|
|
9105
9184
|
break;
|
|
9106
9185
|
}
|
|
@@ -9108,16 +9187,76 @@ function checkSensitiveDataFlow(graph) {
|
|
|
9108
9187
|
}
|
|
9109
9188
|
}
|
|
9110
9189
|
}
|
|
9190
|
+
const tracePath = (idx) => {
|
|
9191
|
+
const chain = [idx];
|
|
9192
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
9193
|
+
while (true) {
|
|
9194
|
+
const parent = tainted.get(chain[chain.length - 1])?.parent;
|
|
9195
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
9196
|
+
chain.push(parent);
|
|
9197
|
+
seen.add(parent);
|
|
9198
|
+
}
|
|
9199
|
+
return chain.reverse();
|
|
9200
|
+
};
|
|
9201
|
+
const taintGraph = (chain) => {
|
|
9202
|
+
const gNodes = [];
|
|
9203
|
+
const gEdges = [];
|
|
9204
|
+
chain.forEach((ni, k) => {
|
|
9205
|
+
const n = nodes[ni];
|
|
9206
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
9207
|
+
gNodes.push({
|
|
9208
|
+
session_id: n.sessionId,
|
|
9209
|
+
seq: n.seq,
|
|
9210
|
+
stage: n.stage,
|
|
9211
|
+
action_detail: n.actionDetail,
|
|
9212
|
+
resources: [...n.resources].sort(),
|
|
9213
|
+
role,
|
|
9214
|
+
instance_id: n.instanceId || ""
|
|
9215
|
+
});
|
|
9216
|
+
if (k > 0) {
|
|
9217
|
+
const prev = chain[k - 1];
|
|
9218
|
+
gEdges.push({
|
|
9219
|
+
from: k - 1,
|
|
9220
|
+
to: k,
|
|
9221
|
+
type: "data",
|
|
9222
|
+
label: taintEdges.get(edgeKey(prev, ni)) || ""
|
|
9223
|
+
});
|
|
9224
|
+
}
|
|
9225
|
+
});
|
|
9226
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
9227
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
9228
|
+
if (chain.length > 2) {
|
|
9229
|
+
expr.push({
|
|
9230
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
9231
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9234
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
9235
|
+
return {
|
|
9236
|
+
property_id: "sensitive-data-flow",
|
|
9237
|
+
property_short: "P2",
|
|
9238
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
9239
|
+
property_expression: expr,
|
|
9240
|
+
nodes: gNodes,
|
|
9241
|
+
edges: gEdges
|
|
9242
|
+
};
|
|
9243
|
+
};
|
|
9111
9244
|
const witness = graph.witnessIdx;
|
|
9112
9245
|
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
9113
9246
|
for (const i of candidates) {
|
|
9114
9247
|
const n = nodes[i];
|
|
9115
9248
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
9249
|
+
const chain = tracePath(i);
|
|
9116
9250
|
violations.push({
|
|
9117
9251
|
propertyId: "sensitive-data-flow",
|
|
9118
|
-
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`,
|
|
9252
|
+
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`,
|
|
9119
9253
|
riskLevel: "critical",
|
|
9120
|
-
path:
|
|
9254
|
+
path: chain.map((j) => ({
|
|
9255
|
+
sessionId: nodes[j].sessionId,
|
|
9256
|
+
seq: nodes[j].seq,
|
|
9257
|
+
detail: nodes[j].actionDetail.slice(0, 60)
|
|
9258
|
+
})),
|
|
9259
|
+
graph: taintGraph(chain)
|
|
9121
9260
|
});
|
|
9122
9261
|
if (witness !== void 0) return violations;
|
|
9123
9262
|
}
|
|
@@ -9659,6 +9798,12 @@ var init_stateful = __esm({
|
|
|
9659
9798
|
nodes_ = [];
|
|
9660
9799
|
ptr = {};
|
|
9661
9800
|
tainted = /* @__PURE__ */ new Set();
|
|
9801
|
+
/** taint 传播路径:nodeIdx -> 使该节点染毒的前驱 nodeIdx(CRED 源为 null) */
|
|
9802
|
+
taintParent = /* @__PURE__ */ new Map();
|
|
9803
|
+
/** "parent:child" -> 触发传播的共享资源(图上的 data 边标签) */
|
|
9804
|
+
taintEdgeLabel = /* @__PURE__ */ new Map();
|
|
9805
|
+
/** 资源 -> 首次染毒该资源的节点 seq */
|
|
9806
|
+
taintOrigin = /* @__PURE__ */ new Map();
|
|
9662
9807
|
/** taint 反索引:资源路径 → 已染毒(O(1) 命中)。 */
|
|
9663
9808
|
taintedResources = /* @__PURE__ */ new Set();
|
|
9664
9809
|
aliveResources = {};
|
|
@@ -9709,7 +9854,7 @@ var init_stateful = __esm({
|
|
|
9709
9854
|
for (const s of sessions || []) {
|
|
9710
9855
|
const sid = String(s.sessionId || "s");
|
|
9711
9856
|
for (const a of s.actionHistory || []) {
|
|
9712
|
-
const node = this.appendNode(a, sid);
|
|
9857
|
+
const node = this.appendNode(a, sid, false, s.agentId || "", s.instanceId || "");
|
|
9713
9858
|
if (!node) {
|
|
9714
9859
|
continue;
|
|
9715
9860
|
}
|
|
@@ -9723,8 +9868,8 @@ var init_stateful = __esm({
|
|
|
9723
9868
|
}
|
|
9724
9869
|
/** 增量推进:把当前动作追加到时间线 → 链指针推进 → 判定。 */
|
|
9725
9870
|
advance(args) {
|
|
9726
|
-
const { aNext, sessionId = "current", isError = false, agentId = "" } = args;
|
|
9727
|
-
const node = this.appendNode(aNext, sessionId, isError, agentId);
|
|
9871
|
+
const { aNext, sessionId = "current", isError = false, agentId = "", instanceId = "" } = args;
|
|
9872
|
+
const node = this.appendNode(aNext, sessionId, isError, agentId, instanceId);
|
|
9728
9873
|
if (!node) {
|
|
9729
9874
|
return { seq: this.seqNext, node: null, matched: null, pendingPrefixes: this.pendingPrefixes, exfil: [], shouldBlock: false };
|
|
9730
9875
|
}
|
|
@@ -9744,7 +9889,7 @@ var init_stateful = __esm({
|
|
|
9744
9889
|
}
|
|
9745
9890
|
};
|
|
9746
9891
|
}
|
|
9747
|
-
appendNode(action, sessionId, isError = false, agentId = "") {
|
|
9892
|
+
appendNode(action, sessionId, isError = false, agentId = "", instanceId = "") {
|
|
9748
9893
|
const ad = String(action.actionDetail || "");
|
|
9749
9894
|
const at = String(action.actionType || "");
|
|
9750
9895
|
if (!ad) return null;
|
|
@@ -9762,6 +9907,7 @@ var init_stateful = __esm({
|
|
|
9762
9907
|
instances: extractInstances(ad, stage),
|
|
9763
9908
|
isError,
|
|
9764
9909
|
agentId: String(action.agentId || agentId || ""),
|
|
9910
|
+
instanceId: String(action.instanceId || instanceId || ""),
|
|
9765
9911
|
lifecycle: lifecycleFor(ad, stage, isError),
|
|
9766
9912
|
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
9767
9913
|
};
|
|
@@ -9869,7 +10015,8 @@ var init_stateful = __esm({
|
|
|
9869
10015
|
stages,
|
|
9870
10016
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
9871
10017
|
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
9872
|
-
path: this.chainPath(stages)
|
|
10018
|
+
path: this.chainPath(stages),
|
|
10019
|
+
graph: this.buildChainGraph(stages, cid)
|
|
9873
10020
|
};
|
|
9874
10021
|
}
|
|
9875
10022
|
}
|
|
@@ -9904,7 +10051,8 @@ var init_stateful = __esm({
|
|
|
9904
10051
|
seq: n.seq,
|
|
9905
10052
|
stage: n.stage,
|
|
9906
10053
|
timestamp: n.timestamp,
|
|
9907
|
-
agentId: n.agentId
|
|
10054
|
+
agentId: n.agentId,
|
|
10055
|
+
instanceId: n.instanceId
|
|
9908
10056
|
});
|
|
9909
10057
|
const idx = stages.indexOf(n.stage);
|
|
9910
10058
|
if (idx + 1 < stages.length) {
|
|
@@ -9916,21 +10064,70 @@ var init_stateful = __esm({
|
|
|
9916
10064
|
}
|
|
9917
10065
|
return path6;
|
|
9918
10066
|
}
|
|
10067
|
+
/** 把链路径转成前端可画的结构化图(相邻节点 next 边)。 */
|
|
10068
|
+
buildChainGraph(stages, chainId) {
|
|
10069
|
+
const path6 = this.chainPath(stages);
|
|
10070
|
+
const gNodes = path6.map((p, i) => {
|
|
10071
|
+
const n = this.nodes_.find((x) => x.sessionId === p.sessionId && x.seq === p.seq);
|
|
10072
|
+
return {
|
|
10073
|
+
session_id: p.sessionId,
|
|
10074
|
+
seq: p.seq,
|
|
10075
|
+
stage: p.stage,
|
|
10076
|
+
action_detail: n ? n.actionDetail : "",
|
|
10077
|
+
resources: n ? [...n.resources].sort() : [],
|
|
10078
|
+
role: stages[i] || "",
|
|
10079
|
+
instance_id: p.instanceId || ""
|
|
10080
|
+
};
|
|
10081
|
+
});
|
|
10082
|
+
const gEdges = [];
|
|
10083
|
+
for (let i = 1; i < gNodes.length; i++) {
|
|
10084
|
+
gEdges.push({ from: i - 1, to: i, type: "next" });
|
|
10085
|
+
}
|
|
10086
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
10087
|
+
return {
|
|
10088
|
+
property_id: "cross-" + chainId,
|
|
10089
|
+
property_short: "P1",
|
|
10090
|
+
property_name: "\u8DE8\u4F1A\u8BDD\u7EC4\u5408\u94FE",
|
|
10091
|
+
property_expression: stages.map((s, i) => ({ token: s, node_index: i })),
|
|
10092
|
+
nodes: gNodes,
|
|
10093
|
+
edges: gEdges
|
|
10094
|
+
};
|
|
10095
|
+
}
|
|
9919
10096
|
// ---------------- 敏感数据 taint(P2) ----------------
|
|
9920
10097
|
advanceTaint(node) {
|
|
9921
10098
|
if (node.lifecycle === "dead") return;
|
|
9922
10099
|
const idx = node.seq;
|
|
9923
10100
|
const taintedRes = /* @__PURE__ */ new Set();
|
|
10101
|
+
let parent = null;
|
|
10102
|
+
let label = "";
|
|
10103
|
+
let foundParent = false;
|
|
9924
10104
|
for (const r of node.resources) {
|
|
9925
|
-
if (this.resourceTainted(r))
|
|
10105
|
+
if (this.resourceTainted(r)) {
|
|
10106
|
+
taintedRes.add(r);
|
|
10107
|
+
if (!foundParent) {
|
|
10108
|
+
const origin = this.taintOrigin.get(r);
|
|
10109
|
+
if (origin !== void 0) {
|
|
10110
|
+
parent = origin;
|
|
10111
|
+
label = r;
|
|
10112
|
+
foundParent = true;
|
|
10113
|
+
}
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
9926
10116
|
}
|
|
9927
10117
|
if (node.stage === "CRED" && node.resources.some((r) => CRED_FILE_RE2.test(r))) {
|
|
9928
10118
|
this.tainted.add(idx);
|
|
10119
|
+
parent = null;
|
|
10120
|
+
label = "";
|
|
9929
10121
|
for (const r of node.resources) taintedRes.add(r);
|
|
9930
10122
|
}
|
|
9931
10123
|
if (taintedRes.size > 0) {
|
|
9932
10124
|
this.tainted.add(idx);
|
|
9933
|
-
|
|
10125
|
+
this.taintParent.set(idx, parent);
|
|
10126
|
+
if (parent !== null) this.taintEdgeLabel.set(`${parent}:${idx}`, label);
|
|
10127
|
+
for (const r of node.resources) {
|
|
10128
|
+
this.taintedResources.add(r);
|
|
10129
|
+
if (!this.taintOrigin.has(r)) this.taintOrigin.set(r, idx);
|
|
10130
|
+
}
|
|
9934
10131
|
}
|
|
9935
10132
|
}
|
|
9936
10133
|
resourceTainted(resource) {
|
|
@@ -9940,16 +10137,85 @@ var init_stateful = __esm({
|
|
|
9940
10137
|
checkExfilAfterTaint(node) {
|
|
9941
10138
|
if (!this.tainted.has(node.seq)) return [];
|
|
9942
10139
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
10140
|
+
const chain = this.traceTaintChain(node.seq);
|
|
9943
10141
|
return [{
|
|
9944
10142
|
property_id: "sensitive-data-flow",
|
|
9945
|
-
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`,
|
|
10143
|
+
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`,
|
|
9946
10144
|
risk_level: "critical",
|
|
9947
|
-
path:
|
|
10145
|
+
path: chain.map((j) => {
|
|
10146
|
+
const n = this.nodes_[j];
|
|
10147
|
+
return [n.sessionId, n.seq, n.actionDetail.slice(0, 60), n.timestamp, null, n.instanceId];
|
|
10148
|
+
}),
|
|
10149
|
+
graph: this.taintGraph(chain)
|
|
9948
10150
|
}];
|
|
9949
10151
|
}
|
|
9950
10152
|
return [];
|
|
9951
10153
|
}
|
|
9952
|
-
|
|
10154
|
+
/** 沿 taint parent 回溯出 CRED 源 → 当前节点的完整路径。 */
|
|
10155
|
+
traceTaintChain(idx) {
|
|
10156
|
+
const chain = [idx];
|
|
10157
|
+
const seen = /* @__PURE__ */ new Set([idx]);
|
|
10158
|
+
while (true) {
|
|
10159
|
+
const parent = this.taintParent.get(chain[chain.length - 1]);
|
|
10160
|
+
if (parent === void 0 || parent === null || seen.has(parent)) break;
|
|
10161
|
+
chain.push(parent);
|
|
10162
|
+
seen.add(parent);
|
|
10163
|
+
}
|
|
10164
|
+
return chain.reverse();
|
|
10165
|
+
}
|
|
10166
|
+
/** 把 taint 路径转成前端可画的结构化图(data 边带共享资源标签)。 */
|
|
10167
|
+
taintGraph(chain) {
|
|
10168
|
+
const gNodes = [];
|
|
10169
|
+
const gEdges = [];
|
|
10170
|
+
chain.forEach((ni, k) => {
|
|
10171
|
+
const n = this.nodes_[ni];
|
|
10172
|
+
const role = k === 0 ? "source" : k === chain.length - 1 ? "sink" : "carrier";
|
|
10173
|
+
gNodes.push({
|
|
10174
|
+
session_id: n.sessionId,
|
|
10175
|
+
seq: n.seq,
|
|
10176
|
+
stage: n.stage,
|
|
10177
|
+
action_detail: n.actionDetail,
|
|
10178
|
+
resources: [...n.resources].sort(),
|
|
10179
|
+
role,
|
|
10180
|
+
instance_id: n.instanceId
|
|
10181
|
+
});
|
|
10182
|
+
if (k > 0) {
|
|
10183
|
+
const prev = chain[k - 1];
|
|
10184
|
+
gEdges.push({
|
|
10185
|
+
from: k - 1,
|
|
10186
|
+
to: k,
|
|
10187
|
+
type: "data",
|
|
10188
|
+
label: this.taintEdgeLabel.get(`${prev}:${ni}`) || ""
|
|
10189
|
+
});
|
|
10190
|
+
}
|
|
10191
|
+
});
|
|
10192
|
+
if (gNodes.length > 0) gNodes[gNodes.length - 1].witness = true;
|
|
10193
|
+
const expr = [{ token: "CRED", node_index: 0 }];
|
|
10194
|
+
if (chain.length > 2) {
|
|
10195
|
+
expr.push({
|
|
10196
|
+
token: "\u5171\u4EAB\u8F7D\u4F53",
|
|
10197
|
+
node_index: Array.from({ length: chain.length - 2 }, (_, i) => i + 1)
|
|
10198
|
+
});
|
|
10199
|
+
}
|
|
10200
|
+
expr.push({ token: "EXFIL", node_index: chain.length - 1 });
|
|
10201
|
+
return {
|
|
10202
|
+
property_id: "sensitive-data-flow",
|
|
10203
|
+
property_short: "P2",
|
|
10204
|
+
property_name: "\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20",
|
|
10205
|
+
property_expression: expr,
|
|
10206
|
+
nodes: gNodes,
|
|
10207
|
+
edges: gEdges
|
|
10208
|
+
};
|
|
10209
|
+
}
|
|
10210
|
+
taintSource(node, chain = []) {
|
|
10211
|
+
if (chain.length > 0) {
|
|
10212
|
+
const src = this.nodes_[chain[0]];
|
|
10213
|
+
if (src) {
|
|
10214
|
+
for (const r of node.resources) {
|
|
10215
|
+
if (src.resources.includes(r)) return `${src.sessionId}#${src.seq} \u8BBF\u95EE ${r}`;
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
}
|
|
9953
10219
|
const sorted = [...this.tainted].sort((a, b) => a - b);
|
|
9954
10220
|
for (const i of sorted) {
|
|
9955
10221
|
if (i === node.seq) continue;
|
|
@@ -9968,6 +10234,9 @@ var init_stateful = __esm({
|
|
|
9968
10234
|
nodes: this.nodes_.map((n) => ({ ...n, resources: [...n.resources] })),
|
|
9969
10235
|
ptrs: { ...this.ptr },
|
|
9970
10236
|
tainted: [...this.tainted].sort((a, b) => a - b),
|
|
10237
|
+
// taint 传播路径(前端攻击链图);边标签不入快照,重启后可从资源交集重建
|
|
10238
|
+
taint_parent: Object.fromEntries([...this.taintParent.entries()].map(([k, v]) => [String(k), v])),
|
|
10239
|
+
taint_origin: Object.fromEntries([...this.taintOrigin.entries()]),
|
|
9971
10240
|
alive: { ...this.aliveResources },
|
|
9972
10241
|
seq_next: this.seqNext
|
|
9973
10242
|
};
|
|
@@ -9980,6 +10249,8 @@ var init_stateful = __esm({
|
|
|
9980
10249
|
seq: Number(raw.seq || 0),
|
|
9981
10250
|
sessionId: String(raw.sessionId || ""),
|
|
9982
10251
|
agentId: String(raw.agentId || ""),
|
|
10252
|
+
instanceId: String(raw.instanceId || ""),
|
|
10253
|
+
// 旧快照无此字段 → 空
|
|
9983
10254
|
actionType: String(raw.actionType || ""),
|
|
9984
10255
|
actionDetail: String(raw.actionDetail || ""),
|
|
9985
10256
|
stage: String(raw.stage || ""),
|
|
@@ -9995,15 +10266,53 @@ var init_stateful = __esm({
|
|
|
9995
10266
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
9996
10267
|
this.ptr = Object.fromEntries(Object.entries(snapshot.ptrs || {}).map(([k, v]) => [k, Number(v)]));
|
|
9997
10268
|
this.tainted = new Set((snapshot.tainted || []).map(Number));
|
|
10269
|
+
this.taintParent = new Map(
|
|
10270
|
+
Object.entries(snapshot.taint_parent || {}).map(
|
|
10271
|
+
([k, v]) => [Number(k), v === null || v === void 0 ? null : Number(v)]
|
|
10272
|
+
)
|
|
10273
|
+
);
|
|
10274
|
+
this.taintOrigin = new Map(
|
|
10275
|
+
Object.entries(snapshot.taint_origin || {}).map(
|
|
10276
|
+
([k, v]) => [String(k), Number(v)]
|
|
10277
|
+
)
|
|
10278
|
+
);
|
|
10279
|
+
this.taintEdgeLabel = /* @__PURE__ */ new Map();
|
|
9998
10280
|
this.taintedResources = /* @__PURE__ */ new Set();
|
|
9999
10281
|
for (const i of this.tainted) {
|
|
10000
10282
|
const n = this.nodes_[i];
|
|
10001
10283
|
if (n) for (const r of n.resources) this.taintedResources.add(r);
|
|
10002
10284
|
}
|
|
10285
|
+
if (this.tainted.size > 0 && this.taintParent.size === 0) this.rebuildTaintParent();
|
|
10286
|
+
if (this.taintOrigin.size === 0) {
|
|
10287
|
+
for (const i of [...this.tainted].sort((a, b) => a - b)) {
|
|
10288
|
+
const n = this.nodes_[i];
|
|
10289
|
+
if (!n) continue;
|
|
10290
|
+
for (const r of n.resources) {
|
|
10291
|
+
if (!this.taintOrigin.has(r)) this.taintOrigin.set(r, i);
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10003
10295
|
this.aliveResources = Object.fromEntries(
|
|
10004
10296
|
Object.entries(snapshot.alive || {}).map(([k, v]) => [k, Number(v)])
|
|
10005
10297
|
);
|
|
10006
10298
|
}
|
|
10299
|
+
/** 旧快照无 parent 信息时,按 seq 顺序找最近的资源相交染毒前驱。 */
|
|
10300
|
+
rebuildTaintParent() {
|
|
10301
|
+
for (const i of [...this.tainted].sort((a, b) => a - b)) {
|
|
10302
|
+
const n = this.nodes_[i];
|
|
10303
|
+
if (!n) continue;
|
|
10304
|
+
let parent = null;
|
|
10305
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
10306
|
+
if (!this.tainted.has(j)) continue;
|
|
10307
|
+
const m = this.nodes_[j];
|
|
10308
|
+
if (m && m.resources.some((r) => n.resources.includes(r))) {
|
|
10309
|
+
parent = j;
|
|
10310
|
+
break;
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
10313
|
+
this.taintParent.set(i, parent);
|
|
10314
|
+
}
|
|
10315
|
+
}
|
|
10007
10316
|
};
|
|
10008
10317
|
}
|
|
10009
10318
|
});
|
|
@@ -10086,9 +10395,9 @@ var init_chain_state = __esm({
|
|
|
10086
10395
|
delete this.data[agentId];
|
|
10087
10396
|
this._scheduleWrite();
|
|
10088
10397
|
}
|
|
10089
|
-
applyAction(agentId, aNext, sessionId, isError = false, sourceAgent = "") {
|
|
10398
|
+
applyAction(agentId, aNext, sessionId, isError = false, sourceAgent = "", instanceId = "") {
|
|
10090
10399
|
const t = this.load(agentId) || new StatefulChainTracker(agentId);
|
|
10091
|
-
const r = t.advance({ aNext, sessionId, isError, agentId: sourceAgent });
|
|
10400
|
+
const r = t.advance({ aNext, sessionId, isError, agentId: sourceAgent, instanceId });
|
|
10092
10401
|
this.save(t);
|
|
10093
10402
|
return r;
|
|
10094
10403
|
}
|
|
@@ -10406,12 +10715,21 @@ async function runAnalysis(input) {
|
|
|
10406
10715
|
formalSessions.push({
|
|
10407
10716
|
sessionId: s.sessionId || "s",
|
|
10408
10717
|
...s.agentId ? { agentId: s.agentId } : {},
|
|
10718
|
+
...s.instanceId ? { instanceId: s.instanceId } : {},
|
|
10409
10719
|
actionHistory: s.actionHistory || []
|
|
10410
10720
|
});
|
|
10411
10721
|
}
|
|
10412
10722
|
}
|
|
10413
|
-
formalSessions.push({
|
|
10414
|
-
|
|
10723
|
+
formalSessions.push({
|
|
10724
|
+
sessionId: "current",
|
|
10725
|
+
agentId: input.agentId,
|
|
10726
|
+
instanceId: input.instanceId || "",
|
|
10727
|
+
actionHistory: input.actionHistory
|
|
10728
|
+
});
|
|
10729
|
+
const formalResult = formalEngine.verify(formalSessions, {
|
|
10730
|
+
...input.aNext,
|
|
10731
|
+
instanceId: input.instanceId || ""
|
|
10732
|
+
});
|
|
10415
10733
|
if (!formalResult.verified) {
|
|
10416
10734
|
const v = formalResult.violations[0];
|
|
10417
10735
|
if (isFormalObserveMode()) {
|
|
@@ -10427,7 +10745,9 @@ async function runAnalysis(input) {
|
|
|
10427
10745
|
riskType: "formal_verification",
|
|
10428
10746
|
confidence: 90,
|
|
10429
10747
|
source: "formal_engine",
|
|
10430
|
-
violatedLayers: ["L2.2"]
|
|
10748
|
+
violatedLayers: ["L2.2"],
|
|
10749
|
+
violationGraph: v?.graph ?? null,
|
|
10750
|
+
violationChain: (v?.path ?? []).map((p) => [p.sessionId, p.seq, p.detail])
|
|
10431
10751
|
};
|
|
10432
10752
|
}
|
|
10433
10753
|
}
|
|
@@ -10780,6 +11100,8 @@ var AnalyzeRequestSchema = z.object({
|
|
|
10780
11100
|
session_id: z.string(),
|
|
10781
11101
|
/** 来源 agent(AgentGate 逐会话带上):记录在链路径里供告警/取证,不参与链匹配。 */
|
|
10782
11102
|
agent_id: z.string().optional(),
|
|
11103
|
+
/** 来源实例:仅用于展示层脱敏(同实例不隐藏),不参与链匹配。 */
|
|
11104
|
+
instance_id: z.string().optional(),
|
|
10783
11105
|
action_history: z.array(ActionItemSchema).default([])
|
|
10784
11106
|
})).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)."),
|
|
10785
11107
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
@@ -10787,6 +11109,7 @@ var AnalyzeRequestSchema = z.object({
|
|
|
10787
11109
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
10788
11110
|
cwd: z.string().default("").describe("Current working directory"),
|
|
10789
11111
|
agent_id: z.string().default("").describe("Caller agent identifier, e.g. 'xiaoo' / 'opencode' / 'opendesk'. Empty = generic mode (no agent-specific rules)."),
|
|
11112
|
+
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."),
|
|
10790
11113
|
metadata: z.record(z.unknown()).optional().describe("Extended metadata")
|
|
10791
11114
|
});
|
|
10792
11115
|
var AnalyzeResponseSchema = z.object({
|
|
@@ -10800,7 +11123,44 @@ var AnalyzeResponseSchema = z.object({
|
|
|
10800
11123
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
10801
11124
|
analysis_duration_ms: z.number().default(0),
|
|
10802
11125
|
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
10803
|
-
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
11126
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([]),
|
|
11127
|
+
// 结构化攻击链图(前端可视化):节点 + 边(next 时序 / data 数据流)
|
|
11128
|
+
violation_graph: z.object({
|
|
11129
|
+
// 性质元信息(前端展示"违反了哪条性质")
|
|
11130
|
+
property_id: z.string().optional(),
|
|
11131
|
+
property_short: z.string().optional(),
|
|
11132
|
+
property_name: z.string().optional(),
|
|
11133
|
+
// 性质表达式 token → 节点位置(前端展示"表达式对应哪个节点")
|
|
11134
|
+
property_expression: z.array(
|
|
11135
|
+
z.object({
|
|
11136
|
+
token: z.string(),
|
|
11137
|
+
node_index: z.union([z.number(), z.array(z.number())])
|
|
11138
|
+
})
|
|
11139
|
+
).optional(),
|
|
11140
|
+
nodes: z.array(
|
|
11141
|
+
z.object({
|
|
11142
|
+
session_id: z.string().default(""),
|
|
11143
|
+
seq: z.number().default(0),
|
|
11144
|
+
stage: z.string().default(""),
|
|
11145
|
+
action_detail: z.string().default(""),
|
|
11146
|
+
resources: z.array(z.string()).default([]),
|
|
11147
|
+
// 节点在性质中的角色:source/carrier/sink(P2)或阶段名(P1)
|
|
11148
|
+
role: z.string().optional(),
|
|
11149
|
+
// 是否为违例见证节点(当前动作补全链 / 触发外传)
|
|
11150
|
+
witness: z.boolean().optional(),
|
|
11151
|
+
// 来源实例(仅展示层脱敏:同实例不隐藏)
|
|
11152
|
+
instance_id: z.string().optional()
|
|
11153
|
+
})
|
|
11154
|
+
),
|
|
11155
|
+
edges: z.array(
|
|
11156
|
+
z.object({
|
|
11157
|
+
from: z.number(),
|
|
11158
|
+
to: z.number(),
|
|
11159
|
+
type: z.enum(["next", "data"]).default("next"),
|
|
11160
|
+
label: z.string().optional()
|
|
11161
|
+
})
|
|
11162
|
+
)
|
|
11163
|
+
}).nullable().default(null)
|
|
10804
11164
|
});
|
|
10805
11165
|
var HealthResponseSchema = z.object({
|
|
10806
11166
|
status: z.string().default("healthy"),
|
|
@@ -11224,7 +11584,9 @@ async function analyzeInner(input, mossLog) {
|
|
|
11224
11584
|
violated_layers: judgment.violatedLayers || [],
|
|
11225
11585
|
policy: "",
|
|
11226
11586
|
violated_policy: `[${judgment.riskType}] ${judgment.reason}`,
|
|
11227
|
-
confidence: judgment.confidence
|
|
11587
|
+
confidence: judgment.confidence,
|
|
11588
|
+
violation_graph: judgment.violationGraph ?? null,
|
|
11589
|
+
violation_chain: judgment.violationChain ?? []
|
|
11228
11590
|
};
|
|
11229
11591
|
mossLog.logResult("Deny", denied.reason);
|
|
11230
11592
|
return denied;
|
|
@@ -11679,6 +12041,9 @@ async function analyzeRoute(c) {
|
|
|
11679
12041
|
`[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`
|
|
11680
12042
|
);
|
|
11681
12043
|
}
|
|
12044
|
+
const instanceId = String(
|
|
12045
|
+
req.instance_id || req.metadata?.instance_id || ""
|
|
12046
|
+
);
|
|
11682
12047
|
const result = await analyze({
|
|
11683
12048
|
sessionId: req.session_id,
|
|
11684
12049
|
promptSession: req.prompt_session,
|
|
@@ -11694,8 +12059,9 @@ async function analyzeRoute(c) {
|
|
|
11694
12059
|
})),
|
|
11695
12060
|
sessionHistory: (req.session_history || []).map((s) => ({
|
|
11696
12061
|
sessionId: s.session_id,
|
|
11697
|
-
// 来源 agent
|
|
12062
|
+
// 来源 agent/实例(AgentGate 逐会话带上,用于链路径取证/展示脱敏;不参与链匹配)
|
|
11698
12063
|
...s.agent_id ? { agentId: s.agent_id } : {},
|
|
12064
|
+
...s.instance_id ? { instanceId: s.instance_id } : {},
|
|
11699
12065
|
actionHistory: (s.action_history || []).map((h) => ({
|
|
11700
12066
|
actionType: h.action_type,
|
|
11701
12067
|
actionDetail: h.action_detail,
|
|
@@ -11719,11 +12085,13 @@ async function analyzeRoute(c) {
|
|
|
11719
12085
|
})),
|
|
11720
12086
|
aNext: {
|
|
11721
12087
|
actionType: req.a_next.action_type,
|
|
11722
|
-
actionDetail: req.a_next.action_detail
|
|
12088
|
+
actionDetail: req.a_next.action_detail,
|
|
12089
|
+
instanceId
|
|
11723
12090
|
},
|
|
11724
12091
|
reason: req.reason,
|
|
11725
12092
|
osType,
|
|
11726
12093
|
cwd: req.cwd,
|
|
12094
|
+
instanceId,
|
|
11727
12095
|
// agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
|
|
11728
12096
|
// 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
|
|
11729
12097
|
agentId,
|
|
@@ -11759,8 +12127,9 @@ async function analyzeRoute(c) {
|
|
|
11759
12127
|
{ actionType: req.a_next.action_type, actionDetail: req.a_next.action_detail },
|
|
11760
12128
|
req.session_id,
|
|
11761
12129
|
Boolean(meta.is_error),
|
|
11762
|
-
agentId
|
|
12130
|
+
agentId,
|
|
11763
12131
|
// 来源:真实 agent(桶 key 可能是 __system__)
|
|
12132
|
+
instanceId
|
|
11764
12133
|
);
|
|
11765
12134
|
const judgment = incMatchedToDeny(inc);
|
|
11766
12135
|
if (judgment) {
|
|
@@ -11781,7 +12150,8 @@ async function analyzeRoute(c) {
|
|
|
11781
12150
|
confidence: 90,
|
|
11782
12151
|
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
11783
12152
|
// 跨会话节点脱敏(时间线按 agent_id 共享,原样返回等于泄露别人的命令原文)
|
|
11784
|
-
violation_chain: redactChain(judgment.violation_chain, req.session_id)
|
|
12153
|
+
violation_chain: redactChain(judgment.violation_chain, instanceId, req.session_id),
|
|
12154
|
+
violation_graph: redactGraph(judgment.violation_graph, instanceId, req.session_id)
|
|
11785
12155
|
})
|
|
11786
12156
|
);
|
|
11787
12157
|
}
|
|
@@ -11801,7 +12171,9 @@ async function analyzeRoute(c) {
|
|
|
11801
12171
|
policy: result.policy,
|
|
11802
12172
|
violated_policy: result.violated_policy,
|
|
11803
12173
|
confidence: result.confidence,
|
|
11804
|
-
analysis_duration_ms: Math.round(durationMs * 10) / 10
|
|
12174
|
+
analysis_duration_ms: Math.round(durationMs * 10) / 10,
|
|
12175
|
+
violation_chain: result.violation_chain ?? [],
|
|
12176
|
+
violation_graph: redactGraph(result.violation_graph, instanceId, req.session_id)
|
|
11805
12177
|
})
|
|
11806
12178
|
);
|
|
11807
12179
|
} catch (err) {
|
|
@@ -11832,7 +12204,8 @@ function incMatchedToDeny(inc) {
|
|
|
11832
12204
|
property_id: "cross-" + inc.matched.chainId,
|
|
11833
12205
|
reason: inc.matched.reason,
|
|
11834
12206
|
risk_level: inc.matched.riskLevel,
|
|
11835
|
-
violation_chain: chain
|
|
12207
|
+
violation_chain: chain,
|
|
12208
|
+
violation_graph: inc.matched.graph ?? null
|
|
11836
12209
|
};
|
|
11837
12210
|
}
|
|
11838
12211
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
@@ -11840,7 +12213,9 @@ function incMatchedToDeny(inc) {
|
|
|
11840
12213
|
property_id: "sensitive-data-flow",
|
|
11841
12214
|
reason: inc.exfil[0].reason,
|
|
11842
12215
|
risk_level: inc.exfil[0].risk_level,
|
|
11843
|
-
|
|
12216
|
+
// P2 的 path 现在包含 CRED 源 → 中间载体 → 外传点的完整链
|
|
12217
|
+
violation_chain: inc.exfil[0].path ?? [],
|
|
12218
|
+
violation_graph: inc.exfil[0].graph ?? null
|
|
11844
12219
|
};
|
|
11845
12220
|
}
|
|
11846
12221
|
return null;
|