progmune-runtime 3.7.7 → 3.7.9

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,137 @@
1
+ "use strict";
2
+ /**
3
+ * Fastify Framework Adapter — Protocol Detection for Fastify
4
+ *
5
+ * 第 6 个框架适配(TS/JS 第 3 个专用检测器,镜像 express-detector 的
6
+ * 代码串分析风格):
7
+ *
8
+ * fastify.get('/x', { preHandler: [auth] }, handler) 路由级认证选项
9
+ * fastify.addHook('preHandler', authFn) 全局认证钩子
10
+ *
11
+ * 规则:
12
+ * FASTIFY_ROUTE_NO_AUTH mutation 路由注册(post/put/patch/delete)
13
+ * 无 preHandler/preValidation 认证选项,
14
+ * 且文件中无认证 addHook——路由级 missing-auth
15
+ *
16
+ * 口径(如实):
17
+ * - get 是读操作不检查(公开读是常见设计)
18
+ * - 认证钩子函数按名字词表识别(auth/login/permission/token/session…);
19
+ * 自定义认证函数若名字不含词表会漏判(保守方向是漏报不是误报)
20
+ * - 认证入口路径按词汇豁免(login/regist/auth/token/health)
21
+ * - 代码串级分析(与 Express 检测器同款):装饰器/配置展开不可见
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.analyzeFastifyApp = analyzeFastifyApp;
58
+ exports.analyzeFastifyFile = analyzeFastifyFile;
59
+ const fs = __importStar(require("fs"));
60
+ const MUTATION_METHODS = new Set(["post", "put", "patch", "delete"]);
61
+ const AUTH_ENTRY_WORDS = [
62
+ "login", "signin", "sign_in", "regist", "signup", "sign_up",
63
+ "token", "auth", "health", "status",
64
+ ];
65
+ const AUTH_FN_WORDS = [
66
+ "auth", "login", "permission", "token", "credential", "session",
67
+ "jwt", "verify", "guard", "protect",
68
+ ];
69
+ function isAuthEntryPath(pathName) {
70
+ const lower = pathName.toLowerCase();
71
+ return AUTH_ENTRY_WORDS.some((w) => lower.includes(w));
72
+ }
73
+ function isAuthFnName(name) {
74
+ const lower = name.toLowerCase();
75
+ return AUTH_FN_WORDS.some((w) => lower.includes(w));
76
+ }
77
+ // ── Analysis(代码串级,镜像 express-detector) ──
78
+ function analyzeFastifyApp(code) {
79
+ const issues = [];
80
+ const routes = [];
81
+ const authHooks = [];
82
+ const hasFastify = /\bFastify\b|\bfastify\b/.test(code);
83
+ if (!hasFastify) {
84
+ return { hasFastify: false, routes, authHooks, issues };
85
+ }
86
+ // 全局认证钩子:addHook('preHandler'|'preValidation'|'onRequest', authFn)
87
+ const hookRe = /\.addHook\s*\(\s*['"](preHandler|preValidation|onRequest)['"]\s*,\s*([A-Za-z_$][\w$]*)/g;
88
+ let m;
89
+ while ((m = hookRe.exec(code)) !== null) {
90
+ if (isAuthFnName(m[2]))
91
+ authHooks.push(m[2]);
92
+ }
93
+ // 路由注册:fastify.post('/x', {...}, handler) —— 捕获到 options 窗口
94
+ const routeRe = /\.(get|post|put|patch|delete|route)\s*\(\s*['"]([^'"]+)['"]/g;
95
+ while ((m = routeRe.exec(code)) !== null) {
96
+ const method = m[1].toLowerCase();
97
+ const pathName = m[2];
98
+ // 从路由调用起点向后截取 400 字符窗口,查 preHandler/preValidation 选项
99
+ const windowStart = m.index;
100
+ const window = code.slice(windowStart, windowStart + 400);
101
+ const hasAuthOption = /\b(preHandler|preValidation)\s*:/g.test(window)
102
+ && (() => {
103
+ // 选项值里出现 auth-like 函数名才认(保守)
104
+ const optMatch = window.match(/\b(preHandler|preValidation)\s*:\s*\[([^\]]*)\]/);
105
+ if (!optMatch)
106
+ return false;
107
+ return optMatch[2].split(",").some((name) => isAuthFnName(name.trim()));
108
+ })();
109
+ routes.push({
110
+ method,
111
+ path: pathName,
112
+ protected: hasAuthOption,
113
+ line: code.slice(0, m.index).split("\n").length,
114
+ });
115
+ if (MUTATION_METHODS.has(method) && !hasAuthOption
116
+ && authHooks.length === 0 && !isAuthEntryPath(pathName)) {
117
+ issues.push({
118
+ severity: "medium",
119
+ rule: "FASTIFY_ROUTE_NO_AUTH",
120
+ message: `Route ${method.toUpperCase()} ${pathName} is registered without ` +
121
+ `preHandler/preValidation auth and the app has no auth hook — ` +
122
+ `any caller can reach it.`,
123
+ route: `${method.toUpperCase()} ${pathName}`,
124
+ line: code.slice(0, m.index).split("\n").length,
125
+ });
126
+ }
127
+ }
128
+ return { hasFastify: true, routes, authHooks, issues };
129
+ }
130
+ function analyzeFastifyFile(filePath) {
131
+ if (!fs.existsSync(filePath))
132
+ return null;
133
+ const code = fs.readFileSync(filePath, "utf-8");
134
+ if (!/from\s+['"]fastify['"]|require\(['"]fastify['"]\)/.test(code))
135
+ return null;
136
+ return analyzeFastifyApp(code);
137
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * fastify-detector.test.ts — Fastify 框架适配器规则回归(纯函数,无文件 I/O)
5
+ *
6
+ * 代码串级分析(镜像 express-detector):路由注册 + preHandler/preValidation
7
+ * 认证选项 + addHook 认证钩子。
8
+ */
9
+ const vitest_1 = require("vitest");
10
+ const fastify_detector_1 = require("./fastify-detector");
11
+ const app = (routes, hooks = "") => `
12
+ import Fastify from "fastify";
13
+ const fastify = Fastify();
14
+ ${routes}
15
+ ${hooks}
16
+ `;
17
+ (0, vitest_1.describe)("fastify-detector", () => {
18
+ (0, vitest_1.it)("R1:无保护 mutation 路由 → FASTIFY_ROUTE_NO_AUTH", () => {
19
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`
20
+ fastify.post("/transfer", async (req, reply) => ({ ok: true }));
21
+ `));
22
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("FASTIFY_ROUTE_NO_AUTH");
23
+ });
24
+ (0, vitest_1.it)("R1:preHandler 认证选项保护不报", () => {
25
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`
26
+ fastify.post("/transfer", { preHandler: [authenticate] }, async (req, reply) => ({ ok: true }));
27
+ `));
28
+ (0, vitest_1.expect)(issues).toHaveLength(0);
29
+ });
30
+ (0, vitest_1.it)("R1:preValidation 认证选项保护不报", () => {
31
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`
32
+ fastify.put("/update", { preValidation: [checkToken] }, async (req, reply) => ({ ok: true }));
33
+ `));
34
+ (0, vitest_1.expect)(issues).toHaveLength(0);
35
+ });
36
+ (0, vitest_1.it)("R1:addHook 认证钩子全局保护不报", () => {
37
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`fastify.post("/transfer", async (req, reply) => ({ ok: true }));`, `fastify.addHook("preHandler", authenticate);`));
38
+ (0, vitest_1.expect)(issues).toHaveLength(0);
39
+ });
40
+ (0, vitest_1.it)("R1:非认证 addHook(如日志)不视为保护", () => {
41
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`fastify.post("/transfer", async (req, reply) => ({ ok: true }));`, `fastify.addHook("onRequest", logRequest);`));
42
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("FASTIFY_ROUTE_NO_AUTH");
43
+ });
44
+ (0, vitest_1.it)("R1:GET 读操作不报", () => {
45
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`
46
+ fastify.get("/articles", async (req, reply) => ({ items: [] }));
47
+ `));
48
+ (0, vitest_1.expect)(issues).toHaveLength(0);
49
+ });
50
+ (0, vitest_1.it)("R1 豁免:login/regist/token 认证入口路径不报", () => {
51
+ const { issues } = (0, fastify_detector_1.analyzeFastifyApp)(app(`
52
+ fastify.post("/login", async (req, reply) => ({ token: "t" }));
53
+ fastify.post("/register", async (req, reply) => ({ ok: true }));
54
+ `));
55
+ (0, vitest_1.expect)(issues).toHaveLength(0);
56
+ });
57
+ (0, vitest_1.it)("非 Fastify 代码不产生任何问题", () => {
58
+ const { hasFastify, issues } = (0, fastify_detector_1.analyzeFastifyApp)(`import express from "express"; const app = express(); app.post("/x", h);`);
59
+ (0, vitest_1.expect)(hasFastify).toBe(false);
60
+ (0, vitest_1.expect)(issues).toHaveLength(0);
61
+ });
62
+ });
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * Flask Framework Adapter — Protocol Detection for Flask
4
+ *
5
+ * 第 5 个框架适配(Python 第 3 个):结构扫描由 tools/extract_framework_flask.py
6
+ * (Python AST)完成——@app.route/@bp.route、methods kwarg、认证装饰器、
7
+ * before_request 认证守卫、Blueprint。
8
+ * 本模块消费结构 JSON 做规则判定:
9
+ *
10
+ * FLASK_ROUTE_NO_AUTH mutation 路由(methods 含 POST/PUT/PATCH/
11
+ * DELETE)无认证装饰器,且项目无认证
12
+ * before_request 守卫——路由级 missing-auth
13
+ *
14
+ * 口径(如实):
15
+ * - @app.route 缺省 methods = GET only(Flask 语义)——公开读不检查
16
+ * - 认证 before_request 按函数名词汇识别(auth/login/permission 等);
17
+ * 自定义守卫函数若名字不含词表会漏判(保守方向是漏报不是误报)
18
+ * - 认证入口端点按 处理器名+路径 词汇豁免(login/regist/token/health)
19
+ * - Blueprint 内的 before_request 与 app 级同权重(项目内任一认证守卫
20
+ * 存在即视为全局保护信号)
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.analyzeFlaskStructure = analyzeFlaskStructure;
24
+ const MUTATION_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
25
+ const AUTH_ENTRY_WORDS = [
26
+ "login", "signin", "sign_in", "regist", "signup", "sign_up",
27
+ "token", "auth", "health", "status",
28
+ ];
29
+ function isAuthEntry(route) {
30
+ const haystack = `${route.handler} ${route.path}`.toLowerCase();
31
+ return AUTH_ENTRY_WORDS.some((w) => haystack.includes(w));
32
+ }
33
+ function analyzeFlaskStructure(data) {
34
+ if (!data.hasFlask || !Array.isArray(data.routes)) {
35
+ return { hasFlask: false, issues: [] };
36
+ }
37
+ const issues = [];
38
+ const hasGlobalAuthGuard = (data.beforeRequestAuth || []).length > 0;
39
+ for (const route of data.routes) {
40
+ const isMutation = (route.methods || []).some((m) => MUTATION_METHODS.has(m));
41
+ if (!isMutation)
42
+ continue;
43
+ const hasAuthDecorator = (route.authDecorators || []).length > 0;
44
+ if (hasAuthDecorator || hasGlobalAuthGuard)
45
+ continue;
46
+ if (isAuthEntry(route))
47
+ continue;
48
+ issues.push({
49
+ severity: "medium",
50
+ rule: "FLASK_ROUTE_NO_AUTH",
51
+ message: `Route ${(route.methods || []).join("/")} ${route.path || "(root)"} has no ` +
52
+ `auth decorator and the app has no auth before_request guard — any ` +
53
+ `visitor can invoke "${route.handler}".`,
54
+ route: `${(route.methods || []).join("/")} ${route.path || "(root)"}`,
55
+ handler: route.handler,
56
+ file: route.file,
57
+ line: route.line,
58
+ });
59
+ }
60
+ return { hasFlask: true, issues };
61
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * flask-detector.test.ts — Flask 框架适配器规则回归(纯函数,无文件 I/O)
5
+ */
6
+ const vitest_1 = require("vitest");
7
+ const flask_detector_1 = require("./flask-detector");
8
+ function structure(partial) {
9
+ return {
10
+ hasFlask: true,
11
+ apps: ["app"],
12
+ blueprints: [],
13
+ routes: [],
14
+ beforeRequestAuth: [],
15
+ filesScanned: 1,
16
+ ...partial,
17
+ };
18
+ }
19
+ const route = (p) => ({
20
+ methods: p.methods,
21
+ path: p.path || "",
22
+ handler: p.handler,
23
+ file: "app.py",
24
+ line: 1,
25
+ target: "app",
26
+ authDecorators: p.authDecorators || [],
27
+ });
28
+ (0, vitest_1.describe)("flask-detector", () => {
29
+ (0, vitest_1.it)("R1:无保护 mutation 路由 → FLASK_ROUTE_NO_AUTH", () => {
30
+ const { issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({
31
+ routes: [route({ methods: ["POST"], handler: "transfer_money" })],
32
+ }));
33
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("FLASK_ROUTE_NO_AUTH");
34
+ });
35
+ (0, vitest_1.it)("R1:@login_required 保护不报", () => {
36
+ const { issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({
37
+ routes: [route({
38
+ methods: ["POST"], handler: "transfer_money",
39
+ authDecorators: ["login_required"],
40
+ })],
41
+ }));
42
+ (0, vitest_1.expect)(issues).toHaveLength(0);
43
+ });
44
+ (0, vitest_1.it)("R1:before_request 认证守卫存在时不报(全局保护)", () => {
45
+ const { issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({
46
+ beforeRequestAuth: ["authenticate"],
47
+ routes: [route({ methods: ["POST"], handler: "transfer_money" })],
48
+ }));
49
+ (0, vitest_1.expect)(issues).toHaveLength(0);
50
+ });
51
+ (0, vitest_1.it)("R1:缺省 methods 的 GET 路由不报(Flask 缺省=GET only)", () => {
52
+ const { issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({
53
+ routes: [route({ methods: ["GET"], handler: "home" })],
54
+ }));
55
+ (0, vitest_1.expect)(issues).toHaveLength(0);
56
+ });
57
+ (0, vitest_1.it)("R1 豁免:login/regist 认证入口端点不报", () => {
58
+ const { issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({
59
+ routes: [
60
+ route({ methods: ["POST"], path: "/login", handler: "login" }),
61
+ route({ methods: ["POST"], path: "/register", handler: "register_user" }),
62
+ ],
63
+ }));
64
+ (0, vitest_1.expect)(issues).toHaveLength(0);
65
+ });
66
+ (0, vitest_1.it)("非 Flask 结构不产生任何问题", () => {
67
+ const { hasFlask, issues } = (0, flask_detector_1.analyzeFlaskStructure)(structure({ hasFlask: false, routes: [] }));
68
+ (0, vitest_1.expect)(hasFlask).toBe(false);
69
+ (0, vitest_1.expect)(issues).toHaveLength(0);
70
+ });
71
+ });
@@ -8,11 +8,11 @@
8
8
  * Each adapter knows the API surface of a specific framework:
9
9
  * middleware chains, route handlers, dependency injection, guards, etc.
10
10
  *
11
- * Current: Express.js (first adapter broke the 0/13 gap)
12
- * Planned: Next.js, NestJS, Fastify, FastAPI, Django
11
+ * Adapters: Express, tRPC, NestJS(partial), FastAPI, Django, Flask, Fastify,
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.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.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; } });
@@ -22,6 +22,18 @@ Object.defineProperty(exports, "extractRoutes", { enumerable: true, get: functio
22
22
  Object.defineProperty(exports, "extractGlobalMiddleware", { enumerable: true, get: function () { return express_detector_1.extractGlobalMiddleware; } });
23
23
  Object.defineProperty(exports, "classifyMiddleware", { enumerable: true, get: function () { return express_detector_1.classifyMiddleware; } });
24
24
  Object.defineProperty(exports, "formatExpressReport", { enumerable: true, get: function () { return express_detector_1.formatExpressReport; } });
25
+ var fastapi_detector_1 = require("./fastapi-detector");
26
+ Object.defineProperty(exports, "analyzeFastapiStructure", { enumerable: true, get: function () { return fastapi_detector_1.analyzeFastapiStructure; } });
27
+ var django_detector_1 = require("./django-detector");
28
+ Object.defineProperty(exports, "analyzeDjangoStructure", { enumerable: true, get: function () { return django_detector_1.analyzeDjangoStructure; } });
29
+ var flask_detector_1 = require("./flask-detector");
30
+ Object.defineProperty(exports, "analyzeFlaskStructure", { enumerable: true, get: function () { return flask_detector_1.analyzeFlaskStructure; } });
31
+ var fastify_detector_1 = require("./fastify-detector");
32
+ Object.defineProperty(exports, "analyzeFastifyApp", { enumerable: true, get: function () { return fastify_detector_1.analyzeFastifyApp; } });
33
+ Object.defineProperty(exports, "analyzeFastifyFile", { enumerable: true, get: function () { return fastify_detector_1.analyzeFastifyFile; } });
34
+ var nextjs_detector_1 = require("./nextjs-detector");
35
+ Object.defineProperty(exports, "analyzeNextApp", { enumerable: true, get: function () { return nextjs_detector_1.analyzeNextApp; } });
36
+ Object.defineProperty(exports, "readNextMiddleware", { enumerable: true, get: function () { return nextjs_detector_1.readNextMiddleware; } });
25
37
  // ── Framework Version-Aware Governance ──
26
38
  var version_awareness_1 = require("./version-awareness");
27
39
  Object.defineProperty(exports, "detectFrameworks", { enumerable: true, get: function () { return version_awareness_1.detectFrameworks; } });
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ /**
3
+ * Next.js Framework Adapter — App Router route handler analysis
4
+ *
5
+ * 第 7 个框架适配(TS/JS 第 4 个专用检测器)。Next.js 的「路由」是文件
6
+ * 而不是代码声明:app 下各段的 route.ts 每个导出 POST/PUT/PATCH/DELETE 的文件
7
+ * 就是一个对外写入口。本模块做文件级结构扫描:
8
+ *
9
+ * mutation 导出 export function POST/PUT/PATCH/DELETE(...)
10
+ * 路由级认证信号 route.ts 内调用 next-auth(getServerSession/auth())
11
+ * 或自定义认证(requireAuth/verifyToken/getToken/
12
+ * withAuth/isAuthenticated 等)
13
+ * 全局认证信号 middleware.ts(项目根或 src/)内容命中认证词表
14
+ *
15
+ * 规则:
16
+ * NEXT_ROUTE_NO_AUTH mutation 路由文件无路由级认证调用,且项目
17
+ * 无认证 middleware——路由级 missing-auth
18
+ *
19
+ * 口径(如实):
20
+ * - GET 导出不检查(公开读是常见设计)
21
+ * - 认证信号按词表识别;自定义认证名不含词表会漏判(保守方向是漏报)
22
+ * - 认证入口路径按词汇豁免(login/regist/auth/token)
23
+ * - Server Components / page.tsx 的非 API 页面不检查(只盯 route.ts
24
+ * 与 pages/api——对外 API 面)
25
+ */
26
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ var desc = Object.getOwnPropertyDescriptor(m, k);
29
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
30
+ desc = { enumerable: true, get: function() { return m[k]; } };
31
+ }
32
+ Object.defineProperty(o, k2, desc);
33
+ }) : (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ o[k2] = m[k];
36
+ }));
37
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
38
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
39
+ }) : function(o, v) {
40
+ o["default"] = v;
41
+ });
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.analyzeNextApp = analyzeNextApp;
61
+ exports.readNextMiddleware = readNextMiddleware;
62
+ const fs = __importStar(require("fs"));
63
+ const path = __importStar(require("path"));
64
+ const MUTATION_EXPORTS = ["POST", "PUT", "PATCH", "DELETE"];
65
+ const AUTH_ENTRY_WORDS = [
66
+ "login", "signin", "sign_in", "regist", "signup", "sign_up",
67
+ "token", "auth", "health",
68
+ ];
69
+ const AUTH_CALL_RE = /\b(getServerSession|requireAuth|requireUser|verifyToken|verifyAuth|isAuthenticated|checkAuth|getToken|withAuth|authSession|authenticate)\s*\(/;
70
+ const AUTH_MIDDLEWARE_RE = /\b(getServerSession|requireAuth|verifyToken|getToken|withAuth|next-auth|authorization|authenticate)\b/;
71
+ function isAuthEntryFile(relFile) {
72
+ const lower = relFile.toLowerCase();
73
+ return AUTH_ENTRY_WORDS.some((w) => lower.includes(w));
74
+ }
75
+ // ── Analysis ──
76
+ /**
77
+ * 扫描 Next.js 项目的 API 路由文件(app 下各段的 route.ts + pages/api 下各段)。
78
+ * @param projectRoot — 项目根目录
79
+ * @param middlewareCode — middleware.ts 内容(调用方预读,可为空)
80
+ */
81
+ function analyzeNextApp(projectRoot, middlewareCode) {
82
+ const routeFiles = [];
83
+ const issues = [];
84
+ let legacyApiFiles = 0; // pages/api 旧式 handler(方法不可静态区分,只计数)
85
+ const hasAuthMiddleware = !!middlewareCode && AUTH_MIDDLEWARE_RE.test(middlewareCode);
86
+ const candidates = [];
87
+ for (const base of ["app", "src/app"]) {
88
+ const dir = path.join(projectRoot, base);
89
+ if (fs.existsSync(dir)) {
90
+ collectRouteFiles(dir, candidates);
91
+ }
92
+ }
93
+ const pagesApi = path.join(projectRoot, "pages", "api");
94
+ if (fs.existsSync(pagesApi)) {
95
+ collectRouteFiles(pagesApi, candidates);
96
+ }
97
+ for (const file of candidates) {
98
+ let code;
99
+ try {
100
+ code = fs.readFileSync(file, "utf-8");
101
+ }
102
+ catch {
103
+ continue;
104
+ }
105
+ const mutations = [];
106
+ for (const m of MUTATION_EXPORTS) {
107
+ if (new RegExp(`export\\s+(async\\s+)?function\\s+${m}\\b`).test(code)) {
108
+ mutations.push(m);
109
+ }
110
+ }
111
+ if (mutations.length === 0) {
112
+ legacyApiFiles++;
113
+ continue;
114
+ }
115
+ const hasAuthCall = AUTH_CALL_RE.test(code);
116
+ const rel = path.relative(projectRoot, file);
117
+ routeFiles.push({ file: rel, mutations, hasAuthCall });
118
+ if (!hasAuthCall && !hasAuthMiddleware && !isAuthEntryFile(rel)) {
119
+ issues.push({
120
+ severity: "medium",
121
+ rule: "NEXT_ROUTE_NO_AUTH",
122
+ message: `API route ${rel} exports ${mutations.join("/")} without an auth ` +
123
+ `check in the handler and the project has no auth middleware — ` +
124
+ `any caller can reach it.`,
125
+ route: rel,
126
+ file: rel,
127
+ });
128
+ }
129
+ }
130
+ return {
131
+ hasNext: routeFiles.length > 0 || legacyApiFiles > 0 || !!middlewareCode,
132
+ routeFiles,
133
+ hasAuthMiddleware,
134
+ issues,
135
+ };
136
+ }
137
+ function collectRouteFiles(dir, out) {
138
+ let entries;
139
+ try {
140
+ entries = fs.readdirSync(dir, { withFileTypes: true });
141
+ }
142
+ catch {
143
+ return;
144
+ }
145
+ for (const e of entries) {
146
+ const full = path.join(dir, e.name);
147
+ if (e.isDirectory()) {
148
+ collectRouteFiles(full, out);
149
+ }
150
+ else if (e.isFile() && e.name === "route.ts") {
151
+ out.push(full);
152
+ }
153
+ else if (e.isFile() && /\.(ts|js)$/.test(e.name) && dir.includes("pages" + path.sep + "api")) {
154
+ out.push(full);
155
+ }
156
+ }
157
+ }
158
+ /** 读取项目的 middleware 代码(根或 src/) */
159
+ function readNextMiddleware(projectRoot) {
160
+ for (const rel of ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"]) {
161
+ const p = path.join(projectRoot, rel);
162
+ if (fs.existsSync(p)) {
163
+ try {
164
+ return fs.readFileSync(p, "utf-8");
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ }
171
+ return undefined;
172
+ }
@@ -0,0 +1,113 @@
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
+ * nextjs-detector.test.ts — Next.js App Router 适配器规则回归(文件系统 I/O,
38
+ * 使用临时目录夹具——与 express-detector.test.ts 同款风格)
39
+ */
40
+ const vitest_1 = require("vitest");
41
+ const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
43
+ const path = __importStar(require("path"));
44
+ const nextjs_detector_1 = require("./nextjs-detector");
45
+ let dir;
46
+ (0, vitest_1.beforeEach)(() => {
47
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "nextjs-det-"));
48
+ });
49
+ (0, vitest_1.afterEach)(() => {
50
+ fs.rmSync(dir, { recursive: true, force: true });
51
+ });
52
+ function writeRoute(rel, code) {
53
+ const full = path.join(dir, rel);
54
+ fs.mkdirSync(path.dirname(full), { recursive: true });
55
+ fs.writeFileSync(full, code);
56
+ }
57
+ const MUTATION_ROUTE = `export async function POST(req: Request) {
58
+ return Response.json({ ok: true });
59
+ }
60
+ `;
61
+ const AUTHED_ROUTE = `import { getServerSession } from "next-auth";
62
+ export async function POST(req: Request) {
63
+ const session = await getServerSession();
64
+ return Response.json({ ok: true });
65
+ }
66
+ `;
67
+ const AUTH_MIDDLEWARE = `import { withAuth } from "next-auth/middleware";
68
+ export default withAuth(function middleware(req) {});
69
+ `;
70
+ (0, vitest_1.describe)("nextjs-detector", () => {
71
+ (0, vitest_1.it)("R1:无认证 mutation 路由文件 → NEXT_ROUTE_NO_AUTH", () => {
72
+ writeRoute("app/api/transfer/route.ts", MUTATION_ROUTE);
73
+ const { hasNext, issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
74
+ (0, vitest_1.expect)(hasNext).toBe(true);
75
+ (0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("NEXT_ROUTE_NO_AUTH");
76
+ });
77
+ (0, vitest_1.it)("R1:路由内 getServerSession 认证调用保护不报", () => {
78
+ writeRoute("app/api/transfer/route.ts", AUTHED_ROUTE);
79
+ const { issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
80
+ (0, vitest_1.expect)(issues).toHaveLength(0);
81
+ });
82
+ (0, vitest_1.it)("R1:认证 middleware 全局保护不报", () => {
83
+ writeRoute("app/api/transfer/route.ts", MUTATION_ROUTE);
84
+ writeRoute("middleware.ts", AUTH_MIDDLEWARE);
85
+ const mw = (0, nextjs_detector_1.readNextMiddleware)(dir);
86
+ const { issues } = (0, nextjs_detector_1.analyzeNextApp)(dir, mw);
87
+ (0, vitest_1.expect)(issues).toHaveLength(0);
88
+ });
89
+ (0, vitest_1.it)("R1:GET 导出不报(公开读)", () => {
90
+ writeRoute("app/api/articles/route.ts", `export async function GET() { return Response.json([]); }`);
91
+ const { issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
92
+ (0, vitest_1.expect)(issues).toHaveLength(0);
93
+ });
94
+ (0, vitest_1.it)("R1 豁免:login/auth 认证入口路径不报", () => {
95
+ writeRoute("app/api/auth/login/route.ts", MUTATION_ROUTE);
96
+ const { issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
97
+ (0, vitest_1.expect)(issues).toHaveLength(0);
98
+ });
99
+ (0, vitest_1.it)("pages/api 旧式路由同样覆盖", () => {
100
+ writeRoute("pages/api/transfer.ts", `export default function handler(req, res) { res.json({ok:true}); }`);
101
+ // 无 export function POST 的旧式 handler 不识别方法 → 无 flag(口径如实)
102
+ writeRoute("pages/api/transfer2.ts", `export default async function POST(req: Request) { return Response.json({}); }`);
103
+ const { hasNext, issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
104
+ (0, vitest_1.expect)(hasNext).toBe(true);
105
+ // transfer2 无 POST 导出匹配(default 导出非具名)——旧式页路由方法不可静态区分,如实
106
+ (0, vitest_1.expect)(issues).toHaveLength(0);
107
+ });
108
+ (0, vitest_1.it)("无 Next.js 结构的目录不产生问题", () => {
109
+ const { hasNext, issues } = (0, nextjs_detector_1.analyzeNextApp)(dir);
110
+ (0, vitest_1.expect)(hasNext).toBe(false);
111
+ (0, vitest_1.expect)(issues).toHaveLength(0);
112
+ });
113
+ });
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.7";
23
+ exports.RUNTIME_VERSION = "3.7.9";
24
24
  function verify(filePath) {
25
25
  const cert = (0, certify_1.certify)(filePath);
26
26
  const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();