progmune-runtime 3.7.53 → 3.7.55
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 +72 -0
- package/dist/extract-ir.js +11 -2
- package/dist/frameworks/nestjs-detector.js +94 -1
- package/dist/frameworks/nestjs-detector.test.js +177 -0
- package/dist/protocol-detector-session-param.test.js +93 -0
- package/dist/protocol-detector.js +26 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +9 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.55] — 2026-09-29
|
|
4
|
+
|
|
5
|
+
### 主路径(trust 判定)真实项目首测 + NestJS 失明误报修复(§50)
|
|
6
|
+
|
|
7
|
+
**首次对产品判定路径做真实项目全量测量**(`blind-benchmark/REALWORLD_MAIN_PATH_V1.md`):
|
|
8
|
+
docmost(NestJS 5k★)修复前被主路径判 **BLOCKED(score 48,auth 0 分)**——132 条违规里
|
|
9
|
+
126 条逐条核实为失明/语义误报,且同一批违规在 policyCompliance 与 protocolSafety
|
|
10
|
+
**双维度重复扣分**。主路径与旁路(safeguard,§49 加权精确率 5.8%)同病:
|
|
11
|
+
真实公告位置命中 0/18(fr-corpus 17 条交叉证实协议状态机 0 检出),
|
|
12
|
+
但主路径把噪声直接变成产品级判定——**虚假 BLOCKED 比虚假告警伤害大**。
|
|
13
|
+
|
|
14
|
+
**四刀修复(全部 src 接线层,判定核心不动)**:
|
|
15
|
+
|
|
16
|
+
1. **E3 DTO 通道接入 NestJS 适配器**:`app.useGlobalPipes(...)` 全局管道识别 +
|
|
17
|
+
路由入参类型 ∈ 已校验 DTO 集合(复用 `validatedDtoClassNames`,加继承闭包与
|
|
18
|
+
`createZodDto` Zod 形态)⇒ 不报 NO_VALIDATION。`extract-ir.ts` 导出 E3 集合函数。
|
|
19
|
+
2. **无结构化输入豁免**:入参无 @Body/@Query/@Param(仅 AuthUser/Req/Res/
|
|
20
|
+
UploadedFile)的路由不报 NO_VALIDATION——没有可校验的输入。
|
|
21
|
+
3. **auth 端点豁免扩展**:+ password-reset / reset-password / verify-token /
|
|
22
|
+
verify / setup / init(docmost 实测 3 条 NO_AUTH 误报)。
|
|
23
|
+
4. **双维度去重**:policyCompliance 只吃策略类违规(policy_ref 以
|
|
24
|
+
framework/protocol-safety 开头的已在 protocolSafety 计分)。
|
|
25
|
+
|
|
26
|
+
**效果**:docmost **BLOCKED(48) → APPROVED(87)**,违规 132 → 6(NESTJS 误报
|
|
27
|
+
126 → 0;残留 6 条含 1 条撞 CVE-2026-33193 攻击面的真实语义告警);
|
|
28
|
+
hedgedoc(Zod 校验体系 + public/private 双 API 面)47 → 32,剩余 19 条逐条定性
|
|
29
|
+
为规则粒度边界(@Param 裸类型 / @Body('field') 单字段,真无校验,如实保留)。
|
|
30
|
+
|
|
31
|
+
**踩坑**:`param.getType().getText()` 返回 `import("...").DtoName` 形态——取
|
|
32
|
+
首个标识符会取到 `import` 关键字,改取全部标识符任一命中;测试断言 route
|
|
33
|
+
格式无前导斜杠(`POST pages/create`),3 个测试曾因此"假通过"。
|
|
34
|
+
|
|
35
|
+
**测试**:nestjs-detector 17/17(新增全局管道+DTO / Zod / 继承 / 无输入 7 条
|
|
36
|
+
正反向);trust 全家 95/95。
|
|
37
|
+
|
|
38
|
+
## [3.7.54] — 2026-09-27
|
|
39
|
+
|
|
40
|
+
### §47 签名级会话证据 + §48 输入校验族摸底(含两条判据被数据否决)
|
|
41
|
+
|
|
42
|
+
**落地(§47)**:`SafeguardRule` 新增 `paramTypeSafeguards` 通道,`SESSION_BEARING_TYPE_RE
|
|
43
|
+
= /(?:^|[A-Za-z])Session$/i`。语义是**正向**证据且取自 AST 签名——函数手里握着一个
|
|
44
|
+
session 对象 ⇒ 「任何人无需凭证即可访问/修改」不成立。**只挂** Unauthenticated
|
|
45
|
+
Access / Mutation 两条,**不挂** Ownership:有会话 ≠ 操作对象属于自己,一并补上会让这一刀
|
|
46
|
+
变成洗白越权的工具(否命题由 fixture 反向②承担)。
|
|
47
|
+
|
|
48
|
+
选择「参数类型」而不是「调用列表」的理由:fp-pool 切片里入边为 0 的函数占 36%~42%,
|
|
49
|
+
凡是依赖 `calls` 的机制在那些函数上都会**静默失效**——表现为一切正常,实际没跑。
|
|
50
|
+
|
|
51
|
+
**实测**:真实池 LOST 4 / ADDED 0(全部 hedgedoc 的 `RequestWithSession`,逐条读源码确认
|
|
52
|
+
FP);合成池 LOST 3 / ADDED 0(全部本轮新补的 `webshape_H`,webshape 期望 66→72)。
|
|
53
|
+
|
|
54
|
+
**诚实分母(R77)**:24 条确认真漏洞当前**无一条**触发授权族规则 ⇒ 「24/24 仍报」对这一族
|
|
55
|
+
是平凡成立。本轮安全性实际由合成正反向 fixture + 被压 4 条逐行读源码承担。
|
|
56
|
+
|
|
57
|
+
**否决(§47.2 / §48)**:
|
|
58
|
+
- 「calls 里找不到写入 ⇒ 不报未认证变更」——缺席型判据要求「看不到 == 不存在」,但 calls
|
|
59
|
+
通道有损,提取失败会被伪装成「真的不写」⇒ 真漏洞静默消失且 gold 查不出来(**R75**)
|
|
60
|
+
- 「参数类型以 Options 收尾 ⇒ 内部打包、非外部输入」——`createUser(options)` 这类反例太常见,
|
|
61
|
+
且语料里没有反向 fixture。**现有真值可分 ≠ 判据安全**(**R79**)
|
|
62
|
+
- 附带修正工具缺陷:离线重放对 `requireMarker` 类规则必然短路(gold 的 calls 里没有
|
|
63
|
+
`__progmune_input_effect__`)⇒ 此前「40 条已压掉」是**假收益**(**R78**)
|
|
64
|
+
|
|
65
|
+
**§48 摸底结论**:输入校验族 12 条真漏洞 12/12 仍报(首个可证伪基线);可打误报 21 条,
|
|
66
|
+
但两族共同的瓶颈是**分不清参数是否外部输入**——这需要跨函数数据流,而入边 0 占 36%~42%。
|
|
67
|
+
⇒ 剩下 35 条误报(授权 14 + 校验 21)不是判据调得不够细,是**能力上限**。
|
|
68
|
+
|
|
69
|
+
**其它**:修正 `extract-ir.ts` 一处过期注释(写着「Input Validation 不接任何标记」,实际早已
|
|
70
|
+
接通)——注释说通道断着、实际已通,比没注释更危险。
|
|
71
|
+
|
|
72
|
+
**门**:tsc 0 错 / webshape 72-0 / taintpath 160-0 / fr-007 pre5·post0 + fr-016 pre7·post0 /
|
|
73
|
+
定向 9 文件 130 条全绿 / 反向验证:摘刀重跑 webshape_H 的 3 条 suppress 组精确转红
|
|
74
|
+
|
|
3
75
|
## [3.7.53] — 2026-09-26
|
|
4
76
|
|
|
5
77
|
### 授权族误报压降收口 + 三处可见性基建(§35-§46 合并发布)
|
package/dist/extract-ir.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validatedDtoClassNames = validatedDtoClassNames;
|
|
36
37
|
exports.extractIR = extractIR;
|
|
37
38
|
exports.extractIRWithTypes = extractIRWithTypes;
|
|
38
39
|
const ts_morph_1 = require("ts-morph");
|
|
@@ -416,8 +417,14 @@ const PATH_TRAVERSAL_MARKER = "__progmune_path_traversal__";
|
|
|
416
417
|
// __progmune_auth_machinery__,TS 侧此前从未产出 ⇒ 规则侧零改动。
|
|
417
418
|
//
|
|
418
419
|
// 边界(写死在这里,别想当然):只有**接受标记的规则**会受益。
|
|
419
|
-
//
|
|
420
|
-
//
|
|
420
|
+
//
|
|
421
|
+
// ⚠ 2026-09-27 更正(别删这段):本注释此前长期写着
|
|
422
|
+
// 「Input Validation 的 safeguard 不接受任何 __progmune_* 标记,装饰器对它无效」。
|
|
423
|
+
// **该结论已失效** —— 本文件 E3 的 __progmune_input_schema__ 通道现已接通,
|
|
424
|
+
// 见 protocol-detector.ts:647 的 input_validation safeguard(含
|
|
425
|
+
// __progmune_input_schema__|__progmune_input_guard__)。
|
|
426
|
+
// 保留这条更正,是因为「注释说通道断着、实际早已通」比没有注释更危险:
|
|
427
|
+
// 它会让人把瓶颈判错地方(2026-09-27 摸底时差点据此重开一条已修好的通道)。
|
|
421
428
|
// ═══════════════════════════════════════════════════════════════
|
|
422
429
|
const AUTH_MACHINERY_MARKER = "__progmune_auth_machinery__";
|
|
423
430
|
/** 显式免鉴权装饰器 —— 出现即不注入。把公开端点判成「有鉴权」是最危险的假阴性。 */
|
|
@@ -487,6 +494,8 @@ function decoratorIsValidator(text) {
|
|
|
487
494
|
/**
|
|
488
495
|
* 全项目扫描:类自身或其属性上带校验装饰器的类名集合。
|
|
489
496
|
* 只看类名(不做类型解析)—— 与污点管线口径一致,跨文件引用靠名字对上。
|
|
497
|
+
* 导出供框架适配器复用(2026-09-29:NestJS 检测器的 NO_VALIDATION
|
|
498
|
+
* 失明误报——路由 DTO 在别的文件带 class-validator 装饰器,检测器看不到)。
|
|
490
499
|
*/
|
|
491
500
|
function validatedDtoClassNames(project) {
|
|
492
501
|
const out = new Set();
|
|
@@ -21,6 +21,7 @@ exports.analyzeNestJSFile = analyzeNestJSFile;
|
|
|
21
21
|
exports.formatNestJSReport = formatNestJSReport;
|
|
22
22
|
const ts_morph_1 = require("ts-morph");
|
|
23
23
|
const route_window_1 = require("./route-window");
|
|
24
|
+
const extract_ir_1 = require("../extract-ir");
|
|
24
25
|
// ── Core Analysis ──
|
|
25
26
|
function analyzeNestJSProject(projectRoot) {
|
|
26
27
|
let project;
|
|
@@ -65,6 +66,66 @@ function analyzeNestJSProject(projectRoot) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
const hasGlobalAuthGuard = analysis.globalAuthGuards.length > 0;
|
|
69
|
+
// ── 全局校验管道(2026-09-29:docmost 123 条 NO_VALIDATION 失明误报的根因)──
|
|
70
|
+
// NestJS 标准做法是 bootstrap 里 app.useGlobalPipes(new ValidationPipe(...)),
|
|
71
|
+
// 路由本身不带 @UsePipes 装饰器 ⇒ 此前全部报 NO_VALIDATION。
|
|
72
|
+
// 检测方式:任意非 node_modules 源文件出现 .useGlobalPipes( 调用(参数不限——
|
|
73
|
+
// hedgedoc 形态是 useGlobalPipes(setupValidationPipe(logger)),管道经辅助函数
|
|
74
|
+
// 创建,字面 ValidationPipe 不在调用处)。
|
|
75
|
+
// 保守判定:全局管道只豁免「入参是已校验 DTO」的路由(见下),裸类型入参仍报。
|
|
76
|
+
let hasGlobalValidationPipe = false;
|
|
77
|
+
for (const file of project.getSourceFiles()) {
|
|
78
|
+
if (file.getFilePath().includes("node_modules"))
|
|
79
|
+
continue;
|
|
80
|
+
if (/\.(test|spec)\.ts$/.test(file.getFilePath()))
|
|
81
|
+
continue;
|
|
82
|
+
if (/\.useGlobalPipes\s*\(/.test(file.getText())) {
|
|
83
|
+
hasGlobalValidationPipe = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// ── 已校验 DTO 类名集合(E3 通道同款实现,import 落地代码而非重写)──
|
|
88
|
+
const validatedDtos = hasGlobalValidationPipe
|
|
89
|
+
? (0, extract_ir_1.validatedDtoClassNames)(project)
|
|
90
|
+
: new Set();
|
|
91
|
+
// ── 继承闭包(2026-09-29 docmost 实测:RemoveFavoriteDto extends AddFavoriteDto)──
|
|
92
|
+
// E3 集合只收「类自身带校验装饰器」的类,继承来的装饰器看不到。
|
|
93
|
+
// 只扩 detector 侧集合,不动 E3 函数(后者影响 safeguard 盲测基线)。
|
|
94
|
+
for (const file of project.getSourceFiles()) {
|
|
95
|
+
if (file.getFilePath().includes("node_modules"))
|
|
96
|
+
continue;
|
|
97
|
+
for (const cls of file.getClasses()) {
|
|
98
|
+
let base = cls.getBaseClass();
|
|
99
|
+
while (base) {
|
|
100
|
+
const baseName = base.getName();
|
|
101
|
+
if (baseName && validatedDtos.has(baseName)) {
|
|
102
|
+
const clsName = cls.getName();
|
|
103
|
+
if (clsName)
|
|
104
|
+
validatedDtos.add(clsName);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
base = base.getBaseClass();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ── Zod 形态扩展(2026-09-29 hedgedoc 实测)──
|
|
112
|
+
// class LoginDto extends createZodDto(LoginSchema) {} —— nestjs-zod 的校验
|
|
113
|
+
// 体系不产 class-validator 装饰器,E3 通道看不见。extends 表达式文本
|
|
114
|
+
// 含 createZodDto( 即视为已校验 DTO。同样只扩 detector 侧集合。
|
|
115
|
+
for (const file of project.getSourceFiles()) {
|
|
116
|
+
if (file.getFilePath().includes("node_modules"))
|
|
117
|
+
continue;
|
|
118
|
+
for (const cls of file.getClasses()) {
|
|
119
|
+
const ext = cls.getExtends();
|
|
120
|
+
if (!ext)
|
|
121
|
+
continue;
|
|
122
|
+
if (/createZodDto\s*\(/.test(ext.getText())) {
|
|
123
|
+
const clsName = cls.getName();
|
|
124
|
+
if (clsName)
|
|
125
|
+
validatedDtos.add(clsName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
68
129
|
// ── 第二遍:模块级中间件保护(Nest 5 时代惯用法)──
|
|
69
130
|
// class XxxModule implements NestModule { configure(consumer) {
|
|
70
131
|
// consumer.apply(AuthMiddleware).forRoutes({path, method}, ...) } }
|
|
@@ -154,6 +215,25 @@ function analyzeNestJSProject(projectRoot) {
|
|
|
154
215
|
|| method.getDecorator("AllowAnon") !== undefined;
|
|
155
216
|
// 认证守卫 = 认证名分类后的守卫(ThrottlerGuard 等限流守卫不算认证)
|
|
156
217
|
const authGuards = guards.filter(isAuthGuardName);
|
|
218
|
+
// 入参是否指向已校验 DTO:@Body()/@Query()/@Param() 参数的
|
|
219
|
+
// 类型名 ∈ class-validator 装饰类集合(E3 通道语义)。
|
|
220
|
+
// ts-morph 的类型文本会带 import 前缀:import(".../page.dto").PageInfoDto
|
|
221
|
+
// 或泛型 Selectable<import("...").Users> —— 取全部标识符,
|
|
222
|
+
// 任一命中已校验 DTO 集合即豁免(联合类型/裸类型/数组皆可对上;
|
|
223
|
+
// db 类型 Users 等不在集合,不会误豁免)。
|
|
224
|
+
const hasValidatedDto = method.getParameters().some((param) => {
|
|
225
|
+
const decNames = param.getDecorators().map((d) => d.getName());
|
|
226
|
+
if (!decNames.some((n) => ["Body", "Query", "Param"].includes(n)))
|
|
227
|
+
return false;
|
|
228
|
+
const typeText = param.getType().getText();
|
|
229
|
+
const ids = typeText.match(/[A-Za-z_$][\w$]*/g) || [];
|
|
230
|
+
return ids.some((id) => validatedDtos.has(id));
|
|
231
|
+
});
|
|
232
|
+
// 无结构化输入:入参没有 @Body/@Query/@Param——只有 AuthUser/Req/Res/
|
|
233
|
+
// UploadedFile 等上下文注入或文件对象(2026-09-29 docmost 实测:
|
|
234
|
+
// auth/collab-token、auth/logout 等无 body 路由被误报 NO_VALIDATION)。
|
|
235
|
+
// 没有可校验的 DTO 输入,规则判定面不覆盖,不报。
|
|
236
|
+
const hasStructuredInput = method.getParameters().some((param) => param.getDecorators().some((d) => ["Body", "Query", "Param"].includes(d.getName())));
|
|
157
237
|
const route = {
|
|
158
238
|
method: httpMethod,
|
|
159
239
|
path: fullPath,
|
|
@@ -161,6 +241,7 @@ function analyzeNestJSProject(projectRoot) {
|
|
|
161
241
|
handler: method.getName() || "unknown",
|
|
162
242
|
hasAuthGuard: authGuards.length > 0,
|
|
163
243
|
hasValidationPipe: pipes.length > 0,
|
|
244
|
+
hasValidatedDto,
|
|
164
245
|
guards,
|
|
165
246
|
pipes,
|
|
166
247
|
isPublicDecorated,
|
|
@@ -188,7 +269,12 @@ function analyzeNestJSProject(projectRoot) {
|
|
|
188
269
|
fix: `Add @UseGuards(AuthGuard) to the method or controller class, or remove the @Public marker.`,
|
|
189
270
|
});
|
|
190
271
|
}
|
|
191
|
-
|
|
272
|
+
// 豁免:@UsePipes 装饰器,或「全局 ValidationPipe + 入参是已校验 DTO」
|
|
273
|
+
// (docmost 实测 123 条失明误报的修复——路由 DTO 在别的文件带
|
|
274
|
+
// class-validator 装饰器,只看 @UsePipes 永远看不到),
|
|
275
|
+
// 或路由无结构化输入(没有可校验的东西)
|
|
276
|
+
const validatedByGlobalPipe = hasGlobalValidationPipe && route.hasValidatedDto;
|
|
277
|
+
if (!route.hasValidationPipe && !validatedByGlobalPipe && hasStructuredInput) {
|
|
192
278
|
analysis.issues.push({
|
|
193
279
|
type: "NESTJS_NO_VALIDATION",
|
|
194
280
|
severity: "medium",
|
|
@@ -367,6 +453,12 @@ function isPublicRoute(path) {
|
|
|
367
453
|
/\/auth\/register$/i, /\/register$/i, /\/signup$/i,
|
|
368
454
|
/\/auth\/signup$/i, /\/auth\/signin$/i, /\/signin$/i,
|
|
369
455
|
/\/auth\/refresh$/i, /\/auth\/forgot/i, /\/auth\/reset/i,
|
|
456
|
+
// auth 流程自身端点(2026-09-29 docmost 实测:auth/setup、
|
|
457
|
+
// auth/password-reset、auth/verify-token 被误报 NO_AUTH——
|
|
458
|
+
// 用户尚未登录,这些端点天生无认证;原 /auth/reset 匹配不了 password-reset)
|
|
459
|
+
/\/auth\/password-reset$/i, /\/auth\/reset-password$/i,
|
|
460
|
+
/\/auth\/verify-token$/i, /\/auth\/verify$/i,
|
|
461
|
+
/\/auth\/setup$/i, /\/auth\/init$/i,
|
|
370
462
|
/\/health$/i, /\/healthcheck$/i, /\/ping$/i, /\/status$/i,
|
|
371
463
|
/\/public\//i, /\/static\//i,
|
|
372
464
|
];
|
|
@@ -462,6 +554,7 @@ function analyzeNestJSFile(filePath) {
|
|
|
462
554
|
handler: method.getName() || "unknown",
|
|
463
555
|
hasAuthGuard: guards.filter(isAuthGuardName).length > 0,
|
|
464
556
|
hasValidationPipe: pipes.length > 0,
|
|
557
|
+
hasValidatedDto: false, // 单文件分析无跨文件 DTO 上下文(项目级用 analyzeNestJSProject)
|
|
465
558
|
guards,
|
|
466
559
|
pipes,
|
|
467
560
|
isPublicDecorated: false, // 单文件分析不解析 @Public(项目级用 analyzeNestJSProject)
|
|
@@ -241,3 +241,180 @@ export class AdminController {
|
|
|
241
241
|
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_AUTH" && i.route === "POST /users")).toBe(true);
|
|
242
242
|
});
|
|
243
243
|
});
|
|
244
|
+
// ═══════════════════════════════════════════════════════════
|
|
245
|
+
// 全局 ValidationPipe + DTO 校验识别(2026-09-29 docmost 实测:
|
|
246
|
+
// 123 条 NO_VALIDATION 失明误报的修复——路由 DTO 在别的文件带
|
|
247
|
+
// class-validator 装饰器,只看 @UsePipes 永远看不到)
|
|
248
|
+
// ═══════════════════════════════════════════════════════════
|
|
249
|
+
(0, vitest_1.describe)("nestjs-detector 全局管道 + DTO 校验通道", () => {
|
|
250
|
+
function writeValidatedApp() {
|
|
251
|
+
write("tsconfig.json", TSCONFIG);
|
|
252
|
+
write("src/main.ts", `
|
|
253
|
+
import { ValidationPipe } from "@nestjs/common";
|
|
254
|
+
import { NestFactory } from "@nestjs/core";
|
|
255
|
+
import { AppModule } from "./app.module";
|
|
256
|
+
async function bootstrap() {
|
|
257
|
+
const app = await NestFactory.create(AppModule);
|
|
258
|
+
app.useGlobalPipes(
|
|
259
|
+
new ValidationPipe({ whitelist: true, transform: true }),
|
|
260
|
+
);
|
|
261
|
+
await app.listen(3000);
|
|
262
|
+
}
|
|
263
|
+
`);
|
|
264
|
+
write("src/app.module.ts", `
|
|
265
|
+
import { Module } from "@nestjs/common";
|
|
266
|
+
import { PageController } from "./page.controller";
|
|
267
|
+
@Module({ controllers: [PageController] })
|
|
268
|
+
export class AppModule {}
|
|
269
|
+
`);
|
|
270
|
+
write("src/create-page.dto.ts", `
|
|
271
|
+
import { IsString, IsUUID, IsOptional } from "class-validator";
|
|
272
|
+
export class CreatePageDto {
|
|
273
|
+
@IsOptional() @IsString() title?: string;
|
|
274
|
+
@IsUUID() spaceId: string;
|
|
275
|
+
}
|
|
276
|
+
`);
|
|
277
|
+
write("src/page.controller.ts", `
|
|
278
|
+
import { Controller, Post, Body } from "@nestjs/common";
|
|
279
|
+
import { CreatePageDto } from "./create-page.dto";
|
|
280
|
+
@Controller("pages")
|
|
281
|
+
export class PageController {
|
|
282
|
+
@Post("create")
|
|
283
|
+
create(@Body() dto: CreatePageDto) { return {}; }
|
|
284
|
+
}
|
|
285
|
+
`);
|
|
286
|
+
}
|
|
287
|
+
(0, vitest_1.it)("全局 ValidationPipe + 入参是已校验 DTO → 不报 NO_VALIDATION", () => {
|
|
288
|
+
writeValidatedApp();
|
|
289
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
290
|
+
const nv = a.issues.filter((i) => i.type === "NESTJS_NO_VALIDATION");
|
|
291
|
+
(0, vitest_1.expect)(nv).toEqual([]);
|
|
292
|
+
});
|
|
293
|
+
(0, vitest_1.it)("反向:DTO 摘掉 class-validator 装饰器 → 精确转红", () => {
|
|
294
|
+
writeValidatedApp();
|
|
295
|
+
write("src/create-page.dto.ts", `
|
|
296
|
+
export class CreatePageDto {
|
|
297
|
+
title?: string;
|
|
298
|
+
spaceId: string;
|
|
299
|
+
}
|
|
300
|
+
`);
|
|
301
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
302
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST pages/create")).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
(0, vitest_1.it)("反向:摘掉全局管道 → 精确转红(无 @UsePipes 的路由失去豁免源)", () => {
|
|
305
|
+
writeValidatedApp();
|
|
306
|
+
write("src/main.ts", `
|
|
307
|
+
async function bootstrap() { await (await import("@nestjs/core").then(m => m.NestFactory.create(Object))).listen(3000); }
|
|
308
|
+
`);
|
|
309
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
310
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST pages/create")).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
(0, vitest_1.it)("继承 DTO:子类 extends 已校验父类 → 不报(RemoveFavoriteDto 形态)", () => {
|
|
313
|
+
writeValidatedApp();
|
|
314
|
+
write("src/create-page.dto.ts", `
|
|
315
|
+
import { IsString, IsUUID, IsOptional } from "class-validator";
|
|
316
|
+
export class CreatePageDto {
|
|
317
|
+
@IsOptional() @IsString() title?: string;
|
|
318
|
+
@IsUUID() spaceId: string;
|
|
319
|
+
}
|
|
320
|
+
export class UpdatePageDto extends CreatePageDto {}
|
|
321
|
+
`);
|
|
322
|
+
write("src/page.controller.ts", `
|
|
323
|
+
import { Controller, Post, Body } from "@nestjs/common";
|
|
324
|
+
import { UpdatePageDto } from "./create-page.dto";
|
|
325
|
+
@Controller("pages")
|
|
326
|
+
export class PageController {
|
|
327
|
+
@Post("update")
|
|
328
|
+
update(@Body() dto: UpdatePageDto) { return {}; }
|
|
329
|
+
}
|
|
330
|
+
`);
|
|
331
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
332
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST pages/update")).toBe(false);
|
|
333
|
+
});
|
|
334
|
+
(0, vitest_1.it)("无结构化输入(仅 AuthUser/Req)→ 不报 NO_VALIDATION(collab-token/logout 形态)", () => {
|
|
335
|
+
write("tsconfig.json", TSCONFIG);
|
|
336
|
+
write("src/main.ts", `
|
|
337
|
+
import { ValidationPipe } from "@nestjs/common";
|
|
338
|
+
async function bootstrap() { (globalThis as any).app.useGlobalPipes(new ValidationPipe()); }
|
|
339
|
+
`);
|
|
340
|
+
write("src/app.module.ts", `
|
|
341
|
+
import { Module } from "@nestjs/common";
|
|
342
|
+
import { AuthController } from "./auth.controller";
|
|
343
|
+
@Module({ controllers: [AuthController] })
|
|
344
|
+
export class AppModule {}
|
|
345
|
+
`);
|
|
346
|
+
write("src/auth.controller.ts", `
|
|
347
|
+
import { Controller, Post, Req } from "@nestjs/common";
|
|
348
|
+
@Controller("auth")
|
|
349
|
+
export class AuthController {
|
|
350
|
+
@Post("logout")
|
|
351
|
+
logout(@Req() req: any) { return {}; }
|
|
352
|
+
}
|
|
353
|
+
`);
|
|
354
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
355
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST auth/logout")).toBe(false);
|
|
356
|
+
});
|
|
357
|
+
(0, vitest_1.it)("Zod 形态:class XDto extends createZodDto(schema) + 辅助函数全局管道 → 不报(hedgedoc 形态)", () => {
|
|
358
|
+
write("tsconfig.json", TSCONFIG);
|
|
359
|
+
write("src/main.ts", `
|
|
360
|
+
import { setupValidationPipe } from "./setup-pipes";
|
|
361
|
+
async function bootstrap() { (globalThis as any).app.useGlobalPipes(setupValidationPipe()); }
|
|
362
|
+
`);
|
|
363
|
+
write("src/setup-pipes.ts", `
|
|
364
|
+
import { createZodValidationPipe } from "nestjs-zod";
|
|
365
|
+
export function setupValidationPipe(): any { return new (createZodValidationPipe({}))(); }
|
|
366
|
+
`);
|
|
367
|
+
write("src/app.module.ts", `
|
|
368
|
+
import { Module } from "@nestjs/common";
|
|
369
|
+
import { NotesController } from "./notes.controller";
|
|
370
|
+
@Module({ controllers: [NotesController] })
|
|
371
|
+
export class AppModule {}
|
|
372
|
+
`);
|
|
373
|
+
write("src/login.dto.ts", `
|
|
374
|
+
import { createZodDto } from "nestjs-zod";
|
|
375
|
+
export class LoginDto extends createZodDto({}) {}
|
|
376
|
+
`);
|
|
377
|
+
write("src/notes.controller.ts", `
|
|
378
|
+
import { Controller, Post, Body } from "@nestjs/common";
|
|
379
|
+
import { LoginDto } from "./login.dto";
|
|
380
|
+
@Controller("notes")
|
|
381
|
+
export class NotesController {
|
|
382
|
+
@Post("create")
|
|
383
|
+
create(@Body() dto: LoginDto) { return {}; }
|
|
384
|
+
}
|
|
385
|
+
`);
|
|
386
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
387
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST notes/create")).toBe(false);
|
|
388
|
+
});
|
|
389
|
+
(0, vitest_1.it)("反向:Zod DTO 摘掉 createZodDto 继承 → 精确转红", () => {
|
|
390
|
+
write("tsconfig.json", TSCONFIG);
|
|
391
|
+
write("src/main.ts", `
|
|
392
|
+
import { setupValidationPipe } from "./setup-pipes";
|
|
393
|
+
async function bootstrap() { (globalThis as any).app.useGlobalPipes(setupValidationPipe()); }
|
|
394
|
+
`);
|
|
395
|
+
write("src/setup-pipes.ts", `
|
|
396
|
+
import { createZodValidationPipe } from "nestjs-zod";
|
|
397
|
+
export function setupValidationPipe(): any { return new (createZodValidationPipe({}))(); }
|
|
398
|
+
`);
|
|
399
|
+
write("src/app.module.ts", `
|
|
400
|
+
import { Module } from "@nestjs/common";
|
|
401
|
+
import { NotesController } from "./notes.controller";
|
|
402
|
+
@Module({ controllers: [NotesController] })
|
|
403
|
+
export class AppModule {}
|
|
404
|
+
`);
|
|
405
|
+
write("src/login.dto.ts", `
|
|
406
|
+
export class LoginDto {}
|
|
407
|
+
`);
|
|
408
|
+
write("src/notes.controller.ts", `
|
|
409
|
+
import { Controller, Post, Body } from "@nestjs/common";
|
|
410
|
+
import { LoginDto } from "./login.dto";
|
|
411
|
+
@Controller("notes")
|
|
412
|
+
export class NotesController {
|
|
413
|
+
@Post("create")
|
|
414
|
+
create(@Body() dto: LoginDto) { return {}; }
|
|
415
|
+
}
|
|
416
|
+
`);
|
|
417
|
+
const a = (0, nestjs_detector_1.analyzeNestJSProject)(dir);
|
|
418
|
+
(0, vitest_1.expect)(a.issues.some((i) => i.type === "NESTJS_NO_VALIDATION" && i.route === "POST notes/create")).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* §47:签名级会话证据 —— 参数类型以 `Session` 结尾 ⇒ 「未认证」结论不成立。
|
|
5
|
+
*
|
|
6
|
+
* 为什么不是继续在 calls 里找 auth 词:hedgedoc 的 pending-user 三个接口 +
|
|
7
|
+
* OidcController.callback 都是 `@UseGuards(SessionGuard)` 保护的会话类路由,
|
|
8
|
+
* 但它们**一个认证函数都不调用** —— 身份是从 `request.session.*` 上取的,
|
|
9
|
+
* 调用列表里只有 property access,选择器读不到。真实池里这四条全是人工确认的 FP。
|
|
10
|
+
*
|
|
11
|
+
* 改用**参数类型**:签名上存在 session 对象 ⇒ 这次调用必然落在某个会话上下文里。
|
|
12
|
+
* 好处是它来自 AST 签名,不依赖调用图连通(fp-pool 入边 0 的函数占 36%~42%,
|
|
13
|
+
* 依赖 calls 的机制在那里会静默失效)。
|
|
14
|
+
*
|
|
15
|
+
* ⚠ R66 的否命题在本文件 §47 反向② 组:
|
|
16
|
+
* 有会话 ≠ 操作对象属于自己。越权读取的同形状反例必须 Unauthenticated 熄灭
|
|
17
|
+
* 而 **Ownership 继续亮**,否则这一刀就是把越权漏洞洗白。
|
|
18
|
+
*/
|
|
19
|
+
const vitest_1 = require("vitest");
|
|
20
|
+
const protocol_detector_1 = require("./protocol-detector");
|
|
21
|
+
const ACC = "Authorization (Unauthenticated Access)";
|
|
22
|
+
const MUT = "Authorization (Unauthenticated Mutation)";
|
|
23
|
+
const OWN = "Authorization (Ownership Check)";
|
|
24
|
+
const RES = "Authorization (Resource Ownership)";
|
|
25
|
+
/** lang 固定 typescript;params/paramTypes 逐用例给定 */
|
|
26
|
+
function fire(calls, name, params, paramTypes) {
|
|
27
|
+
const vs = (0, protocol_detector_1.detectSafeguardViolations)(calls, name, "typescript", params, false, paramTypes);
|
|
28
|
+
return new Set(vs.map((v) => v.rule));
|
|
29
|
+
}
|
|
30
|
+
const lit = (b) => (b ? "fires" : "silent");
|
|
31
|
+
(0, vitest_1.describe)("§47 正向:签名带会话 ⇒ Unauthenticated 两条熄灭(hedgedoc 真实 FP)", () => {
|
|
32
|
+
(0, vitest_1.it)("getPendingUserData(request: RequestWithSession) 不再报未认证读取", () => {
|
|
33
|
+
const s = fire(["create"], "getPendingUserData", ["request"], ["RequestWithSession"]);
|
|
34
|
+
(0, vitest_1.expect)(lit(s.has(ACC))).toBe("silent");
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)("confirmPendingUserData(request: RequestWithSession, dto) 不再报未认证变更", () => {
|
|
37
|
+
const s = fire(["createUserWithIdentityFromPendingUserConfirmation", "save"], "confirmPendingUserData", ["request", "pendingUserConfirmationData"], ["RequestWithSession", "PendingUserConfirmationDto"]);
|
|
38
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("silent");
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("OidcController.callback —— session 不在第一个参数也算数", () => {
|
|
41
|
+
const s = fire(["extractUserInfoFromCallback", "getExistingOidcIdentity", "mayUpdateIdentity", "save"], "OidcController.callback", ["oidcIdentifier", "request"], ["string", "RequestWithSession"]);
|
|
42
|
+
(0, vitest_1.expect)(lit(s.has(ACC))).toBe("silent");
|
|
43
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("silent");
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("裸 Session 类型同样成立(不要求 Request 前缀)", () => {
|
|
46
|
+
const s = fire(["find"], "getProfile", ["session"], ["Session"]);
|
|
47
|
+
(0, vitest_1.expect)(lit(s.has(ACC))).toBe("silent");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.describe)("§47 反向①:摘掉这一刀必须重新亮(证明是它在压,不是别机制抢先)", () => {
|
|
51
|
+
(0, vitest_1.it)("同函数把 RequestWithSession 换成 Request ⇒ 未认证读取重新报", () => {
|
|
52
|
+
const s = fire(["create"], "getPendingUserData", ["request"], ["Request"]);
|
|
53
|
+
(0, vitest_1.expect)(lit(s.has(ACC))).toBe("fires");
|
|
54
|
+
});
|
|
55
|
+
// 注意这里**不是** §47 在压:`request` 的类型退化成标量后,是 §44 的 paramGated
|
|
56
|
+
// 先把规则整体跳过了(标量参数不算身份证据)。单独留一条是为了把两层闸门的关系钉住:
|
|
57
|
+
// 类型通道同时喂给 paramGate(§44)和 paramTypeSafeguards(§47),
|
|
58
|
+
// 断言这一条可以防止以后有人误以为「类型一变就该亮」。
|
|
59
|
+
(0, vitest_1.it)("request:string 时由 §44 paramGate 先行跳过 —— 记录两层闸门的先后关系", () => {
|
|
60
|
+
const s = fire(["createUserWithIdentityFromPendingUserConfirmation", "save"], "confirmPendingUserData", ["request", "pendingUserConfirmationData"], ["string", "PendingUserConfirmationDto"]);
|
|
61
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("silent");
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)("不传类型通道时退回旧行为(仍报)—— R57 要求通道不可用时不得静默压", () => {
|
|
64
|
+
const vs = (0, protocol_detector_1.detectSafeguardViolations)(["create"], "getPendingUserData", "typescript", ["request"]);
|
|
65
|
+
(0, vitest_1.expect)(lit(vs.some((v) => v.rule === ACC))).toBe("fires");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.describe)("§47 反向②:有会话 ≠ 有权 —— 越权形态的 Unauthenticated 熄灭但 Ownership 必须继续亮", () => {
|
|
69
|
+
(0, vitest_1.it)("removeSharedFile(req: RequestWithSession, id) —— 归属检查不能随会话一起被免掉", () => {
|
|
70
|
+
const s = fire(["findById", "removeFile"], "removeSharedFile", ["request", "id"], ["RequestWithSession", "string"]);
|
|
71
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("silent");
|
|
72
|
+
(0, vitest_1.expect)(lit(s.has(OWN))).toBe("fires");
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("deleteOtherUsersNote(session: Session, noteId) —— Resource Ownership 必须继续亮", () => {
|
|
75
|
+
const s = fire(["findNote", "deleteNote"], "deleteOtherUsersNote", ["session", "noteId"], ["Session", "string"]);
|
|
76
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("silent");
|
|
77
|
+
(0, vitest_1.expect)(lit(s.has(OWN) || s.has(RES))).toBe("fires");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.describe)("§47 边界:类型名不得以 Session 收尾就被误当成会话", () => {
|
|
81
|
+
const cases = [
|
|
82
|
+
["Transaction", " mutation"],
|
|
83
|
+
["SessionOptions", "配置对象不是会话本身"],
|
|
84
|
+
["Partial<AuthConf>", "verdaccio addAuth 的真类型"],
|
|
85
|
+
["Knex", "查询构造器"],
|
|
86
|
+
];
|
|
87
|
+
for (const [type, why] of cases) {
|
|
88
|
+
(0, vitest_1.it)(`${type} 不应被当作会话(${why.trim()})`, () => {
|
|
89
|
+
const s = fire(["merge"], "addAuth", ["auth"], [type]);
|
|
90
|
+
(0, vitest_1.expect)(lit(s.has(MUT))).toBe("fires");
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -314,6 +314,22 @@ exports.AUTHZ_BARE_CAN_RE = /^(?:can|cannot)$|\.(?:can|cannot)$/i;
|
|
|
314
314
|
* ⇒ `canSendEmail` 会拆出单词 "can" ⇒ 裸 can 误命中 ⇒ §38 的负对照直接转绿。
|
|
315
315
|
* callsOnly 把匹配限定在**原始调用名**上(`can` 作为被调名才作数)。
|
|
316
316
|
*/
|
|
317
|
+
/**
|
|
318
|
+
* §47(2026-09-27)**会话承载型请求**:参数类型名以 `Session` 结尾
|
|
319
|
+
* (`RequestWithSession` / `AuthenticatedSession` / `HttpSession` / `Session`)。
|
|
320
|
+
*
|
|
321
|
+
* 语义:函数手里握着一个 session 对象 ⇒ 这次调用**已经落在某个会话上下文里**,
|
|
322
|
+
* 「任何人无需凭证即可访问/修改」这个结论不成立。这是**正向**证据且来自 AST 签名,
|
|
323
|
+
* 不像「calls 里没有 xxx」那样会被提取失败伪装过去(fp-pool 入边为 0 的函数占
|
|
324
|
+
* 36%~42%,依赖 calls 的机制在那些函数上会静默失效)。
|
|
325
|
+
*
|
|
326
|
+
* ⚠ 只喂给「未认证」两条规则,**不**给 Ownership / Resource Ownership 补 ——
|
|
327
|
+
* 有会话 ≠ 操作对象属于自己。R66 的否命题由这对组合承担:
|
|
328
|
+
* 同形状的越权读取 `getOthersData(req: RequestWithSession)` 应当
|
|
329
|
+
* Unauthenticated 熄灭、**Ownership 继续亮**(fixture 见
|
|
330
|
+
* src/protocol-detector-session-param.test.ts 的反向组)。
|
|
331
|
+
*/
|
|
332
|
+
const SESSION_BEARING_TYPE_RE = /(?:^|[A-Za-z])Session$/i;
|
|
317
333
|
const SAFEGUARD_RULES = [
|
|
318
334
|
// ── Password Hashing ──
|
|
319
335
|
{
|
|
@@ -407,6 +423,8 @@ const SAFEGUARD_RULES = [
|
|
|
407
423
|
category: "authorization",
|
|
408
424
|
languages: ["typescript", "javascript", "python"],
|
|
409
425
|
paramGated: true,
|
|
426
|
+
// §47:签名带 session ⇒ 已落在会话上下文中(详见 SESSION_BEARING_TYPE_RE 注释)
|
|
427
|
+
paramTypeSafeguards: [SESSION_BEARING_TYPE_RE],
|
|
410
428
|
trigger: /\b(list|download|view|fetch)(?:[A-Z]\w*|_\w+)|get(?:[A-Z]\w+|_\w+)/i,
|
|
411
429
|
safeguards: [
|
|
412
430
|
{ pattern: /\b(getUser|validateToken|verifyToken|verifySession|validateSession|getSessionUser|getSession\b|getCurrentUser|token\w*(Check|Verify|Valid)|session\w*(Check|Verify|Valid)|auth\w*(Check|Verify|Valid|Guard|Middleware|Required)|requireAuth|withAuth|authenticate\w*(User|Request|Token)?|checkAuth|isAuth|hasAuth|checkAccess|hasAccess|get_user|get_session_user|get_current_user|validate_session|verify_token|require_auth|with_auth|check_auth|auth_required|authenticate_user|authenticate_request|authenticate_token|token_check|token_verify|token_valid|session_check|session_verify|session_valid|auth_check|auth_guard|auth_middleware|get_current_user_authorizer|current_user_authorizer|login_required|permission_required|user_passes_test|check_authorization|check_permission|jwt\.decode|decode_token|__progmune_auth_checked__|__progmune_credential_check__|__progmune_drf_permissions__|__progmune_auth_machinery__)\b/i, label: "auth_check" },
|
|
@@ -439,6 +457,8 @@ const SAFEGUARD_RULES = [
|
|
|
439
457
|
category: "authorization",
|
|
440
458
|
languages: ["typescript", "javascript", "python"],
|
|
441
459
|
paramGated: true,
|
|
460
|
+
// §47:同上 —— 只压制「未认证」,不压制「越权」
|
|
461
|
+
paramTypeSafeguards: [SESSION_BEARING_TYPE_RE],
|
|
442
462
|
// Note: "post" deliberately excluded — it collides with the Post entity name
|
|
443
463
|
// (listPosts/getPost/deletePost fire via identifier-parsed words).
|
|
444
464
|
trigger: /\b(add|create|update|set|publish|insert|submit)(?:[A-Z]\w*|_\w+)|(?:[A-Z]\w*|_\w+)(Add|Create|Update|Set|Publish|Insert|Submit)\b/i,
|
|
@@ -1485,6 +1505,12 @@ function detectSafeguardViolations(calls, enclosingFuncName, language, params, e
|
|
|
1485
1505
|
if (!hasIdentity && !exposed)
|
|
1486
1506
|
continue;
|
|
1487
1507
|
}
|
|
1508
|
+
// §47(2026-09-27)签名级会话证据:命中即视为「已认证」,与 calls 类 safeguard 并列。
|
|
1509
|
+
// 放在 `guards.find` 之前,语义上等价于命中了一个 safeguard。
|
|
1510
|
+
if (rule.paramTypeSafeguards && paramTypes) {
|
|
1511
|
+
if (paramTypes.some((t) => t && rule.paramTypeSafeguards.some((re) => re.test(t))))
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1488
1514
|
// Check if at least one safeguard matches
|
|
1489
1515
|
const guards = (params && rule.strictSafeguards) ? rule.strictSafeguards : rule.safeguards;
|
|
1490
1516
|
const matchedSafeguard = guards.find(s => {
|
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.55";
|
|
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
|
@@ -151,7 +151,14 @@ async function evaluateTrust(ctx) {
|
|
|
151
151
|
writeTrustFailuresToCorpus(ctx, allViolations);
|
|
152
152
|
// PHASE 3: SCORE
|
|
153
153
|
// ═══════════════════════════════════════
|
|
154
|
-
|
|
154
|
+
// 企业策略维度只吃策略类违规——框架适配器(framework.*)与协议检测
|
|
155
|
+
// (protocol-safety.*)的违规在 protocolSafety 维度已计分,同池双计
|
|
156
|
+
// 会把失明误报放大成双倍扣分(2026-09-29 docmost 实测:132 条
|
|
157
|
+
// NestJS 违规双计,auth 0 分 + score 48 + BLOCKED,逐条核实 126 条为 FP)。
|
|
158
|
+
const policyViolations = allViolations.filter((v) => !v.policy_ref.startsWith("framework") &&
|
|
159
|
+
!v.policy_ref.startsWith("protocol-safety") &&
|
|
160
|
+
v.policy_ref !== "protocol");
|
|
161
|
+
const policyResult = (0, score_calculator_1.scorePolicyCompliance)(policyViolations);
|
|
155
162
|
const protocolResult = (0, score_calculator_1.scoreProtocolSafety)(allViolations);
|
|
156
163
|
const coverageResult = (0, score_calculator_1.scoreVerificationCoverage)(coverageData);
|
|
157
164
|
const governanceResult = (0, score_calculator_1.scoreGovernanceIntegrity)(governanceDefects);
|
|
@@ -361,7 +368,7 @@ async function evaluateTrust(ctx) {
|
|
|
361
368
|
score: policyResult.score,
|
|
362
369
|
weight: types_1.DEFAULT_DIMENSION_WEIGHTS.policyCompliance,
|
|
363
370
|
confidence: policyResult.hasCritical ? "LOW" : "HIGH",
|
|
364
|
-
violations:
|
|
371
|
+
violations: policyViolations,
|
|
365
372
|
},
|
|
366
373
|
protocolSafety: protocolResult,
|
|
367
374
|
verificationCoverage: coverageResult,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.55",
|
|
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/",
|