progmune-runtime 3.7.26 → 3.7.27
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 +13 -0
- package/dist/protocol-detector.js +14 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +15 -0
- package/package.json +1 -1
- package/tools/extract_ir.py +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.27] — 2026-09-13
|
|
4
|
+
|
|
5
|
+
### 归属校验 Python 化(A2,修复回归 fr-005 驱动)
|
|
6
|
+
|
|
7
|
+
- **跨用户资源写入检测**(`tools/extract_ir.py` 新标记 `__progmune_cross_user_write__`):函数把请求 payload 里的外来资源 id(folder_id)带着持久化(insert/add/create)却没有资源归属守卫 → 标记。**严格归属守卫词表**(任一出现即抑制):归属字段比较(user_id/owner_id/user.id)、`has_*/check_*_folder_access` 助手、`get_*_by_id_and_user_id` 限定查询——**不含 `has_permission`**(fr-005 真值函数含特性权限检查,与资源归属无关);`insert_` 前缀持久化原语不标(归属决策在上游路由层)
|
|
8
|
+
- **引擎 IR 层消费**(`src/trust/engine.ts`):`AUTHZ_CROSS_USER_WRITE` 违规直接报出(同 PATH_TRAVERSAL 模式);`protocol-detector.ts` 同名规则供 source-level 路径
|
|
9
|
+
- **fr-005 闭环验收**:修复前 DETECTED(open-webui main.py::chat_completion,真值位置);修复后 0 误报(has_folder_write_access 抑制);chats.py create_new_chat / automations.py(有守卫)均不标
|
|
10
|
+
- **修复回归战绩 2/8**(fr-007 路径穿越 TS 化 + fr-005 归属校验 Python 化)
|
|
11
|
+
|
|
12
|
+
### 回归
|
|
13
|
+
|
|
14
|
+
- Python 盲测 64/64 零漂移 ✓ / Python 源级金标新规则 0 命中 ✓ / skyvern 24 无漂移 ✓ / 引擎测试 73 green ✓ / build ✓ / check 免疫正常 ✓
|
|
15
|
+
|
|
3
16
|
## [3.7.26] — 2026-09-13
|
|
4
17
|
|
|
5
18
|
### 精度修复批次(AI 生成项目验证 V2 + 真实修复回归驱动的引擎改动)
|
|
@@ -688,6 +688,20 @@ const SAFEGUARD_RULES = [
|
|
|
688
688
|
conceptMissing: ["PathTraversalPrevention", "InputPathValidation"],
|
|
689
689
|
conceptExpected: ["path allowlist", "basename normalization", "path sanitization"],
|
|
690
690
|
},
|
|
691
|
+
{
|
|
692
|
+
name: "Authorization (Cross-User Resource Write)",
|
|
693
|
+
category: "authorization",
|
|
694
|
+
// 2026-09-13(REALWORLD_FIX_REGRESSION_V1 fr-005):Python 提取器在
|
|
695
|
+
// 「请求 payload 外来资源 id(folder_id)带着持久化、无归属守卫」时
|
|
696
|
+
// 注入标记。证据语义全部在提取器侧(严格归属守卫词表——不含
|
|
697
|
+
// has_permission 类特性权限检查,见 tools/extract_ir.py)。
|
|
698
|
+
languages: ["python"],
|
|
699
|
+
trigger: /\b(__progmune_cross_user_write__)\b/,
|
|
700
|
+
safeguards: [],
|
|
701
|
+
violationMessage: "Data is persisted with a user-supplied foreign resource id (folder_id) without an ownership guard — any authenticated user can write into another user's resource.",
|
|
702
|
+
conceptMissing: ["ResourceOwnership", "CrossUserReference"],
|
|
703
|
+
conceptExpected: ["folder write-access check", "get_*_by_id_and_user_id query"],
|
|
704
|
+
},
|
|
691
705
|
{
|
|
692
706
|
name: "XSS (Unsafe Template Rendering)",
|
|
693
707
|
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.27";
|
|
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
|
@@ -1265,6 +1265,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1265
1265
|
// 在 IR 层直接消费——call-sequence 构建会过滤 __progmune_ 前缀
|
|
1266
1266
|
// (P4.5 历史),标记到不了 checkSpecificViolations;此前的 Python
|
|
1267
1267
|
// 标记因此在引擎管线中一直是死的。此处同时点亮 Python 与 TS 两侧。
|
|
1268
|
+
// 2026-09-13(fr-005):同层消费跨用户资源写入标记。
|
|
1268
1269
|
for (const f of functions) {
|
|
1269
1270
|
const calls = f.calls || [];
|
|
1270
1271
|
if (calls.includes("__progmune_path_traversal__")) {
|
|
@@ -1281,6 +1282,20 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1281
1282
|
policy_ref: "protocol-safety.specific",
|
|
1282
1283
|
});
|
|
1283
1284
|
}
|
|
1285
|
+
if (calls.includes("__progmune_cross_user_write__")) {
|
|
1286
|
+
violations.push({
|
|
1287
|
+
severity: "medium",
|
|
1288
|
+
rule_id: "AUTHZ_CROSS_USER_WRITE",
|
|
1289
|
+
file: f.file,
|
|
1290
|
+
function: f.name || "unknown",
|
|
1291
|
+
message: "Data is persisted with a user-supplied foreign resource id (folder_id) without an ownership guard — any authenticated user can write into another user's resource.",
|
|
1292
|
+
evidence: "__progmune_cross_user_write__",
|
|
1293
|
+
why: "Extractor-verified: folder_id from request payload is persisted (insert/add/create) " +
|
|
1294
|
+
"with no ownership comparison or folder write-access check in this function.",
|
|
1295
|
+
fix: "Verify folder write access for the acting user (ownership query or access-control helper) before persisting.",
|
|
1296
|
+
policy_ref: "protocol-safety.specific",
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1284
1299
|
}
|
|
1285
1300
|
for (const f of functions) {
|
|
1286
1301
|
if (!f.protocol)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.27",
|
|
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
|
@@ -691,6 +691,47 @@ def has_template_tag_decorator(node):
|
|
|
691
691
|
return False
|
|
692
692
|
|
|
693
693
|
|
|
694
|
+
CROSS_USER_WRITE_MARKER = "__progmune_cross_user_write__"
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def has_cross_user_resource_write(node):
|
|
698
|
+
"""跨用户资源写入(REALWORLD_FIX_REGRESSION_V1 fr-005 open-webui):
|
|
699
|
+
函数把请求 payload 里的外来资源 id(folder_id 等)连同持久化调用
|
|
700
|
+
写入(insert/add/create),却没有资源归属守卫——任意已认证用户可
|
|
701
|
+
把数据写进其他用户的资源。归属守卫证据(任一出现即抑制):
|
|
702
|
+
① 归属字段比较 user_id/owner_id/author_id/user.id/folder.user_id
|
|
703
|
+
② 文件夹写权限助手 has_*/check_*_folder_access / has_folder_access
|
|
704
|
+
③ 按用户 id 限定的查询 get_*_by_id_and_user_id。
|
|
705
|
+
注意:不把 has_permission 算作归属证据——fr-005 真值函数含
|
|
706
|
+
features.direct_tool_servers 特性权限检查(user.role != 'admin' 同
|
|
707
|
+
族,has_ownership_checked 会误放行),与资源归属无关。
|
|
708
|
+
另:函数名以 insert_/save_/add_ 开头 = 持久化原语层(模型方法),
|
|
709
|
+
归属决策在上游路由层,不标记。"""
|
|
710
|
+
try:
|
|
711
|
+
name = getattr(node, "name", "")
|
|
712
|
+
if re.match(r"^(insert_|save_|add_)", name):
|
|
713
|
+
return False
|
|
714
|
+
text = ast.unparse(node)
|
|
715
|
+
except Exception:
|
|
716
|
+
return False
|
|
717
|
+
if "folder_id" not in text:
|
|
718
|
+
return False
|
|
719
|
+
if not any(k in text for k in ("metadata", "form_data", "body", "request")):
|
|
720
|
+
return False
|
|
721
|
+
if not re.search(r"insert_new_chat|insert_new\w*|\binsert\(|\.add\(|\.create\(", text):
|
|
722
|
+
return False
|
|
723
|
+
if re.search(r"\b(user_id|owner_id|author_id|user\.id|folder\.user_id)\s*[!=]==?", text):
|
|
724
|
+
return False
|
|
725
|
+
if re.search(
|
|
726
|
+
r"has_folder_write_access|has_folder_access|"
|
|
727
|
+
r"has_\w*_folder_access|check_\w*_folder_access|check_folder\w*|"
|
|
728
|
+
r"get_\w+_by_id_and_user_id",
|
|
729
|
+
text,
|
|
730
|
+
):
|
|
731
|
+
return False
|
|
732
|
+
return True
|
|
733
|
+
|
|
734
|
+
|
|
694
735
|
def has_ownership_checked(node):
|
|
695
736
|
"""Inline ownership comparison: an identity-named parameter (user,
|
|
696
737
|
current_user, profile, author, owner) compared with ==/!= against another
|
|
@@ -861,6 +902,8 @@ def extract_calls(node, unsafe_vars=None, imports=None, module_constants=None, g
|
|
|
861
902
|
calls.append(TEMPLATE_TAG_MARKER)
|
|
862
903
|
if has_ownership_checked(node) and OWNERSHIP_CHECKED_MARKER not in calls:
|
|
863
904
|
calls.append(OWNERSHIP_CHECKED_MARKER)
|
|
905
|
+
if has_cross_user_resource_write(node) and CROSS_USER_WRITE_MARKER not in calls:
|
|
906
|
+
calls.append(CROSS_USER_WRITE_MARKER)
|
|
864
907
|
if has_command_taint_flow(node) and CMD_FLOW_MARKER not in calls:
|
|
865
908
|
calls.append(CMD_FLOW_MARKER)
|
|
866
909
|
if has_csrf_exempt(node) and CSRF_MARKER not in calls:
|