progmune-runtime 3.7.15 → 3.7.17

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,152 @@
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
+ exports.extractJavaFile = extractJavaFile;
37
+ exports.extractIRJava = extractIRJava;
38
+ /**
39
+ * extract-ir-java.ts — Java 语言 IR 提取(纯 TS 词法,零工具链依赖)
40
+ *
41
+ * 与 C/Go 提取器同模式:逐 .java 文件扫描方法声明 → FunctionInfo 列表。
42
+ * Java 语言支持(3.7.17 里程碑 1):注册表 LANGUAGE_EXTRACTORS 一项 +
43
+ * evaluateTrust/engine 语言分派;Spring Security 路由覆盖模型见
44
+ * src/frameworks/spring-detector.ts。
45
+ *
46
+ * 注意:提取为词法近似(方法签名正则)——注解驱动的协议金标建立后
47
+ * 再评估是否需 AST(JavaParser 等不引入,保持零依赖)。
48
+ */
49
+ const fs = __importStar(require("fs"));
50
+ const path = __importStar(require("path"));
51
+ const JAVA_EXTENSIONS = new Set([".java"]);
52
+ const SKIP_DIRS = new Set([
53
+ "build", "target", "out", "bin", "node_modules", ".git", ".gradle",
54
+ "generated", "test", "tests", "__pycache__",
55
+ ]);
56
+ const JAVA_KEYWORDS = new Set([
57
+ "if", "for", "while", "switch", "catch", "synchronized", "return",
58
+ "new", "case", "do", "try", "else", "instanceof",
59
+ ]);
60
+ const MODIFIERS = "(?:public|protected|private|static|final|synchronized|abstract|default|native|strictfp|transient|volatile|\\s)+";
61
+ /** 单文件提取 */
62
+ function extractJavaFile(filePath) {
63
+ const out = [];
64
+ let code;
65
+ try {
66
+ code = fs.readFileSync(filePath, "utf-8");
67
+ }
68
+ catch {
69
+ return out;
70
+ }
71
+ // 方法声明:修饰符 + 返回类型 名字(参数) [throws ...] {
72
+ const re = /(?:\b(?:public|protected|private)\s+)?(?:(?:static|final|synchronized|abstract|default)\s+)*(?:[\w$<>\[\],.\s]+?)\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?:throws\s+[^{]+?)?\{/g;
73
+ let m;
74
+ while ((m = re.exec(code)) !== null) {
75
+ const name = m[1];
76
+ if (JAVA_KEYWORDS.has(name))
77
+ continue;
78
+ // 前置字符过滤:方法名不应紧跟 '.'(调用)或 '='(赋值)等
79
+ const pre = code.slice(Math.max(0, m.index - 1), m.index);
80
+ if (/[.=@(]/.test(pre))
81
+ continue;
82
+ // 可见性(近似):行内是否有 public/protected(或文件在接口里默认 public)
83
+ const head = code.slice(m.index, m.index + 40);
84
+ const visM = head.match(/\b(public|protected|private)\b/);
85
+ const exported = visM ? visM[1] !== "private" : /^interface\s/.test(code.slice(0, m.index).split("\n").pop() || "") ? true : undefined;
86
+ const params = [];
87
+ const rawParams = m[2].trim();
88
+ if (rawParams.length > 0) {
89
+ for (const raw of rawParams.split(",")) {
90
+ const t = raw.trim();
91
+ if (!t)
92
+ continue;
93
+ // 最后一个词为参数名,其余为类型(含泛型/数组)
94
+ const parts = t.split(/\s+/);
95
+ const pname = parts.pop() || "";
96
+ const ptype = parts.join(" ") || "?";
97
+ if (/^[A-Za-z_$][\w$]*$/.test(pname) && pname !== "final") {
98
+ params.push({ name: pname, type: ptype });
99
+ }
100
+ }
101
+ }
102
+ // 返回类型:方法名前的一段(简化取最近一个类型令牌)
103
+ const returnType = "unknown";
104
+ out.push({
105
+ name,
106
+ params,
107
+ returnType,
108
+ file: filePath,
109
+ exported,
110
+ });
111
+ }
112
+ return out;
113
+ }
114
+ function collectJavaFiles(root) {
115
+ const files = [];
116
+ const walk = (dir) => {
117
+ let entries;
118
+ try {
119
+ entries = fs.readdirSync(dir, { withFileTypes: true });
120
+ }
121
+ catch {
122
+ return;
123
+ }
124
+ for (const e of entries) {
125
+ if (e.isSymbolicLink())
126
+ continue;
127
+ const full = path.join(dir, e.name);
128
+ if (e.isDirectory()) {
129
+ if (SKIP_DIRS.has(e.name))
130
+ continue;
131
+ walk(full);
132
+ }
133
+ else if (JAVA_EXTENSIONS.has(path.extname(e.name))) {
134
+ files.push(full);
135
+ }
136
+ }
137
+ };
138
+ walk(root);
139
+ return files;
140
+ }
141
+ /** 注册表入口 */
142
+ function extractIRJava(projectRoot) {
143
+ const files = collectJavaFiles(projectRoot);
144
+ const all = [];
145
+ for (const file of files) {
146
+ for (const fn of extractJavaFile(file)) {
147
+ fn.file = path.relative(projectRoot, fn.file);
148
+ all.push(fn);
149
+ }
150
+ }
151
+ return all;
152
+ }
@@ -55,6 +55,7 @@ const extract_ir_1 = require("./extract-ir");
55
55
  const extract_ir_python_1 = require("./extract-ir-python");
56
56
  const extract_ir_c_1 = require("./extract-ir-c");
57
57
  const extract_ir_go_1 = require("./extract-ir-go");
58
+ const extract_ir_java_1 = require("./extract-ir-java");
58
59
  const SKIP_DIRS = new Set([
59
60
  "node_modules", "dist", "build", ".git", ".progmune_corpus",
60
61
  "__pycache__", "venv", ".venv",
@@ -111,6 +112,11 @@ exports.LANGUAGE_EXTRACTORS = [
111
112
  detect: (p) => hasSourceFiles(p, new Set([".go"])),
112
113
  extract: (p) => (0, extract_ir_go_1.extractIRGo)(p),
113
114
  },
115
+ {
116
+ language: "java",
117
+ detect: (p) => hasSourceFiles(p, new Set([".java"])),
118
+ extract: (p) => (0, extract_ir_java_1.extractIRJava)(p),
119
+ },
114
120
  ];
115
121
  /** 项目检测到的语言列表(审计/标签用)。 */
116
122
  function detectLanguages(projectRoot) {
@@ -0,0 +1,280 @@
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
+ exports.antToRegex = antToRegex;
37
+ exports.analyzeSpringProject = analyzeSpringProject;
38
+ /**
39
+ * spring-detector.ts — Spring (Boot) Framework Adapter — 路由覆盖模型
40
+ *
41
+ * Java 语言支持里程碑 2。真实语料(gothinkster/spring-boot-realworld-
42
+ * example-app 1581★)证实 Spring 认证惯例 = **安全配置类集中声明**:
43
+ *
44
+ * @Configuration @EnableWebSecurity
45
+ * class WebSecurityConfig extends WebSecurityConfigurerAdapter {
46
+ * configure(HttpSecurity http) {
47
+ * http...authorizeRequests()
48
+ * .antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
49
+ * .antMatchers(HttpMethod.GET, "/articles/**", "/tags").permitAll()
50
+ * .antMatchers(HttpMethod.GET, "/articles/feed").authenticated()
51
+ * .anyRequest().authenticated(); // ← 兜底
52
+ * }
53
+ *
54
+ * 路由保护 = Spring 规则序(首个匹配者胜)+ anyRequest 兜底。检测器
55
+ * 解析安全配置规则 → 控制器注解路由(@RestController + @*Mapping)→
56
+ * 判定 mutation 是否最终公开。代码串级 + ant 模式匹配(与 Gin/Fiber
57
+ * 项目级模型同族;注册在配置而非路由上——见 REALWORLD 系列根因②)。
58
+ */
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const route_window_1 = require("./route-window");
62
+ const MUTATION_METHODS = new Set(["post", "put", "patch", "delete"]);
63
+ const ACCESS_AUTH = new Set([
64
+ "authenticated", "fullyAuthenticated", "hasRole", "hasAnyRole",
65
+ "hasAuthority", "hasAnyAuthority", "rememberMe", "denyAll",
66
+ ]);
67
+ function isAuthEntryPath(p) {
68
+ return /\/?(login|signin|signup|sign_in|register|refresh)(\/|$)/i.test(p);
69
+ }
70
+ /** Spring ant 模式 → 正则(** → 任意段,* → 单段) */
71
+ function antToRegex(pattern) {
72
+ const esc = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
73
+ const re = esc.replace(/\*\*/g, "__DOUBLE__").replace(/\*/g, "[^/]*").replace(/__DOUBLE__/g, ".*");
74
+ return new RegExp("^" + re + "$");
75
+ }
76
+ function parseSecurityConfig(code) {
77
+ const rules = [];
78
+ let catchAll = null;
79
+ // 逐个 .antMatchers(...) 与其后 .access() 配对
80
+ const re = /\.antMatchers\(\s*([^)]*)\)\s*\.(\w+)\s*\(|\.anyRequest\(\)\s*\.(\w+)\s*\(/g;
81
+ let m;
82
+ while ((m = re.exec(code)) !== null) {
83
+ if (m[3] !== undefined) {
84
+ catchAll = m[3];
85
+ continue;
86
+ }
87
+ const argText = m[1];
88
+ let method;
89
+ let rest = argText.trim();
90
+ const hm = rest.match(/^HttpMethod\.(\w+)/);
91
+ if (hm) {
92
+ method = hm[1].toLowerCase();
93
+ rest = rest.slice(hm[0].length).replace(/^,/, "").trim();
94
+ }
95
+ const patterns = [];
96
+ for (const pm of rest.matchAll(/"([^"]+)"/g))
97
+ patterns.push(pm[1]);
98
+ if (patterns.length === 0) {
99
+ // 无引号模式(变量引用)→ 宽匹配不了,跳过(保守:不豁免)
100
+ continue;
101
+ }
102
+ rules.push({ method, patterns, access: m[2] });
103
+ }
104
+ return { rules, catchAll };
105
+ }
106
+ function parseControllers(root) {
107
+ const out = [];
108
+ const walk = (dir) => {
109
+ let entries;
110
+ try {
111
+ entries = fs.readdirSync(dir, { withFileTypes: true });
112
+ }
113
+ catch {
114
+ return;
115
+ }
116
+ for (const e of entries) {
117
+ const full = path.join(dir, e.name);
118
+ if (e.isDirectory()) {
119
+ if (["build", "target", ".git", ".gradle"].includes(e.name))
120
+ continue;
121
+ walk(full);
122
+ }
123
+ else if (e.name.endsWith(".java")) {
124
+ const code = fs.readFileSync(full, "utf-8");
125
+ const clsRe = /@(?:RestController|Controller)(?!\w)[\s\S]*?(?=class\s+([A-Za-z_]\w*))/g;
126
+ let cm;
127
+ while ((cm = clsRe.exec(code)) !== null) {
128
+ const name = cm[1] || "Unknown";
129
+ const clsAt = code.indexOf("class " + name, cm.index);
130
+ if (clsAt < 0)
131
+ continue;
132
+ const header = code.slice(cm.index, clsAt); // 类声明头(类级注解区)
133
+ const body = code.slice(clsAt); // 类体(方法注解区)
134
+ out.push({ controller: name, header, body, path: full });
135
+ }
136
+ }
137
+ }
138
+ };
139
+ walk(root);
140
+ return out;
141
+ }
142
+ function methodAccessOf(ann) {
143
+ const lower = ann.toLowerCase().replace(/^@/, "");
144
+ const method = lower.startsWith("requestmapping")
145
+ ? (() => {
146
+ const mm = ann.match(/method\s*=\s*(?:RequestMethod\.)?(\w+)/);
147
+ return mm ? mm[1].toLowerCase() : "";
148
+ })()
149
+ : lower.replace(/mapping.*/, "").replace(/^@/, "").trim();
150
+ const pm = ann.match(/(?:path\s*=\s*)?["']([^"']*)["']/);
151
+ return { method, rpath: pm ? pm[1] : "" };
152
+ }
153
+ function analyzeSpringProject(projectRoot) {
154
+ const issues = [];
155
+ // 1) 安全配置(全仓找 WebSecurityConfigurerAdapter / SecurityFilterChain)
156
+ let secCode = "";
157
+ let hasSecurityConfig = false;
158
+ let catchAll = null;
159
+ let rules = [];
160
+ const collect = (dir) => {
161
+ let entries;
162
+ try {
163
+ entries = fs.readdirSync(dir, { withFileTypes: true });
164
+ }
165
+ catch {
166
+ return;
167
+ }
168
+ for (const e of entries) {
169
+ const full = path.join(dir, e.name);
170
+ if (e.isDirectory()) {
171
+ if (["build", "target", ".git", ".gradle", "node_modules"].includes(e.name))
172
+ continue;
173
+ collect(full);
174
+ }
175
+ else if (e.name.endsWith(".java")) {
176
+ const code = fs.readFileSync(full, "utf-8");
177
+ if (/WebSecurityConfigurerAdapter|SecurityFilterChain/.test(code) && /authorizeRequests|authorizeHttpRequests/.test(code)) {
178
+ hasSecurityConfig = true;
179
+ secCode += code + "\n";
180
+ }
181
+ }
182
+ }
183
+ };
184
+ collect(projectRoot);
185
+ if (hasSecurityConfig) {
186
+ const parsed = parseSecurityConfig(secCode);
187
+ rules = parsed.rules;
188
+ catchAll = parsed.catchAll;
189
+ }
190
+ const registerRootsSet = new Set();
191
+ // 2) 控制器路由
192
+ const routes = [];
193
+ const allPaths = [];
194
+ const filesScanned = (() => {
195
+ let n = 0;
196
+ const cnt = (dir) => {
197
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
198
+ const full = path.join(dir, e.name);
199
+ if (e.isDirectory()) {
200
+ if (["build", "target", ".git", ".gradle", "node_modules"].includes(e.name))
201
+ continue;
202
+ cnt(full);
203
+ }
204
+ else if (e.name.endsWith(".java"))
205
+ n++;
206
+ }
207
+ };
208
+ cnt(projectRoot);
209
+ return n;
210
+ })();
211
+ for (const c of parseControllers(projectRoot)) {
212
+ // 类级 @RequestMapping 只认类声明头(方法级 @RequestMapping 不算前缀)
213
+ const clsReq = c.header.match(/@RequestMapping\s*\((?:path\s*=\s*)?["']([^"']*)["']/);
214
+ const prefix = clsReq ? clsReq[1].replace(/^\//, "") : "";
215
+ const annRe = /@(Get|Post|Put|Delete|Patch|Request)Mapping\s*(\([^)]*\))?/g;
216
+ let m;
217
+ while ((m = annRe.exec(c.body)) !== null) {
218
+ const ann = "@" + m[1] + "Mapping" + (m[2] || "()");
219
+ const { method, rpath } = methodAccessOf(ann);
220
+ if (!method)
221
+ continue;
222
+ const seg = rpath.replace(/^\//, "");
223
+ const fullPath = "/" + (prefix ? prefix + (seg ? "/" + seg : "") : seg);
224
+ // 找到该 handler 起始行(用于 line + 后续注解如 @PreAuthorize)
225
+ const line = c.body.slice(0, m.index).split("\n").length;
226
+ const window = c.body.slice(m.index, m.index + 600);
227
+ const preAuth = /@PreAuthorize/.test(window);
228
+ allPaths.push(fullPath);
229
+ routes.push({
230
+ method,
231
+ path: fullPath,
232
+ controller: c.controller,
233
+ access: preAuth ? "authenticated(@PreAuthorize)" : "",
234
+ protectedFlag: !!preAuth,
235
+ line,
236
+ });
237
+ }
238
+ }
239
+ // register 集合根(同文件/项目内 /login 姊妹)
240
+ const registerRoots = (0, route_window_1.collectRegisterRoots)(allPaths);
241
+ // 3) 逐路由判定(Spring 序:首个匹配规则胜;无匹配 → 兜底)
242
+ for (const r of routes) {
243
+ let access = null;
244
+ for (const rule of rules) {
245
+ if (rule.method && rule.method !== r.method)
246
+ continue;
247
+ if (!rule.patterns.some((p) => antToRegex(p).test(r.path)))
248
+ continue;
249
+ access = rule.access; // 首个匹配
250
+ break;
251
+ }
252
+ const finalAccess = access ?? catchAll ?? (hasSecurityConfig ? "deny" : "public");
253
+ r.access = finalAccess;
254
+ r.protectedFlag = ACCESS_AUTH.has(finalAccess) || r.protectedFlag;
255
+ if (!MUTATION_METHODS.has(r.method))
256
+ continue;
257
+ if (r.protectedFlag)
258
+ continue;
259
+ if (isAuthEntryPath(r.path))
260
+ continue;
261
+ if (r.method === "post" && (0, route_window_1.isRegisterRoot)(r.path, registerRoots))
262
+ continue;
263
+ issues.push({
264
+ severity: "medium",
265
+ rule: "SPRING_ROUTE_NO_AUTH",
266
+ message: `Mutation route ${r.method.toUpperCase()} ${r.path} is reachable without ` +
267
+ `authentication (access=${finalAccess}) — any caller can invoke it.`,
268
+ route: `${r.method.toUpperCase()} ${r.path}`,
269
+ line: r.line,
270
+ });
271
+ }
272
+ return {
273
+ filesScanned,
274
+ hasSecurityConfig,
275
+ catchAll,
276
+ rules,
277
+ routes,
278
+ issues,
279
+ };
280
+ }
@@ -0,0 +1,122 @@
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
+ * spring-detector.test.ts — Spring 路由覆盖模型回归(纯函数 + 临时目录)
38
+ */
39
+ const vitest_1 = require("vitest");
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const spring_detector_1 = require("./spring-detector");
44
+ let dir;
45
+ (0, vitest_1.beforeEach)(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), "spring-det-")); });
46
+ (0, vitest_1.afterEach)(() => { fs.rmSync(dir, { recursive: true, force: true }); });
47
+ function writeJava(rel, content) {
48
+ const fp = path.join(dir, rel);
49
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
50
+ fs.writeFileSync(fp, content);
51
+ }
52
+ const PKG = "package app;\nimport org.springframework.web.bind.annotation.*;\n";
53
+ const SEC = (catchAll) => `${PKG}
54
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
55
+ @Configuration @EnableWebSecurity
56
+ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
57
+ @Override protected void configure(HttpSecurity http) throws Exception {
58
+ http.csrf().disable().authorizeRequests()
59
+ .antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
60
+ .antMatchers(HttpMethod.GET, "/articles/**", "/tags").permitAll()
61
+ .antMatchers(HttpMethod.GET, "/articles/feed").authenticated()
62
+ .anyRequest().${catchAll}();
63
+ http.addFilterBefore(new JwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
64
+ }
65
+ }`;
66
+ const CTRL = `${PKG}
67
+ @RestController @RequestMapping(path = "articles")
68
+ public class ArticleApi {
69
+ @PostMapping public Object create() { return null; }
70
+ @PutMapping("/{slug}") public Object update() { return null; }
71
+ @GetMapping("/{slug}") public Object one() { return null; }
72
+ @DeleteMapping("/{slug}") public Object del() { return null; }
73
+ }`;
74
+ (0, vitest_1.describe)("antToRegex", () => {
75
+ (0, vitest_1.it)("ant 模式转正则", () => {
76
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/articles/**").test("/articles/abc")).toBe(true);
77
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/articles/**").test("/articles/abc/def")).toBe(true);
78
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/articles/*").test("/articles/abc")).toBe(true);
79
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/articles/*").test("/articles/abc/def")).toBe(false);
80
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/users").test("/users")).toBe(true);
81
+ (0, vitest_1.expect)((0, spring_detector_1.antToRegex)("/users").test("/users/x")).toBe(false);
82
+ });
83
+ });
84
+ (0, vitest_1.describe)("analyzeSpringProject", () => {
85
+ (0, vitest_1.it)("anyRequest().authenticated() 兜底:受保护 mutation 不报", () => {
86
+ writeJava("sec/WebSecurityConfig.java", SEC("authenticated"));
87
+ writeJava("api/ArticleApi.java", CTRL);
88
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
89
+ (0, vitest_1.expect)(a.hasSecurityConfig).toBe(true);
90
+ (0, vitest_1.expect)(a.catchAll).toBe("authenticated");
91
+ (0, vitest_1.expect)(a.issues).toHaveLength(0);
92
+ const art = a.routes.find((r) => r.method === "post" && r.path === "/articles");
93
+ (0, vitest_1.expect)(art.access).toBe("authenticated");
94
+ });
95
+ (0, vitest_1.it)("翻兜底为 permitAll → mutation 重现(敏感性)", () => {
96
+ writeJava("sec/WebSecurityConfig.java", SEC("permitAll"));
97
+ writeJava("api/ArticleApi.java", CTRL);
98
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
99
+ const flags = a.issues.map((i) => i.route);
100
+ (0, vitest_1.expect)(flags).toContain("POST /articles");
101
+ (0, vitest_1.expect)(flags).toContain("PUT /articles/{slug}");
102
+ (0, vitest_1.expect)(flags).toContain("DELETE /articles/{slug}");
103
+ // GET 读不查
104
+ (0, vitest_1.expect)(flags).not.toContain("GET /articles/{slug}");
105
+ });
106
+ (0, vitest_1.it)("register/login(permitAll + 姊妹佐证)不报", () => {
107
+ writeJava("sec/WebSecurityConfig.java", SEC("authenticated"));
108
+ writeJava("api/UsersApi.java", `${PKG}
109
+ @RestController public class UsersApi {
110
+ @RequestMapping(path = "/users", method = POST) public Object reg() { return null; }
111
+ @RequestMapping(path = "/users/login", method = POST) public Object login() { return null; }
112
+ }`);
113
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
114
+ (0, vitest_1.expect)(a.issues).toHaveLength(0);
115
+ });
116
+ (0, vitest_1.it)("无安全配置 → mutation 报(无认证裸奔)", () => {
117
+ writeJava("api/ArticleApi.java", CTRL);
118
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
119
+ (0, vitest_1.expect)(a.hasSecurityConfig).toBe(false);
120
+ (0, vitest_1.expect)(a.issues.some((i) => i.route === "POST /articles")).toBe(true);
121
+ });
122
+ });
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.15";
23
+ exports.RUNTIME_VERSION = "3.7.17";
24
24
  function verify(filePath) {
25
25
  const cert = (0, certify_1.certify)(filePath);
26
26
  const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
@@ -77,6 +77,7 @@ const extract_ir_1 = require("../extract-ir");
77
77
  const extract_ir_python_1 = require("../extract-ir-python");
78
78
  const extract_ir_c_1 = require("../extract-ir-c");
79
79
  const extract_ir_go_1 = require("../extract-ir-go");
80
+ const extract_ir_java_1 = require("../extract-ir-java");
80
81
  // ── Main Entry Point ──
81
82
  /**
82
83
  * Trust 决策主入口:收集 → 归一化 → 评分 → 决策 → 组装。
@@ -1211,6 +1212,7 @@ async function collectProtocolViolations(ctx, callGraph) {
1211
1212
  python: () => (0, extract_ir_python_1.extractIRPython)(ctx.projectPath),
1212
1213
  c: () => (0, extract_ir_c_1.extractIRC)(ctx.projectPath),
1213
1214
  go: () => (0, extract_ir_go_1.extractIRGo)(ctx.projectPath),
1215
+ java: () => (0, extract_ir_java_1.extractIRJava)(ctx.projectPath),
1214
1216
  };
1215
1217
  const extractFn = autoExtractor[lang];
1216
1218
  if (extractFn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "3.7.15",
3
+ "version": "3.7.17",
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/",
@@ -88,7 +88,8 @@
88
88
  "test:soak": "vitest run tests/soak/",
89
89
  "test:p7": "vitest run tests/p7-*/",
90
90
  "fetch:cves": "tsx scripts/fetch-cves.ts",
91
- "test:cve": "vitest run src/cve-benchmark.test.ts"
91
+ "test:cve": "vitest run src/cve-benchmark.test.ts",
92
+ "audit:realworld": "node scripts/realworld-audit.js"
92
93
  },
93
94
  "keywords": [
94
95
  "program-synthesis",