progmune-runtime 3.7.18 → 3.7.19

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.
@@ -93,3 +93,55 @@ public class Plain {
93
93
  (0, vitest_1.expect)(fns.some((f) => f.name === "add")).toBe(true);
94
94
  });
95
95
  });
96
+ // ── 协议行金标 v1:token 生命周期(verify 先于 use,2026-09-02)──
97
+ /** 真实语料 token 链的 verify-before-use 判定(金标规则 v1):
98
+ * doFilterInternal 的调用序须满足 getSubFromToken(verify)先于
99
+ * setAuthentication(use/信任)。 */
100
+ function tokenVerifyBeforeUse(calls) {
101
+ if (!calls)
102
+ return { ok: false, why: "无调用边" };
103
+ const verifyIdx = calls.findIndex((c) => /getSubFromToken|verify/.test(c));
104
+ const useIdx = calls.findIndex((c) => c === "setAuthentication");
105
+ if (useIdx === -1)
106
+ return { ok: true, why: "无 use(本链不消费认证)" };
107
+ if (verifyIdx === -1)
108
+ return { ok: false, why: "use(setAuthentication) 之前无 verify" };
109
+ return verifyIdx < useIdx
110
+ ? { ok: true, why: "verify→use 序正确" }
111
+ : { ok: false, why: "use 先于 verify" };
112
+ }
113
+ const REAL_FILTER = `package io.spring.api.security;
114
+ public class JwtTokenFilter {
115
+ @Override
116
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
117
+ throws Exception {
118
+ getTokenString(request.getHeader(header))
119
+ .flatMap(token -> jwtService.getSubFromToken(token))
120
+ .ifPresent(id -> {
121
+ if (SecurityContextHolder.getContext().getAuthentication() == null) {
122
+ setAuthentication(id, request); // use:信任已验 token
123
+ }
124
+ });
125
+ chain.doFilter(request, response);
126
+ }
127
+ }`;
128
+ // 变异:删掉 verify(真实违规:未验 token 直接信任)
129
+ const MUT_FILTER = REAL_FILTER.replace(" .flatMap(token -> jwtService.getSubFromToken(token))", " .map(id -> id)");
130
+ (0, vitest_1.describe)("协议行金标 v1 — token 生命周期(verify-before-use)", () => {
131
+ (0, vitest_1.it)("原文链:verify 先于 use → 合规", () => {
132
+ const fp = path.join(dir, "JwtTokenFilter.java");
133
+ fs.writeFileSync(fp, REAL_FILTER);
134
+ const fns = (0, extract_ir_java_1.extractJavaFile)(fp);
135
+ const calls = fns.find((f) => f.name === "doFilterInternal").calls;
136
+ (0, vitest_1.expect)(tokenVerifyBeforeUse(calls).ok).toBe(true);
137
+ (0, vitest_1.expect)(tokenVerifyBeforeUse(calls).why).toContain("verify→use");
138
+ });
139
+ (0, vitest_1.it)("变异(摘 verify):use 前无 verify → 违规被判定(0-FP 语义负例)", () => {
140
+ const fp = path.join(dir, "JwtTokenFilter.java");
141
+ fs.writeFileSync(fp, MUT_FILTER);
142
+ const fns = (0, extract_ir_java_1.extractJavaFile)(fp);
143
+ const calls = fns.find((f) => f.name === "doFilterInternal").calls;
144
+ (0, vitest_1.expect)(tokenVerifyBeforeUse(calls).ok).toBe(false);
145
+ (0, vitest_1.expect)(tokenVerifyBeforeUse(calls).why).toContain("无 verify");
146
+ });
147
+ });
@@ -76,8 +76,10 @@ function antToRegex(pattern) {
76
76
  function parseSecurityConfig(code) {
77
77
  const rules = [];
78
78
  let catchAll = null;
79
- // 逐个 .antMatchers(...) 与其后 .access() 配对
80
- const re = /\.antMatchers\(\s*([^)]*)\)\s*\.(\w+)\s*\(|\.anyRequest\(\)\s*\.(\w+)\s*\(/g;
79
+ // 逐个 .antMatchers/.requestMatchers(...) 与其后 .access() 配对
80
+ // (旧 DSL antMatchers + 新式 SecurityFilterChain authorizeHttpRequests
81
+ // requestMatchers——2026-09-02 Spring 方言扩展)
82
+ const re = /\.(?:antMatchers|requestMatchers)\s*\(\s*([^)]*)\)\s*\.(\w+)\s*\(|\.anyRequest\(\)\s*\.(\w+)\s*\(/g;
81
83
  let m;
82
84
  while ((m = re.exec(code)) !== null) {
83
85
  if (m[3] !== undefined) {
@@ -212,6 +214,8 @@ function analyzeSpringProject(projectRoot) {
212
214
  // 类级 @RequestMapping 只认类声明头(方法级 @RequestMapping 不算前缀)
213
215
  const clsReq = c.header.match(/@RequestMapping\s*\((?:path\s*=\s*)?["']([^"']*)["']/);
214
216
  const prefix = clsReq ? clsReq[1].replace(/^\//, "") : "";
217
+ // 类级方法安全:@PreAuthorize/@Secured 于类声明头 → 全部方法受保护
218
+ const classPreAuth = /@PreAuthorize|@Secured/.test(c.header);
215
219
  const annRe = /@(Get|Post|Put|Delete|Patch|Request)Mapping\s*(\([^)]*\))?/g;
216
220
  let m;
217
221
  while ((m = annRe.exec(c.body)) !== null) {
@@ -224,7 +228,7 @@ function analyzeSpringProject(projectRoot) {
224
228
  // 找到该 handler 起始行(用于 line + 后续注解如 @PreAuthorize)
225
229
  const line = c.body.slice(0, m.index).split("\n").length;
226
230
  const window = c.body.slice(m.index, m.index + 600);
227
- const preAuth = /@PreAuthorize/.test(window);
231
+ const preAuth = classPreAuth || /@PreAuthorize|@Secured/.test(window);
228
232
  allPaths.push(fullPath);
229
233
  routes.push({
230
234
  method,
@@ -120,3 +120,52 @@ public class ArticleApi {
120
120
  (0, vitest_1.expect)(a.issues.some((i) => i.route === "POST /articles")).toBe(true);
121
121
  });
122
122
  });
123
+ // ── Spring 方言扩展:SecurityFilterChain bean + requestMatchers + @PreAuthorize ──
124
+ const SEC_BEAN = `${PKG}
125
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
126
+ @Configuration @EnableWebSecurity
127
+ public class WebSecurityConfig {
128
+ @Bean
129
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
130
+ http.csrf().disable().authorizeHttpRequests(auth -> auth
131
+ .requestMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
132
+ .requestMatchers(HttpMethod.GET, "/articles/**", "/tags").permitAll()
133
+ .requestMatchers(HttpMethod.GET, "/articles/feed").authenticated()
134
+ .anyRequest().authenticated());
135
+ return http.build();
136
+ }
137
+ }`;
138
+ const ADMIN_CTRL = `${PKG}
139
+ @RestController @RequestMapping(path = "admin")
140
+ @PreAuthorize("hasRole('ADMIN')")
141
+ public class AdminApi {
142
+ @DeleteMapping("/users/{id}") public Object ban() { return null; }
143
+ @PostMapping("/reset") public Object reset() { return null; }
144
+ }`;
145
+ (0, vitest_1.describe)("spring 方言扩展(2026-09-02)", () => {
146
+ (0, vitest_1.it)("SecurityFilterChain bean + authorizeHttpRequests + requestMatchers 解析", () => {
147
+ writeJava("sec/WebSecurityConfig.java", SEC_BEAN);
148
+ writeJava("api/ArticleApi.java", CTRL);
149
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
150
+ (0, vitest_1.expect)(a.hasSecurityConfig).toBe(true);
151
+ (0, vitest_1.expect)(a.catchAll).toBe("authenticated");
152
+ (0, vitest_1.expect)(a.issues).toHaveLength(0);
153
+ const post = a.routes.find((r) => r.method === "post" && r.path === "/articles");
154
+ (0, vitest_1.expect)(post.access).toBe("authenticated");
155
+ });
156
+ (0, vitest_1.it)("类级 @PreAuthorize → 该类 mutation 全部受保护(不报)", () => {
157
+ writeJava("sec/WebSecurityConfig.java", SEC("permitAll")); // 兜底全公开
158
+ writeJava("api/AdminApi.java", ADMIN_CTRL);
159
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
160
+ // 兜底 permitAll 下,无注解类会报;@PreAuthorize 类不报
161
+ (0, vitest_1.expect)(a.issues.some((i) => i.route === "DELETE /admin/users/{id}")).toBe(false);
162
+ (0, vitest_1.expect)(a.issues.some((i) => i.route === "POST /admin/reset")).toBe(false);
163
+ });
164
+ (0, vitest_1.it)("方言反证:requestMatchers permitAll 的 mutation 公开(register 豁免外仍查)", () => {
165
+ const cfg = SEC_BEAN.replace(".requestMatchers(HttpMethod.GET, \"/articles/**\", \"/tags\").permitAll()", ".requestMatchers(HttpMethod.GET, \"/articles/**\", \"/tags\", \"/payments\").permitAll()").replace(/\.anyRequest\(\)\.authenticated\(\)/, ".anyRequest().permitAll()");
166
+ writeJava("sec/WebSecurityConfig.java", cfg);
167
+ writeJava("api/ArticleApi.java", CTRL);
168
+ const a = (0, spring_detector_1.analyzeSpringProject)(dir);
169
+ (0, vitest_1.expect)(a.issues.some((i) => i.route === "POST /articles")).toBe(true);
170
+ });
171
+ });
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.18";
23
+ exports.RUNTIME_VERSION = "3.7.19";
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.18",
3
+ "version": "3.7.19",
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/",