progmune-runtime 3.7.47 → 3.7.49
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 +41 -0
- package/dist/extract-ir-taint-structural.test.js +212 -0
- package/dist/extract-ir.js +60 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +47 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.49] — 2026-09-22
|
|
4
|
+
|
|
5
|
+
### TS 侧「框架鉴权」语义标记:装饰器里的鉴权第一次对规则可见(E2)
|
|
6
|
+
|
|
7
|
+
- **缺口**:NestJS 的鉴权写在装饰器里(`@UseGuards(JwtAuthGuard)`,类级或方法级),
|
|
8
|
+
既不在函数体文本里、也不在 `calls` 里 ⇒ 规则**无论如何都看不到**。
|
|
9
|
+
探针:`@UseGuards(JwtAuthGuard)` 的 `createGroup` 被报
|
|
10
|
+
「Authorization (Unauthenticated Mutation)」,而它明明有守卫。
|
|
11
|
+
- **更底层的发现**:规则侧定义了 23 个 `__progmune_*` 语义标记;Python 提取器产出
|
|
12
|
+
**23/23**,TS 提取器只产出 **4/23**(C/Go/Java **0/23**)。FP 观测池 100% 是 TS/JS
|
|
13
|
+
⇒ 规则在 TS 上跑的时候 19 条证据通道是断的。本轮补的是其中一条(且是唯一
|
|
14
|
+
「规则侧已经等着认、只是没人产」的那条)。
|
|
15
|
+
- **修复**(`src/extract-ir.ts`,类方法分支追加注入):
|
|
16
|
+
类级 + 方法级装饰器 ⇒ `__progmune_auth_machinery__`(镜像 `tools/extract_ir.py:1134`
|
|
17
|
+
的「类级框架守卫」)。**规则侧零改动**——Authorization 两条规则的 `auth_check`
|
|
18
|
+
safeguard 早已接受该标记。
|
|
19
|
+
- **边界(写死)**:`@Public()` / `@SkipAuth()` 显式免鉴权不注入;
|
|
20
|
+
`@UseGuards(ThrottlerGuard)` 等非鉴权守卫不注入;`@ApiBearerAuth()` 等 Swagger
|
|
21
|
+
文档装饰器不注入;顶层函数装饰器不在本轮范围内。
|
|
22
|
+
- **实测效果**(FP 观测池 6 切片 / 367 条):Authorization 违规 **−24 条**
|
|
23
|
+
(Unauthenticated Access −11 / Mutation −13),**新增 0**,非 Authorization 规则
|
|
24
|
+
**0 变动**(与判据一致:只有接受标记的规则会受益;Input Validation 的 safeguard
|
|
25
|
+
不接受任何标记,装饰器救不了它)。
|
|
26
|
+
- 回归:tsc ✓ / taintpath 160 条 ✓ / fr-007 pre 5·post 0、fr-016 pre 7·post 0 ✓ /
|
|
27
|
+
定向测试 176 全绿(新增 E2 组 11 条)/ 反向验证四刀各落自己那组、残留 0。
|
|
28
|
+
**TS 盲测 LOST 0 / ADDED 0 —— 但这是空过**:generated 语料里装饰器命中数为 0,
|
|
29
|
+
该门测不到本轮(R23 家族第三次),真证据来自 FP 观测池。
|
|
30
|
+
|
|
31
|
+
## [3.7.48] — 2026-09-22
|
|
32
|
+
|
|
33
|
+
### IR 提取失败必须发声(OOM 杀后的「0 违规假干净」根治)
|
|
34
|
+
|
|
35
|
+
- **事故**:skyvern E1 重扫时 python3 提取器被 OOM 杀(SIGKILL),ir.json 被写成 2 字节空数组——引擎带着空 IR 照样输出「0 违规、85 分 APPROVED」,结果自洽、无任何迹象表明它是废票(静默降级比 R8 陈旧产物更阴)
|
|
36
|
+
- **修复**(`src/trust/engine.ts` + `types.ts`):
|
|
37
|
+
- 提取异常不再静默吞掉——异常信息记入 `extractionWarning`
|
|
38
|
+
- **提取后验尸**:ir.json 存在但函数数=0 且项目确有源码 ⇒ 记失败并 console.warn
|
|
39
|
+
- TrustDecision 新增 `overall.extractionWarning` 字段;失败时 `coverageConfidence` 强制降 LOW,摘要明示「本次扫描是废票,不得据此得出干净结论」
|
|
40
|
+
- **E2E 验证**:空 ir.json + 1 个源文件 → warning 触发 / confidence LOW / 摘要正确
|
|
41
|
+
- 回归:engine 21 + ir-staleness 12 + structural 165 全绿;build ✓
|
|
42
|
+
- 后续:python3 提取分块(skyvern 级项目单进程内存峰值不可控,按目录分批提取、引擎侧合并)——机器资源恢复后按四门验收
|
|
43
|
+
|
|
3
44
|
## [3.7.47] — 2026-09-21
|
|
4
45
|
|
|
5
46
|
### 类方法的真实调用必须进 IR(状态机在 OO 代码上没有输入)
|
|
@@ -2115,3 +2115,215 @@ export function dispatchTool(args: any) {
|
|
|
2115
2115
|
}, 150000);
|
|
2116
2116
|
}
|
|
2117
2117
|
});
|
|
2118
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2119
|
+
// E2:框架鉴权(装饰器)必须对规则可见(2026-09-22)
|
|
2120
|
+
//
|
|
2121
|
+
// 判据(先抄准再写 expect,R22):
|
|
2122
|
+
// protocol-detector.ts:398 / :430 —— Authorization (Unauthenticated Access /
|
|
2123
|
+
// Mutation) 两条规则的 auth_check safeguard 接受 __progmune_auth_machinery__;
|
|
2124
|
+
// 探针实测:@UseGuards(JwtAuthGuard) 的 createGroup 修复前报
|
|
2125
|
+
// 「Authorization (Unauthenticated Mutation)」,注入标记后该条消失,
|
|
2126
|
+
// 而 Input Validation 仍在(它的 safeguard 不接受任何标记 ⇒ 装饰器救不了它)。
|
|
2127
|
+
//
|
|
2128
|
+
// 反面风险(注入过头 = 假阴性,比误报更危险):
|
|
2129
|
+
// @Public()/@SkipAuth() 是「显式免鉴权」,不得注入;
|
|
2130
|
+
// @UseGuards(ThrottlerGuard) 是限流不是鉴权,不得注入;
|
|
2131
|
+
// @ApiBearerAuth() 只是 Swagger 文档,不实施鉴权,不得注入。
|
|
2132
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2133
|
+
(0, vitest_1.describe)("E2:框架鉴权装饰器必须对规则可见(此前 TS 侧从未产出该标记)", () => {
|
|
2134
|
+
const AUTH_MARK = "__progmune_auth_machinery__";
|
|
2135
|
+
const cases = [
|
|
2136
|
+
{
|
|
2137
|
+
name: "正例·① 类级 @UseGuards(JwtAuthGuard)",
|
|
2138
|
+
fn: "GroupController.createGroup",
|
|
2139
|
+
files: {
|
|
2140
|
+
"a.ts": `
|
|
2141
|
+
declare const UseGuards: any;
|
|
2142
|
+
declare const Controller: any;
|
|
2143
|
+
declare const Post: any;
|
|
2144
|
+
declare class JwtAuthGuard {}
|
|
2145
|
+
@UseGuards(JwtAuthGuard)
|
|
2146
|
+
@Controller('groups')
|
|
2147
|
+
export class GroupController {
|
|
2148
|
+
createGroup(dto: any) { return this.svc.create(dto); }
|
|
2149
|
+
}
|
|
2150
|
+
`,
|
|
2151
|
+
},
|
|
2152
|
+
want: [AUTH_MARK],
|
|
2153
|
+
why: "NestJS 最常见的形态:鉴权写在类上,方法体内什么都没有 ⇒ 规则原来看不见",
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
name: "正例·② 方法级 @UseGuards(JwtAuthGuard)",
|
|
2157
|
+
fn: "C.m",
|
|
2158
|
+
files: {
|
|
2159
|
+
"a.ts": `
|
|
2160
|
+
declare const UseGuards: any;
|
|
2161
|
+
declare class JwtAuthGuard {}
|
|
2162
|
+
export class C {
|
|
2163
|
+
@UseGuards(JwtAuthGuard)
|
|
2164
|
+
m() { return 1; }
|
|
2165
|
+
}
|
|
2166
|
+
`,
|
|
2167
|
+
},
|
|
2168
|
+
want: [AUTH_MARK],
|
|
2169
|
+
why: "守卫挂在单个端点上(而不是整个控制器)",
|
|
2170
|
+
},
|
|
2171
|
+
{
|
|
2172
|
+
name: "正例·③ @UseGuards(AuthGuard('jwt'))",
|
|
2173
|
+
fn: "C.m",
|
|
2174
|
+
files: {
|
|
2175
|
+
"a.ts": `
|
|
2176
|
+
declare const UseGuards: any;
|
|
2177
|
+
declare function AuthGuard(x: string): any;
|
|
2178
|
+
export class C {
|
|
2179
|
+
@UseGuards(AuthGuard('jwt'))
|
|
2180
|
+
m() { return 1; }
|
|
2181
|
+
}
|
|
2182
|
+
`,
|
|
2183
|
+
},
|
|
2184
|
+
want: [AUTH_MARK],
|
|
2185
|
+
why: "NestJS 官方 passport 形态:守卫是工厂调用,实参里含 jwt",
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
name: "正例·④ @Roles('admin')",
|
|
2189
|
+
fn: "C.m",
|
|
2190
|
+
files: {
|
|
2191
|
+
"a.ts": `
|
|
2192
|
+
declare const Roles: any;
|
|
2193
|
+
export class C {
|
|
2194
|
+
@Roles('admin')
|
|
2195
|
+
m() { return 1; }
|
|
2196
|
+
}
|
|
2197
|
+
`,
|
|
2198
|
+
},
|
|
2199
|
+
want: [AUTH_MARK],
|
|
2200
|
+
why: "角色/权限装饰器同样是框架鉴权",
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
name: "正例·⑤ @Authorize()(无实参)",
|
|
2204
|
+
fn: "C.m",
|
|
2205
|
+
files: {
|
|
2206
|
+
"a.ts": `
|
|
2207
|
+
declare const Authorize: any;
|
|
2208
|
+
export class C {
|
|
2209
|
+
@Authorize()
|
|
2210
|
+
m() { return 1; }
|
|
2211
|
+
}
|
|
2212
|
+
`,
|
|
2213
|
+
},
|
|
2214
|
+
want: [AUTH_MARK],
|
|
2215
|
+
why: "无实参装饰器:无法靠实参判断,按装饰器名判定(保守放行)",
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
name: "负对照·① @Public() 显式免鉴权,不得注入",
|
|
2219
|
+
fn: "C.m",
|
|
2220
|
+
files: {
|
|
2221
|
+
"a.ts": `
|
|
2222
|
+
declare const UseGuards: any;
|
|
2223
|
+
declare const Public: any;
|
|
2224
|
+
declare class JwtAuthGuard {}
|
|
2225
|
+
@UseGuards(JwtAuthGuard)
|
|
2226
|
+
export class C {
|
|
2227
|
+
@Public()
|
|
2228
|
+
m() { return 1; }
|
|
2229
|
+
}
|
|
2230
|
+
`,
|
|
2231
|
+
},
|
|
2232
|
+
want: [],
|
|
2233
|
+
dontWant: [AUTH_MARK],
|
|
2234
|
+
why: "公开端点被判成「有鉴权」= 假阴性,比误报危险得多,必须挡住",
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
name: "负对照·② @UseGuards(ThrottlerGuard) 是限流不是鉴权",
|
|
2238
|
+
fn: "C.m",
|
|
2239
|
+
files: {
|
|
2240
|
+
"a.ts": `
|
|
2241
|
+
declare const UseGuards: any;
|
|
2242
|
+
declare class ThrottlerGuard {}
|
|
2243
|
+
export class C {
|
|
2244
|
+
@UseGuards(ThrottlerGuard)
|
|
2245
|
+
m() { return 1; }
|
|
2246
|
+
}
|
|
2247
|
+
`,
|
|
2248
|
+
},
|
|
2249
|
+
want: [],
|
|
2250
|
+
dontWant: [AUTH_MARK],
|
|
2251
|
+
why: "同名装饰器、非鉴权语义 ⇒ 实参必须看起来像鉴权守卫(R19:分母里最容易漏的就是被拒掉的那些)",
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
name: "负对照·③ @ApiBearerAuth() 只是 Swagger 文档",
|
|
2255
|
+
fn: "C.m",
|
|
2256
|
+
files: {
|
|
2257
|
+
"a.ts": `
|
|
2258
|
+
declare const ApiBearerAuth: any;
|
|
2259
|
+
export class C {
|
|
2260
|
+
@ApiBearerAuth()
|
|
2261
|
+
m() { return 1; }
|
|
2262
|
+
}
|
|
2263
|
+
`,
|
|
2264
|
+
},
|
|
2265
|
+
want: [],
|
|
2266
|
+
dontWant: [AUTH_MARK],
|
|
2267
|
+
why: "名字里带 auth,但不实施鉴权 ⇒ Api* 一律跳过",
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
name: "负对照·④ 无装饰器不得注入",
|
|
2271
|
+
fn: "C.m",
|
|
2272
|
+
files: {
|
|
2273
|
+
"a.ts": `
|
|
2274
|
+
export class C { m() { return 1; } }
|
|
2275
|
+
`,
|
|
2276
|
+
},
|
|
2277
|
+
want: [],
|
|
2278
|
+
dontWant: [AUTH_MARK],
|
|
2279
|
+
why: "基线:没有鉴权证据就不许凭空给出鉴权",
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
name: "负对照·⑤ 顶层函数(非类)不在本轮范围内",
|
|
2283
|
+
fn: "handler",
|
|
2284
|
+
files: {
|
|
2285
|
+
"a.ts": `
|
|
2286
|
+
declare const UseGuards: any;
|
|
2287
|
+
declare class JwtAuthGuard {}
|
|
2288
|
+
@UseGuards(JwtAuthGuard)
|
|
2289
|
+
export function handler() { return 1; }
|
|
2290
|
+
`,
|
|
2291
|
+
},
|
|
2292
|
+
want: [],
|
|
2293
|
+
dontWant: [AUTH_MARK],
|
|
2294
|
+
why: "本轮只覆盖类/方法(镜像 Python 的『类级守卫』)。顶层函数装饰器罕见,边界写死在此",
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
name: "正对照·⑥ 真实调用与既有标记不得被挤掉(E1 不回归)",
|
|
2298
|
+
fn: "Uploader.read",
|
|
2299
|
+
files: {
|
|
2300
|
+
"a.ts": `
|
|
2301
|
+
import * as fs from "fs";
|
|
2302
|
+
declare const UseGuards: any;
|
|
2303
|
+
declare class JwtAuthGuard {}
|
|
2304
|
+
@UseGuards(JwtAuthGuard)
|
|
2305
|
+
export class Uploader {
|
|
2306
|
+
read(filePath: string) { return fs.readFileSync(filePath, "utf-8"); }
|
|
2307
|
+
}
|
|
2308
|
+
`,
|
|
2309
|
+
},
|
|
2310
|
+
want: [AUTH_MARK, "readFileSync"],
|
|
2311
|
+
why: "本轮是**追加**标记,不是替换;E1 补进来的真实调用必须仍在",
|
|
2312
|
+
},
|
|
2313
|
+
];
|
|
2314
|
+
for (const c of cases) {
|
|
2315
|
+
(0, vitest_1.it)(`${c.name} —— ${c.why}`, () => {
|
|
2316
|
+
const dir = makeProject(c.files);
|
|
2317
|
+
try {
|
|
2318
|
+
const calls = marksFor(dir, c.fn);
|
|
2319
|
+
for (const w of c.want)
|
|
2320
|
+
(0, vitest_1.expect)(calls).toContain(w);
|
|
2321
|
+
for (const d of c.dontWant ?? [])
|
|
2322
|
+
(0, vitest_1.expect)(calls).not.toContain(d);
|
|
2323
|
+
}
|
|
2324
|
+
finally {
|
|
2325
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2326
|
+
}
|
|
2327
|
+
}, 150000);
|
|
2328
|
+
}
|
|
2329
|
+
});
|
package/dist/extract-ir.js
CHANGED
|
@@ -390,6 +390,57 @@ function extractDirectCalls(func, preText) {
|
|
|
390
390
|
// 与 protocol-detector 的同名规则(source-level benchmark 路径)。
|
|
391
391
|
// ═══════════════════════════════════════════════════════════════
|
|
392
392
|
const PATH_TRAVERSAL_MARKER = "__progmune_path_traversal__";
|
|
393
|
+
// ═══════════════════════════════════════════════════════════════
|
|
394
|
+
// E2(2026-09-22):TS 侧「框架鉴权」语义标记 —— 镜像 tools/extract_ir.py:1134
|
|
395
|
+
//
|
|
396
|
+
// Python 侧由**类级框架守卫**触发(类名含 authenticator / DRF permission_classes);
|
|
397
|
+
// TS 侧的等价物是 NestJS 的类/方法级守卫装饰器:@UseGuards(JwtAuthGuard)、@Roles(...) 等。
|
|
398
|
+
// 装饰器里的鉴权既不在函数体文本里、也不在 calls 里,规则**无论如何都看不到**——
|
|
399
|
+
// 探针:@UseGuards(JwtAuthGuard) 的 createGroup 被报「未鉴权」,注入本标记后该条消失。
|
|
400
|
+
//
|
|
401
|
+
// 消费方:protocol-detector 的 Authorization (Unauthenticated Access / Mutation)
|
|
402
|
+
// 两条规则的 auth_check safeguard(protocol-detector.ts:398 / :430)——它们**早已接受**
|
|
403
|
+
// __progmune_auth_machinery__,TS 侧此前从未产出 ⇒ 规则侧零改动。
|
|
404
|
+
//
|
|
405
|
+
// 边界(写死在这里,别想当然):只有**接受标记的规则**会受益。
|
|
406
|
+
// Input Validation 的 safeguard 不接受任何 __progmune_* 标记(protocol-detector.ts:481
|
|
407
|
+
// 是纯词形匹配),装饰器对它无效;实测注入后 Input Validation 条仍在。
|
|
408
|
+
// ═══════════════════════════════════════════════════════════════
|
|
409
|
+
const AUTH_MACHINERY_MARKER = "__progmune_auth_machinery__";
|
|
410
|
+
/** 显式免鉴权装饰器 —— 出现即不注入。把公开端点判成「有鉴权」是最危险的假阴性。 */
|
|
411
|
+
const AUTH_SKIP_DECORATOR = /^(?:Public|SkipAuth|AllowAnonymous|IsPublic|NoAuth|ApiExcludeEndpoint)$/i;
|
|
412
|
+
/** 承载鉴权的装饰器名(NestJS 约定:Guard = 鉴权/授权)。 */
|
|
413
|
+
const AUTH_DECORATOR = /^(?:UseGuards|Roles|Permissions|Authorize|RequireAuth|RequiresAuth|Authenticated|UseAuth|CheckPermissions|RequirePermissions|AdminOnly)$/i;
|
|
414
|
+
/** @UseGuards(X) 的实参须像鉴权守卫 —— 用于排除 @UseGuards(ThrottlerGuard) 之类。 */
|
|
415
|
+
const GUARD_ARG_AUTH = /auth|jwt|session|login|permission|role|admin|bearer|credential/i;
|
|
416
|
+
/**
|
|
417
|
+
* 由类/方法装饰器判定是否注入框架鉴权标记。文本匹配(与 computeMarkerCalls 口径一致)。
|
|
418
|
+
* @returns [] 或 [AUTH_MACHINERY_MARKER]
|
|
419
|
+
*/
|
|
420
|
+
function authMachineryFromDecorators(decorators) {
|
|
421
|
+
let skip = false;
|
|
422
|
+
let auth = false;
|
|
423
|
+
for (const d of decorators) {
|
|
424
|
+
const text = (d?.getText?.() ?? "").trim();
|
|
425
|
+
const name = /^@([A-Za-z_$][\w$]*)/.exec(text)?.[1];
|
|
426
|
+
if (!name)
|
|
427
|
+
continue;
|
|
428
|
+
if (AUTH_SKIP_DECORATOR.test(name)) {
|
|
429
|
+
skip = true;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
// Swagger 文档装饰器(@ApiBearerAuth …)只描述、不实施鉴权
|
|
433
|
+
if (/^Api/i.test(name))
|
|
434
|
+
continue;
|
|
435
|
+
if (!AUTH_DECORATOR.test(name) && !/guard|auth/i.test(name))
|
|
436
|
+
continue;
|
|
437
|
+
const args = /^@[\w$]*\(([\s\S]*)\)$/.exec(text)?.[1] ?? "";
|
|
438
|
+
if (args.trim() && !GUARD_ARG_AUTH.test(args))
|
|
439
|
+
continue;
|
|
440
|
+
auth = true;
|
|
441
|
+
}
|
|
442
|
+
return auth && !skip ? [AUTH_MACHINERY_MARKER] : [];
|
|
443
|
+
}
|
|
393
444
|
/**
|
|
394
445
|
* 标识符是否「看起来像基目录」—— G-A2 的定案依据。
|
|
395
446
|
*
|
|
@@ -2877,6 +2928,15 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
2877
2928
|
// 顺序与函数声明 / 箭头两个分支保持一致:先真实调用,再追加语义标记。
|
|
2878
2929
|
const mCalls = extractDirectCalls(m, mText);
|
|
2879
2930
|
mCalls.push(...computeMarkerCalls(mText, mParams.map((p) => p.getName()), sinkParams, onMethodHit, guardFns, directGuardFns, shapers));
|
|
2931
|
+
// E2(2026-09-22):类级 + 方法级守卫装饰器 → 框架鉴权标记。鉴权写在装饰器里,
|
|
2932
|
+
// 函数体文本与 calls 都看不到(镜像 Python 提取器的「类级框架守卫」处理)。
|
|
2933
|
+
for (const mk of authMachineryFromDecorators([
|
|
2934
|
+
...(cls.getDecorators?.() ?? []),
|
|
2935
|
+
...(m.getDecorators?.() ?? []),
|
|
2936
|
+
])) {
|
|
2937
|
+
if (!mCalls.includes(mk))
|
|
2938
|
+
mCalls.push(mk);
|
|
2939
|
+
}
|
|
2880
2940
|
funcs.push({
|
|
2881
2941
|
name: `${cn}.${mn}`,
|
|
2882
2942
|
params: mParams.map((p) => ({ name: p.getName(), type: getParamType(p), typeDetail: getParamTypeDetail(p) })),
|
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.49";
|
|
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
|
@@ -103,7 +103,7 @@ async function evaluateTrust(ctx) {
|
|
|
103
103
|
// ── Phase 5: Build call graph for cross-function propagation ──
|
|
104
104
|
const callGraph = (0, call_graph_propagator_1.buildCallGraphFromIR)(path.join(ctx.projectPath, "ir.json"));
|
|
105
105
|
const enterpriseViolations = collectEnterpriseViolations(ctx);
|
|
106
|
-
const { violations: protocolViolations, coverage: mappingCoverageData, ssgCoverage: ssgCov, annotationSuggestions: cAnnotationSuggestions } = await collectProtocolViolations(ctx, callGraph);
|
|
106
|
+
const { violations: protocolViolations, coverage: mappingCoverageData, ssgCoverage: ssgCov, annotationSuggestions: cAnnotationSuggestions, extractionWarning } = await collectProtocolViolations(ctx, callGraph);
|
|
107
107
|
const expressResult = collectExpressViolations(ctx);
|
|
108
108
|
const nestjsResult = collectNestJSViolations(ctx);
|
|
109
109
|
const trpcResult = collectTRPCViolations(ctx);
|
|
@@ -218,12 +218,20 @@ async function evaluateTrust(ctx) {
|
|
|
218
218
|
score: effectiveScore,
|
|
219
219
|
decision,
|
|
220
220
|
confidence,
|
|
221
|
-
coverageConfidence:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
coverageConfidence: extractionWarning
|
|
222
|
+
? {
|
|
223
|
+
score: 0,
|
|
224
|
+
margin: 25,
|
|
225
|
+
level: "LOW",
|
|
226
|
+
summary: `${extractionWarning}——本次扫描是废票,不得据此得出「干净」结论`,
|
|
227
|
+
}
|
|
228
|
+
: {
|
|
229
|
+
score: coverageConfidence.score,
|
|
230
|
+
margin: coverageConfidence.margin,
|
|
231
|
+
level: coverageConfidence.level,
|
|
232
|
+
summary: coverageConfidence.summary,
|
|
233
|
+
},
|
|
234
|
+
extractionWarning,
|
|
227
235
|
mappingCoverage: mappingCoverageData.totalApis > 0
|
|
228
236
|
? {
|
|
229
237
|
rate: Math.round(((mappingCoverageData.lookupHits + mappingCoverageData.llmHits) /
|
|
@@ -1208,6 +1216,8 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1208
1216
|
const annotationRuleNames = new Set();
|
|
1209
1217
|
// C 注解建议(函数级作用域——返回值在 try 外组装)
|
|
1210
1218
|
let annotationSuggestions;
|
|
1219
|
+
// 2026-09-22:提取失败标记(跨 try 作用域,随结果返回)
|
|
1220
|
+
let extractionWarning;
|
|
1211
1221
|
try {
|
|
1212
1222
|
// ── P4.5 校准:TS/JS 项目在 ir.json 缺失时先提取 IR ──
|
|
1213
1223
|
// 序列必须来自「函数体内真实调用」而非「文件内函数声明名单」——
|
|
@@ -1255,10 +1265,35 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1255
1265
|
`需重提取运行 PROGMUNE_IR_REEXTRACT=always 或删除 ir.json)`);
|
|
1256
1266
|
}
|
|
1257
1267
|
if (shouldExtract) {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1268
|
+
try {
|
|
1269
|
+
const ir = extractFn();
|
|
1270
|
+
// C 走合并形态(与 execute/MCP 写盘一致);TS/Python 保持裸数组形态
|
|
1271
|
+
const payload = lang === "c" ? { typeMap: {}, functions: ir } : ir;
|
|
1272
|
+
fs.writeFileSync(path.join(ctx.projectPath, "ir.json"), JSON.stringify(payload, null, 2));
|
|
1273
|
+
}
|
|
1274
|
+
catch (extractErr) {
|
|
1275
|
+
// 2026-09-22:提取异常不再静默吞掉——OOM 杀(exit 137)等失败
|
|
1276
|
+
// 必须发声,否则引擎带着空 IR 输出「0 违规」的假干净结论
|
|
1277
|
+
extractionWarning = `IR 提取失败(${extractErr?.message || extractErr})——结果不可信`;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
// 2026-09-22:提取后验尸——ir.json 存在但函数数为 0 且项目确有
|
|
1281
|
+
// 源码时,说明提取被静默截断(如 python3 被 OOM 杀后留下 2 字节
|
|
1282
|
+
// 空数组),同样记为失败
|
|
1283
|
+
if (!extractionWarning) {
|
|
1284
|
+
try {
|
|
1285
|
+
const irPath2 = path.join(ctx.projectPath, "ir.json");
|
|
1286
|
+
if (fs.existsSync(irPath2)) {
|
|
1287
|
+
const raw = fs.readFileSync(irPath2, "utf-8");
|
|
1288
|
+
const parsed = JSON.parse(raw);
|
|
1289
|
+
const fnCount = Array.isArray(parsed) ? parsed.length : (parsed.functions || []).length;
|
|
1290
|
+
if (fnCount === 0 && freshness.scannedFiles > 0) {
|
|
1291
|
+
extractionWarning = `IR 提取产物为空(0 函数,但项目有 ${freshness.scannedFiles} 个源文件)——提取可能被 OOM 截断,结果不可信`;
|
|
1292
|
+
console.warn(`[progmune] ⚠️ ${extractionWarning}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
catch { /* best-effort */ }
|
|
1262
1297
|
}
|
|
1263
1298
|
}
|
|
1264
1299
|
catch { /* best-effort — 回退正则扫描 */ }
|
|
@@ -1590,6 +1625,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1590
1625
|
},
|
|
1591
1626
|
} : undefined,
|
|
1592
1627
|
annotationSuggestions,
|
|
1628
|
+
extractionWarning,
|
|
1593
1629
|
};
|
|
1594
1630
|
}
|
|
1595
1631
|
function extractCallSequencesFromProject(projectPath, language, keepNames) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.49",
|
|
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/",
|