progmune-runtime 3.7.67 → 3.7.68
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/CHANGELOG.md +44 -0
- package/dist/protocol-detector-mixed-surface.test.js +154 -0
- package/dist/protocol-detector.js +87 -29
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +13 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.68] — 2026-10-16
|
|
4
|
+
|
|
5
|
+
### 调用方混合并集豁免(修冤枉第九刀)
|
|
6
|
+
|
|
7
|
+
**根因**:第五刀(全部调用方已守卫)与第七刀(全部调用方是作业)
|
|
8
|
+
各留下边界:① 守卫传播 ≤3 跳,第 4 跳的服务层函数(如
|
|
9
|
+
ExportService.getWorkspaceBaseUrl)在守卫集外;② 第七刀 B 判据是
|
|
10
|
+
**裸名族级**保守——同名族任一成员被 HTTP 面调用则全族不豁免,
|
|
11
|
+
族内「只被作业调用」的成员(如 VerificationNotificationService.
|
|
12
|
+
getPageContext,同为 getPageContext 裸名的 Page 族成员被 controller
|
|
13
|
+
调用)被连坐照报。两者语义上都没有未鉴权请求面:每条到达 F 的
|
|
14
|
+
路径要么已过守卫、要么源起作业处理器。
|
|
15
|
+
|
|
16
|
+
- **判据(computeMixedSurfacePropagatedSet)**:函数 F 的每个直接
|
|
17
|
+
调用方都 ∈ {已守卫传播集} ∪ {作业面传播集} ⇒ 豁免。保守规则:
|
|
18
|
+
R1 调用方集合非空(HTTP 入口无项目调用方,永不豁免);R2 每条
|
|
19
|
+
调用边必须可解析(与第五刀共享 computeResolvedCallerMap 分层消歧,
|
|
20
|
+
歧义 fail-safe);R3 任一调用方在并集外 → 照报;R4 快照轮次语义,
|
|
21
|
+
默认 1 轮(传递闭包留数据驱动);R5 返回集合只含并集新增成员
|
|
22
|
+
- **引擎接线**:豁免仅 authorization 类、函数自身不直接带标记
|
|
23
|
+
(R5/R6);被压告警进 `safeguardAlerts.suppressed` 带
|
|
24
|
+
`suppressedByMixedSurfacePropagation` 标记(标记而非丢弃,R9);
|
|
25
|
+
`PROGMUNE_MIXED_SURFACE_TOGGLE=off` 逃生门
|
|
26
|
+
- **同条件 A/B(docmost,各 fresh IR)**:主告警流 685→669
|
|
27
|
+
(−16 = Access 13 + Mutation 2 + Ownership 1,消失清单与离线重放
|
|
28
|
+
预测逐条一致、新增 0);**control(toggle off)685/140 与 3.7.67
|
|
29
|
+
基线逐条一致**;主路径 violations 11=11 / score 84=84 /
|
|
30
|
+
NEEDS_REVIEW 不变;suppressed 140→156(156=117 守卫 +23 作业
|
|
31
|
+
+16 混合,669+156=825 证据守恒)
|
|
32
|
+
- **FN 风险逐条复核**:16 条全核调用方来源——3 条守卫类
|
|
33
|
+
(@UseGuards(JwtAuthGuard) 导出路由 → export 工具函数,守卫传播
|
|
34
|
+
第 4 跳);13 条作业类(@Processor 队列 → 通知/历史服务方法 →
|
|
35
|
+
下游 getter/写入,B 判据族级连坐的成员)。TP 0
|
|
36
|
+
- **C4/C5 主力类探针结论(knife9-mixed-surface-probe)**:
|
|
37
|
+
repo-update 类 137 / getter 类 213 中,无调用方(HTTP 入口)62/92、
|
|
38
|
+
歧义边 50/70、并集外调用方 23/42——「公开设计端点/可达但非
|
|
39
|
+
鉴权敏感」是语义问题,调用方面传播已到边界(守卫深度敏感性:
|
|
40
|
+
maxDepth 3→6 仅 +6 条)
|
|
41
|
+
- **回归锁定**:单测 11 例(protocol-detector-mixed-surface.test.ts,
|
|
42
|
+
含与第七刀族级保守的差异用例)+ 引擎产品路径 2 + 检测器族
|
|
43
|
+
199/199 + 引擎 239/239;三闸门(taintpath 160 / webshape 72 /
|
|
44
|
+
fr-007 pre5-post0 + fr-016 pre7-post0);**TS 盲测 119 项目对
|
|
45
|
+
3.7.67 提交基线 LOST 0 / ADDED 0**
|
|
46
|
+
|
|
3
47
|
## [3.7.67] — 2026-10-12
|
|
4
48
|
|
|
5
49
|
### 词撞类豁免(修冤枉第八刀:mutation/access trigger 词段碰撞)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const protocol_detector_1 = require("./protocol-detector");
|
|
5
|
+
/**
|
|
6
|
+
* 调用方混合并集豁免(修冤枉第九刀,2026-10-16)单元测试。
|
|
7
|
+
*
|
|
8
|
+
* 判据(见 computeMixedSurfacePropagatedSet 注释):
|
|
9
|
+
* 函数 F 的每个直接调用方都 ∈ {已守卫传播集} ∪ {作业面传播集} ⇒
|
|
10
|
+
* F 无未鉴权请求面(每条到达 F 的路径要么已过守卫、要么源起作业)。
|
|
11
|
+
* R1-R5:调用方非空 / 边全解析 / 全部落在并集内 / 快照轮次默认 1 /
|
|
12
|
+
* 返回集合只含并集新增成员。
|
|
13
|
+
* 消费方(engine)负责 R5/R6:只压 authorization 类、且不压直接标记函数。
|
|
14
|
+
*/
|
|
15
|
+
const MG = "__progmune_auth_machinery__";
|
|
16
|
+
const MJ = "__progmune_no_request_surface__";
|
|
17
|
+
/** 便捷:guard/job 集 + 混合集一并算 */
|
|
18
|
+
function sets(fns, opts) {
|
|
19
|
+
const guard = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
|
|
20
|
+
const job = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
|
|
21
|
+
const mixed = (0, protocol_detector_1.computeMixedSurfacePropagatedSet)(fns, guard, job, opts);
|
|
22
|
+
return { guard, job, mixed };
|
|
23
|
+
}
|
|
24
|
+
(0, vitest_1.describe)("computeMixedSurfacePropagatedSet(调用方混合并集,第九刀)", () => {
|
|
25
|
+
(0, vitest_1.it)("混合调用方(已守卫 + 作业)⇒ 豁免", () => {
|
|
26
|
+
const fns = [
|
|
27
|
+
{ name: "GuardedController.create", file: "c/page.controller.ts", calls: [MG, "log"] },
|
|
28
|
+
{ name: "AuditService.log", file: "s/audit.service.ts", calls: ["fn"] },
|
|
29
|
+
{ name: "DigestJob.run", file: "q/digest.job.ts", calls: [MJ, "fn"] },
|
|
30
|
+
{ name: "MixedTarget.fn", file: "m/target.ts", calls: [] },
|
|
31
|
+
];
|
|
32
|
+
// AuditService.log:全部调用方已守卫 ⇒ 守卫集成员;
|
|
33
|
+
// MixedTarget.fn 调用方 = {AuditService.log[g], DigestJob.run[j]} ⇒ 混合豁免
|
|
34
|
+
const { guard, job, mixed } = sets(fns);
|
|
35
|
+
(0, vitest_1.expect)(guard.has("AuditService.log")).toBe(true);
|
|
36
|
+
(0, vitest_1.expect)(job.has("DigestJob.run")).toBe(true);
|
|
37
|
+
(0, vitest_1.expect)(mixed.has("MixedTarget.fn")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("全部调用方已守卫但超过守卫传播深度(第 4 跳)⇒ 并集豁免", () => {
|
|
40
|
+
const fns = [
|
|
41
|
+
{ name: "AuthRoot", file: "f.ts", calls: [MG, "b"] },
|
|
42
|
+
{ name: "b", file: "f.ts", calls: ["c"] },
|
|
43
|
+
{ name: "c", file: "f.ts", calls: ["d"] },
|
|
44
|
+
{ name: "d", file: "f.ts", calls: ["e"] },
|
|
45
|
+
{ name: "e", file: "f.ts", calls: [] },
|
|
46
|
+
];
|
|
47
|
+
const { guard, mixed } = sets(fns);
|
|
48
|
+
(0, vitest_1.expect)(guard.has("d")).toBe(true); // 第 3 跳:在守卫集内
|
|
49
|
+
(0, vitest_1.expect)(guard.has("e")).toBe(false); // 第 4 跳:守卫传播边界外
|
|
50
|
+
(0, vitest_1.expect)(mixed.has("e")).toBe(true); // 并集判据罩住(调用方 d ∈ 守卫集)
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("作业调用方与作业面集族级保守的差异:按边解析只豁免作业链成员", () => {
|
|
53
|
+
// docmost 实测形态:作业处理器与被调方同「类根」(processExpiring
|
|
54
|
+
// → getPageContext),逐边消歧可解析。B 判据(第七刀)在裸名族含
|
|
55
|
+
// HTTP 调用方时全族不豁免;并集判据按边解析——只豁免作业链上
|
|
56
|
+
// 那个成员,HTTP 面成员照报。
|
|
57
|
+
const fns = [
|
|
58
|
+
{ name: "VerificationNotificationService.processExpiring", file: "s/verification.service.ts", calls: [MJ, "getPageContext"] },
|
|
59
|
+
{ name: "VerificationNotificationService.getPageContext", file: "s/verification.service.ts", calls: [] },
|
|
60
|
+
{ name: "PageNotificationService.getPageContext", file: "s/page.service.ts", calls: [] },
|
|
61
|
+
{ name: "PageNotificationService.notify", file: "s/page.service.ts", calls: ["getPageContext"] }, // 同文件 HTTP 面调用方
|
|
62
|
+
];
|
|
63
|
+
const { job, mixed } = sets(fns);
|
|
64
|
+
// 第七刀 B 判据:裸名族含非作业调用方 ⇒ 全族不豁免
|
|
65
|
+
(0, vitest_1.expect)(job.has("VerificationNotificationService.getPageContext")).toBe(false);
|
|
66
|
+
(0, vitest_1.expect)(job.has("PageNotificationService.getPageContext")).toBe(false);
|
|
67
|
+
// 第九刀并集:边解析逐条——Verification 成员只被作业调用 ⇒ 豁免;
|
|
68
|
+
// Page 成员被 notify(并集外)调用 ⇒ 不豁免
|
|
69
|
+
(0, vitest_1.expect)(mixed.has("VerificationNotificationService.getPageContext")).toBe(true);
|
|
70
|
+
(0, vitest_1.expect)(mixed.has("PageNotificationService.getPageContext")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("任一调用方在并集外 ⇒ 不豁免(fail-safe)", () => {
|
|
73
|
+
const fns = [
|
|
74
|
+
{ name: "GuardedController.create", file: "c.ts", calls: [MG, "write"] },
|
|
75
|
+
{ name: "PublicController.import", file: "c2.ts", calls: ["write"] },
|
|
76
|
+
{ name: "write", file: "r.ts", calls: [] },
|
|
77
|
+
];
|
|
78
|
+
const { mixed } = sets(fns);
|
|
79
|
+
(0, vitest_1.expect)(mixed.has("write")).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)("无调用方(入口/未连通)⇒ 不豁免(R1)", () => {
|
|
82
|
+
const fns = [{ name: "orphanEntry", file: "m.ts", calls: [] }];
|
|
83
|
+
const { mixed } = sets(fns);
|
|
84
|
+
(0, vitest_1.expect)(mixed.has("orphanEntry")).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)("歧义调用边 ⇒ 不豁免(R2 fail-safe)", () => {
|
|
87
|
+
// 同名裸名两个候选、调用方无法消歧(不同文件不同目录不同类根)
|
|
88
|
+
const fns = [
|
|
89
|
+
{ name: "GuardedController.create", file: "c/ctrl.ts", calls: [MG, "load"] },
|
|
90
|
+
{ name: "XxxService.load", file: "x/x.service.ts", calls: [] },
|
|
91
|
+
{ name: "YyyService.load", file: "y/y.service.ts", calls: [] },
|
|
92
|
+
];
|
|
93
|
+
const { mixed } = sets(fns);
|
|
94
|
+
(0, vitest_1.expect)(mixed.has("XxxService.load")).toBe(false);
|
|
95
|
+
(0, vitest_1.expect)(mixed.has("YyyService.load")).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)("默认 1 轮:并集下游的下游不豁免(传递闭包未开)", () => {
|
|
98
|
+
const fns = [
|
|
99
|
+
{ name: "Job.run", file: "q.ts", calls: [MJ, "b"] },
|
|
100
|
+
{ name: "b", file: "f.ts", calls: ["c"] },
|
|
101
|
+
{ name: "c", file: "f.ts", calls: ["d"] },
|
|
102
|
+
{ name: "d", file: "f.ts", calls: [] },
|
|
103
|
+
];
|
|
104
|
+
const { job, mixed } = sets(fns);
|
|
105
|
+
(0, vitest_1.expect)(job.has("b")).toBe(true); // b 已在作业面集(第七刀,第 1 跳)
|
|
106
|
+
(0, vitest_1.expect)(mixed.has("b")).toBe(false); // R5:既有成员不重复进 mixed
|
|
107
|
+
(0, vitest_1.expect)(mixed.has("c")).toBe(true); // c 调用方 {b[j]} ⊆ 并集 ⇒ 混合豁免
|
|
108
|
+
(0, vitest_1.expect)(mixed.has("d")).toBe(false); // d 调用方 {c} 不在并集(1 轮)⇒ 不豁免
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)("maxRounds=2 ⇒ 传递两跳成立(快照轮次语义)", () => {
|
|
111
|
+
const fns = [
|
|
112
|
+
{ name: "Job.run", file: "q.ts", calls: [MJ, "b"] },
|
|
113
|
+
{ name: "b", file: "f.ts", calls: ["c"] },
|
|
114
|
+
{ name: "c", file: "f.ts", calls: ["d"] },
|
|
115
|
+
{ name: "d", file: "f.ts", calls: [] },
|
|
116
|
+
];
|
|
117
|
+
const { mixed } = sets(fns, { maxRounds: 2 });
|
|
118
|
+
(0, vitest_1.expect)(mixed.has("c")).toBe(true);
|
|
119
|
+
(0, vitest_1.expect)(mixed.has("d")).toBe(true); // 第 2 轮:d 调用方 {c} ⊆ 并集∪第 1 轮新增
|
|
120
|
+
});
|
|
121
|
+
(0, vitest_1.it)("返回集合只含并集新增成员(R5:种子/既有成员由各自刀处理)", () => {
|
|
122
|
+
const fns = [
|
|
123
|
+
{ name: "GuardedController.create", file: "c.ts", calls: [MG, "service"] },
|
|
124
|
+
{ name: "Job.run", file: "q.ts", calls: [MJ, "jobOnly"] },
|
|
125
|
+
{ name: "service", file: "s.ts", calls: ["target"] },
|
|
126
|
+
{ name: "jobOnly", file: "s.ts", calls: ["target"] },
|
|
127
|
+
{ name: "target", file: "s.ts", calls: [] },
|
|
128
|
+
];
|
|
129
|
+
const { guard, job, mixed } = sets(fns);
|
|
130
|
+
// 种子/既有成员(GuardedController.create、Job.run、service、jobOnly)不重复进 mixed
|
|
131
|
+
(0, vitest_1.expect)(guard.has("service")).toBe(true); // service 全部调用方已守卫 ⇒ 守卫集成员
|
|
132
|
+
(0, vitest_1.expect)(job.has("jobOnly")).toBe(true); // jobOnly 只被作业调用 ⇒ 作业面成员
|
|
133
|
+
(0, vitest_1.expect)(mixed.has("GuardedController.create")).toBe(false);
|
|
134
|
+
(0, vitest_1.expect)(mixed.has("Job.run")).toBe(false);
|
|
135
|
+
(0, vitest_1.expect)(mixed.has("service")).toBe(false);
|
|
136
|
+
(0, vitest_1.expect)(mixed.has("jobOnly")).toBe(false);
|
|
137
|
+
// 新增成员:target 调用方 = {service[g], jobOnly[j]} ⇒ 混合豁免
|
|
138
|
+
(0, vitest_1.expect)(mixed.has("target")).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
(0, vitest_1.it)("自调不提供豁免证据", () => {
|
|
141
|
+
const fns = [{ name: "CleanupService.cleanup", file: "s.ts", calls: ["cleanup"] }];
|
|
142
|
+
const { mixed } = sets(fns);
|
|
143
|
+
(0, vitest_1.expect)(mixed.has("CleanupService.cleanup")).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.it)("直接标记函数不进入混合新增集合(自身已是守卫/作业种子)", () => {
|
|
146
|
+
const fns = [
|
|
147
|
+
{ name: "GuardedController.create", file: "c.ts", calls: [MG, "self2"] },
|
|
148
|
+
{ name: "self2", file: "c.ts", calls: [] },
|
|
149
|
+
];
|
|
150
|
+
const { mixed } = sets(fns);
|
|
151
|
+
(0, vitest_1.expect)(mixed.has("GuardedController.create")).toBe(false);
|
|
152
|
+
(0, vitest_1.expect)(mixed.has("self2")).toBe(false); // self2 已在守卫集(第 1 跳)
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -14,8 +14,10 @@ exports.isAuthFunctionName = isAuthFunctionName;
|
|
|
14
14
|
exports.classifyParamType = classifyParamType;
|
|
15
15
|
exports.detectSafeguardViolations = detectSafeguardViolations;
|
|
16
16
|
exports.buildCallerMap = buildCallerMap;
|
|
17
|
+
exports.computeResolvedCallerMap = computeResolvedCallerMap;
|
|
17
18
|
exports.computeGuardPropagatedSet = computeGuardPropagatedSet;
|
|
18
19
|
exports.computeJobSurfacePropagatedSet = computeJobSurfacePropagatedSet;
|
|
20
|
+
exports.computeMixedSurfacePropagatedSet = computeMixedSurfacePropagatedSet;
|
|
19
21
|
exports.detectSafeguardViolationsV7 = detectSafeguardViolationsV7;
|
|
20
22
|
exports.detectProtocolViolations = detectProtocolViolations;
|
|
21
23
|
exports.validateProtocolState = validateProtocolState;
|
|
@@ -1643,31 +1645,17 @@ function buildCallerMap(funcs) {
|
|
|
1643
1645
|
return callerMap;
|
|
1644
1646
|
}
|
|
1645
1647
|
/**
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
* 语义:「函数的**全部**直接调用方都已守卫 ⇒ 该函数视为守卫后」。
|
|
1650
|
-
* 证据:docmost 补标注 111 条授权族告警全 FP,其中 49 条(44%)是
|
|
1651
|
-
* controller 已 @UseGuards、service/repo 层函数被误报。保守量化
|
|
1652
|
-
* (blind-benchmark/auth-guard-propagation-probe.ts):
|
|
1653
|
-
* docmost 745 条授权族告警可压 47 条(6.3%),TS 盲测可压 0 条。
|
|
1654
|
-
*
|
|
1655
|
-
* 保守规则(R1-R4,fail-safe 方向):
|
|
1656
|
-
* R1 只有【全部】调用方已守卫才豁免;任一调用方无守卫证据 → 照报
|
|
1657
|
-
* R2 守卫沿调用链向下传播 ≤ maxDepth(默认 3)跳
|
|
1658
|
-
* R3 传播通道只认 `marker`(默认 __progmune_auth_machinery__)一个标记
|
|
1659
|
-
* R4 裸名调用解析分层消歧(唯一才解析,否则给全部候选挂未解析边):
|
|
1660
|
-
* ① 名字后缀唯一 ② 同文件唯一 ③ 同目录唯一
|
|
1661
|
-
* ④ 类根对齐唯一(调用方 XController/Service → 候选类根同为 X)
|
|
1662
|
-
* R6 豁免只给【非直接标记】函数——自身带标记的维持既有 E2 语义
|
|
1663
|
-
* (只接受标记的规则由 detector 自身处理;不接受标记的规则照报)
|
|
1648
|
+
* 反向调用图(分层消歧,R4):被调【全限定名】→ 调用方列表
|
|
1649
|
+
* (resolved=false = 歧义 fail-safe)。computeGuardPropagatedSet(第五刀)
|
|
1650
|
+
* 与 computeMixedSurfacePropagatedSet(第九刀)共享同一边解析。
|
|
1664
1651
|
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1652
|
+
* 边解析分层消歧(R4):① 后缀唯一 ② 同文件唯一 ③ 同目录唯一
|
|
1653
|
+
* ④ 类根对齐唯一(XController→XService→XRepo 的 MVC 命名惯例);
|
|
1654
|
+
* 全部失败 ⇒ 给全部同名候选挂未解析边(fail-safe)。
|
|
1655
|
+
* 调用方自身排除:controller.create 调 "create" 时自己就是后缀候选
|
|
1656
|
+
* 之一,同文件/同目录层会错误自我捕获(递归/自调不提供守卫证据)。
|
|
1667
1657
|
*/
|
|
1668
|
-
function
|
|
1669
|
-
const marker = opts?.marker ?? "__progmune_auth_machinery__";
|
|
1670
|
-
const maxDepth = opts?.maxDepth ?? 3;
|
|
1658
|
+
function computeResolvedCallerMap(funcs) {
|
|
1671
1659
|
const byName = new Map();
|
|
1672
1660
|
for (const f of funcs)
|
|
1673
1661
|
if (!byName.has(f.name))
|
|
@@ -1680,10 +1668,6 @@ function computeGuardPropagatedSet(funcs, opts) {
|
|
|
1680
1668
|
bySuffix.set(bare, []);
|
|
1681
1669
|
bySuffix.get(bare).push(f.name);
|
|
1682
1670
|
}
|
|
1683
|
-
// 反向调用图:被调【全限定名】→ 调用方列表(resolved=false = 歧义 fail-safe)。
|
|
1684
|
-
// 边解析分层消歧(R4):① 后缀唯一 ② 同文件唯一 ③ 同目录唯一
|
|
1685
|
-
// ④ 类根对齐唯一(XController→XService→XRepo 的 MVC 命名惯例);
|
|
1686
|
-
// 全部失败 ⇒ 给全部同名候选挂未解析边(fail-safe)。
|
|
1687
1671
|
const dirOf = (p) => (p ? p.split("/").slice(0, -1).join("/") : "");
|
|
1688
1672
|
const rootOf = (n) => {
|
|
1689
1673
|
const cls = n.split(".")[0] || n;
|
|
@@ -1694,8 +1678,6 @@ function computeGuardPropagatedSet(funcs, opts) {
|
|
|
1694
1678
|
for (const c of f.calls || []) {
|
|
1695
1679
|
if (!bySuffix.has(c))
|
|
1696
1680
|
continue; // 非项目函数名(外部库调用)→ 无守卫语义
|
|
1697
|
-
// 排除调用方自身:controller.create 调 "create" 时,自己就是后缀
|
|
1698
|
-
// 候选之一,同文件/同目录层会错误自我捕获(递归/自调不提供守卫证据)
|
|
1699
1681
|
const cands = bySuffix.get(c).filter((n) => n !== f.name);
|
|
1700
1682
|
if (cands.length === 0)
|
|
1701
1683
|
continue;
|
|
@@ -1740,6 +1722,35 @@ function computeGuardPropagatedSet(funcs, opts) {
|
|
|
1740
1722
|
}
|
|
1741
1723
|
}
|
|
1742
1724
|
}
|
|
1725
|
+
return calleeToCallers;
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* 修冤枉第五刀(2026-10-10):守卫沿调用链传播——
|
|
1729
|
+
* 授权族告警的「守卫在 controller 层、名字级看不见」根因的保守豁免。
|
|
1730
|
+
*
|
|
1731
|
+
* 语义:「函数的**全部**直接调用方都已守卫 ⇒ 该函数视为守卫后」。
|
|
1732
|
+
* 证据:docmost 补标注 111 条授权族告警全 FP,其中 49 条(44%)是
|
|
1733
|
+
* controller 已 @UseGuards、service/repo 层函数被误报。保守量化
|
|
1734
|
+
* (blind-benchmark/auth-guard-propagation-probe.ts):
|
|
1735
|
+
* docmost 745 条授权族告警可压 47 条(6.3%),TS 盲测可压 0 条。
|
|
1736
|
+
*
|
|
1737
|
+
* 保守规则(R1-R4,fail-safe 方向):
|
|
1738
|
+
* R1 只有【全部】调用方已守卫才豁免;任一调用方无守卫证据 → 照报
|
|
1739
|
+
* R2 守卫沿调用链向下传播 ≤ maxDepth(默认 3)跳
|
|
1740
|
+
* R3 传播通道只认 `marker`(默认 __progmune_auth_machinery__)一个标记
|
|
1741
|
+
* R4 裸名调用解析分层消歧(唯一才解析,否则给全部候选挂未解析边):
|
|
1742
|
+
* ① 名字后缀唯一 ② 同文件唯一 ③ 同目录唯一
|
|
1743
|
+
* ④ 类根对齐唯一(调用方 XController/Service → 候选类根同为 X)
|
|
1744
|
+
* R6 豁免只给【非直接标记】函数——自身带标记的维持既有 E2 语义
|
|
1745
|
+
* (只接受标记的规则由 detector 自身处理;不接受标记的规则照报)
|
|
1746
|
+
*
|
|
1747
|
+
* 返回集合含直接标记函数本身(消费方须自行应用 R6 过滤),
|
|
1748
|
+
* 因为传递闭包的种子就是它们。
|
|
1749
|
+
*/
|
|
1750
|
+
function computeGuardPropagatedSet(funcs, opts) {
|
|
1751
|
+
const marker = opts?.marker ?? "__progmune_auth_machinery__";
|
|
1752
|
+
const maxDepth = opts?.maxDepth ?? 3;
|
|
1753
|
+
const calleeToCallers = computeResolvedCallerMap(funcs);
|
|
1743
1754
|
// 种子:直接带标记的函数
|
|
1744
1755
|
const guarded = new Set();
|
|
1745
1756
|
for (const f of funcs)
|
|
@@ -1827,6 +1838,53 @@ function computeJobSurfacePropagatedSet(funcs, opts) {
|
|
|
1827
1838
|
}
|
|
1828
1839
|
return exempt;
|
|
1829
1840
|
}
|
|
1841
|
+
/**
|
|
1842
|
+
* 修冤枉第九刀(2026-10-16):调用方混合并集豁免——
|
|
1843
|
+
* 「函数的每个直接调用方都 ∈ {已守卫传播集} ∪ {作业面传播集}」。
|
|
1844
|
+
*
|
|
1845
|
+
* 第五刀要求【全部调用方已守卫】,第七刀要求【全部调用方是作业】;
|
|
1846
|
+
* 混合形态(部分调用方是已守卫 controller、部分是无请求面作业)两者都
|
|
1847
|
+
* 覆盖不到,但语义上同样无未鉴权请求面:每条到达 F 的路径要么已过守卫
|
|
1848
|
+
* (guardSet 成员自身及其调用链上方都已守卫),要么源起作业处理器
|
|
1849
|
+
* (jobSet 成员无请求面)。C4 repo 层 update* 语义类与 C5 getter 语义类
|
|
1850
|
+
* 的主力根因就是这种调用方混合(knife8-word-collision-probe 分类结论)。
|
|
1851
|
+
*
|
|
1852
|
+
* 保守规则(与第五刀共享边解析,见 computeResolvedCallerMap R4):
|
|
1853
|
+
* R1 调用方集合非空——入口函数(HTTP 面)无项目调用方,永不豁免
|
|
1854
|
+
* R2 每条调用边都必须可解析(resolved=true),歧义 fail-safe
|
|
1855
|
+
* R3 调用方必须 ∈ guardSet ∪ jobSet;任一调用方不在此并集 → 照报
|
|
1856
|
+
* R4 快照轮次语义(与第五刀一致);默认 1 轮(只豁免直接混合下游;
|
|
1857
|
+
* 传递闭包留数据驱动再开,与第七刀同决策)
|
|
1858
|
+
* R5 返回集合只含并集【新增】成员——guardSet/jobSet 成员由各自刀处理
|
|
1859
|
+
*
|
|
1860
|
+
* guardSet/jobSet 由调用方传入(engine 处与第五/七刀同源:
|
|
1861
|
+
* computeGuardPropagatedSet / computeJobSurfacePropagatedSet 的返回值)。
|
|
1862
|
+
*/
|
|
1863
|
+
function computeMixedSurfacePropagatedSet(funcs, guardSet, jobSet, opts) {
|
|
1864
|
+
const maxRounds = opts?.maxRounds ?? 1;
|
|
1865
|
+
const calleeToCallers = computeResolvedCallerMap(funcs);
|
|
1866
|
+
const base = new Set([...guardSet, ...jobSet]);
|
|
1867
|
+
const mixed = new Set(); // R5:只记本轮新增
|
|
1868
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
1869
|
+
const toAdd = [];
|
|
1870
|
+
for (const f of funcs) {
|
|
1871
|
+
if (base.has(f.name))
|
|
1872
|
+
continue;
|
|
1873
|
+
const callers = calleeToCallers.get(f.name) || [];
|
|
1874
|
+
if (callers.length === 0)
|
|
1875
|
+
continue; // R1:无调用方(入口/未连通)→ 不豁免
|
|
1876
|
+
if (callers.every((c) => c.resolved && base.has(c.caller)))
|
|
1877
|
+
toAdd.push(f.name);
|
|
1878
|
+
}
|
|
1879
|
+
if (toAdd.length === 0)
|
|
1880
|
+
break;
|
|
1881
|
+
for (const n of toAdd) {
|
|
1882
|
+
base.add(n);
|
|
1883
|
+
mixed.add(n);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
return mixed;
|
|
1887
|
+
}
|
|
1830
1888
|
/**
|
|
1831
1889
|
* v7: Build safeguard context — own → same-file → caller chain.
|
|
1832
1890
|
*
|
package/dist/sdk.js
CHANGED
|
@@ -20,7 +20,7 @@ const risk_model_1 = require("./risk-model");
|
|
|
20
20
|
const protocol_knowledge_1 = require("./protocol-knowledge");
|
|
21
21
|
const evidence_repository_1 = require("./evidence-repository");
|
|
22
22
|
/** Runtime version — stable public identifier. Internal layers evolve underneath. */
|
|
23
|
-
exports.RUNTIME_VERSION = "3.7.
|
|
23
|
+
exports.RUNTIME_VERSION = "3.7.68";
|
|
24
24
|
function verify(filePath) {
|
|
25
25
|
const cert = (0, certify_1.certify)(filePath);
|
|
26
26
|
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
package/dist/trust/engine.js
CHANGED
|
@@ -1512,6 +1512,14 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1512
1512
|
const jobSurfacePropagated = process.env.PROGMUNE_JOB_SURFACE_TOGGLE === "off"
|
|
1513
1513
|
? new Set()
|
|
1514
1514
|
: (0, protocol_detector_1.computeJobSurfacePropagatedSet)(functions);
|
|
1515
|
+
// 修冤枉第九刀(2026-10-16):调用方混合并集豁免——「每个直接
|
|
1516
|
+
// 调用方都 ∈ {已守卫传播集} ∪ {作业面传播集}」⇒ 无未鉴权请求面。
|
|
1517
|
+
// 保守判据见 computeMixedSurfacePropagatedSet(R1-R5):调用方
|
|
1518
|
+
// 非空、边全解析、默认 1 轮。豁免仅作用于 authorization 类且
|
|
1519
|
+
// 函数自身不直接带标记(R5/R6)——与第五/七刀同:标记而非丢弃。
|
|
1520
|
+
const mixedSurfacePropagated = process.env.PROGMUNE_MIXED_SURFACE_TOGGLE === "off"
|
|
1521
|
+
? new Set()
|
|
1522
|
+
: (0, protocol_detector_1.computeMixedSurfacePropagatedSet)(functions, guardPropagated, jobSurfacePropagated);
|
|
1515
1523
|
for (const f of functions) {
|
|
1516
1524
|
const fCalls = f.calls || [];
|
|
1517
1525
|
let svs;
|
|
@@ -1528,6 +1536,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1528
1536
|
const directlyJobMarked = fCalls.includes("__progmune_no_request_surface__");
|
|
1529
1537
|
const guardExempt = !directlyMarked && guardPropagated.has(f.name);
|
|
1530
1538
|
const jobExempt = !directlyJobMarked && jobSurfacePropagated.has(f.name);
|
|
1539
|
+
const mixedExempt = !directlyMarked && !directlyJobMarked && mixedSurfacePropagated.has(f.name);
|
|
1531
1540
|
for (const v of svs) {
|
|
1532
1541
|
const alert = {
|
|
1533
1542
|
rule: v.rule,
|
|
@@ -1547,6 +1556,10 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1547
1556
|
alert.suppressedByJobSurfacePropagation = true;
|
|
1548
1557
|
suppressed = true;
|
|
1549
1558
|
}
|
|
1559
|
+
if (mixedExempt) {
|
|
1560
|
+
alert.suppressedByMixedSurfacePropagation = true;
|
|
1561
|
+
suppressed = true;
|
|
1562
|
+
}
|
|
1550
1563
|
if (suppressed) {
|
|
1551
1564
|
safeguardAlertsSuppressed.push(alert);
|
|
1552
1565
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.68",
|
|
4
4
|
"description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/",
|