progmune-runtime 3.7.21 → 3.7.23
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 +33 -0
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/dist/call-sequence.js +58 -4
- package/dist/call-sequence.test.js +55 -0
- package/dist/certify.js +20 -15
- package/dist/check.js +18 -0
- package/dist/decision-engine.js +26 -3
- package/dist/execute.js +84 -1
- package/dist/extract-ir-java.js +190 -27
- package/dist/extract-ir-java.test.js +163 -2
- package/dist/frameworks/spring-detector.js +27 -3
- package/dist/frameworks/spring-detector.test.js +73 -0
- package/dist/mcp-server.mjs +1 -0
- package/dist/policy/cli.js +8 -1
- package/dist/policy/engine.js +82 -18
- package/dist/policy/engine.test.js +135 -0
- package/dist/sdk.js +1 -1
- package/dist/ssg-validator.js +3 -0
- package/dist/trust/engine.js +66 -6
- package/dist/trust/protocol-rules-liveness.test.js +114 -0
- package/dist/trust/ssg-bridge.js +8 -2
- package/dist/trust/ssg-bridge.test.js +46 -0
- package/package.json +6 -3
- package/protocols.json +300 -152
- package/scripts/init-policy.js +49 -0
- package/templates/.progmune-policy.tier1.json +5 -0
- package/templates/.progmune-policy.tier2.json +9 -0
- package/templates/.progmune-policy.tier3.json +11 -0
package/dist/policy/engine.js
CHANGED
|
@@ -46,6 +46,10 @@ exports.loadEnterprisePolicyConfig = loadEnterprisePolicyConfig;
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const types_1 = require("./types");
|
|
49
|
+
// 静态导入(vitest 环境下 lazy require 的 CJS 互操作不可靠——engine.ts
|
|
50
|
+
// 同款陷阱,2026-08-27 DSH 修复先例;两模块无环,静态导入安全)
|
|
51
|
+
const risk_model_1 = require("../risk-model");
|
|
52
|
+
const protocol_knowledge_1 = require("../protocol-knowledge");
|
|
49
53
|
function confidenceToNumber(c) {
|
|
50
54
|
if (c === "high")
|
|
51
55
|
return 2;
|
|
@@ -93,8 +97,39 @@ function loadPolicyConfig(projectPath, configPath) {
|
|
|
93
97
|
return { rules: merged, source: cfgFile };
|
|
94
98
|
}
|
|
95
99
|
catch (e) {
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
// 审计修复(fail-closed 信号):解析失败不再静默回退——显式携带
|
|
101
|
+
// configError,由调用方(policy CLI / MCP)决定拒绝评估并报错
|
|
102
|
+
console.error(`⚠️ Failed to load policy config: ${e.message}.`);
|
|
103
|
+
return {
|
|
104
|
+
rules: [...types_1.DEFAULT_POLICY],
|
|
105
|
+
source: "built-in defaults (config error)",
|
|
106
|
+
configError: `Failed to parse ${cfgFile}: ${e.message}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** 真实调用提取(certificate.file 的 best-effort 词法扫描,供 risk 规则
|
|
111
|
+
* 使用——不伪造输入;读不到文件时返回空数组,由调用方按 fail-closed
|
|
112
|
+
* 计为违规) */
|
|
113
|
+
function extractCallsBestEffort(file) {
|
|
114
|
+
if (!file)
|
|
115
|
+
return [];
|
|
116
|
+
try {
|
|
117
|
+
const fs = require("fs");
|
|
118
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
119
|
+
const calls = [];
|
|
120
|
+
const re = /(?<=^|[^\w$])([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\(/g;
|
|
121
|
+
let m;
|
|
122
|
+
while ((m = re.exec(code)) !== null) {
|
|
123
|
+
const seg = m[1].split(".").pop() || "";
|
|
124
|
+
if (/^(if|for|while|switch|catch|return|new|case|do|super|this)$/.test(seg))
|
|
125
|
+
continue;
|
|
126
|
+
if (calls.length < 200 && !calls.includes(seg))
|
|
127
|
+
calls.push(seg);
|
|
128
|
+
}
|
|
129
|
+
return calls;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
98
133
|
}
|
|
99
134
|
}
|
|
100
135
|
function evaluatePolicy(ctx, rules) {
|
|
@@ -177,24 +212,44 @@ function evaluatePolicy(ctx, rules) {
|
|
|
177
212
|
const minSeverity = rule.threshold ?? 2;
|
|
178
213
|
const minConfidence = rule.require ?? 70;
|
|
179
214
|
try {
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
const sevOrder = ["Low", "Medium", "High", "Critical"];
|
|
186
|
-
return sevOrder.indexOf(p.severity) >= minSeverity && p.confidence >= minConfidence;
|
|
187
|
-
});
|
|
188
|
-
if (criticalOrHigh.length > 0) {
|
|
215
|
+
// 真实调用提取(certificate.file 的 best-effort 词法扫描)——
|
|
216
|
+
// 审计修复(Kimi 2026-09-06):不再伪造 ["SSL_CTX_new","SSL_connect"]
|
|
217
|
+
// 假输入喂评估;输入不可得时按 fail-closed 计为违规
|
|
218
|
+
const calls = extractCallsBestEffort(ctx.certificate.file);
|
|
219
|
+
if (calls.length === 0) {
|
|
189
220
|
violations.push({
|
|
190
221
|
rule,
|
|
191
|
-
actual:
|
|
192
|
-
expected:
|
|
193
|
-
detail:
|
|
222
|
+
actual: "no call data extractable from certificate file",
|
|
223
|
+
expected: "risk assessment over real call sequence",
|
|
224
|
+
detail: "risk rule requires a readable file with calls; unavailable input is a violation (fail-closed)",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const risk = (0, risk_model_1.assessRisk)(calls);
|
|
229
|
+
const criticalOrHigh = risk.patterns.filter((p) => {
|
|
230
|
+
const sevOrder = ["Low", "Medium", "High", "Critical"];
|
|
231
|
+
return sevOrder.indexOf(p.severity) >= minSeverity && p.confidence >= minConfidence;
|
|
194
232
|
});
|
|
233
|
+
if (criticalOrHigh.length > 0) {
|
|
234
|
+
violations.push({
|
|
235
|
+
rule,
|
|
236
|
+
actual: `${criticalOrHigh.length} risk pattern(s) ≥ severity threshold`,
|
|
237
|
+
expected: `0 patterns at this severity+confidence level`,
|
|
238
|
+
detail: criticalOrHigh.map((p) => `${p.patternName} (${p.severity}, ${p.confidence}%): ${p.detail}`).join("; "),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
195
241
|
}
|
|
196
242
|
}
|
|
197
|
-
catch {
|
|
243
|
+
catch {
|
|
244
|
+
// fail-closed:risk model 不可用 → 显式违规,而非静默跳过
|
|
245
|
+
// (审计修复:原空 catch 使整条规则无声失效)
|
|
246
|
+
violations.push({
|
|
247
|
+
rule,
|
|
248
|
+
actual: "risk model unavailable",
|
|
249
|
+
expected: "risk assessment completed",
|
|
250
|
+
detail: "risk-model module failed to load — failing closed (explicit violation)",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
198
253
|
break;
|
|
199
254
|
}
|
|
200
255
|
// ── Knowledge Base Coverage ──
|
|
@@ -202,11 +257,20 @@ function evaluatePolicy(ctx, rules) {
|
|
|
202
257
|
const minStable = rule.threshold ?? 3;
|
|
203
258
|
let stableCount = 0;
|
|
204
259
|
try {
|
|
205
|
-
const
|
|
206
|
-
const kb = buildKnowledgeBase();
|
|
260
|
+
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
|
207
261
|
stableCount = kb.units.filter((u) => u.maturity === "stable").length;
|
|
208
262
|
}
|
|
209
|
-
catch {
|
|
263
|
+
catch {
|
|
264
|
+
// fail-closed 显式化(审计修复):原空 catch 后 stableCount=0 恰好
|
|
265
|
+
// 触发违规,但消息误导为「0 stable assets」——改为明确的不可用违规
|
|
266
|
+
violations.push({
|
|
267
|
+
rule,
|
|
268
|
+
actual: "knowledge base unavailable",
|
|
269
|
+
expected: `>= ${minStable} stable assets`,
|
|
270
|
+
detail: "protocol-knowledge module failed to load — failing closed (explicit violation)",
|
|
271
|
+
});
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
210
274
|
if (stableCount < minStable) {
|
|
211
275
|
violations.push({
|
|
212
276
|
rule,
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
* policy/engine.test.ts — 策略引擎 fail-closed 回归(审计修复 2026-09-06)
|
|
38
|
+
*
|
|
39
|
+
* 锁定 Kimi 审计的三条修复:
|
|
40
|
+
* 1. risk 规则不再伪造 ["SSL_CTX_new","SSL_connect"] 输入——无真实调用
|
|
41
|
+
* 数据时按 fail-closed 计违规
|
|
42
|
+
* 2. 配置解析失败显式携带 configError(不再静默回退默认)
|
|
43
|
+
* 3. execute 写盘策略门:项目 opt-in(.progmune-policy.json)时 BLOCK
|
|
44
|
+
* 回滚写盘;未配置时无操作
|
|
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 engine_1 = require("./engine");
|
|
51
|
+
const execute_1 = require("../execute");
|
|
52
|
+
let dir;
|
|
53
|
+
(0, vitest_1.beforeEach)(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-policy-")); });
|
|
54
|
+
(0, vitest_1.afterEach)(() => { fs.rmSync(dir, { recursive: true, force: true }); });
|
|
55
|
+
function baseCtx(file) {
|
|
56
|
+
return {
|
|
57
|
+
certificate: {
|
|
58
|
+
validated: true,
|
|
59
|
+
confidence: "high",
|
|
60
|
+
provenanceIntact: true,
|
|
61
|
+
fingerprint: "test-fp",
|
|
62
|
+
violations: 0,
|
|
63
|
+
plsbCoverage: "10/13",
|
|
64
|
+
plsbRecall: 1,
|
|
65
|
+
degraded: false,
|
|
66
|
+
sessionId: "test-session",
|
|
67
|
+
file,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
},
|
|
70
|
+
accountability: {
|
|
71
|
+
humanEvents: 1,
|
|
72
|
+
aiEvents: 1,
|
|
73
|
+
automatedEvents: 0,
|
|
74
|
+
custodyGap: false,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
(0, vitest_1.describe)("policy engine(fail-closed 回归)", () => {
|
|
79
|
+
(0, vitest_1.it)("risk 规则:无可提取调用数据 → 显式违规(fail-closed,不再伪造输入)", () => {
|
|
80
|
+
const f = path.join(dir, "empty.ts");
|
|
81
|
+
fs.writeFileSync(f, "");
|
|
82
|
+
const result = (0, engine_1.evaluatePolicy)(baseCtx(f));
|
|
83
|
+
const riskViolations = result.violations.filter((v) => v.rule.type === "risk");
|
|
84
|
+
(0, vitest_1.expect)(riskViolations.length).toBeGreaterThanOrEqual(1);
|
|
85
|
+
(0, vitest_1.expect)(riskViolations[0].detail).toContain("fail-closed");
|
|
86
|
+
});
|
|
87
|
+
(0, vitest_1.it)("risk 规则:真实调用提取后良性代码不产生风险违规", () => {
|
|
88
|
+
const f = path.join(dir, "benign.ts");
|
|
89
|
+
fs.writeFileSync(f, "function hello() { console.log('x'); return computeSum(a, b); }");
|
|
90
|
+
const result = (0, engine_1.evaluatePolicy)(baseCtx(f));
|
|
91
|
+
const riskViolations = result.violations.filter((v) => v.rule.type === "risk");
|
|
92
|
+
(0, vitest_1.expect)(riskViolations).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)("loadPolicyConfig:JSON 解析失败显式携带 configError(不再静默回退)", () => {
|
|
95
|
+
fs.writeFileSync(path.join(dir, ".progmune-policy.json"), "{ broken json !!!");
|
|
96
|
+
const res = (0, engine_1.loadPolicyConfig)(dir);
|
|
97
|
+
(0, vitest_1.expect)(res.configError).toBeDefined();
|
|
98
|
+
(0, vitest_1.expect)(res.configError).toContain("Failed to parse");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.describe)("execute 写盘策略门(opt-in)", () => {
|
|
102
|
+
const MARKED = `// @progmune-generated session=s1 timestamp=2026-09-06T00:00:00.000Z
|
|
103
|
+
function doThing() { return 1; }
|
|
104
|
+
`;
|
|
105
|
+
(0, vitest_1.it)("未配置 .progmune-policy.json → 无操作(旧行为)", () => {
|
|
106
|
+
const f = path.join(dir, "out.ts");
|
|
107
|
+
fs.writeFileSync(f, MARKED);
|
|
108
|
+
const gate = (0, execute_1.applyPolicyGateAfterWrite)(dir, f);
|
|
109
|
+
(0, vitest_1.expect)(gate.blocked).toBe(false);
|
|
110
|
+
(0, vitest_1.expect)(fs.existsSync(f)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.it)("配置阻断规则 → BLOCK 回滚(新文件删除)", () => {
|
|
113
|
+
fs.writeFileSync(path.join(dir, ".progmune-policy.json"), JSON.stringify({
|
|
114
|
+
inherit: false,
|
|
115
|
+
rules: [{ type: "confidence", severity: "block", threshold: 2 }],
|
|
116
|
+
}));
|
|
117
|
+
const f = path.join(dir, "out.ts");
|
|
118
|
+
fs.writeFileSync(f, MARKED);
|
|
119
|
+
const gate = (0, execute_1.applyPolicyGateAfterWrite)(dir, f);
|
|
120
|
+
(0, vitest_1.expect)(gate.blocked).toBe(true);
|
|
121
|
+
(0, vitest_1.expect)(gate.decision).toBe("BLOCK");
|
|
122
|
+
(0, vitest_1.expect)(fs.existsSync(f)).toBe(false); // 回滚 = 删除新文件
|
|
123
|
+
});
|
|
124
|
+
(0, vitest_1.it)("配置阻断规则 → BLOCK 回滚(已有文件恢复原内容)", () => {
|
|
125
|
+
fs.writeFileSync(path.join(dir, ".progmune-policy.json"), JSON.stringify({
|
|
126
|
+
inherit: false,
|
|
127
|
+
rules: [{ type: "confidence", severity: "block", threshold: 2 }],
|
|
128
|
+
}));
|
|
129
|
+
const f = path.join(dir, "out.ts");
|
|
130
|
+
const prev = "// original content\n";
|
|
131
|
+
const gate = (0, execute_1.applyPolicyGateAfterWrite)(dir, f, prev);
|
|
132
|
+
(0, vitest_1.expect)(gate.blocked).toBe(true);
|
|
133
|
+
(0, vitest_1.expect)(fs.readFileSync(f, "utf-8")).toBe(prev); // 恢复原内容
|
|
134
|
+
});
|
|
135
|
+
});
|
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.23";
|
|
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/ssg-validator.js
CHANGED
|
@@ -759,6 +759,9 @@ function rejectionToJSON(rejection) {
|
|
|
759
759
|
function parseProtocolsFromJSON(protocolDef) {
|
|
760
760
|
const protocols = [];
|
|
761
761
|
for (const [funcName, rule] of Object.entries(protocolDef.rules)) {
|
|
762
|
+
// 未人工确认的提案规则不加载——AI 只提案,人不签字不生效
|
|
763
|
+
if (rule.status && rule.status !== "confirmed")
|
|
764
|
+
continue;
|
|
762
765
|
protocols.push({
|
|
763
766
|
function: funcName,
|
|
764
767
|
protocol: {
|
package/dist/trust/engine.js
CHANGED
|
@@ -1192,6 +1192,11 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1192
1192
|
let ssgTotalCalls = 0;
|
|
1193
1193
|
let ssgMatchedCalls = 0;
|
|
1194
1194
|
let ssgViolationCount = 0;
|
|
1195
|
+
// 截断序列计数(预算耗尽——尾部调用未验证,覆盖率降级信号)
|
|
1196
|
+
let truncatedSeqCount = 0;
|
|
1197
|
+
// Oracle 隔离政策(docs/ORACLE_ISOLATION_POLICY.md):注解合并注册的
|
|
1198
|
+
// 绑定与代码作者同源——单独统计并在报告中披露(独立度指标)
|
|
1199
|
+
const annotationRuleNames = new Set();
|
|
1195
1200
|
// C 注解建议(函数级作用域——返回值在 try 外组装)
|
|
1196
1201
|
let annotationSuggestions;
|
|
1197
1202
|
try {
|
|
@@ -1248,24 +1253,64 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1248
1253
|
// ir.json 两种形态:extractIR/extractIRPython 的裸数组、extractProjectIR
|
|
1249
1254
|
// 的 { typeMap, functions } 合并对象(execute/MCP 写盘)——统一取函数列表。
|
|
1250
1255
|
const functions = Array.isArray(ir) ? ir : (ir.functions || []);
|
|
1256
|
+
// 裸名出现次数(Java 接收者限定匹配:碰撞名不注册裸键——
|
|
1257
|
+
// User.update 与 Article.update 同名时裸键 "update" 会命中所有
|
|
1258
|
+
// .update( 调用点,REALWORLD_JAVA_ANNOTATION_V1 名碰撞 9 FP 根因)
|
|
1259
|
+
const nameCounts = new Map();
|
|
1260
|
+
for (const f of functions) {
|
|
1261
|
+
const n = String(f.name);
|
|
1262
|
+
nameCounts.set(n, (nameCounts.get(n) || 0) + 1);
|
|
1263
|
+
}
|
|
1251
1264
|
for (const f of functions) {
|
|
1252
1265
|
if (!f.protocol)
|
|
1253
1266
|
continue;
|
|
1254
1267
|
const protocol = { ...f.protocol };
|
|
1255
|
-
const
|
|
1268
|
+
const fname = String(f.name);
|
|
1269
|
+
const existing = protocolRulesData.rules.get(fname);
|
|
1256
1270
|
if (existing?.namespace && !protocol.namespace) {
|
|
1257
1271
|
protocol.namespace = existing.namespace;
|
|
1258
1272
|
}
|
|
1259
1273
|
// 修复路径渲染真实函数名(fixPath 输出项目原语而非通用规则名)
|
|
1260
|
-
protocol.displayName =
|
|
1261
|
-
|
|
1274
|
+
protocol.displayName = fname;
|
|
1275
|
+
// Java(className 存在且 name 无点):总是注册 Class.method 限定键;
|
|
1276
|
+
// 裸键仅在项目内唯一时注册(碰撞名只留限定键,匹配走限定调用)
|
|
1277
|
+
const qualified = f.className && !fname.includes(".")
|
|
1278
|
+
? `${f.className}.${fname}` : undefined;
|
|
1279
|
+
const nameUnique = (nameCounts.get(fname) || 0) === 1;
|
|
1280
|
+
if (qualified) {
|
|
1281
|
+
protocolRulesData.rules.set(qualified, protocol);
|
|
1282
|
+
annotationRuleNames.add(qualified);
|
|
1283
|
+
}
|
|
1284
|
+
if (!f.className || nameUnique) {
|
|
1285
|
+
protocolRulesData.rules.set(fname, protocol);
|
|
1286
|
+
annotationRuleNames.add(fname);
|
|
1287
|
+
}
|
|
1262
1288
|
// CamelCase 真实命名(C 代码普遍,如 ACLCheckAllPerm)注册的规则
|
|
1263
1289
|
// 原样无法被任何匹配策略触达(normalize 只作用于调用名;词段匹配
|
|
1264
1290
|
// 要求 ≥2 个下划线词段)——同步注册规范化形态使注解原语可被按名命中。
|
|
1265
1291
|
// 加性改动:snake_case 注解(TS/Python 惯例)normalized === 原名,无变化。
|
|
1266
|
-
const normalized = (0, ssg_bridge_1.normalizeName)(
|
|
1267
|
-
if (normalized !==
|
|
1292
|
+
const normalized = (0, ssg_bridge_1.normalizeName)(fname);
|
|
1293
|
+
if (normalized !== fname && (!f.className || nameUnique)) {
|
|
1268
1294
|
protocolRulesData.rules.set(normalized, protocol);
|
|
1295
|
+
annotationRuleNames.add(normalized);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
catch { /* best-effort */ }
|
|
1301
|
+
}
|
|
1302
|
+
// ── 项目别名重校验(注解合并后)──
|
|
1303
|
+
// 注解合并注册的限定键(Java Class.method)在 loadProtocolRules 时还不
|
|
1304
|
+
// 存在,指向它们的别名当时被「规则不存在」校验拒绝——合并后重跑一次,
|
|
1305
|
+
// 把「变量名→限定规则」映射补进 aliasIndex(如 jwtservice.getsubfromtoken
|
|
1306
|
+
// → DefaultJwtService.getSubFromToken;变量名≠类名的文档化桥接通道)
|
|
1307
|
+
if (protocolRulesData) {
|
|
1308
|
+
try {
|
|
1309
|
+
const pa = (0, ssg_bridge_1.loadProjectAliases)(ctx.projectPath, new Set(protocolRulesData.rules.keys()));
|
|
1310
|
+
if (pa) {
|
|
1311
|
+
for (const [callName, ruleName] of Object.entries(pa.aliases)) {
|
|
1312
|
+
if (!protocolRulesData.aliasIndex.has(callName)) {
|
|
1313
|
+
protocolRulesData.aliasIndex.set(callName, ruleName);
|
|
1269
1314
|
}
|
|
1270
1315
|
}
|
|
1271
1316
|
}
|
|
@@ -1298,6 +1343,8 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1298
1343
|
}
|
|
1299
1344
|
catch { /* best-effort */ }
|
|
1300
1345
|
for (const seq of callSequences) {
|
|
1346
|
+
if (seq.truncated)
|
|
1347
|
+
truncatedSeqCount++;
|
|
1301
1348
|
try {
|
|
1302
1349
|
const semantic = await (0, api_semantic_mapper_1.mapSequenceToSemanticWithLLM)(seq.calls);
|
|
1303
1350
|
// ── Phase 5: Cross-function propagation ──
|
|
@@ -1444,9 +1491,22 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1444
1491
|
totalCalls: ssgTotalCalls,
|
|
1445
1492
|
matchedCalls: ssgMatchedCalls,
|
|
1446
1493
|
ssgViolations: ssgViolationCount,
|
|
1447
|
-
|
|
1494
|
+
// 截断序列显式上报(审计修复 2026-09-06):预算耗尽时尾部调用
|
|
1495
|
+
// (close_file 类释放操作)不可见——覆盖率降级信号,不再静默
|
|
1496
|
+
truncatedSequences: truncatedSeqCount,
|
|
1497
|
+
summary: (0, ssg_bridge_1.summarizeSSGCoverage)(ssgResults) +
|
|
1498
|
+
(truncatedSeqCount > 0
|
|
1499
|
+
? ` (${truncatedSeqCount} sequence(s) truncated — tail calls unverified)`
|
|
1500
|
+
: ""),
|
|
1448
1501
|
aliasWarnings: protocolRulesData.aliasWarnings?.length
|
|
1449
1502
|
? protocolRulesData.aliasWarnings : undefined,
|
|
1503
|
+
// Oracle 独立度(docs/ORACLE_ISOLATION_POLICY.md):规则/别名均为
|
|
1504
|
+
// 人工确认(加载端过滤);项目注解与代码作者同源,单独披露
|
|
1505
|
+
oracleIndependence: {
|
|
1506
|
+
confirmedRules: protocolRulesData.rules.size - annotationRuleNames.size,
|
|
1507
|
+
sameSourceAnnotations: annotationRuleNames.size,
|
|
1508
|
+
confirmedAliases: protocolRulesData.aliasIndex.size,
|
|
1509
|
+
},
|
|
1450
1510
|
} : undefined,
|
|
1451
1511
|
annotationSuggestions,
|
|
1452
1512
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
* protocol-rules-liveness.test.ts — 协议规则活性守卫(2026-09-06)
|
|
38
|
+
*
|
|
39
|
+
* Kimi 源码审计发现:SSG 验证器按命名空间隔离状态(stateMap 以
|
|
40
|
+
* namespace 为键),而 protocols.json 中多条规则的 pre_states 引用
|
|
41
|
+
* 其他命名空间建立的状态(如 session_fixation 的规则 pre SESSION_ACTIVE,
|
|
42
|
+
* SESSION_ACTIVE 由 auth 的 create_session 建立)——规则在本命名空间
|
|
43
|
+
* 永远无法满足前置,实际永不触发(G5 同族:2026-08-28 data_integrity
|
|
44
|
+
* 的 check_resource_ownership pre AUTHENTICATED 已发现一次个案)。
|
|
45
|
+
*
|
|
46
|
+
* 本测试把该现象固化为硬性校验:每条规则的 pre_states 必须能在其
|
|
47
|
+
* 命名空间内到达(初始状态 + 本命名空间规则的 post_states 闭包)。
|
|
48
|
+
* 例外(printlab 业务链跨命名空间依赖)显式列入 KNOWN_CROSS_NS,
|
|
49
|
+
* 待跨命名空间状态引用特性实现后移除。
|
|
50
|
+
*/
|
|
51
|
+
const vitest_1 = require("vitest");
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
function loadProtocols() {
|
|
55
|
+
const p = path.join(__dirname, "..", "..", "protocols.json");
|
|
56
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
57
|
+
}
|
|
58
|
+
/** 命名空间内可到达状态:初始状态 + post_states 闭包(同验证器语义) */
|
|
59
|
+
function reachableStates(ns, rules, nsInit) {
|
|
60
|
+
const states = new Set([nsInit[ns] ?? "INIT"]);
|
|
61
|
+
let changed = true;
|
|
62
|
+
while (changed) {
|
|
63
|
+
changed = false;
|
|
64
|
+
for (const r of Object.values(rules)) {
|
|
65
|
+
if ((r.namespace ?? "_global") !== ns)
|
|
66
|
+
continue;
|
|
67
|
+
const pre = new Set(r.pre_states ?? []);
|
|
68
|
+
if (![...pre].every((s) => states.has(s)))
|
|
69
|
+
continue;
|
|
70
|
+
for (const p of r.post_states ?? []) {
|
|
71
|
+
if (!states.has(p)) {
|
|
72
|
+
states.add(p);
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return states;
|
|
79
|
+
}
|
|
80
|
+
(0, vitest_1.describe)("protocol rules liveness(跨命名空间死规则守卫)", () => {
|
|
81
|
+
(0, vitest_1.it)("每条 confirmed 规则的 pre_states 在其命名空间内可达(跨命名空间引用为显式例外)", () => {
|
|
82
|
+
const { rules, namespaceInitialStates } = loadProtocols();
|
|
83
|
+
const nsInit = namespaceInitialStates ?? {};
|
|
84
|
+
// Oracle 隔离政策:proposed 规则不参与判定,也不做活性断言
|
|
85
|
+
// (在人工确认时由 rule-propose 流程检查)
|
|
86
|
+
const activeRules = {};
|
|
87
|
+
for (const [name, r] of Object.entries(rules)) {
|
|
88
|
+
if (!r.status || r.status === "confirmed")
|
|
89
|
+
activeRules[name] = r;
|
|
90
|
+
}
|
|
91
|
+
// 文档化例外:printlab 业务链(printlab_order ↔ printlab_print 跨
|
|
92
|
+
// 命名空间依赖:queue_order → start_print、upload_stl pre AUTHENTICATED
|
|
93
|
+
// 等)——待跨命名空间状态引用特性后移除
|
|
94
|
+
const KNOWN_CROSS_NS = new Set([
|
|
95
|
+
"upload_stl", "slice_model", "generate_gcode", "estimate_cost",
|
|
96
|
+
"queue_order", "start_print", "complete_print", "ship_order",
|
|
97
|
+
"deliver_order", "fail_print",
|
|
98
|
+
]);
|
|
99
|
+
const dead = [];
|
|
100
|
+
for (const [name, r] of Object.entries(activeRules)) {
|
|
101
|
+
const ns = r.namespace ?? "_global";
|
|
102
|
+
const reach = reachableStates(ns, activeRules, nsInit);
|
|
103
|
+
for (const s of r.pre_states ?? []) {
|
|
104
|
+
if (!reach.has(s)) {
|
|
105
|
+
dead.push(`${name} (ns=${ns}, pre=${s} 不可达)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
(0, vitest_1.expect)(dead.filter((d) => !KNOWN_CROSS_NS.has(d.split(" ")[0]))).toEqual([]);
|
|
110
|
+
// 例外清单必须与实际死规则一致(防止例外清单腐化)
|
|
111
|
+
(0, vitest_1.expect)(dead.map((d) => d.split(" ")[0]).sort())
|
|
112
|
+
.toEqual([...KNOWN_CROSS_NS].sort());
|
|
113
|
+
});
|
|
114
|
+
});
|
package/dist/trust/ssg-bridge.js
CHANGED
|
@@ -265,12 +265,16 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
265
265
|
// 仅项目函数适用——真实 C 语料验证中 ReadFile/WriteFile/DeleteFile 经
|
|
266
266
|
// normalized 撞上 read_file/write_file/delete_file 是 11/24 FP 的主导源,
|
|
267
267
|
// 而注解桥接(ACLCheckAllPerm → acl_check_all_perm)全是项目函数,不受影响。
|
|
268
|
+
// Java 限定键(Class.method)与限定调用(user.update)大小写不敏感精确
|
|
269
|
+
// 匹配(变量名 user vs 类名 User);带点调用不参与规范化/词段形态匹配——
|
|
270
|
+
// 末段回退会重造名碰撞(article.update 撞 User.update 的裸键/词段)。
|
|
271
|
+
const isDotted = dotIdx >= 0;
|
|
268
272
|
for (const ruleName of ruleNames) {
|
|
269
|
-
if (lowerApi === ruleName) {
|
|
273
|
+
if (lowerApi === ruleName.toLowerCase()) {
|
|
270
274
|
return ruleName;
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
|
-
if (isProjectFn) {
|
|
277
|
+
if (isProjectFn && !(projectFunctions && isDotted)) {
|
|
274
278
|
for (const ruleName of ruleNames) {
|
|
275
279
|
if (normalized === ruleName) {
|
|
276
280
|
return ruleName;
|
|
@@ -280,6 +284,8 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
280
284
|
for (const ruleName of ruleNames) {
|
|
281
285
|
if (projectFunctions && !isProjectFn)
|
|
282
286
|
continue;
|
|
287
|
+
if (projectFunctions && isDotted)
|
|
288
|
+
continue; // 带点调用只走限定精确匹配(+别名/域提示)
|
|
283
289
|
const ruleWords = ruleName.split("_");
|
|
284
290
|
const callWords = normalized.split("_");
|
|
285
291
|
// Rule must have at least 2 words for this strategy (single-word rules
|
|
@@ -113,6 +113,52 @@ function step(api, domain, description = "") {
|
|
|
113
113
|
(0, vitest_1.expect)(result.stats.unmatchedCalls).toBe(steps.length);
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
|
+
// ── 接收者限定名匹配(Java 名碰撞根因修复,2026-09-06)──
|
|
117
|
+
(0, vitest_1.describe)("qualified call → rule inference", () => {
|
|
118
|
+
(0, vitest_1.it)("限定调用精确匹配限定规则(大小写不敏感:user.update → User.update)", () => {
|
|
119
|
+
const prev = rules.get("User.update");
|
|
120
|
+
rules.set("User.update", {
|
|
121
|
+
pre_states: ["PASSWORD_HASHED"],
|
|
122
|
+
post_states: ["PASSWORD_UPDATED"],
|
|
123
|
+
namespace: "registration",
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
const result = (0, ssg_bridge_1.validateSequenceWithSSG)([step("user.update", "registration", "Update user data")], rules, nsInit, "test.java", aliasIndex, undefined, new Set(["user.update", "update"]));
|
|
127
|
+
(0, vitest_1.expect)(result.stats.matchedCalls).toBeGreaterThanOrEqual(1);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
if (prev)
|
|
131
|
+
rules.set("User.update", prev);
|
|
132
|
+
else
|
|
133
|
+
rules.delete("User.update");
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.it)("不同类限定调用不误配(article.update 不命中 User.update 规则)", () => {
|
|
137
|
+
const prev = rules.get("User.update");
|
|
138
|
+
rules.set("User.update", {
|
|
139
|
+
pre_states: ["PASSWORD_HASHED"],
|
|
140
|
+
post_states: ["PASSWORD_UPDATED"],
|
|
141
|
+
namespace: "registration",
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
const result = (0, ssg_bridge_1.validateSequenceWithSSG)([step("article.update", "registration", "Update article content")], rules, nsInit, "test.java", aliasIndex, undefined, new Set(["article.update", "update"]));
|
|
145
|
+
(0, vitest_1.expect)(result.stats.matchedCalls).toBe(0);
|
|
146
|
+
(0, vitest_1.expect)(result.stats.unmatchedCalls).toBe(1);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
if (prev)
|
|
150
|
+
rules.set("User.update", prev);
|
|
151
|
+
else
|
|
152
|
+
rules.delete("User.update");
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
(0, vitest_1.it)("带点调用不参与词段匹配(helper.verify_hash 不命中 verify_hash)", () => {
|
|
156
|
+
// 无 projectFunctions 时不限制(回退路径旧行为);提供后带点调用
|
|
157
|
+
// 只走限定精确匹配 + 别名——末段词段回退会重造名碰撞
|
|
158
|
+
const result = (0, ssg_bridge_1.validateSequenceWithSSG)([step("helper.verify_hash", "auth", "Verify something")], rules, nsInit, "test.java", aliasIndex, undefined, new Set(["helper.verify_hash", "verify_hash"]));
|
|
159
|
+
(0, vitest_1.expect)(result.stats.matchedCalls).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
116
162
|
(0, vitest_1.describe)("alias-based matching (exact)", () => {
|
|
117
163
|
(0, vitest_1.it)("matches bcrypt.compare to verify_hash via alias", () => {
|
|
118
164
|
(0, vitest_1.expect)(aliasIndex.get("bcrypt.compare")).toBe("verify_hash");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.23",
|
|
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/",
|
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
"tools/extract_ir.py",
|
|
13
13
|
"tools/extract_framework_py.py",
|
|
14
14
|
"tools/extract_framework_django.py",
|
|
15
|
-
"tools/extract_framework_flask.py"
|
|
15
|
+
"tools/extract_framework_flask.py",
|
|
16
|
+
"scripts/init-policy.js",
|
|
17
|
+
"templates/"
|
|
16
18
|
],
|
|
17
19
|
"main": "dist/mcp-server.mjs",
|
|
18
20
|
"bin": {
|
|
19
|
-
"progmune-runtime": "dist/mcp-server.mjs"
|
|
21
|
+
"progmune-runtime": "dist/mcp-server.mjs",
|
|
22
|
+
"progmune-init-policy": "scripts/init-policy.js"
|
|
20
23
|
},
|
|
21
24
|
"scripts": {
|
|
22
25
|
"prepare": "npm run build",
|