progmune-runtime 3.7.12 → 3.7.13

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.13] — 2026-09-02
4
+
5
+ ### Go 语言支持 + Gin/Fiber 框架适配——12/13
6
+
7
+ - **Go IR 提取器**(`src/extract-ir-go.ts`,纯 TS 词法——与 C 提取器同哲学:零外部工具链,npm 安装态可用,不重蹈 Python 桥的安装态覆辙):func 签名(多行/接收者方法)、调用提取(obj.Method() 取 Method)、注释注解 `// @progmune(...)` + 文档标签、exported=首字母大写、vendor/testdata/*_test.go 表面过滤、Go 关键字排除、反引号 raw string 掩码;8 个单测
8
+ - **注册表接入**:LANGUAGE_EXTRACTORS 第 4 语言(detect .go → extractIRGo)→ extractProjectIR/evaluateTrust 全链路自动生效;**引擎 autoExtractor 补 go**(修复 Go 项目静默走正则回退的同类陷阱——首轮金标全错暴露)
9
+ - **Go 协议盲测 v1**(`scan-protocol-golang.ts`,自包含生成+扫描):3 干净(直连/方法/helper)× 3 植入违规(missing_auth/read_without_open/leak)——**TP 5 / FP 0 / FN 0 → P=100% / R=100%**
10
+ - **Gin(第 11 个)**:`gin-detector.ts`——GIN_ROUTE_NO_AUTH(r.POST 中间件链 + Use/Group 组级认证);Go 方法名大写(r.POST)正则 i 标志修复;"middleware" 词误撞修复(loggerMiddleware 类工具中间件≠认证)
11
+ - **Fiber(第 12 个)**:`fiber-detector.ts`——FIBER_ROUTE_NO_AUTH(app.Post 中间件链 + Use);同款修复
12
+ - **引擎**:collectGinViolations/collectFiberViolations(Go 目录结构特殊:cmd/internal/pkg + 根目录 main.go)+ ginCoverage/fiberCoverage
13
+ - **文档**:Go 语言行 ❌→✅ 注解驱动(Beta)(README 双语/矩阵双语/CLAUDE.md/落地页 + i18n);框架 **12/13** 口径(剩余 Spring Boot 需 Java 支持先行)
14
+ - **验证**:23 个新单测(Go 提取器 8 + Gin 8 + Fiber 7);全套件 **204/204**;Python 盲测 v1.2 64 零漂移;C 演示不变;check 免疫正常
15
+
3
16
  ## [3.7.12] — 2026-09-01
4
17
 
5
18
  ### Koa + Hapi 框架结构适配——10/13
package/README.md CHANGED
@@ -104,11 +104,12 @@ Progmune is honest about what it can and cannot verify.
104
104
  | **TypeScript / JavaScript** | ✅ Production | Blind benchmark: **recall 98.5% / precision 100%** (795 gold findings, 100 projects) |
105
105
  | **Python** | ✅ Production | Blind benchmark: **recall 100% / precision 100%** (729 gold findings, 90 projects); real-world validation: PyGoat (OWASP vulnerable-by-design Django app) **67 TP / 0 FP, 100% labeled precision**; three well-written apps (django/fastapi realworld, django-unicorn) with 0 false-positive true findings |
106
106
  | **C** | ✅ Annotation-driven (Beta) | IR extraction via the registry + SSG state machine; **annotate ~2-3 protocol primitives per protocol to get trusted verification** (gold 5/5 on real modules: redis ACL / libssh client / libssh server / libssh callback-dispatch / uftpd transfer-auth — all 0 FP with precise violation localization; app-level gold v2: **P=91.7% / R=100% / F1=95.7%**). Unannotated auto-detection stays out of scope (0 TP on real corpus — positioning decision recorded in the C Language Status doc); TLS-level coverage still absent. See [C Language Status](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md). |
107
- | **Go, Java** | None | Planned |
107
+ | **Go** | Annotation-driven (Beta) | IR extraction via registry + SSG state machine; synthetic gold v1: **P=100% / R=100%** (clean×3 + violations×3); zero external toolchain (pure-TS lexical extractor — works from npm installs) |
108
+ | **Java** | ❌ None | Planned |
108
109
 
109
110
  **Multi-language IR (registry-based).** TypeScript (ts-morph) and Python (AST) extractors are registered entries in one registry (`src/extract-project-ir.ts`); `extractProjectIR` merges every detected language into a single function IR shared by the agent loop, `execute()`, and the MCP server — the agent composes function-protocol chains across both languages. Adding a language (Go, Java, …) = register one extractor entry; callers don't change.
110
111
 
111
- **Framework adapters: 10/13.** Express ✅, tRPC ✅, FastAPI ✅, Django ✅ (structural route-level auth analysis — every mutation route checked for auth), Flask ✅ (route/before_request auth guard analysis), Fastify ✅ (route preHandler/hook auth analysis) and Next.js ✅ (App Router route-handler auth analysis), NestJS ✅ (decorator route parsing + global APP_GUARD recognition + @Public exemption), Koa ✅ (route/auth-middleware chain analysis) and Hapi ✅ (route-config auth strategy analysis) have dedicated detectors. Next.js version-aware governance also exists. Spring Boot/Go frameworks and a few TS frameworks remain — framework adaptation is the #1 product gap.
112
+ **Framework adapters: 12/13.** Express ✅, tRPC ✅, FastAPI ✅, Django ✅ (structural route-level auth analysis — every mutation route checked for auth), Flask ✅ (route/before_request auth guard analysis), Fastify ✅ (route preHandler/hook auth analysis) and Next.js ✅ (App Router route-handler auth analysis), NestJS ✅ (decorator route parsing + global APP_GUARD recognition + @Public exemption), Koa ✅, Hapi ✅ (route-config auth strategy analysis), Gin ✅ and Fiber ✅ (Go route/auth-middleware analysis) have dedicated detectors. Next.js version-aware governance also exists. Spring Boot/Go frameworks and a few TS frameworks remain — framework adaptation is the #1 product gap.
112
113
 
113
114
  ### What Progmune does NOT cover (honest boundaries)
114
115
 
@@ -234,7 +235,7 @@ High-impact contribution areas:
234
235
  - **Runtime Pipeline:** Detect → Explain → Repair → Validate (L1–L4)
235
236
  - **Trust Engine:** 4-dimension scoring with binary explainability gate
236
237
  - **MCP Tools:** 19 — `progmune_trust_check`, `progmune_score`, `progmune_policy_check`, `progmune_certify`, and more
237
- - **Framework Adapters:** Express ✅, tRPC ✅, FastAPI ✅, Django ✅, Flask ✅, Fastify ✅, Next.js ✅, NestJS ✅ (10/13)
238
+ - **Framework Adapters:** Express ✅, tRPC ✅, FastAPI ✅, Django ✅, Flask ✅, Fastify ✅, Next.js ✅, NestJS ✅ (12/13)
238
239
  - **Knowledge Base:** 31 domains, 148 protocol rules, 22 detectors, 26 safeguards, PLSB 13/13 categories — plus 15 source-level detection rules (Python)
239
240
  - **Corpus:** 2,500+ trajectories across 6+ repositories; blind benchmarks 100 (TS) + 90 (Python) projects; real-world validation on 4 application repos
240
241
  - **Current focus:** Enterprise PoC validation + remaining framework-internal boundary FPs
package/README.zh-CN.md CHANGED
@@ -104,11 +104,12 @@ Progmune 对能验证什么、不能验证什么保持诚实。
104
104
  | **TypeScript / JavaScript** | ✅ 生产 | 盲测基准:**召回 98.5% / 精确率 100%**(795 条 gold finding,100 个项目) |
105
105
  | **Python** | ✅ 生产 | 盲测基准:**召回 100% / 精确率 100%**(729 条 gold finding,90 个项目);真实应用验证:PyGoat(OWASP 故意脆弱 Django 应用)**67 TP / 0 FP,标记精确率 100%**;三个良构应用(django/fastapi realworld、django-unicorn)0 误报真阳性 |
106
106
  | **C** | ✅ 注解驱动(Beta) | IR 提取接入注册表 + SSG 状态机;**每协议标注 ~2-3 个原语即获得可信验证**(真实模块金标 5/5:redis ACL / libssh 客户端 / libssh 服务端 / libssh 回调分发 / uftpd 传送授权——全部 0 误报 + 违规精确定位;应用级金标 v2:**P=91.7% / R=100% / F1=95.7%**)。未注解自动检测不在范围(真实语料 0 TP——定位决议见 C 语言状态文档);TLS 级覆盖仍无。见 [C 语言状态](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md)。 |
107
- | **Go, Java** | | 规划中 |
107
+ | **Go** | 注解驱动(Beta) | IR 提取接入注册表 + SSG 状态机;合成金标 v1:**P=100% / R=100%**(3 干净 × 3 植入违规);零外部工具链(纯 TS 词法提取,npm 安装态可用) |
108
+ | **Java** | ❌ 无 | 规划中 |
108
109
 
109
110
  **多语言 IR(注册表式)。** TypeScript(ts-morph)与 Python(AST)提取器是同一注册表(`src/extract-project-ir.ts`)中的注册项;`extractProjectIR` 把检测到的所有语言合并为一份函数 IR,由 agent loop、`execute()` 与 MCP server 共享——agent 可在两种语言上编排函数协议链。新增语言(Go、Java……)= 注册一条提取器,调用方零改动。
110
111
 
111
- **框架适配器:10/13。** Express ✅、tRPC ✅、FastAPI ✅、Django ✅(结构级路由认证分析——逐条检查写操作入口的认证)、Flask ✅(路由/before_request 认证守卫分析)、Fastify ✅(路由 preHandler/钩子认证分析)与 Next.js ✅(App Router 路由处理器认证分析)、NestJS ✅(装饰器路由解析 + 全局 APP_GUARD 守卫识别 + @Public 豁免)、Koa ✅(路由/认证中间件链分析)与 Hapi ✅(路由配置认证策略分析)有专用检测器;Next.js 另有版本感知治理。Spring Boot、Go 框架及少数 TS 框架待适配——框架适配是 #1 产品缺口。
112
+ **框架适配器:12/13。** Express ✅、tRPC ✅、FastAPI ✅、Django ✅(结构级路由认证分析——逐条检查写操作入口的认证)、Flask ✅(路由/before_request 认证守卫分析)、Fastify ✅(路由 preHandler/钩子认证分析)与 Next.js ✅(App Router 路由处理器认证分析)、NestJS ✅(装饰器路由解析 + 全局 APP_GUARD 守卫识别 + @Public 豁免)、Koa ✅、Hapi ✅(路由配置认证策略分析)、Gin ✅ 与 Fiber ✅(Go 路由/认证中间件分析)有专用检测器;Next.js 另有版本感知治理。Spring Boot、Go 框架及少数 TS 框架待适配——框架适配是 #1 产品缺口。
112
113
 
113
114
  ### Progmune 不覆盖什么(诚实边界)
114
115
 
@@ -233,7 +234,7 @@ Progmune 建立在"**LLM 输出是统计表演而非推理**"这一前提上—
233
234
  - **运行时管线:** 检测 → 解释 → 修复 → 验证(L1–L4)
234
235
  - **信任引擎:** 四维评分 + 二元可解释性门
235
236
  - **MCP 工具:** 19 个——`progmune_trust_check`、`progmune_score`、`progmune_policy_check`、`progmune_certify` 等
236
- - **框架适配器:** Express ✅、tRPC ✅、FastAPI ✅、Django ✅、Flask ✅、Fastify ✅、Next.js ✅、NestJS ✅(10/13)
237
+ - **框架适配器:** Express ✅、tRPC ✅、FastAPI ✅、Django ✅、Flask ✅、Fastify ✅、Next.js ✅、NestJS ✅(12/13)
237
238
  - **知识库:** 31 个域、148 条协议规则、22 个检测器、26 条防护规则、PLSB 13/13 类别——另加 15 条源码级检测规则(Python)
238
239
  - **语料:** 6+ 仓库 2,500+ 轨迹;盲测基准 100(TS)+ 90(Python)个项目;4 个应用仓库真实验证
239
240
  - **当前重点:** 企业 PoC 验证 + 剩余框架内部边界误报
@@ -0,0 +1,383 @@
1
+ "use strict";
2
+ /**
3
+ * Go IR Extractor — pure-TS function extraction for the merged multi-language
4
+ * registry (extract-project-ir.ts LANGUAGE_EXTRACTORS)。无子进程、无 Go
5
+ * 工具链依赖(npm 安装态可用——与 C 提取器同哲学,区别于 Python 桥)。
6
+ *
7
+ * Route: the registry "go" entry calls extractIRGo; the result merges into
8
+ * the same FunctionInfo list as TypeScript/Python/C and flows into
9
+ * call-sequence (P4.6), SSG validation and the agent loop with no rewiring.
10
+ *
11
+ * Extraction is a best-effort lexical parse of Go definitions:
12
+ * - func Name(params) returns { ... } 普通函数
13
+ * - func (r *Repo) Name(params) returns { } 接收者方法(方法名=Name)
14
+ * - 多行签名(平衡括号)、返回值类型 best-effort 记录
15
+ * - 直接调用提取(obj.Method() 取 Method——与 C 成员调用同规则)
16
+ * - 注释注解:// @progmune(namespace=..., pre=[...], post=[...])
17
+ * + @purpose/@tags/@requires/@produces/@useWhen/@inputs/@outputs 文档标签
18
+ * - exported = 首字母大写(Go 语言约定)
19
+ *
20
+ * Known limits(如实):
21
+ * - 接口方法/函数值分发静态不可见(interface 与 func 变量同 L3 边界)
22
+ * - 泛型类型参数(func F[T any](...))best-effort 剥除,不做完整解析
23
+ * - 无数据流/指针分析——与 C 的 L3/L4 结论同源
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.extractIRGo = extractIRGo;
60
+ const fs = __importStar(require("fs"));
61
+ const path = __importStar(require("path"));
62
+ // ── Constants ──
63
+ const GO_EXTENSIONS = new Set([".go"]);
64
+ /** Walk skip set(生产表面守卫,与 C/Python 提取器口径一致)。 */
65
+ const SKIP_DIRS = new Set([
66
+ "vendor", "testdata", "test", "tests", "examples", "example", "docs",
67
+ "doc", "third_party", "node_modules", ".git", "scripts", "tools",
68
+ ]);
69
+ /** Go 关键字/内置(调用提取时排除)。 */
70
+ const GO_KEYWORDS = new Set([
71
+ "break", "case", "chan", "const", "continue", "default", "defer", "else",
72
+ "fallthrough", "for", "func", "go", "goto", "if", "import", "interface",
73
+ "map", "package", "range", "return", "select", "struct", "switch", "type",
74
+ "var", "append", "cap", "close", "complex", "copy", "delete", "imag",
75
+ "len", "make", "new", "panic", "print", "println", "real", "recover",
76
+ ]);
77
+ function isTestFilename(name) {
78
+ return name.endsWith("_test.go");
79
+ }
80
+ function maskLine(line, state) {
81
+ let out = "";
82
+ for (let i = 0; i < line.length; i++) {
83
+ const ch = line[i];
84
+ const next = line[i + 1];
85
+ if (state.inBlock) {
86
+ if (ch === "*" && next === "/") {
87
+ state.inBlock = false;
88
+ i++;
89
+ }
90
+ out += " ";
91
+ }
92
+ else if (state.inString) {
93
+ if (ch === "\\") {
94
+ out += " ";
95
+ i++;
96
+ }
97
+ else if (ch === '"') {
98
+ state.inString = false;
99
+ out += " ";
100
+ }
101
+ else
102
+ out += " ";
103
+ }
104
+ else if (state.inRaw) {
105
+ if (ch === "`") {
106
+ state.inRaw = false;
107
+ out += " ";
108
+ }
109
+ else
110
+ out += " ";
111
+ }
112
+ else if (state.inRune) {
113
+ if (ch === "\\") {
114
+ out += " ";
115
+ i++;
116
+ }
117
+ else if (ch === "'") {
118
+ state.inRune = false;
119
+ out += " ";
120
+ }
121
+ else
122
+ out += " ";
123
+ }
124
+ else {
125
+ if (ch === "/" && next === "*") {
126
+ state.inBlock = true;
127
+ i++;
128
+ out += " ";
129
+ }
130
+ else if (ch === "/" && next === "/") {
131
+ out += " ";
132
+ break;
133
+ }
134
+ else if (ch === '"') {
135
+ state.inString = true;
136
+ out += " ";
137
+ }
138
+ else if (ch === "`") {
139
+ state.inRaw = true;
140
+ out += " ";
141
+ }
142
+ else if (ch === "'") {
143
+ state.inRune = true;
144
+ out += " ";
145
+ }
146
+ else
147
+ out += ch;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+ function parseGoAnnotation(text) {
153
+ const m = text.match(/@progmune\s*\(\s*([^)]*)\)/);
154
+ if (!m)
155
+ return null;
156
+ const body = m[1];
157
+ const ann = { pre_states: [], post_states: [] };
158
+ const ns = body.match(/namespace\s*=\s*"([^"]+)"/);
159
+ if (ns)
160
+ ann.namespace = ns[1];
161
+ const states = (key) => {
162
+ const mm = body.match(new RegExp(`${key}\\s*=\\s*\\[([^\\]]*)\\]`));
163
+ if (!mm)
164
+ return [];
165
+ const vals = mm[1].match(/"([^"]+)"/g) || [];
166
+ return vals.map((v) => v.slice(1, -1));
167
+ };
168
+ ann.pre_states = states("pre");
169
+ ann.post_states = states("post");
170
+ const inv = states("invalidate");
171
+ if (inv.length > 0)
172
+ ann.invalidate = inv;
173
+ return ann;
174
+ }
175
+ function docTag(text, tag) {
176
+ const m = text.match(new RegExp(`@${tag}\\s+([^\\n*]+)`));
177
+ return m ? m[1].trim() : undefined;
178
+ }
179
+ // ── 调用提取 ──
180
+ function extractCallsFromBody(body) {
181
+ const calls = new Set();
182
+ const re = /([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
183
+ let m;
184
+ while ((m = re.exec(body)) !== null) {
185
+ const name = m[1];
186
+ if (GO_KEYWORDS.has(name))
187
+ continue;
188
+ if (GO_KEYWORDS.has(name))
189
+ continue;
190
+ calls.add(name);
191
+ }
192
+ return Array.from(calls);
193
+ }
194
+ // ── 顶层参数切分 ──
195
+ function splitTopLevelParams(s) {
196
+ const parts = [];
197
+ let depth = 0;
198
+ let cur = "";
199
+ for (const ch of s) {
200
+ if (ch === "(" || ch === "[" || ch === "{")
201
+ depth++;
202
+ if (ch === ")" || ch === "]" || ch === "}")
203
+ depth--;
204
+ if (ch === "," && depth === 0) {
205
+ parts.push(cur.trim());
206
+ cur = "";
207
+ }
208
+ else
209
+ cur += ch;
210
+ }
211
+ if (cur.trim())
212
+ parts.push(cur.trim());
213
+ return parts.filter(Boolean);
214
+ }
215
+ // ── 单文件提取 ──
216
+ function extractGoFile(filePath) {
217
+ let content;
218
+ try {
219
+ content = fs.readFileSync(filePath, "utf-8");
220
+ }
221
+ catch {
222
+ return [];
223
+ }
224
+ const lines = content.split("\n");
225
+ const functions = [];
226
+ for (let i = 0; i < lines.length; i++) {
227
+ // 函数定义行:func Name( 或 func (receiver) Name(
228
+ const plain = lines[i].match(/^\s*func\s+([A-Za-z_][A-Za-z0-9_]*)/);
229
+ const receiver = lines[i].match(/^\s*func\s*\([^)]*\)\s*([A-Za-z_][A-Za-z0-9_]*)/);
230
+ const name = plain ? plain[1] : receiver ? receiver[1] : null;
231
+ if (!name)
232
+ continue;
233
+ // 多行签名:从 func 起拼接直到找到 { 或 ;
234
+ let sigText = lines[i];
235
+ let j = i;
236
+ while (!sigText.includes("{") && !sigText.includes(";") && j < lines.length - 1) {
237
+ j++;
238
+ sigText += "\n" + lines[j];
239
+ }
240
+ const braceIdx = sigText.indexOf("{");
241
+ if (braceIdx < 0)
242
+ continue; // 接口方法声明(无函数体)
243
+ const signature = sigText.slice(0, braceIdx);
244
+ // 注释块注解:函数定义行上方的连续注释(// 与 /* */)
245
+ const commentLines = [];
246
+ for (let k = i - 1; k >= 0; k--) {
247
+ const t = lines[k].trim();
248
+ if (t.startsWith("//") || t.startsWith("/*") || t.startsWith("*")) {
249
+ commentLines.unshift(t);
250
+ }
251
+ else if (t === "") {
252
+ if (commentLines.length === 0)
253
+ continue;
254
+ break;
255
+ }
256
+ else {
257
+ break;
258
+ }
259
+ }
260
+ const commentText = commentLines.join("\n");
261
+ // 注解解析
262
+ let protocol;
263
+ const ann = parseGoAnnotation(commentText);
264
+ if (ann) {
265
+ protocol = {
266
+ pre_states: ann.pre_states,
267
+ post_states: ann.post_states,
268
+ ...(ann.invalidate ? { invalidate: ann.invalidate } : {}),
269
+ ...(ann.namespace ? { namespace: ann.namespace } : {}),
270
+ };
271
+ }
272
+ const purpose = docTag(commentText, "purpose");
273
+ const tags = docTag(commentText, "tags")?.split(/[,\s]+/).filter(Boolean);
274
+ const requires = docTag(commentText, "requires")?.split(/[,\s]+/).filter(Boolean);
275
+ const produces = docTag(commentText, "produces")?.split(/[,\s]+/).filter(Boolean);
276
+ const useWhen = docTag(commentText, "useWhen")?.split(/[,\s]+/).filter(Boolean);
277
+ // 参数列表:签名中第一个 ( ... )
278
+ const openParen = signature.indexOf("(");
279
+ const closeParen = signature.indexOf(")", openParen);
280
+ let params = [];
281
+ if (openParen >= 0 && closeParen > openParen) {
282
+ params = splitTopLevelParams(signature.slice(openParen + 1, closeParen));
283
+ }
284
+ // 返回类型:签名尾部(剥除泛型括号)
285
+ const returnType = signature.slice(closeParen + 1).replace(/\[[^\]]*\]/g, "").trim();
286
+ // 函数体:从签名行(j)的 { 起逐行扫描到匹配 }(掩码后括号平衡,
287
+ // 增量计数——每行只数一次,避免累积体重复计数)
288
+ let body = "";
289
+ let depth = 0;
290
+ let started = false;
291
+ let endLine = j;
292
+ const bodyMask = { inBlock: false, inString: false, inRaw: false, inRune: false };
293
+ for (let li = j; li < lines.length; li++) {
294
+ const maskedLine = maskLine(lines[li], bodyMask);
295
+ if (!started) {
296
+ const b = maskedLine.indexOf("{");
297
+ if (b < 0)
298
+ continue; // 多行签名的后续行还没到 {
299
+ started = true;
300
+ for (const ch of maskedLine.slice(b)) {
301
+ if (ch === "{")
302
+ depth++;
303
+ else if (ch === "}")
304
+ depth--;
305
+ }
306
+ }
307
+ else {
308
+ for (const ch of maskedLine) {
309
+ if (ch === "{")
310
+ depth++;
311
+ else if (ch === "}")
312
+ depth--;
313
+ }
314
+ }
315
+ body += lines[li] + "\n";
316
+ if (depth === 0) {
317
+ endLine = li;
318
+ break;
319
+ }
320
+ }
321
+ if (!started || depth !== 0)
322
+ continue;
323
+ // 调用提取(掩码态重置后重扫完整函数体)
324
+ const resetMask = { inBlock: false, inString: false, inRaw: false, inRune: false };
325
+ const maskedBody = body.split("\n").map((l) => maskLine(l, resetMask)).join("\n");
326
+ const calls = extractCallsFromBody(maskedBody).filter((c) => c !== name);
327
+ functions.push({
328
+ name,
329
+ file: filePath,
330
+ params: params.map((p) => ({ name: p.split(/[\s\[\]]/)[0] || p, type: p })),
331
+ returnType: returnType || "unknown",
332
+ calls,
333
+ exported: /^[A-Z]/.test(name),
334
+ tags: tags && tags.length > 0 ? tags : undefined,
335
+ ...(purpose ? { purpose } : {}),
336
+ ...(requires && requires.length > 0 ? { requires } : {}),
337
+ ...(produces && produces.length > 0 ? { produces } : {}),
338
+ ...(useWhen && useWhen.length > 0 ? { useWhen } : {}),
339
+ ...(protocol ? { protocol } : {}),
340
+ });
341
+ i = endLine; // 跳过已消费的行
342
+ }
343
+ return functions;
344
+ }
345
+ // ── 项目级入口(注册表调用) ──
346
+ function collectGoFiles(root) {
347
+ const files = [];
348
+ const walk = (dir) => {
349
+ let entries;
350
+ try {
351
+ entries = fs.readdirSync(dir, { withFileTypes: true });
352
+ }
353
+ catch {
354
+ return;
355
+ }
356
+ for (const e of entries) {
357
+ if (e.isSymbolicLink())
358
+ continue;
359
+ const full = path.join(dir, e.name);
360
+ if (e.isDirectory()) {
361
+ if (SKIP_DIRS.has(e.name))
362
+ continue;
363
+ walk(full);
364
+ }
365
+ else if (GO_EXTENSIONS.has(path.extname(e.name)) && !isTestFilename(e.name)) {
366
+ files.push(full);
367
+ }
368
+ }
369
+ };
370
+ walk(root);
371
+ return files;
372
+ }
373
+ function extractIRGo(projectRoot) {
374
+ const files = collectGoFiles(projectRoot);
375
+ const all = [];
376
+ for (const file of files) {
377
+ for (const fn of extractGoFile(file)) {
378
+ fn.file = path.relative(projectRoot, fn.file);
379
+ all.push(fn);
380
+ }
381
+ }
382
+ return all;
383
+ }
@@ -0,0 +1,179 @@
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
+ * extract-ir-go.test.ts — Go IR 提取器回归(临时目录夹具)
38
+ *
39
+ * 纯 TS 词法提取(与 C 提取器同哲学):函数签名(多行/接收者方法)、
40
+ * 调用(obj.Method() 取 Method)、注释注解 @progmune + 文档标签、
41
+ * exported=首字母大写、非生产表面过滤。
42
+ */
43
+ const vitest_1 = require("vitest");
44
+ const fs = __importStar(require("fs"));
45
+ const os = __importStar(require("os"));
46
+ const path = __importStar(require("path"));
47
+ const extract_ir_go_1 = require("./extract-ir-go");
48
+ let dir;
49
+ (0, vitest_1.beforeEach)(() => {
50
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "go-ir-"));
51
+ });
52
+ (0, vitest_1.afterEach)(() => {
53
+ fs.rmSync(dir, { recursive: true, force: true });
54
+ });
55
+ function write(rel, content) {
56
+ const full = path.join(dir, rel);
57
+ fs.mkdirSync(path.dirname(full), { recursive: true });
58
+ fs.writeFileSync(full, content);
59
+ }
60
+ (0, vitest_1.describe)("extract-ir-go", () => {
61
+ (0, vitest_1.it)("普通函数 + 调用提取(含 obj.Method() 成员调用)", () => {
62
+ write("main.go", `
63
+ package main
64
+
65
+ func doWork() {
66
+ helper()
67
+ obj.Save()
68
+ }
69
+
70
+ func helper() {}
71
+ `);
72
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
73
+ const doWork = ir.find((f) => f.name === "doWork");
74
+ (0, vitest_1.expect)(doWork?.calls).toEqual(vitest_1.expect.arrayContaining(["helper", "Save"]));
75
+ });
76
+ (0, vitest_1.it)("接收者方法(func (r *Repo) Name)", () => {
77
+ write("repo.go", `
78
+ package main
79
+
80
+ func (r *Repo) SaveUser(u User) error {
81
+ return nil
82
+ }
83
+ `);
84
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
85
+ (0, vitest_1.expect)(ir.some((f) => f.name === "SaveUser")).toBe(true);
86
+ (0, vitest_1.expect)(ir.find((f) => f.name === "SaveUser")?.exported).toBe(true);
87
+ });
88
+ (0, vitest_1.it)("多行签名 + 返回类型", () => {
89
+ write("multi.go", `
90
+ package main
91
+
92
+ func Process(
93
+ name string,
94
+ count int,
95
+ ) (string, error) {
96
+ return name, nil
97
+ }
98
+ `);
99
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
100
+ const fn = ir.find((f) => f.name === "Process");
101
+ (0, vitest_1.expect)(fn).toBeTruthy();
102
+ (0, vitest_1.expect)(fn?.returnType).toContain("string");
103
+ });
104
+ (0, vitest_1.it)("注释注解 @progmune + 文档标签", () => {
105
+ write("auth.go", `
106
+ package main
107
+
108
+ // @progmune(namespace="auth", pre=["UNAUTHENTICATED"], post=["PASSWORD_VERIFIED"])
109
+ // @purpose 凭证比对
110
+ // @tags auth
111
+ func VerifyPassword(user, password string) bool {
112
+ return password == "secret"
113
+ }
114
+ `);
115
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
116
+ const fn = ir.find((f) => f.name === "VerifyPassword");
117
+ (0, vitest_1.expect)(fn?.protocol?.pre_states).toEqual(["UNAUTHENTICATED"]);
118
+ (0, vitest_1.expect)(fn?.protocol?.post_states).toEqual(["PASSWORD_VERIFIED"]);
119
+ (0, vitest_1.expect)(fn?.protocol?.namespace).toBe("auth");
120
+ (0, vitest_1.expect)(fn?.purpose).toBe("凭证比对");
121
+ (0, vitest_1.expect)(fn?.tags).toContain("auth");
122
+ (0, vitest_1.expect)(fn?.exported).toBe(true);
123
+ });
124
+ (0, vitest_1.it)("接口方法声明(无函数体)不提取", () => {
125
+ write("iface.go", `
126
+ package main
127
+
128
+ type Store interface {
129
+ Save(u User) error
130
+ }
131
+ `);
132
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
133
+ (0, vitest_1.expect)(ir.some((f) => f.name === "Save")).toBe(false);
134
+ });
135
+ (0, vitest_1.it)("字符串/注释里的伪调用不提取", () => {
136
+ write("str.go", `
137
+ package main
138
+
139
+ func tricky() {
140
+ s := "notAFunction() inside string"
141
+ // notAFunction() in comment
142
+ realCall()
143
+ }
144
+
145
+ func realCall() {}
146
+ `);
147
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
148
+ const fn = ir.find((f) => f.name === "tricky");
149
+ (0, vitest_1.expect)(fn?.calls).toContain("realCall");
150
+ (0, vitest_1.expect)(fn?.calls).not.toContain("notAFunction");
151
+ });
152
+ (0, vitest_1.it)("非生产表面过滤:vendor/testdata/测试文件跳过", () => {
153
+ write("main.go", `package main\nfunc Keep() {}\n`);
154
+ write("vendor/dep.go", `package dep\nfunc SkipVendor() {}\n`);
155
+ write("testdata/sample.go", `package sample\nfunc SkipTestdata() {}\n`);
156
+ write("x_test.go", `package main\nfunc SkipTest() {}\n`);
157
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
158
+ (0, vitest_1.expect)(ir.map((f) => f.name)).toEqual(["Keep"]);
159
+ });
160
+ (0, vitest_1.it)("Go 关键字不提取为调用(for/if/go/defer 等)", () => {
161
+ write("ctrl.go", `
162
+ package main
163
+
164
+ func run() {
165
+ for i := 0; i < 3; i++ {
166
+ go step(i)
167
+ defer cleanup()
168
+ }
169
+ }
170
+
171
+ func step(i int) {}
172
+ func cleanup() {}
173
+ `);
174
+ const ir = (0, extract_ir_go_1.extractIRGo)(dir);
175
+ const fn = ir.find((f) => f.name === "run");
176
+ (0, vitest_1.expect)(fn?.calls).toEqual(vitest_1.expect.arrayContaining(["step", "cleanup"]));
177
+ (0, vitest_1.expect)(fn?.calls).not.toEqual(vitest_1.expect.arrayContaining(["for", "go", "defer", "if"]));
178
+ });
179
+ });
@@ -54,6 +54,7 @@ const path = __importStar(require("path"));
54
54
  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
+ const extract_ir_go_1 = require("./extract-ir-go");
57
58
  const SKIP_DIRS = new Set([
58
59
  "node_modules", "dist", "build", ".git", ".progmune_corpus",
59
60
  "__pycache__", "venv", ".venv",
@@ -105,6 +106,11 @@ exports.LANGUAGE_EXTRACTORS = [
105
106
  detect: (p) => hasSourceFiles(p, new Set([".c", ".h"])),
106
107
  extract: (p) => (0, extract_ir_c_1.extractIRC)(p),
107
108
  },
109
+ {
110
+ language: "go",
111
+ detect: (p) => hasSourceFiles(p, new Set([".go"])),
112
+ extract: (p) => (0, extract_ir_go_1.extractIRGo)(p),
113
+ },
108
114
  ];
109
115
  /** 项目检测到的语言列表(审计/标签用)。 */
110
116
  function detectLanguages(projectRoot) {
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * Fiber Framework Adapter — Protocol Detection for Fiber (Go)
4
+ *
5
+ * 第 12 个框架适配(Go 第 2 个,代码串级——Fiber 路由与 Express 同构):
6
+ *
7
+ * app.Post("/x", authMW, handler) 路由级认证中间件链
8
+ * app.Use(authMW) / group.Use(authMW) 全局/组级认证中间件
9
+ *
10
+ * 规则:
11
+ * FIBER_ROUTE_NO_AUTH mutation 路由(post/put/patch/delete)中间件
12
+ * 链无认证名中间件,且文件内无认证 Use 中间件
13
+ *
14
+ * 口径(如实):
15
+ * - get 读操作不检查;认证入口路径词汇豁免(login/regist/auth/token)
16
+ * - 认证中间件按名字词表识别(auth/jwt/login/permission/token/session/
17
+ * verify/guard/passport)
18
+ * - 文件级窗口(与 Express/Koa 检测器同款)
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
+ exports.analyzeFiberApp = analyzeFiberApp;
55
+ exports.analyzeFiberFile = analyzeFiberFile;
56
+ const fs = __importStar(require("fs"));
57
+ const MUTATION_METHODS = new Set(["post", "put", "patch", "delete"]);
58
+ const AUTH_ENTRY_WORDS = [
59
+ "login", "signin", "sign_in", "regist", "signup", "sign_up",
60
+ "token", "auth", "health",
61
+ ];
62
+ const AUTH_FN_WORDS = [
63
+ "auth", "login", "permission", "token", "credential", "session",
64
+ "jwt", "verify", "guard", "protect", "passport",
65
+ ];
66
+ function isAuthEntryPath(pathName) {
67
+ const lower = pathName.toLowerCase();
68
+ return AUTH_ENTRY_WORDS.some((w) => lower.includes(w));
69
+ }
70
+ function isAuthFnName(name) {
71
+ const lower = name.toLowerCase();
72
+ return AUTH_FN_WORDS.some((w) => lower.includes(w));
73
+ }
74
+ // ── Analysis(代码串级) ──
75
+ function analyzeFiberApp(code) {
76
+ const issues = [];
77
+ const routes = [];
78
+ const authMiddleware = [];
79
+ const hasFiber = /fiber\.New\(|"github.com\/gofiber\/fiber/.test(code);
80
+ if (!hasFiber) {
81
+ return { hasFiber: false, routes, authMiddleware, issues };
82
+ }
83
+ // 全局/组级认证中间件:app.Use(authMW) / group.Use(authMW)
84
+ const useRe = /\.Use\s*\(\s*([A-Za-z_][\w]*)/g;
85
+ let m;
86
+ while ((m = useRe.exec(code)) !== null) {
87
+ if (isAuthFnName(m[1]))
88
+ authMiddleware.push(m[1]);
89
+ }
90
+ // 路由注册:app.Post("/x", mw1, mw2, handler)——Go 方法名大写(Post 惯例)
91
+ const routeRe = /\.(get|post|put|patch|delete)\s*\(\s*"([^"]+)"/gi;
92
+ while ((m = routeRe.exec(code)) !== null) {
93
+ const method = m[1].toLowerCase();
94
+ const pathName = m[2];
95
+ const window = code.slice(m.index + m[0].length, m.index + m[0].length + 300);
96
+ const mwNames = window.match(/[A-Za-z_][\w]*/g) || [];
97
+ const hasAuthMw = mwNames.some((name) => isAuthFnName(name));
98
+ routes.push({
99
+ method,
100
+ path: pathName,
101
+ protected: hasAuthMw,
102
+ line: code.slice(0, m.index).split("\n").length,
103
+ });
104
+ if (MUTATION_METHODS.has(method) && !hasAuthMw
105
+ && authMiddleware.length === 0 && !isAuthEntryPath(pathName)) {
106
+ issues.push({
107
+ severity: "medium",
108
+ rule: "FIBER_ROUTE_NO_AUTH",
109
+ message: `Route ${method.toUpperCase()} ${pathName} has no auth middleware ` +
110
+ `and no auth Use middleware — any caller can reach it.`,
111
+ route: `${method.toUpperCase()} ${pathName}`,
112
+ line: code.slice(0, m.index).split("\n").length,
113
+ });
114
+ }
115
+ }
116
+ return { hasFiber: true, routes, authMiddleware, issues };
117
+ }
118
+ function analyzeFiberFile(filePath) {
119
+ if (!fs.existsSync(filePath))
120
+ return null;
121
+ const code = fs.readFileSync(filePath, "utf-8");
122
+ if (!/gofiber|fiber\.New\(/.test(code))
123
+ return null;
124
+ return analyzeFiberApp(code);
125
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * fiber-detector.test.ts — Fiber 框架适配器规则回归(纯函数,无文件 I/O)
5
+ */
6
+ const vitest_1 = require("vitest");
7
+ const fiber_detector_1 = require("./fiber-detector");
8
+ const app = (routes, extra = "") => `
9
+ import "github.com/gofiber/fiber/v2"
10
+
11
+ func main() {
12
+ app := fiber.New()
13
+ ${extra}
14
+ ${routes}
15
+ }
16
+ `;
17
+ (0, vitest_1.describe)("fiber-detector", () => {
18
+ (0, vitest_1.it)("R1:无认证中间件的 mutation 路由 → FIBER_ROUTE_NO_AUTH", () => {
19
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`
20
+ app.Post("/transfer", func(c *fiber.Ctx) error { return nil })
21
+ `));
22
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("FIBER_ROUTE_NO_AUTH");
23
+ });
24
+ (0, vitest_1.it)("R1:路由级认证中间件保护不报", () => {
25
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`
26
+ app.Post("/transfer", authMiddleware, func(c *fiber.Ctx) error { return nil })
27
+ `));
28
+ (0, vitest_1.expect)(issues).toHaveLength(0);
29
+ });
30
+ (0, vitest_1.it)("R1:全局 Use 认证中间件保护不报", () => {
31
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`app.Post("/transfer", func(c *fiber.Ctx) error { return nil })`, `app.Use(authMiddleware)`));
32
+ (0, vitest_1.expect)(issues).toHaveLength(0);
33
+ });
34
+ (0, vitest_1.it)("R1:非认证中间件不视为保护", () => {
35
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`
36
+ app.Post("/transfer", logger, func(c *fiber.Ctx) error { return nil })
37
+ `));
38
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("FIBER_ROUTE_NO_AUTH");
39
+ });
40
+ (0, vitest_1.it)("R1:GET 读操作不报", () => {
41
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`
42
+ app.Get("/articles", func(c *fiber.Ctx) error { return nil })
43
+ `));
44
+ (0, vitest_1.expect)(issues).toHaveLength(0);
45
+ });
46
+ (0, vitest_1.it)("R1 豁免:login 认证入口路径不报", () => {
47
+ const { issues } = (0, fiber_detector_1.analyzeFiberApp)(app(`
48
+ app.Post("/login", func(c *fiber.Ctx) error { return nil })
49
+ `));
50
+ (0, vitest_1.expect)(issues).toHaveLength(0);
51
+ });
52
+ (0, vitest_1.it)("非 Fiber 代码不产生任何问题", () => {
53
+ const { hasFiber, issues } = (0, fiber_detector_1.analyzeFiberApp)(`import express from "express";`);
54
+ (0, vitest_1.expect)(hasFiber).toBe(false);
55
+ (0, vitest_1.expect)(issues).toHaveLength(0);
56
+ });
57
+ });
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * Gin Framework Adapter — Protocol Detection for Gin (Go)
4
+ *
5
+ * 第 11 个框架适配(Go 第 1 个,代码串级——Go 路由注册与 Koa/Express 同构):
6
+ *
7
+ * r.POST("/x", authMW, handler) 路由级认证中间件链
8
+ * r.Use(authMW) / r.Group("/api", mw) 组级认证中间件
9
+ *
10
+ * 规则:
11
+ * GIN_ROUTE_NO_AUTH mutation 路由(post/put/patch/delete)中间件
12
+ * 链无认证名中间件,且文件内无认证 Use/Group
13
+ * 中间件——路由级 missing-auth
14
+ *
15
+ * 口径(如实):
16
+ * - get 读操作不检查;认证入口路径词汇豁免(login/regist/auth/token)
17
+ * - 认证中间件按名字词表识别(auth/jwt/login/permission/token/session/
18
+ * verify/guard/passport)
19
+ * - 文件级窗口(与 Express/Koa 检测器同款)
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.analyzeGinApp = analyzeGinApp;
56
+ exports.analyzeGinFile = analyzeGinFile;
57
+ const fs = __importStar(require("fs"));
58
+ const MUTATION_METHODS = new Set(["post", "put", "patch", "delete"]);
59
+ const AUTH_ENTRY_WORDS = [
60
+ "login", "signin", "sign_in", "regist", "signup", "sign_up",
61
+ "token", "auth", "health",
62
+ ];
63
+ const AUTH_FN_WORDS = [
64
+ "auth", "login", "permission", "token", "credential", "session",
65
+ "jwt", "verify", "guard", "protect", "passport",
66
+ ];
67
+ function isAuthEntryPath(pathName) {
68
+ const lower = pathName.toLowerCase();
69
+ return AUTH_ENTRY_WORDS.some((w) => lower.includes(w));
70
+ }
71
+ function isAuthFnName(name) {
72
+ const lower = name.toLowerCase();
73
+ // 注意:不含 "middleware" 词——loggerMiddleware 等工具中间件不该撞认证
74
+ // (authMiddleware 类靠 "auth" 命中)
75
+ return AUTH_FN_WORDS.some((w) => lower.includes(w));
76
+ }
77
+ // ── Analysis(代码串级) ──
78
+ function analyzeGinApp(code) {
79
+ const issues = [];
80
+ const routes = [];
81
+ const authGroupMiddleware = [];
82
+ const hasGin = /gin\.(Default|New)\(|"github.com\/gin-gonic\/gin"/.test(code);
83
+ if (!hasGin) {
84
+ return { hasGin: false, routes, authGroupMiddleware, issues };
85
+ }
86
+ // 组级认证中间件:r.Use(authMW) / r.Group("/api", authMW) / g.Use(authMW)
87
+ const useRe = /\.Use\s*\(\s*([A-Za-z_][\w]*)/g;
88
+ let m;
89
+ while ((m = useRe.exec(code)) !== null) {
90
+ if (isAuthFnName(m[1]))
91
+ authGroupMiddleware.push(m[1]);
92
+ }
93
+ const groupRe = /\.Group\s*\(\s*"[^"]*"\s*,\s*([A-Za-z_][\w]*)/g;
94
+ while ((m = groupRe.exec(code)) !== null) {
95
+ if (isAuthFnName(m[1]))
96
+ authGroupMiddleware.push(m[1]);
97
+ }
98
+ // 路由注册:r.POST("/x", mw1, mw2, handler)——Go 方法名大写(POST 惯例)
99
+ const routeRe = /\.(get|post|put|patch|delete)\s*\(\s*"([^"]+)"/gi;
100
+ while ((m = routeRe.exec(code)) !== null) {
101
+ const method = m[1].toLowerCase();
102
+ const pathName = m[2];
103
+ const window = code.slice(m.index + m[0].length, m.index + m[0].length + 300);
104
+ const mwNames = window.match(/[A-Za-z_][\w]*/g) || [];
105
+ const hasAuthMw = mwNames.some((name) => isAuthFnName(name));
106
+ routes.push({
107
+ method,
108
+ path: pathName,
109
+ protected: hasAuthMw,
110
+ line: code.slice(0, m.index).split("\n").length,
111
+ });
112
+ if (MUTATION_METHODS.has(method) && !hasAuthMw
113
+ && authGroupMiddleware.length === 0 && !isAuthEntryPath(pathName)) {
114
+ issues.push({
115
+ severity: "medium",
116
+ rule: "GIN_ROUTE_NO_AUTH",
117
+ message: `Route ${method.toUpperCase()} ${pathName} has no auth middleware ` +
118
+ `and no auth Use/Group middleware — any caller can reach it.`,
119
+ route: `${method.toUpperCase()} ${pathName}`,
120
+ line: code.slice(0, m.index).split("\n").length,
121
+ });
122
+ }
123
+ }
124
+ return { hasGin: true, routes, authGroupMiddleware, issues };
125
+ }
126
+ function analyzeGinFile(filePath) {
127
+ if (!fs.existsSync(filePath))
128
+ return null;
129
+ const code = fs.readFileSync(filePath, "utf-8");
130
+ if (!/gin-gonic|gin\.(Default|New)\(/.test(code))
131
+ return null;
132
+ return analyzeGinApp(code);
133
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * gin-detector.test.ts — Gin 框架适配器规则回归(纯函数,无文件 I/O)
5
+ */
6
+ const vitest_1 = require("vitest");
7
+ const gin_detector_1 = require("./gin-detector");
8
+ const app = (routes, extra = "") => `
9
+ import "github.com/gin-gonic/gin"
10
+
11
+ func main() {
12
+ r := gin.Default()
13
+ ${extra}
14
+ ${routes}
15
+ }
16
+ `;
17
+ (0, vitest_1.describe)("gin-detector", () => {
18
+ (0, vitest_1.it)("R1:无认证中间件的 mutation 路由 → GIN_ROUTE_NO_AUTH", () => {
19
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
20
+ r.POST("/transfer", func(c *gin.Context) {})
21
+ `));
22
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("GIN_ROUTE_NO_AUTH");
23
+ });
24
+ (0, vitest_1.it)("R1:路由级认证中间件保护不报", () => {
25
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
26
+ r.POST("/transfer", authMiddleware, func(c *gin.Context) {})
27
+ `));
28
+ (0, vitest_1.expect)(issues).toHaveLength(0);
29
+ });
30
+ (0, vitest_1.it)("R1:组级 Use 认证中间件保护不报", () => {
31
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`r.POST("/transfer", func(c *gin.Context) {})`, `r.Use(authMiddleware)`));
32
+ (0, vitest_1.expect)(issues).toHaveLength(0);
33
+ });
34
+ (0, vitest_1.it)("R1:Group 认证中间件保护不报", () => {
35
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
36
+ api := r.Group("/api", authMiddleware)
37
+ api.POST("/transfer", func(c *gin.Context) {})
38
+ `));
39
+ (0, vitest_1.expect)(issues).toHaveLength(0);
40
+ });
41
+ (0, vitest_1.it)("R1:非认证中间件(logger)不视为保护", () => {
42
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
43
+ r.POST("/transfer", loggerMiddleware, func(c *gin.Context) {})
44
+ `));
45
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("GIN_ROUTE_NO_AUTH");
46
+ });
47
+ (0, vitest_1.it)("R1:GET 读操作不报", () => {
48
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
49
+ r.GET("/articles", func(c *gin.Context) {})
50
+ `));
51
+ (0, vitest_1.expect)(issues).toHaveLength(0);
52
+ });
53
+ (0, vitest_1.it)("R1 豁免:login 认证入口路径不报", () => {
54
+ const { issues } = (0, gin_detector_1.analyzeGinApp)(app(`
55
+ r.POST("/login", func(c *gin.Context) {})
56
+ `));
57
+ (0, vitest_1.expect)(issues).toHaveLength(0);
58
+ });
59
+ (0, vitest_1.it)("非 Gin 代码不产生任何问题", () => {
60
+ const { hasGin, issues } = (0, gin_detector_1.analyzeGinApp)(`import express from "express";`);
61
+ (0, vitest_1.expect)(hasGin).toBe(false);
62
+ (0, vitest_1.expect)(issues).toHaveLength(0);
63
+ });
64
+ });
@@ -12,7 +12,7 @@
12
12
  * Next.js (App Router) — 7 dedicated detectors.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.generateVersionAwarenessReport = exports.checkFileRename = exports.checkFrameworkConventions = exports.detectFrameworks = exports.analyzeNestJSFile = exports.analyzeNestJSProject = exports.analyzeHapiFile = exports.analyzeHapiApp = exports.analyzeKoaFile = exports.analyzeKoaApp = exports.readNextMiddleware = exports.analyzeNextApp = exports.analyzeFastifyFile = exports.analyzeFastifyApp = exports.analyzeFlaskStructure = exports.analyzeDjangoStructure = exports.analyzeFastapiStructure = exports.formatExpressReport = exports.classifyMiddleware = exports.extractGlobalMiddleware = exports.extractRoutes = exports.detectExpressApp = exports.analyzeExpressProject = exports.analyzeExpressFile = exports.analyzeExpressApp = void 0;
15
+ exports.generateVersionAwarenessReport = exports.checkFileRename = exports.checkFrameworkConventions = exports.detectFrameworks = exports.analyzeNestJSFile = exports.analyzeNestJSProject = exports.analyzeFiberFile = exports.analyzeFiberApp = exports.analyzeGinFile = exports.analyzeGinApp = exports.analyzeHapiFile = exports.analyzeHapiApp = exports.analyzeKoaFile = exports.analyzeKoaApp = exports.readNextMiddleware = exports.analyzeNextApp = exports.analyzeFastifyFile = exports.analyzeFastifyApp = exports.analyzeFlaskStructure = exports.analyzeDjangoStructure = exports.analyzeFastapiStructure = exports.formatExpressReport = exports.classifyMiddleware = exports.extractGlobalMiddleware = exports.extractRoutes = exports.detectExpressApp = exports.analyzeExpressProject = exports.analyzeExpressFile = exports.analyzeExpressApp = void 0;
16
16
  var express_detector_1 = require("./express-detector");
17
17
  Object.defineProperty(exports, "analyzeExpressApp", { enumerable: true, get: function () { return express_detector_1.analyzeExpressApp; } });
18
18
  Object.defineProperty(exports, "analyzeExpressFile", { enumerable: true, get: function () { return express_detector_1.analyzeExpressFile; } });
@@ -40,6 +40,12 @@ Object.defineProperty(exports, "analyzeKoaFile", { enumerable: true, get: functi
40
40
  var hapi_detector_1 = require("./hapi-detector");
41
41
  Object.defineProperty(exports, "analyzeHapiApp", { enumerable: true, get: function () { return hapi_detector_1.analyzeHapiApp; } });
42
42
  Object.defineProperty(exports, "analyzeHapiFile", { enumerable: true, get: function () { return hapi_detector_1.analyzeHapiFile; } });
43
+ var gin_detector_1 = require("./gin-detector");
44
+ Object.defineProperty(exports, "analyzeGinApp", { enumerable: true, get: function () { return gin_detector_1.analyzeGinApp; } });
45
+ Object.defineProperty(exports, "analyzeGinFile", { enumerable: true, get: function () { return gin_detector_1.analyzeGinFile; } });
46
+ var fiber_detector_1 = require("./fiber-detector");
47
+ Object.defineProperty(exports, "analyzeFiberApp", { enumerable: true, get: function () { return fiber_detector_1.analyzeFiberApp; } });
48
+ Object.defineProperty(exports, "analyzeFiberFile", { enumerable: true, get: function () { return fiber_detector_1.analyzeFiberFile; } });
43
49
  var nestjs_detector_1 = require("./nestjs-detector");
44
50
  Object.defineProperty(exports, "analyzeNestJSProject", { enumerable: true, get: function () { return nestjs_detector_1.analyzeNestJSProject; } });
45
51
  Object.defineProperty(exports, "analyzeNestJSFile", { enumerable: true, get: function () { return nestjs_detector_1.analyzeNestJSFile; } });
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.12";
23
+ exports.RUNTIME_VERSION = "3.7.13";
24
24
  function verify(filePath) {
25
25
  const cert = (0, certify_1.certify)(filePath);
26
26
  const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
@@ -69,11 +69,14 @@ const fastify_detector_1 = require("../frameworks/fastify-detector");
69
69
  const nextjs_detector_1 = require("../frameworks/nextjs-detector");
70
70
  const koa_detector_1 = require("../frameworks/koa-detector");
71
71
  const hapi_detector_1 = require("../frameworks/hapi-detector");
72
+ const gin_detector_1 = require("../frameworks/gin-detector");
73
+ const fiber_detector_1 = require("../frameworks/fiber-detector");
72
74
  const ssg_bridge_1 = require("./ssg-bridge");
73
75
  const call_sequence_1 = require("../call-sequence");
74
76
  const extract_ir_1 = require("../extract-ir");
75
77
  const extract_ir_python_1 = require("../extract-ir-python");
76
78
  const extract_ir_c_1 = require("../extract-ir-c");
79
+ const extract_ir_go_1 = require("../extract-ir-go");
77
80
  // ── Main Entry Point ──
78
81
  /**
79
82
  * Trust 决策主入口:收集 → 归一化 → 评分 → 决策 → 组装。
@@ -106,6 +109,8 @@ async function evaluateTrust(ctx) {
106
109
  const nextjsResult = collectNextjsViolations(ctx);
107
110
  const koaResult = collectKoaViolations(ctx);
108
111
  const hapiResult = collectHapiViolations(ctx);
112
+ const ginResult = collectGinViolations(ctx);
113
+ const fiberResult = collectFiberViolations(ctx);
109
114
  const coverageData = collectVerificationCoverage(ctx);
110
115
  const governanceDefects = collectGovernanceDefects(ctx);
111
116
  // ═══════════════════════════════════════
@@ -130,6 +135,8 @@ async function evaluateTrust(ctx) {
130
135
  ...nextjsResult.violations,
131
136
  ...koaResult.violations,
132
137
  ...hapiResult.violations,
138
+ ...ginResult.violations,
139
+ ...fiberResult.violations,
133
140
  ];
134
141
  // ═══════════════════════════════════════
135
142
  // PHASE 3: SCORE
@@ -312,6 +319,24 @@ async function evaluateTrust(ctx) {
312
319
  issuesFound: hapiResult.violations.length,
313
320
  }
314
321
  : undefined,
322
+ /** Gin framework adapter coverage — Go route/auth-middleware analysis */
323
+ ginCoverage: ginResult.coverage.routes > 0
324
+ ? {
325
+ appsDetected: ginResult.coverage.apps,
326
+ totalRoutes: ginResult.coverage.routes,
327
+ filesScanned: ginResult.coverage.filesScanned,
328
+ issuesFound: ginResult.violations.length,
329
+ }
330
+ : undefined,
331
+ /** Fiber framework adapter coverage — Go route/auth-middleware analysis */
332
+ fiberCoverage: fiberResult.coverage.routes > 0
333
+ ? {
334
+ appsDetected: fiberResult.coverage.apps,
335
+ totalRoutes: fiberResult.coverage.routes,
336
+ filesScanned: fiberResult.coverage.filesScanned,
337
+ issuesFound: fiberResult.violations.length,
338
+ }
339
+ : undefined,
315
340
  },
316
341
  dimensions: {
317
342
  policyCompliance: {
@@ -450,6 +475,70 @@ function mapPolicyViolation(rv, filePath, _enterprisePolicy) {
450
475
  * Collect Express-specific security violations from the framework detector.
451
476
  * Maps ExpressSecurityIssue[] → TrustViolation[].
452
477
  */
478
+ function collectGoFrameworkViolations(ctx, analyzer, rulePrefix, fixText, policyRef) {
479
+ const violations = [];
480
+ const coverage = { apps: 0, routes: 0, filesScanned: 0 };
481
+ try {
482
+ const fs = require("fs");
483
+ // Go 项目结构特殊:cmd/internal/pkg + 根目录 main.go 都要扫
484
+ const candidateDirs = ["cmd", "internal", "pkg", "src", "server", "app", "api", "routes", "lib", "handlers", "middleware"];
485
+ const extensions = languageToExtensions(ctx.language);
486
+ const files = [];
487
+ for (const dir of candidateDirs) {
488
+ const dirPath = path.join(ctx.projectPath, dir);
489
+ if (!fs.existsSync(dirPath))
490
+ continue;
491
+ try {
492
+ files.push(...walkDir(dirPath, extensions, 100));
493
+ }
494
+ catch { /* skip */ }
495
+ }
496
+ try {
497
+ for (const entry of fs.readdirSync(ctx.projectPath)) {
498
+ const full = path.join(ctx.projectPath, entry);
499
+ if (!fs.statSync(full).isFile())
500
+ continue;
501
+ if (extensions.some((ext) => entry.endsWith(ext)))
502
+ files.push(full);
503
+ }
504
+ }
505
+ catch { /* best-effort */ }
506
+ for (const file of files) {
507
+ if (file.includes("_test."))
508
+ continue;
509
+ coverage.filesScanned++;
510
+ try {
511
+ const analysis = analyzer(file);
512
+ if (!analysis || !analysis[`has${rulePrefix}`])
513
+ continue;
514
+ coverage.apps++;
515
+ coverage.routes += analysis.routes.length;
516
+ for (const issue of analysis.issues) {
517
+ violations.push({
518
+ severity: issue.severity === "low" ? "low" : issue.severity,
519
+ rule_id: issue.rule,
520
+ file: path.relative(ctx.projectPath, file),
521
+ function: "unknown",
522
+ message: issue.message,
523
+ evidence: issue.route || "",
524
+ why: `Framework structural analysis: ${issue.message}`,
525
+ fix: fixText,
526
+ policy_ref: policyRef,
527
+ });
528
+ }
529
+ }
530
+ catch { /* skip unreadable files */ }
531
+ }
532
+ }
533
+ catch { /* best-effort */ }
534
+ return { violations, coverage };
535
+ }
536
+ function collectGinViolations(ctx) {
537
+ return collectGoFrameworkViolations(ctx, gin_detector_1.analyzeGinFile, "Gin", "Add an auth middleware to the route registration, or register auth middleware with r.Use/r.Group.", "framework-safety.gin");
538
+ }
539
+ function collectFiberViolations(ctx) {
540
+ return collectGoFrameworkViolations(ctx, fiber_detector_1.analyzeFiberFile, "Fiber", "Add an auth middleware to the route registration, or register auth middleware with app.Use.", "framework-safety.fiber");
541
+ }
453
542
  function collectKoaViolations(ctx) {
454
543
  const violations = [];
455
544
  const coverage = { apps: 0, routes: 0, filesScanned: 0 };
@@ -1121,6 +1210,7 @@ async function collectProtocolViolations(ctx, callGraph) {
1121
1210
  javascript: () => (0, extract_ir_1.extractIR)(ctx.projectPath),
1122
1211
  python: () => (0, extract_ir_python_1.extractIRPython)(ctx.projectPath),
1123
1212
  c: () => (0, extract_ir_c_1.extractIRC)(ctx.projectPath),
1213
+ go: () => (0, extract_ir_go_1.extractIRGo)(ctx.projectPath),
1124
1214
  };
1125
1215
  const extractFn = autoExtractor[lang];
1126
1216
  if (extractFn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "3.7.12",
3
+ "version": "3.7.13",
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/",