progmune-runtime 3.7.24 → 3.7.25
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 +11 -0
- package/README.md +4 -0
- package/dist/immune-reporter.js +52 -12
- package/dist/immune-reporter.test.js +149 -0
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.25] — 2026-09-10
|
|
4
|
+
|
|
5
|
+
### 失败语料上报链路修复(中央 hub 恢复数据流)
|
|
6
|
+
|
|
7
|
+
- **诊断**:中央 hub 自 2026-05-18 起零新增(totalCount=12),npm 周下载 1000-2000 但数据全丢——两个断点:① `immune-reporter` 读废弃路径 `failure_corpus/`(语料已统一到 `.progmune_corpus/`)② 端点默认 `localhost:3000`,安装态用户到不了中央 hub
|
|
8
|
+
- **修复**:数据源对齐 `.progmune_corpus/{date}/fail_*.json`(`PROGMUNE_PROJECT_DIR`/`PROGMUNE_CORPUS_DIR` 感知,损坏记录跳过);端点默认 `https://progmune-runtime.fly.dev/report`;`PROGMUNE_HUB=off` 可关闭上报
|
|
9
|
+
- **默认脱敏**:函数名 SHA-256 截 12 位(同名同哈希,模式聚合仍有效);`PROGMUNE_FINGERPRINT_DETAIL=1` 才发送原文函数名;源码与变量值永不上传
|
|
10
|
+
- **隐私口径如实披露**:README/落地页/privacy 页——「代码不上传」保持为真,「无遥测」表述改为如实披露脱敏指纹默认上报与关闭方式
|
|
11
|
+
- **中央 hub 防护层上线**:字段白名单 + 类型校验(SVL-1~4、时间戳 ±窗口、序列长度上限);请求体 512KB / 每请求 ≤500 条;实例+时间戳+模式指纹去重;每实例每 10 分钟 ≤5 次;每日 ≤10000 条;可选 `PROGMUNE_HUB_TOKEN` Bearer 认证(客户端 `PROGMUNE_HUB_TOKEN` 环境变量同步支持)
|
|
12
|
+
- 回归 12 green;hub 冒烟六场景(正常/畸形/去重/超大/限流/认证)全过
|
|
13
|
+
|
|
3
14
|
## [3.7.24] — 2026-09-06
|
|
4
15
|
|
|
5
16
|
### 审计复检三项修复
|
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
[](https://modelcontextprotocol.io)
|
|
7
7
|
[]()
|
|
8
8
|
[]()
|
|
9
|
+
[](https://www.npmjs.com/package/progmune-runtime)
|
|
10
|
+
[](https://www.npmjs.com/package/progmune-runtime)
|
|
11
|
+
[](https://github.com/shenlian19831109/progmune-runtime)
|
|
12
|
+
[](https://github.com/shenlian19831109/progmune-runtime/releases)
|
|
9
13
|
|
|
10
14
|
> [中文版](https://github.com/shenlian19831109/progmune-runtime/blob/main/README.zh-CN.md) · English Version
|
|
11
15
|
|
package/dist/immune-reporter.js
CHANGED
|
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildHeaders = buildHeaders;
|
|
37
|
+
exports.resolveEndpoint = resolveEndpoint;
|
|
38
|
+
exports.maskFunctionName = maskFunctionName;
|
|
36
39
|
exports.extractFingerprints = extractFingerprints;
|
|
37
40
|
exports.reportFingerprints = reportFingerprints;
|
|
38
41
|
exports.previewFingerprints = previewFingerprints;
|
|
@@ -41,8 +44,34 @@ const path = __importStar(require("path"));
|
|
|
41
44
|
const https = __importStar(require("https"));
|
|
42
45
|
const http = __importStar(require("http"));
|
|
43
46
|
const crypto = __importStar(require("crypto"));
|
|
44
|
-
|
|
45
|
-
const
|
|
47
|
+
// ── 数据源(与 failure-corpus.ts 统一路径,项目级、可写) ──
|
|
48
|
+
const PROJECT_DIR = process.env.PROGMUNE_PROJECT_DIR || process.cwd();
|
|
49
|
+
const CORPUS_DIR = process.env.PROGMUNE_CORPUS_DIR || path.resolve(PROJECT_DIR, ".progmune_corpus");
|
|
50
|
+
const CURSOR_FILE = path.join(CORPUS_DIR, ".report_cursor.json");
|
|
51
|
+
// ── 端点与开关 ──
|
|
52
|
+
const HUB_ENV = process.env.PROGMUNE_HUB;
|
|
53
|
+
const HUB_TOKEN = process.env.PROGMUNE_HUB_TOKEN;
|
|
54
|
+
const DETAIL_ENABLED = process.env.PROGMUNE_FINGERPRINT_DETAIL === "1";
|
|
55
|
+
const OFF_PATTERN = /^(off|0|false|no|disabled)$/i;
|
|
56
|
+
/** 上报请求头:hub 设了 PROGMUNE_HUB_TOKEN 时需带 Bearer 认证。 */
|
|
57
|
+
function buildHeaders(token = HUB_TOKEN) {
|
|
58
|
+
const headers = { "Content-Type": "application/json" };
|
|
59
|
+
if (token)
|
|
60
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
61
|
+
return headers;
|
|
62
|
+
}
|
|
63
|
+
/** 解析上报端点:显式关闭 → null;未设 → 中央 hub;其他 → 自定义 URL。 */
|
|
64
|
+
function resolveEndpoint(env = HUB_ENV) {
|
|
65
|
+
if (env && OFF_PATTERN.test(env))
|
|
66
|
+
return null;
|
|
67
|
+
return env || "https://progmune-runtime.fly.dev/report";
|
|
68
|
+
}
|
|
69
|
+
/** 脱敏:默认哈希函数名;DETAIL 开关打开时保留原文。 */
|
|
70
|
+
function maskFunctionName(name, detail = DETAIL_ENABLED) {
|
|
71
|
+
if (detail)
|
|
72
|
+
return name;
|
|
73
|
+
return "fn:" + crypto.createHash("sha256").update(name).digest("hex").substring(0, 12);
|
|
74
|
+
}
|
|
46
75
|
function getInstanceId() {
|
|
47
76
|
const host = require("os").hostname();
|
|
48
77
|
const cwd = process.cwd();
|
|
@@ -62,8 +91,8 @@ function saveReportCursor(timestamp, count) {
|
|
|
62
91
|
fs.mkdirSync(dir, { recursive: true });
|
|
63
92
|
fs.writeFileSync(CURSOR_FILE, JSON.stringify({ lastTimestamp: timestamp, reportedCount: count, updatedAt: new Date().toISOString() }, null, 2));
|
|
64
93
|
}
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
/** 收集语料目录中的新失败指纹(含脱敏处理)。corpusDir 参数便于测试注入。 */
|
|
95
|
+
function extractFingerprints(cursor, corpusDir = CORPUS_DIR) {
|
|
67
96
|
if (!fs.existsSync(corpusDir))
|
|
68
97
|
return [];
|
|
69
98
|
const fingerprints = [];
|
|
@@ -73,16 +102,22 @@ function extractFingerprints(cursor) {
|
|
|
73
102
|
if (!fs.statSync(datePath).isDirectory())
|
|
74
103
|
continue;
|
|
75
104
|
for (const file of fs.readdirSync(datePath).sort()) {
|
|
76
|
-
if (!file.endsWith(".json"))
|
|
105
|
+
if (!file.endsWith(".json") || !file.startsWith("fail_"))
|
|
77
106
|
continue;
|
|
78
107
|
const filePath = path.join(datePath, file);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
let record;
|
|
109
|
+
try {
|
|
110
|
+
record = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
continue; /* 损坏记录跳过,best-effort */
|
|
114
|
+
}
|
|
115
|
+
// 游标存在且该记录时间戳 <= 游标,跳过
|
|
116
|
+
if (cursor && cursor.lastTimestamp && (record.timestamp || "") <= cursor.lastTimestamp)
|
|
82
117
|
continue;
|
|
83
118
|
const funcSeq = (record.actionSequence || [])
|
|
84
119
|
.filter((a) => a.kind === "call")
|
|
85
|
-
.map((a) => a.function);
|
|
120
|
+
.map((a) => maskFunctionName(String(a.function)));
|
|
86
121
|
fingerprints.push({
|
|
87
122
|
instance_id: instanceId,
|
|
88
123
|
timestamp: record.timestamp,
|
|
@@ -99,6 +134,10 @@ function extractFingerprints(cursor) {
|
|
|
99
134
|
}
|
|
100
135
|
/** @requires CORPUS @produces FINGERPRINT_REPORT */
|
|
101
136
|
async function reportFingerprints() {
|
|
137
|
+
const endpoint = resolveEndpoint();
|
|
138
|
+
if (endpoint === null) {
|
|
139
|
+
return { success: true, message: "上报已禁用(PROGMUNE_HUB=off),跳过" };
|
|
140
|
+
}
|
|
102
141
|
const cursor = getReportCursor();
|
|
103
142
|
const fingerprints = extractFingerprints(cursor);
|
|
104
143
|
if (fingerprints.length === 0) {
|
|
@@ -109,11 +148,11 @@ async function reportFingerprints() {
|
|
|
109
148
|
const maxTimestamp = fingerprints[fingerprints.length - 1].timestamp;
|
|
110
149
|
const payload = JSON.stringify({ fingerprints });
|
|
111
150
|
return new Promise((resolve) => {
|
|
112
|
-
const url = new URL(
|
|
151
|
+
const url = new URL(endpoint);
|
|
113
152
|
const transport = url.protocol === "https:" ? https : http;
|
|
114
|
-
const req = transport.request(
|
|
153
|
+
const req = transport.request(endpoint, {
|
|
115
154
|
method: "POST",
|
|
116
|
-
headers:
|
|
155
|
+
headers: buildHeaders(),
|
|
117
156
|
}, (res) => {
|
|
118
157
|
let data = "";
|
|
119
158
|
res.on("data", (chunk) => data += chunk);
|
|
@@ -128,6 +167,7 @@ async function reportFingerprints() {
|
|
|
128
167
|
}
|
|
129
168
|
});
|
|
130
169
|
});
|
|
170
|
+
req.setTimeout(8000, () => req.destroy(new Error("上报超时")));
|
|
131
171
|
req.on("error", (e) => resolve({ success: false, message: `网络错误: ${e.message}` }));
|
|
132
172
|
req.write(payload);
|
|
133
173
|
req.end();
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const immune_reporter_1 = require("./immune-reporter");
|
|
41
|
+
/**
|
|
42
|
+
* Immune Reporter 测试——2026-09 修复轮:
|
|
43
|
+
* 默认脱敏、PROGMUNE_HUB=off 开关、语料路径对齐 .progmune_corpus。
|
|
44
|
+
* 涉及文件系统的用例使用临时目录注入(corpusDir 参数),不触碰真实语料。
|
|
45
|
+
*/
|
|
46
|
+
let tmpDir;
|
|
47
|
+
(0, vitest_1.beforeEach)(() => {
|
|
48
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "progmune-reporter-"));
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.afterEach)(() => {
|
|
51
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.describe)("resolveEndpoint", () => {
|
|
54
|
+
(0, vitest_1.it)("未设置 PROGMUNE_HUB 时默认指向中央 hub", () => {
|
|
55
|
+
(0, vitest_1.expect)((0, immune_reporter_1.resolveEndpoint)(undefined)).toBe("https://progmune-runtime.fly.dev/report");
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)("PROGMUNE_HUB=off 及其变体(0/false/no/disabled)关闭上报", () => {
|
|
58
|
+
for (const v of ["off", "0", "false", "no", "disabled", "OFF"]) {
|
|
59
|
+
(0, vitest_1.expect)((0, immune_reporter_1.resolveEndpoint)(v)).toBeNull();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)("自定义 URL 原样生效", () => {
|
|
63
|
+
(0, vitest_1.expect)((0, immune_reporter_1.resolveEndpoint)("http://localhost:9999/report")).toBe("http://localhost:9999/report");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.describe)("maskFunctionName", () => {
|
|
67
|
+
(0, vitest_1.it)("默认脱敏:同名函数产生相同哈希(模式聚合保持有效)", () => {
|
|
68
|
+
const a = (0, immune_reporter_1.maskFunctionName)("generate_jwt", false);
|
|
69
|
+
const b = (0, immune_reporter_1.maskFunctionName)("generate_jwt", false);
|
|
70
|
+
(0, vitest_1.expect)(a).toBe(b);
|
|
71
|
+
(0, vitest_1.expect)(a).toMatch(/^fn:[0-9a-f]{12}$/);
|
|
72
|
+
(0, vitest_1.expect)(a).not.toContain("generate_jwt");
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("不同函数名产生不同哈希", () => {
|
|
75
|
+
(0, vitest_1.expect)((0, immune_reporter_1.maskFunctionName)("generate_jwt", false)).not.toBe((0, immune_reporter_1.maskFunctionName)("create_session", false));
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)("PROGMUNE_FINGERPRINT_DETAIL=1 时保留原文", () => {
|
|
78
|
+
(0, vitest_1.expect)((0, immune_reporter_1.maskFunctionName)("generate_jwt", true)).toBe("generate_jwt");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.describe)("buildHeaders", () => {
|
|
82
|
+
(0, vitest_1.it)("无 token 时只带 Content-Type", () => {
|
|
83
|
+
(0, vitest_1.expect)((0, immune_reporter_1.buildHeaders)(undefined)).toEqual({ "Content-Type": "application/json" });
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)("设置 PROGMUNE_HUB_TOKEN 时带 Bearer 认证头", () => {
|
|
86
|
+
const h = (0, immune_reporter_1.buildHeaders)("secret-token");
|
|
87
|
+
(0, vitest_1.expect)(h["Authorization"]).toBe("Bearer secret-token");
|
|
88
|
+
(0, vitest_1.expect)(h["Content-Type"]).toBe("application/json");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.describe)("extractFingerprints", () => {
|
|
92
|
+
function writeRecord(date, id, record) {
|
|
93
|
+
const dir = path.join(tmpDir, date);
|
|
94
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
+
fs.writeFileSync(path.join(dir, `fail_${id}.json`), JSON.stringify(record));
|
|
96
|
+
}
|
|
97
|
+
(0, vitest_1.it)("从 .progmune_corpus 路径读取并默认脱敏函数名", () => {
|
|
98
|
+
writeRecord("2026-09-09", "1", {
|
|
99
|
+
timestamp: "2026-09-09T10:00:00.000Z",
|
|
100
|
+
violatedSVL: "SVL-4",
|
|
101
|
+
constraintType: "protocol",
|
|
102
|
+
actionSequence: [{ kind: "call", function: "generate_jwt" }],
|
|
103
|
+
ssgState: ["UNAUTHENTICATED"],
|
|
104
|
+
});
|
|
105
|
+
const fps = (0, immune_reporter_1.extractFingerprints)({ lastTimestamp: null }, tmpDir);
|
|
106
|
+
(0, vitest_1.expect)(fps.length).toBe(1);
|
|
107
|
+
(0, vitest_1.expect)(fps[0].functionSequence[0]).toMatch(/^fn:[0-9a-f]{12}$/);
|
|
108
|
+
(0, vitest_1.expect)(fps[0].preState).toEqual([["UNAUTHENTICATED"]]);
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)("游标之后的新记录才会上报,旧记录跳过", () => {
|
|
111
|
+
writeRecord("2026-09-01", "old", {
|
|
112
|
+
timestamp: "2026-09-01T10:00:00.000Z",
|
|
113
|
+
violatedSVL: "SVL-4",
|
|
114
|
+
constraintType: "protocol",
|
|
115
|
+
actionSequence: [],
|
|
116
|
+
});
|
|
117
|
+
writeRecord("2026-09-09", "new", {
|
|
118
|
+
timestamp: "2026-09-09T10:00:00.000Z",
|
|
119
|
+
violatedSVL: "SVL-4",
|
|
120
|
+
constraintType: "protocol",
|
|
121
|
+
actionSequence: [],
|
|
122
|
+
});
|
|
123
|
+
const fps = (0, immune_reporter_1.extractFingerprints)({ lastTimestamp: "2026-09-05T00:00:00.000Z" }, tmpDir);
|
|
124
|
+
(0, vitest_1.expect)(fps.length).toBe(1);
|
|
125
|
+
(0, vitest_1.expect)(fps[0].timestamp).toBe("2026-09-09T10:00:00.000Z");
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)("非 fail_ 前缀文件与损坏 JSON 跳过,不中断", () => {
|
|
128
|
+
writeRecord("2026-09-09", "ok", {
|
|
129
|
+
timestamp: "2026-09-09T10:00:00.000Z",
|
|
130
|
+
violatedSVL: "SVL-4",
|
|
131
|
+
constraintType: "protocol",
|
|
132
|
+
actionSequence: [],
|
|
133
|
+
});
|
|
134
|
+
fs.writeFileSync(path.join(tmpDir, "2026-09-09", "trajectory_x.json"), "{}");
|
|
135
|
+
fs.writeFileSync(path.join(tmpDir, "2026-09-09", "fail_broken.json"), "{corrupted");
|
|
136
|
+
const fps = (0, immune_reporter_1.extractFingerprints)({ lastTimestamp: null }, tmpDir);
|
|
137
|
+
(0, vitest_1.expect)(fps.length).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
(0, vitest_1.it)("无调用序列时 functionSequence 为空数组而非报错", () => {
|
|
140
|
+
writeRecord("2026-09-09", "empty", {
|
|
141
|
+
timestamp: "2026-09-09T10:00:00.000Z",
|
|
142
|
+
violatedSVL: "SVL-2",
|
|
143
|
+
constraintType: "types",
|
|
144
|
+
});
|
|
145
|
+
const fps = (0, immune_reporter_1.extractFingerprints)({ lastTimestamp: null }, tmpDir);
|
|
146
|
+
(0, vitest_1.expect)(fps.length).toBe(1);
|
|
147
|
+
(0, vitest_1.expect)(fps[0].functionSequence).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
});
|
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.25";
|
|
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.25",
|
|
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/",
|