progmune-runtime 3.7.57 → 3.7.59
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 +49 -0
- package/README.md +12 -11
- package/README.zh-CN.md +12 -11
- package/dist/frameworks/express-detector.js +20 -5
- package/dist/protocol-detector-identity-substring.test.js +139 -0
- package/dist/protocol-detector.js +17 -0
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
- package/tools/extract_ir.py +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.58] — 2026-10-04(未发布)
|
|
4
|
+
|
|
5
|
+
### fr-001 点亮:身份映射子串匹配(修复回归 6/17)
|
|
6
|
+
|
|
7
|
+
**新能力(查询语义漏报根因第一刀)**:身份映射查找(OAuth subject / SCIM
|
|
8
|
+
external-ID)用 JSON 列 `.contains()` 做匹配——contains() 在 JSON 列上退化为
|
|
9
|
+
子串 LIKE(open-webui CVE-2026-87016 修复注释原话),subject 含 `%`/`_`
|
|
10
|
+
通配符可绑定任意账户(含管理员)。
|
|
11
|
+
|
|
12
|
+
- 提取器 `tools/extract_ir.py`:`has_identity_substring_match`(三条件:
|
|
13
|
+
函数名身份查找词形 + 体内 `.contains(` 调用 + 对象链含 oauth/scim 字段)
|
|
14
|
+
⇒ 注入 `__progmune_identity_substring_match__`
|
|
15
|
+
- 引擎规则 "Identity Lookup with Substring Match"(python-only marker-driven)
|
|
16
|
+
- **复测**:修复前 users.py 命中 2/2 真值函数(get_user_by_oauth_sub /
|
|
17
|
+
get_user_by_scim_external_id),修复后 0;Python 盲测 90 项目零触发
|
|
18
|
+
(合成语料无此形态,零漂移);定向测试 4/4(2 正 2 反)
|
|
19
|
+
- **fr-corpus 战绩 5/17 → 6/17**
|
|
20
|
+
|
|
21
|
+
**另:ZipSlip 候选关闭(如实记录)**——immich GHSA-jrp5 按 R20 三问判定
|
|
22
|
+
不可点亮:污点根是 DB 字段(跨请求存储污点),现有管线结构性够不着,
|
|
23
|
+
归入漏报根因新子类(详见 CAPABILITY_THREE_PROOFS_V1.md)。
|
|
24
|
+
|
|
25
|
+
### fr-014 点亮 + fr-015 关闭(2026-10-05)
|
|
26
|
+
|
|
27
|
+
- **fr-014(8/17,DETECTED-with-boundary)**:mockoon 管理 API 裸奔检出——
|
|
28
|
+
两处 Express 检测器可见性缺口修复:①`extractRoutes` 支持模板字符串路径
|
|
29
|
+
(库形态模块路径全是 `${prefix}/...` 变量拼前缀,此前整文件 0 路由);
|
|
30
|
+
②`app: Express` 形参的 setup 模块纳入整 app 无认证判定(此前落在
|
|
31
|
+
creator 门与 hasAnyAuth 门之间的盲区)。修复后残留归因到匿名中间件
|
|
32
|
+
函数体内的 bearer 校验超出名字级分类;AUTH_MIDDLEWARE_PATTERNS 补
|
|
33
|
+
timingSafeEqual/extractBearerToken(对具名形态有效)。express-realworld
|
|
34
|
+
回归 20 路由零新增误报。
|
|
35
|
+
- **fr-015 判定关闭(corpus_invalid)**:真值 sink 在虚拟模块
|
|
36
|
+
(#og-image-virtual/public-assets.mjs,pre 快照不存在),mini 切片无 sink;
|
|
37
|
+
且 pre-fix 已有 isBlockedUrl 守卫未接在 fonts 路径——fr-009 同族
|
|
38
|
+
「守卫存在但不足」。检测无从谈起,如实不覆盖。
|
|
39
|
+
|
|
40
|
+
### fr-012 复测点亮 + fr-004 判定关闭(2026-10-05)
|
|
41
|
+
|
|
42
|
+
- **fr-012(7/17,DETECTED-with-boundary)**:MCP 工具实参污点根(3.7.33 C 系列)
|
|
43
|
+
上线后修复前 markdownUpload 已命中路径穿越标记——此前未复测。修复后残留
|
|
44
|
+
2 条归因到 local 部署 by-design 分支(IS_REMOTE 拒绝 filePath 是修复,
|
|
45
|
+
local 保留文件读取是设计;提取器无分支语义,fr-009 同族边界)。
|
|
46
|
+
- **fr-004 判定关闭**:形态是闭包晚绑定(循环内 cookies 被内层函数晚绑定
|
|
47
|
+
捕获,多工具服务器时共享最后连接的 cookie jar)而非污点流——检测需变量
|
|
48
|
+
绑定分析,词形判据下 FP 爆炸,不硬做。根因归类修正为「作用域语义
|
|
49
|
+
(闭包晚绑定)」。注:fix_commit cd9db21c 是「重构形式的安全修复」
|
|
50
|
+
(fr-013 教训的镜像:fix 性质必须以公告摘要核对)。
|
|
51
|
+
|
|
3
52
|
## [3.7.57] — 2026-10-04(未发布)
|
|
4
53
|
|
|
5
54
|
### §51 held-out 验证 + §52-53 决策门禁 + 修冤枉第一刀(Data Mutation 族移除)
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://modelcontextprotocol.io)
|
|
7
|
-
[]()
|
|
8
|
-
[]()
|
|
7
|
+
[-22c55e)]()
|
|
8
|
+
[-22c55e)]()
|
|
9
9
|
[](https://www.npmjs.com/package/progmune-runtime)
|
|
10
10
|
[](https://www.npmjs.com/package/progmune-runtime)
|
|
11
11
|
[](https://github.com/shenlian19831109/progmune-runtime)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
> [中文版](https://github.com/shenlian19831109/progmune-runtime/blob/main/README.zh-CN.md) · English Version
|
|
15
15
|
|
|
16
|
-
**Verify AI-generated code before it reaches production.** Progmune checks
|
|
16
|
+
**Verify AI-generated code before it reaches production.** Progmune checks protocol lifecycles (TLS handshakes, auth flows, payment integrity, resource management — annotation-driven), framework route auth (production), and real-fix-validated taint detections (path traversal, SSRF) — plus a ranked evidence stream of safeguard alerts for human review. Every capability claim is tiered by evidence: [Capability Audit](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md).
|
|
17
17
|
|
|
18
18
|
Progmune doesn't trust what the model says. It verifies what the program actually does.
|
|
19
19
|
|
|
@@ -50,11 +50,10 @@ AI code generators produce syntactically valid code that often violates **protoc
|
|
|
50
50
|
|
|
51
51
|
| Category | Example violations detected |
|
|
52
52
|
|----------|---------------------------|
|
|
53
|
-
| **TLS /
|
|
54
|
-
| **
|
|
55
|
-
| **
|
|
56
|
-
| **
|
|
57
|
-
| **Data Integrity** | Mutation without audit trail, missing input validation |
|
|
53
|
+
| **Protocol lifecycles (annotation-driven)** | TLS handshake, auth flows, payment integrity, resource management — you declare the protocol primitives (~2–3 annotations per protocol), the state machine verifies execution order. Validated: C real modules 5/5 gold, app-level P=91.7%/R=100%. **Automatic detection of these classes on unannotated code is research-grade** — 0 hits on 17 real-world security fixes (measured, see [fix-regression corpus](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/fix-regression-corpus.json)) |
|
|
54
|
+
| **Framework route auth (TypeScript/Python, production)** | Missing auth guard / validation on mutation routes — NestJS/Express/tRPC/FastAPI/Django/Flask/Fastify/Next.js/Koa/Hapi/Gin/Fiber/Spring: validated on real corpora with guard-removal counter-proofs; real-world blind tests on docmost + immich after fix rounds |
|
|
55
|
+
| **Taint-marker detections (real-fix validated)** | Path traversal (TS+Python) and SSRF (TS+Python): extractor taint flow → sink, validated against real security fixes at their ground-truth sites (fr-007, fr-016, fr-010, fr-011) — plus a held-out hit: SSRF = GHSA-hq46 on immich. See [Capability Audit](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md) |
|
|
56
|
+
| **Input validation / data integrity safeguards** | Missing input validation before persistence, missing FK checks — evidence stream (ranked), not a verdict: real-world weighted precision ≈6%, ranked head ≈46% |
|
|
58
57
|
| **Injection (Python, source-level)** | SQL built with f-string/`%`/`.format`/concatenation, command injection via dynamic subprocess args, SSRF via user-controlled URL fetches, SSTI via template-string sinks, XXE via external-entity parser config, eval/exec on user input |
|
|
59
58
|
| **Web (Python, source-level)** | XSS via `{{ var\|safe }}`/autoescape-off templates, path traversal via user-controlled file paths, CSRF via `@csrf_exempt` or GET state changes, authorization by client cookies, hardcoded JWT secrets (incl. cross-module constants) |
|
|
60
59
|
|
|
@@ -105,8 +104,8 @@ Progmune is honest about what it can and cannot verify.
|
|
|
105
104
|
|
|
106
105
|
| Language | Status | Evidence |
|
|
107
106
|
|----------|--------|----------|
|
|
108
|
-
| **TypeScript / JavaScript** | ✅ Production |
|
|
109
|
-
| **Python** | ✅ Production |
|
|
107
|
+
| **TypeScript / JavaScript** | ✅ Production | Synthetic blind benchmark: **recall 98.5% / precision 100%** (795 gold findings, 100 generated projects). Real-world numbers are measured separately and published honestly — see [Capability Audit](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md): safeguard alert stream ≈6% weighted precision (ranked head ≈46%), 47% location recall on 84 real advisories, 1 real-advisory hit on the main decision path |
|
|
108
|
+
| **Python** | ✅ Production | Synthetic blind benchmark: **recall 100% / precision 100%** (729 gold findings, 90 generated projects); real-world validation: PyGoat (OWASP vulnerable-by-design Django app) **67 TP / 0 FP, 100% labeled precision**; three well-written apps (django/fastapi realworld, django-unicorn) with 0 false-positive true findings |
|
|
110
109
|
| **C** | ✅ Annotation-driven (Beta) | IR extraction via the registry + SSG state machine; **annotate ~2-3 protocol primitives per protocol to get trusted verification** (gold 5/5 on real modules: redis ACL / libssh client / libssh server / libssh callback-dispatch / uftpd transfer-auth — all 0 FP with precise violation localization; app-level gold v2: **P=91.7% / R=100% / F1=95.7%**). Unannotated auto-detection stays out of scope (0 TP on real corpus — positioning decision recorded in the C Language Status doc); TLS-level coverage still absent. See [C Language Status](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md). |
|
|
111
110
|
| **Go** | ✅ Annotation-driven (Beta) | IR extraction via registry + SSG state machine; synthetic gold v1: **P=100% / R=100%** (clean×3 + violations×3); zero external toolchain (pure-TS lexical extractor — works from npm installs) |
|
|
112
111
|
| **Java** | 🔶 Annotation-driven (Beta) | Two layers, verified on real corpora. **Framework layer ✅**: Spring adapter (`spring-detector.ts` — security rule ordering + ant patterns + controller annotation routes) on gothinkster/spring-boot-realworld (1581★): 19 routes / 12 mutations fully parsed, 0 issues, catch-all flip counter-proof ×10; Spring Boot 3 modern dialect re-verified (SecurityFilterChain bean + String[] allowlist expansion): 15 routes, 0 issues. **Core layer**: pure-TS lexical extractor (zero toolchain deps) at **100%/100% recovery vs a tree-sitter AST baseline (1216/1216)**; SSG annotation-driven protocol rows (token / auth-register / resource) engine-locked with a real-corpus closed loop (**1 real TP / 0 FP**). Unannotated auto-detection stays out of scope (same decision as C/Go). Production label pending enterprise PoC. See [Java Language Status](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/java-language-status.md). |
|
|
@@ -144,6 +143,8 @@ npx progmune-init-policy --tier 3 # Observe: tools/demos — report only
|
|
|
144
143
|
|
|
145
144
|
Public, reproducible precision data. All numbers measured against gold-annotated benchmarks.
|
|
146
145
|
|
|
146
|
+
> ⚠ **口径(Scope)**:以下数字是**合成基准**(模板生成的项目 + 人工金标)——它证明"规则在这些形态上工作",**不证明**"在真实项目上同样准确"。真实项目的数字单列在 [Capability Audit(三要件)](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md):真实告警流加权精确率 ≈6%(排序后头部 ≈46%)、84 条真实公告位置召回 47%(下界)、主路径真实公告命中 1 次(SSRF = GHSA-hq46)。合成 P100% 与真实项目大量误报可以同时为真——两者测的是不同的事。
|
|
147
|
+
|
|
147
148
|
### TypeScript (Blind Benchmark v6 — 100 projects)
|
|
148
149
|
|
|
149
150
|
| Metric | Value |
|
|
@@ -165,7 +166,7 @@ Public, reproducible precision data. All numbers measured against gold-annotated
|
|
|
165
166
|
| Metric | Value |
|
|
166
167
|
|--------|-------|
|
|
167
168
|
| Labeled precision | **100%** (67 true positives / 0 false positives, per-detection human review) |
|
|
168
|
-
| Classes covered | 14 vulnerability classes
|
|
169
|
+
| Classes covered | 14 vulnerability classes on **Python** (Django lab app) — SQLi, SSRF, path traversal, XSS, SSTI, XXE, command injection, deserialization, CSRF (both shapes), cookie authorization, hardcoded secrets. ⚠ These classes are **not** all wired on TypeScript: 15 of 48 rules are Python-only and never fire on TS projects (XSS/CSRF/XXE/hardcoded secrets included) — measured, see §49.18 in [PATH_GUARD_EVIDENCE_DESIGN](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/PATH_GUARD_EVIDENCE_DESIGN_V1.md) |
|
|
169
170
|
| Well-written apps | django-realworld, fastapi-realworld, django-unicorn — 0 false-positive true findings; 3 documented framework-internal boundary FPs |
|
|
170
171
|
|
|
171
172
|
→ [Real-world validation report](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/REALWORLD_APP_V1.md) · [Benchmark baseline](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/BASELINE_v6.md)
|
package/README.zh-CN.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://modelcontextprotocol.io)
|
|
7
|
-
[]()
|
|
8
|
-
[]()
|
|
7
|
+
[-22c55e)]()
|
|
8
|
+
[-22c55e)]()
|
|
9
9
|
|
|
10
10
|
> [English Version](https://github.com/shenlian19831109/progmune-runtime/blob/main/README.md) · 中文版
|
|
11
11
|
|
|
12
|
-
**在 AI 生成的代码进入生产前验证它。** Progmune
|
|
12
|
+
**在 AI 生成的代码进入生产前验证它。** Progmune 检查协议生命周期(TLS 握手、认证流程、支付完整性、资源管理——注解驱动)、框架路由认证(生产)、真实修复验证过的污点检测(路径穿越、SSRF)——外加一条排序后的防护告警证据流供人工审查。每项能力声明都按证据分级:[能力审计](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md)。
|
|
13
13
|
|
|
14
14
|
Progmune 不信任模型说的话,它验证程序实际做的事。
|
|
15
15
|
|
|
@@ -46,11 +46,10 @@ AI 代码生成器产出语法合法的代码,却常常违反**协议生命周
|
|
|
46
46
|
|
|
47
47
|
| 类别 | 检测到的违规示例 |
|
|
48
48
|
|------|----------------|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
| **数据完整性** | 变更无审计轨迹、缺少输入校验 |
|
|
49
|
+
| **协议生命周期(注解驱动)** | TLS 握手、认证流程、支付完整性、资源管理——你声明协议原语(每协议 ~2-3 个注解),状态机验证执行顺序。已验证:C 真实模块金标 5/5、应用级 P=91.7%/R=100%。**未注解代码的自动检测为研究级**——17 条真实安全修复命中 0(实测,见 [修复回归语料](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/fix-regression-corpus.json)) |
|
|
50
|
+
| **框架路由认证(TS/Python,生产)** | 写操作路由缺认证守卫/校验——NestJS/Express/tRPC/FastAPI/Django/Flask/Fastify/Next.js/Koa/Hapi/Gin/Fiber/Spring:真实语料验证 + 摘保护反证;docmost + immich 真实项目盲测(修复轮后) |
|
|
51
|
+
| **污点标记检测(真实修复验证)** | 路径穿越(TS+Python)与 SSRF(TS+Python):提取器污点流 → sink,以真实安全修复的真值位置验证(fr-007、fr-016、fr-010、fr-011)+ held-out 命中:immich 上 SSRF = GHSA-hq46。见 [能力审计](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md) |
|
|
52
|
+
| **输入校验 / 数据完整性防护** | 持久化前缺输入校验、缺外键检查——证据流(已排序)而非判定:真实世界加权精确率 ≈6%,排序头部 ≈46% |
|
|
54
53
|
| **注入类(Python,源码级)** | 用 f-string/`%`/`.format`/拼接构造 SQL、动态 subprocess 参数导致命令注入、用户可控 URL 抓取导致 SSRF、模板字符串 sink 导致 SSTI、外部实体解析器配置导致 XXE、对用户输入 eval/exec |
|
|
55
54
|
| **Web 类(Python,源码级)** | `{{ var\|safe }}`/autoescape off 模板导致 XSS、用户可控文件路径导致路径穿越、`@csrf_exempt` 或 GET 状态变更导致 CSRF、客户端 cookie 授权、硬编码 JWT 密钥(含跨模块常量) |
|
|
56
55
|
|
|
@@ -101,8 +100,8 @@ Progmune 对能验证什么、不能验证什么保持诚实。
|
|
|
101
100
|
|
|
102
101
|
| 语言 | 状态 | 证据 |
|
|
103
102
|
|------|------|------|
|
|
104
|
-
| **TypeScript / JavaScript** | ✅ 生产 |
|
|
105
|
-
| **Python** | ✅ 生产 |
|
|
103
|
+
| **TypeScript / JavaScript** | ✅ 生产 | 合成盲测基准:**召回 98.5% / 精确率 100%**(795 条 gold finding,100 个生成项目)。真实项目数字单列并如实发布——见 [能力审计(三要件)](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md):告警流加权精确率 ≈6%(排序后头部 ≈46%)、84 条真实公告位置召回 47%(下界)、主路径真实公告命中 1 次 |
|
|
104
|
+
| **Python** | ✅ 生产 | 合成盲测基准:**召回 100% / 精确率 100%**(729 条 gold finding,90 个生成项目);真实应用验证:PyGoat(OWASP 故意脆弱 Django 应用)**67 TP / 0 FP,标记精确率 100%**;三个良构应用(django/fastapi realworld、django-unicorn)0 误报真阳性 |
|
|
106
105
|
| **C** | ✅ 注解驱动(Beta) | IR 提取接入注册表 + SSG 状态机;**每协议标注 ~2-3 个原语即获得可信验证**(真实模块金标 5/5:redis ACL / libssh 客户端 / libssh 服务端 / libssh 回调分发 / uftpd 传送授权——全部 0 误报 + 违规精确定位;应用级金标 v2:**P=91.7% / R=100% / F1=95.7%**)。未注解自动检测不在范围(真实语料 0 TP——定位决议见 C 语言状态文档);TLS 级覆盖仍无。见 [C 语言状态](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md)。 |
|
|
107
106
|
| **Go** | ✅ 注解驱动(Beta) | IR 提取接入注册表 + SSG 状态机;合成金标 v1:**P=100% / R=100%**(3 干净 × 3 植入违规);零外部工具链(纯 TS 词法提取,npm 安装态可用) |
|
|
108
107
|
| **Java** | ❌ 无 | 规划中 |
|
|
@@ -137,6 +136,8 @@ npx progmune-init-policy --tier 3 # 观察:工具/demo——只报告不拦
|
|
|
137
136
|
|
|
138
137
|
公开、可复现的精确率数据。所有数字均对 gold 标注基准测量。
|
|
139
138
|
|
|
139
|
+
> ⚠ **口径**:以下数字是**合成基准**(模板生成项目 + 人工金标)——它证明"规则在这些形态上工作",**不证明**"在真实项目上同样准确"。真实项目数字单列在 [能力审计(三要件)](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/CAPABILITY_THREE_PROOFS_V1.md):真实告警流加权精确率 ≈6%(排序后头部 ≈46%)、84 条真实公告位置召回 47%(下界)、主路径真实公告命中 1 次(SSRF = GHSA-hq46)。合成 P100% 与真实项目大量误报可以同时为真——两者测的是不同的事。
|
|
140
|
+
|
|
140
141
|
### TypeScript(盲测基准 v6——100 个项目)
|
|
141
142
|
|
|
142
143
|
| 指标 | 值 |
|
|
@@ -158,7 +159,7 @@ npx progmune-init-policy --tier 3 # 观察:工具/demo——只报告不拦
|
|
|
158
159
|
| 指标 | 值 |
|
|
159
160
|
|------|-----|
|
|
160
161
|
| 标记精确率 | **100%**(67 真阳性 / 0 误报,逐条人工核实) |
|
|
161
|
-
| 覆盖类别 | 14
|
|
162
|
+
| 覆盖类别 | 14 个漏洞类别,**Python 口径**(Django lab 应用)——SQLi、SSRF、路径穿越、XSS、SSTI、XXE、命令注入、反序列化、CSRF(双形态)、cookie 授权、硬编码密钥。⚠ 这些类别在 TypeScript 上**并未全部接线**:48 条规则中 15 条 python-only,在 TS 项目上永不触发(含 XSS/CSRF/XXE/硬编码密钥)——实测数据见 [PATH_GUARD_EVIDENCE_DESIGN §49.18](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/PATH_GUARD_EVIDENCE_DESIGN_V1.md) |
|
|
162
163
|
| 良构应用 | django-realworld、fastapi-realworld、django-unicorn——0 误报真阳性 |
|
|
163
164
|
|
|
164
165
|
→ [真实验证报告](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/REALWORLD_APP_V1.md) · [基准基线](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/BASELINE_v6.md)
|
|
@@ -76,6 +76,11 @@ const AUTH_MIDDLEWARE_PATTERNS = [
|
|
|
76
76
|
/\bensureAuthenticated\b/,
|
|
77
77
|
/\bauthGuard\b/,
|
|
78
78
|
/\bAuthGuard\b/,
|
|
79
|
+
// 匿名中间件函数体内的 bearer 校验证据(2026-10-05 fr-014 mockoon 修复形态:
|
|
80
|
+
// app.use(`${prefix}*`, (req,res,next) => { hasValidAdminApiToken(...) })——
|
|
81
|
+
// 认证在函数体里,中间件名不可见)
|
|
82
|
+
/\btimingSafeEqual\b/,
|
|
83
|
+
/\bextractBearerToken\b/,
|
|
79
84
|
/\brequire\w*[Aa]uth\b/,
|
|
80
85
|
/\bcheckAuth\b/,
|
|
81
86
|
/\bprotect\b/,
|
|
@@ -161,17 +166,22 @@ function extractRoutes(code, appName) {
|
|
|
161
166
|
for (const method of methods) {
|
|
162
167
|
// Pattern: app.get('/path', middleware1, middleware2, handler)
|
|
163
168
|
// or: router.post('/path', handler)
|
|
164
|
-
|
|
169
|
+
// 2026-10-05(fr-014 mockoon):路径也支持模板字符串
|
|
170
|
+
// app.get(`${adminApiPrefix}/events`, ...)——库形态模块(app 经参数传入)
|
|
171
|
+
// 的路径几乎全是变量拼前缀的模板串,字符串字面量 regex 整文件 0 路由。
|
|
172
|
+
// 捕获组:1=单引号 2=双引号 3=模板串;path 取三者首个非空。
|
|
173
|
+
const routeRegex = new RegExp(`\\b${receivers}\\.${method}\\s*\\(\\s*(?:'([^']+)'|\"([^\"]+)\"|\`([^\`]+)\`)\\s*,([^;]+)\\)`, "gi");
|
|
165
174
|
let match;
|
|
166
175
|
while ((match = routeRegex.exec(code)) !== null) {
|
|
167
|
-
const
|
|
176
|
+
const routePath = match[1] ?? match[2] ?? match[3] ?? "";
|
|
177
|
+
const rawArgs = match[4].trim();
|
|
168
178
|
const args = rawArgs.split(",").map(a => a.trim()).filter(a => a.length > 0);
|
|
169
179
|
// Last arg is the handler, everything before is middleware
|
|
170
180
|
const handler = args[args.length - 1] || "anonymous";
|
|
171
181
|
const middlewares = args.slice(0, -1);
|
|
172
182
|
routes.push({
|
|
173
183
|
method,
|
|
174
|
-
path:
|
|
184
|
+
path: routePath,
|
|
175
185
|
handler,
|
|
176
186
|
middlewares,
|
|
177
187
|
line: code.slice(0, match.index).split("\n").length,
|
|
@@ -274,9 +284,14 @@ function analyzeExpressApp(code) {
|
|
|
274
284
|
const hasSession = globalMiddleware.some(m => m.type === "session") || /\bsession\s*\(/.test(code);
|
|
275
285
|
// ── Generate Issues ──
|
|
276
286
|
// 整 app 无认证:仅真 app(实例化 express())且自身有非公开 mutation
|
|
277
|
-
// 路由时报——main.ts 只挂载路由模块(真实认证在 controllers
|
|
287
|
+
// 路由时报——main.ts 只挂载路由模块(真实认证在 controllers 内)不算裸。
|
|
288
|
+
// 2026-10-05(fr-014 mockoon):库形态 setup 模块(app: Express 经参数
|
|
289
|
+
// 传入、在调用方 app 上挂载)同样报——此前「非 creator 且无 auth」落在
|
|
290
|
+
// 两条判定路之间(整 app 报要求 creator、逐路由报要求 hasAnyAuth),
|
|
291
|
+
// 管理 API 裸奔的系统性盲区。
|
|
292
|
+
const appIsParam = /\bapp\s*:\s*Express\b/.test(code);
|
|
278
293
|
const hasNakedMutation = routes.some(nonPublicMutation);
|
|
279
|
-
if (!hasAnyAuth && appIsCreator && hasNakedMutation) {
|
|
294
|
+
if (!hasAnyAuth && (appIsCreator || appIsParam) && hasNakedMutation) {
|
|
280
295
|
issues.push({
|
|
281
296
|
severity: "critical",
|
|
282
297
|
rule: "EXPRESS_NO_AUTH_MIDDLEWARE",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* fr-001 定向回归:身份映射查找用 JSON.contains 子串匹配
|
|
38
|
+
* (REALWORLD_FIX_REGRESSION_V1 fr-001,open-webui CVE-2026-87016)
|
|
39
|
+
*
|
|
40
|
+
* 判据在 tools/extract_ir.py 的 has_identity_substring_match:
|
|
41
|
+
* ① 函数名身份查找形态(oauth/scim/sso/saml + sub/subject/external_id/identity)
|
|
42
|
+
* ② 体内有 <对象>.contains( 调用
|
|
43
|
+
* ③ contains 对象链含 oauth/scim 字段
|
|
44
|
+
* 三条件同时成立才注入 __progmune_identity_substring_match__ 标记。
|
|
45
|
+
*/
|
|
46
|
+
const vitest_1 = require("vitest");
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const extract_ir_python_1 = require("./extract-ir-python");
|
|
51
|
+
let dir;
|
|
52
|
+
(0, vitest_1.beforeEach)(() => {
|
|
53
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), "fr001-"));
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.afterEach)(() => {
|
|
56
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
57
|
+
});
|
|
58
|
+
function write(rel, content) {
|
|
59
|
+
const full = path.join(dir, rel);
|
|
60
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
61
|
+
fs.writeFileSync(full, content);
|
|
62
|
+
}
|
|
63
|
+
const MARKER = "__progmune_identity_substring_match__";
|
|
64
|
+
(0, vitest_1.describe)("fr-001 identity substring match(提取器标记)", () => {
|
|
65
|
+
(0, vitest_1.it)("修复前形态:oauth JSON 列 contains() 做身份映射 → 注入标记", () => {
|
|
66
|
+
write("users.py", `
|
|
67
|
+
class User:
|
|
68
|
+
oauth = None
|
|
69
|
+
scim = None
|
|
70
|
+
|
|
71
|
+
async def get_user_by_oauth_sub(provider, sub, db=None):
|
|
72
|
+
query = select(User)
|
|
73
|
+
if dialect == 'sqlite':
|
|
74
|
+
oauth_match = User.oauth.contains({provider: {'sub': sub}})
|
|
75
|
+
query = query.where(oauth_match)
|
|
76
|
+
row = await session.execute(query)
|
|
77
|
+
return row
|
|
78
|
+
|
|
79
|
+
async def get_user_by_scim_external_id(provider, external_id, db=None):
|
|
80
|
+
query = select(User)
|
|
81
|
+
if dialect == 'sqlite':
|
|
82
|
+
scim_match = User.scim.contains({provider: {'external_id': external_id}})
|
|
83
|
+
query = query.where(scim_match)
|
|
84
|
+
return (await session.execute(query)).scalars().first()
|
|
85
|
+
`);
|
|
86
|
+
const ir = (0, extract_ir_python_1.extractIRPython)(dir);
|
|
87
|
+
const oauthFn = ir.find((f) => f.name === "get_user_by_oauth_sub");
|
|
88
|
+
const scimFn = ir.find((f) => f.name === "get_user_by_scim_external_id");
|
|
89
|
+
(0, vitest_1.expect)(oauthFn).toBeDefined();
|
|
90
|
+
(0, vitest_1.expect)(scimFn).toBeDefined();
|
|
91
|
+
(0, vitest_1.expect)(oauthFn.calls).toContain(MARKER);
|
|
92
|
+
(0, vitest_1.expect)(scimFn.calls).toContain(MARKER);
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)("修复后形态:下标提取 + 精确等值 → 无标记", () => {
|
|
95
|
+
write("users.py", `
|
|
96
|
+
async def get_user_by_oauth_sub(provider, sub, db=None):
|
|
97
|
+
# Subscript, never contains(): on a JSON column contains() degrades to a substring LIKE.
|
|
98
|
+
query = select(User).where(User.oauth[provider]['sub'].as_string() == sub)
|
|
99
|
+
row = (await session.execute(query)).scalars().first()
|
|
100
|
+
return row
|
|
101
|
+
|
|
102
|
+
async def get_user_by_scim_external_id(provider, external_id, db=None):
|
|
103
|
+
query = select(User).where(User.scim[provider]['external_id'].as_string() == external_id)
|
|
104
|
+
return (await session.execute(query)).scalars().first()
|
|
105
|
+
`);
|
|
106
|
+
const ir = (0, extract_ir_python_1.extractIRPython)(dir);
|
|
107
|
+
for (const f of ir) {
|
|
108
|
+
(0, vitest_1.expect)(f.calls).not.toContain(MARKER);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)("负例:非身份查找函数用 .contains → 无标记(函数名不含 oauth/scim 词形)", () => {
|
|
112
|
+
write("utils.py", `
|
|
113
|
+
def has_feature(config, name):
|
|
114
|
+
return config.features.contains({'name': name})
|
|
115
|
+
|
|
116
|
+
async def get_user_by_id(user_id, db=None):
|
|
117
|
+
query = select(User).where(User.id == user_id)
|
|
118
|
+
return (await session.execute(query)).scalars().first()
|
|
119
|
+
`);
|
|
120
|
+
const ir = (0, extract_ir_python_1.extractIRPython)(dir);
|
|
121
|
+
for (const f of ir) {
|
|
122
|
+
(0, vitest_1.expect)(f.calls).not.toContain(MARKER);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)("负例:身份词形函数但 contains 对象不是 oauth/scim 字段 → 无标记", () => {
|
|
126
|
+
write("utils.py", `
|
|
127
|
+
async def get_oauth_subject_from_claim(claim, db=None):
|
|
128
|
+
# 函数名含 oauth+subject,但 contains 的对象是普通 dict,不是模型 JSON 列
|
|
129
|
+
payload = {'claims': {'sub': claim}}
|
|
130
|
+
if payload.contains({'provider': 'x'}):
|
|
131
|
+
return claim
|
|
132
|
+
return None
|
|
133
|
+
`);
|
|
134
|
+
const ir = (0, extract_ir_python_1.extractIRPython)(dir);
|
|
135
|
+
for (const f of ir) {
|
|
136
|
+
(0, vitest_1.expect)(f.calls).not.toContain(MARKER);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -845,6 +845,23 @@ const SAFEGUARD_RULES = [
|
|
|
845
845
|
conceptMissing: ["ResourceOwnership", "CrossUserReference"],
|
|
846
846
|
conceptExpected: ["folder write-access check", "get_*_by_id_and_user_id query"],
|
|
847
847
|
},
|
|
848
|
+
{
|
|
849
|
+
name: "Identity Lookup with Substring Match",
|
|
850
|
+
category: "auth",
|
|
851
|
+
// 2026-10-04(REALWORLD_FIX_REGRESSION_V1 fr-001):Python 提取器在
|
|
852
|
+
// 「身份映射查找函数(oauth_sub/scim_external_id 词形)对 oauth/scim
|
|
853
|
+
// JSON 列调用 .contains()」时注入标记——JSON 列的 contains() 退化为
|
|
854
|
+
// 子串 LIKE(open-webui CVE-2026-87016 修复注释原话),subject 含
|
|
855
|
+
// %/_ 通配符可绑定任意账户(含管理员)。证据语义在提取器侧
|
|
856
|
+
// (has_identity_substring_match,三条件:函数名身份查找形态 +
|
|
857
|
+
// 体内有 .contains( 调用 + 对象链含 oauth/scim 字段)。
|
|
858
|
+
languages: ["python"],
|
|
859
|
+
trigger: /\b(__progmune_identity_substring_match__)\b/,
|
|
860
|
+
safeguards: [],
|
|
861
|
+
violationMessage: "Identity mapping lookup (OAuth subject / SCIM external-id) matches with JSON contains() — on a JSON column contains() degrades to a substring LIKE, so a subject containing %/_ wildcards can bind to an arbitrary account (incl. admin). Use subscript extraction + equality (e.g. User.oauth[provider]['sub'].as_string() == sub).",
|
|
862
|
+
conceptMissing: ["ExactIdentityMatch", "SubscriptExtraction"],
|
|
863
|
+
conceptExpected: ["oauth[provider]['sub'] == sub", "scim[provider]['external_id'] == external_id"],
|
|
864
|
+
},
|
|
848
865
|
{
|
|
849
866
|
name: "XSS (Unsafe Template Rendering)",
|
|
850
867
|
category: "xss",
|
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.59";
|
|
24
24
|
function verify(filePath) {
|
|
25
25
|
const cert = (0, certify_1.certify)(filePath);
|
|
26
26
|
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.59",
|
|
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/",
|
package/tools/extract_ir.py
CHANGED
|
@@ -745,6 +745,42 @@ def has_template_tag_decorator(node):
|
|
|
745
745
|
|
|
746
746
|
CROSS_USER_WRITE_MARKER = "__progmune_cross_user_write__"
|
|
747
747
|
|
|
748
|
+
IDENTITY_SUBSTRING_MARKER = "__progmune_identity_substring_match__"
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def has_identity_substring_match(node):
|
|
752
|
+
"""身份映射查找用 JSON.contains 子串匹配(REALWORLD_FIX_REGRESSION_V1
|
|
753
|
+
fr-001 open-webui CVE-2026-87016):get_user_by_oauth_sub /
|
|
754
|
+
get_user_by_scim_external_id 在 SQLite 分支用
|
|
755
|
+
User.oauth.contains({provider: {'sub': sub}}) 做身份映射——JSON 列的
|
|
756
|
+
contains() 退化为子串 LIKE(修复注释原话:Subscript, never contains()),
|
|
757
|
+
subject 含 %/_ 通配符可绑定任意账户(含管理员)。
|
|
758
|
+
判据(保守,语义定义,三条件同时成立):
|
|
759
|
+
① 函数名是身份查找形态:含 (oauth|scim|sso|saml) 且含
|
|
760
|
+
(sub|subject|external_id|identity);
|
|
761
|
+
② 函数体内有 <对象>.contains( 调用;
|
|
762
|
+
③ contains 的对象名链含 oauth/scim 字段(排除泛型 JSON 配置检查)。"""
|
|
763
|
+
try:
|
|
764
|
+
name = getattr(node, "name", "")
|
|
765
|
+
if not re.search(r"(oauth|scim|sso|saml)", name, re.I):
|
|
766
|
+
return False
|
|
767
|
+
if not re.search(r"(sub|subject|external_id|identity)", name, re.I):
|
|
768
|
+
return False
|
|
769
|
+
text = ast.unparse(node)
|
|
770
|
+
except Exception:
|
|
771
|
+
return False
|
|
772
|
+
for child in ast.walk(node):
|
|
773
|
+
if not isinstance(child, ast.Call):
|
|
774
|
+
continue
|
|
775
|
+
fn = child.func
|
|
776
|
+
if not isinstance(fn, ast.Attribute) or fn.attr != "contains":
|
|
777
|
+
continue
|
|
778
|
+
# contains 的对象链:User.oauth.contains(...) → value 是 User.oauth
|
|
779
|
+
obj_text = ast.unparse(fn.value)
|
|
780
|
+
if re.search(r"\.(oauth|scim)\b", obj_text, re.I):
|
|
781
|
+
return True
|
|
782
|
+
return False
|
|
783
|
+
|
|
748
784
|
|
|
749
785
|
def has_cross_user_resource_write(node):
|
|
750
786
|
"""跨用户资源写入(REALWORLD_FIX_REGRESSION_V1 fr-005 open-webui):
|
|
@@ -957,6 +993,8 @@ def extract_calls(node, unsafe_vars=None, imports=None, module_constants=None, g
|
|
|
957
993
|
calls.append(OWNERSHIP_CHECKED_MARKER)
|
|
958
994
|
if has_cross_user_resource_write(node) and CROSS_USER_WRITE_MARKER not in calls:
|
|
959
995
|
calls.append(CROSS_USER_WRITE_MARKER)
|
|
996
|
+
if has_identity_substring_match(node) and IDENTITY_SUBSTRING_MARKER not in calls:
|
|
997
|
+
calls.append(IDENTITY_SUBSTRING_MARKER)
|
|
960
998
|
if has_command_taint_flow(node) and CMD_FLOW_MARKER not in calls:
|
|
961
999
|
calls.append(CMD_FLOW_MARKER)
|
|
962
1000
|
if has_csrf_exempt(node) and CSRF_MARKER not in calls:
|