progmune-runtime 3.7.31 → 3.7.35

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.
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ /**
3
+ * 污点标记管线三处结构性修复(C1/C2/C3)的定向回归(2026-09-19)。
4
+ *
5
+ * 立项背景:这三处是从「污点源数据流试点 V1」里拆出来的**正确性修复**——
6
+ * 它们让标记管线对真实 TS 工程「看得见」,但不改变判别逻辑(判别力是 G1
7
+ * PATH_GUARD_EVIDENCE 的事,另一个条目)。
8
+ *
9
+ * 为什么必须有本文件(重要):
10
+ * TS 795 盲测对这三处【零覆盖】——实测把整份 generated 语料重跑后,
11
+ * per-function calls 差异 0、__progmune 标记 826 vs 826、flags 3086 LOST 0/ADDED 0。
12
+ * 即「TS 795 零漂移」在这里是**空过**(vacuous pass):它不是说这三处没风险,
13
+ * 而是说盲测语料根本走不到这些分支。因此真正的门是本文件的定向用例。
14
+ *
15
+ * 三处修复:
16
+ * C3 UNTRUSTED_ROOTS —— 污点根由「Express 专属变量名」改为「按传输面声明」
17
+ * C1 methodSinkParamMap—— 除类方法外,纳入**顶层函数**的 sink 形参
18
+ * C2 directCallRe —— 跨函数传播除成员调用外,识别**裸调用** func(x)
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ const vitest_1 = require("vitest");
55
+ const fs = __importStar(require("fs"));
56
+ const os = __importStar(require("os"));
57
+ const path = __importStar(require("path"));
58
+ const extract_ir_1 = require("./extract-ir");
59
+ const TSCONFIG = JSON.stringify({
60
+ compilerOptions: {
61
+ target: "ES2020",
62
+ module: "commonjs",
63
+ moduleResolution: "node",
64
+ strict: false,
65
+ skipLibCheck: true,
66
+ noEmit: true,
67
+ },
68
+ include: ["**/*.ts"],
69
+ });
70
+ function makeProject(files) {
71
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-taint-struct-"));
72
+ fs.writeFileSync(path.join(dir, "tsconfig.json"), TSCONFIG);
73
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "fixture", version: "0.0.0", private: true }));
74
+ for (const [rel, body] of Object.entries(files)) {
75
+ const abs = path.join(dir, rel);
76
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
77
+ fs.writeFileSync(abs, body);
78
+ }
79
+ return dir;
80
+ }
81
+ function marksFor(dir, fnName) {
82
+ const fns = (0, extract_ir_1.extractIR)(dir);
83
+ const f = fns.find((x) => x.name === fnName);
84
+ return f ? (f.calls ?? []) : [];
85
+ }
86
+ const PATH_MARK = "__progmune_path_traversal__";
87
+ (0, vitest_1.describe)("C3:污点根按传输面声明(不再只认 Express 的 req.*)", () => {
88
+ (0, vitest_1.it)("Express 形态仍必须标记(不得因改表回归)", () => {
89
+ const dir = makeProject({
90
+ "legacy.ts": `
91
+ import * as fs from "fs";
92
+ export function read(req: any) {
93
+ const name = req.params.name;
94
+ return fs.readFileSync("/data/" + name, "utf-8");
95
+ }
96
+ `,
97
+ });
98
+ try {
99
+ (0, vitest_1.expect)(marksFor(dir, "read")).toContain(PATH_MARK);
100
+ }
101
+ finally {
102
+ fs.rmSync(dir, { recursive: true, force: true });
103
+ }
104
+ }, 150000);
105
+ (0, vitest_1.it)("MCP 工具实参 params.arguments 作为根(fr-012 形态)", () => {
106
+ const dir = makeProject({
107
+ "mcp.ts": `
108
+ import * as fs from "fs";
109
+ export function uploadMarkdown(args: any) {
110
+ const filePath = params.arguments.file_path;
111
+ return fs.readFileSync(filePath, "utf-8");
112
+ }
113
+ `,
114
+ });
115
+ try {
116
+ (0, vitest_1.expect)(marksFor(dir, "uploadMarkdown")).toContain(PATH_MARK);
117
+ }
118
+ finally {
119
+ fs.rmSync(dir, { recursive: true, force: true });
120
+ }
121
+ }, 150000);
122
+ (0, vitest_1.it)("反例:常量路径不得标记(防止根表放宽后过度标记)", () => {
123
+ const dir = makeProject({
124
+ "benign.ts": `
125
+ import * as fs from "fs";
126
+ export function readDocs() {
127
+ return fs.readFileSync("/data/terms.md", "utf-8");
128
+ }
129
+ `,
130
+ });
131
+ try {
132
+ (0, vitest_1.expect)(marksFor(dir, "readDocs")).not.toContain(PATH_MARK);
133
+ }
134
+ finally {
135
+ fs.rmSync(dir, { recursive: true, force: true });
136
+ }
137
+ }, 150000);
138
+ });
139
+ (0, vitest_1.describe)("C1:顶层函数的 sink 形参必须入表(此前只登记类方法)", () => {
140
+ (0, vitest_1.it)("顶层函数 + 污点实参 → 跨函数传导后被标记", () => {
141
+ const dir = makeProject({
142
+ "handler.ts": `
143
+ import * as fs from "fs";
144
+ export function markdownUpload(filePath: string) {
145
+ return fs.readFileSync(filePath, "utf-8");
146
+ }
147
+ export function dispatchTool(args: any) {
148
+ const target = params.arguments.file_path;
149
+ return markdownUpload(target);
150
+ }
151
+ `,
152
+ });
153
+ try {
154
+ (0, vitest_1.expect)(marksFor(dir, "markdownUpload")).toContain(PATH_MARK);
155
+ }
156
+ finally {
157
+ fs.rmSync(dir, { recursive: true, force: true });
158
+ }
159
+ }, 150000);
160
+ });
161
+ (0, vitest_1.describe)("C2:裸调用形态的跨函数传播(此前只匹配 obj.sink(...))", () => {
162
+ (0, vitest_1.it)("顶层函数被裸调用且传入污点 → 标记(fr-012 的 markdownUpload 形态)", () => {
163
+ const dir = makeProject({
164
+ "store.ts": `
165
+ import * as fs from "fs";
166
+ export function save(name: string, content: string) {
167
+ fs.writeFileSync("/flows/" + name, content);
168
+ }
169
+ `,
170
+ "route.ts": `
171
+ import { save } from "./store";
172
+ export function createFlow(req: any) {
173
+ const name = req.body.name;
174
+ save(name, "{}");
175
+ }
176
+ `,
177
+ });
178
+ try {
179
+ (0, vitest_1.expect)(marksFor(dir, "createFlow")).toContain(PATH_MARK);
180
+ }
181
+ finally {
182
+ fs.rmSync(dir, { recursive: true, force: true });
183
+ }
184
+ }, 150000);
185
+ (0, vitest_1.it)("反例:给裸调用传常量不得标记", () => {
186
+ const dir = makeProject({
187
+ "store.ts": `
188
+ import * as fs from "fs";
189
+ export function save(name: string, content: string) {
190
+ fs.writeFileSync("/flows/" + name, content);
191
+ }
192
+ `,
193
+ "route.ts": `
194
+ import { save } from "./store";
195
+ export function seedFlow() {
196
+ save("default.json", "{}");
197
+ }
198
+ `,
199
+ });
200
+ try {
201
+ (0, vitest_1.expect)(marksFor(dir, "seedFlow")).not.toContain(PATH_MARK);
202
+ }
203
+ finally {
204
+ fs.rmSync(dir, { recursive: true, force: true });
205
+ }
206
+ }, 150000);
207
+ });
208
+ /**
209
+ * C5(2026-09-19):不可信根**直接写在 sink 实参里**(无中间变量)此前不标记。
210
+ *
211
+ * 发现途径:新建的 blind-benchmark taintpath 语料族里有两条最朴素的用例
212
+ * (`readFileSync("/d/" + req.params.name)`、模板字符串形态)跑出「不标记」。
213
+ * 根因是外层 `tainted.size > 0` 的闸门——而 SSRF 侧从来不要求中间变量。
214
+ *
215
+ * 按 R6:**每条「不得标记」都要配正对照**。这里的负对照是同形状无污点的常量路径。
216
+ */
217
+ (0, vitest_1.describe)("C5:内联不可信根(不经中间变量)必须标记", () => {
218
+ const cases = [
219
+ {
220
+ name: "字符串拼接直连",
221
+ fn: "readInline",
222
+ code: `export function readInline(req: any) {
223
+ return fs.readFileSync("/srv/data/" + req.params.name, "utf-8");
224
+ }`,
225
+ expectMark: true,
226
+ why: "真实 Express 工程最常见的写法",
227
+ },
228
+ {
229
+ name: "模板字符串直连",
230
+ fn: "readTemplate",
231
+ code: "export function readTemplate(req: any) {\n return fs.readFileSync(`/srv/data/${req.query.name}`, \"utf-8\");\n}",
232
+ expectMark: true,
233
+ why: "query 根 + 模板插值",
234
+ },
235
+ {
236
+ name: "写侧 header 根",
237
+ fn: "writeHeader",
238
+ code: `export function writeHeader(request: any, data: any) {
239
+ return fs.writeFileSync("/srv/data/" + request.headers["x-name"], data);
240
+ }`,
241
+ expectMark: true,
242
+ why: "写 sink + request.headers 根",
243
+ },
244
+ {
245
+ name: "MCP 实参直连",
246
+ fn: "writeMcp",
247
+ code: `export function writeMcp(params: any, data: any) {
248
+ return fs.writeFileSync(params.arguments.path, data);
249
+ }`,
250
+ expectMark: true,
251
+ why: "传输面里的第二个根:MCP 工具实参",
252
+ },
253
+ {
254
+ name: "负对照:常量路径不得标记",
255
+ fn: "readConstant",
256
+ code: `export function readConstant() {
257
+ return fs.readFileSync("/srv/data/index.html", "utf-8");
258
+ }`,
259
+ expectMark: false,
260
+ why: "R6:证明前面的用例不是靠「只要有 fs sink 就标记」蒙对",
261
+ },
262
+ {
263
+ name: "负对照:有守卫时内联根仍不标记",
264
+ fn: "readInlineGuarded",
265
+ code: `export function readInlineGuarded(req: any) {
266
+ const target = path.resolve("/srv/data", req.params.name);
267
+ if (!target.startsWith("/srv/data")) throw new Error("outside");
268
+ return fs.readFileSync(target, "utf-8");
269
+ }`,
270
+ expectMark: false,
271
+ why: "C5 只补召回,不得绕过 G1 的判别力",
272
+ },
273
+ ];
274
+ for (const c of cases) {
275
+ (0, vitest_1.it)(`${c.name} —— ${c.why}`, () => {
276
+ const dir = makeProject({ "a.ts": `import * as fs from "fs";\nimport * as path from "path";\n\n${c.code}\n` });
277
+ try {
278
+ const marks = marksFor(dir, c.fn);
279
+ if (c.expectMark)
280
+ (0, vitest_1.expect)(marks).toContain(PATH_MARK);
281
+ else
282
+ (0, vitest_1.expect)(marks).not.toContain(PATH_MARK);
283
+ }
284
+ finally {
285
+ fs.rmSync(dir, { recursive: true, force: true });
286
+ }
287
+ }, 150000);
288
+ }
289
+ });