progmune-runtime 3.7.11 → 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 +24 -0
- package/README.md +4 -3
- package/README.zh-CN.md +4 -3
- package/dist/extract-ir-go.js +383 -0
- package/dist/extract-ir-go.test.js +179 -0
- package/dist/extract-project-ir.js +6 -0
- package/dist/frameworks/fiber-detector.js +125 -0
- package/dist/frameworks/fiber-detector.test.js +57 -0
- package/dist/frameworks/gin-detector.js +133 -0
- package/dist/frameworks/gin-detector.test.js +64 -0
- package/dist/frameworks/hapi-detector.js +132 -0
- package/dist/frameworks/hapi-detector.test.js +60 -0
- package/dist/frameworks/index.js +13 -1
- package/dist/frameworks/koa-detector.js +128 -0
- package/dist/frameworks/koa-detector.test.js +57 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +212 -0
- package/package.json +1 -1
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hapi Framework Adapter — Protocol Detection for Hapi
|
|
4
|
+
*
|
|
5
|
+
* 第 10 个框架适配(TS/JS 第 6 个专用检测器,代码串级):
|
|
6
|
+
*
|
|
7
|
+
* server.auth.strategy('jwt', 'jwt', {...}) 认证策略声明
|
|
8
|
+
* server.route({ method: 'POST', path: '/x',
|
|
9
|
+
* options: { auth: 'jwt' } }) 路由级认证
|
|
10
|
+
*
|
|
11
|
+
* 规则:
|
|
12
|
+
* HAPI_ROUTE_NO_AUTH mutation 路由(method 含 POST/PUT/PATCH/
|
|
13
|
+
* DELETE)的 options 无 auth 字段,或显式
|
|
14
|
+
* auth: false——路由级 missing-auth / 显式公开
|
|
15
|
+
*
|
|
16
|
+
* 口径(如实):
|
|
17
|
+
* - get 读操作不检查;认证入口路径词汇豁免(login/regist/auth/token)
|
|
18
|
+
* - 策略名本身不作为认证证明——必须出现在路由 options.auth
|
|
19
|
+
* (auth: 'jwt' / auth: { strategy: 'jwt' } 均识别)
|
|
20
|
+
* - auth: 'try'(可选认证)视为受保护(非公开)
|
|
21
|
+
*/
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.analyzeHapiApp = analyzeHapiApp;
|
|
57
|
+
exports.analyzeHapiFile = analyzeHapiFile;
|
|
58
|
+
const fs = __importStar(require("fs"));
|
|
59
|
+
const MUTATION_METHODS = new Set(["post", "put", "patch", "delete"]);
|
|
60
|
+
const AUTH_ENTRY_WORDS = [
|
|
61
|
+
"login", "signin", "sign_in", "regist", "signup", "sign_up",
|
|
62
|
+
"token", "auth", "health",
|
|
63
|
+
];
|
|
64
|
+
function isAuthEntryPath(pathName) {
|
|
65
|
+
const lower = pathName.toLowerCase();
|
|
66
|
+
return AUTH_ENTRY_WORDS.some((w) => lower.includes(w));
|
|
67
|
+
}
|
|
68
|
+
// ── Analysis(代码串级) ──
|
|
69
|
+
function analyzeHapiApp(code) {
|
|
70
|
+
const issues = [];
|
|
71
|
+
const routes = [];
|
|
72
|
+
const strategies = [];
|
|
73
|
+
const hasHapi = /@hapi\/hapi|\bHapi\.server\b|\bhapi\.server\b/.test(code);
|
|
74
|
+
if (!hasHapi) {
|
|
75
|
+
return { hasHapi: false, routes, strategies, issues };
|
|
76
|
+
}
|
|
77
|
+
// 认证策略声明:server.auth.strategy('name', ...)
|
|
78
|
+
const strategyRe = /\.auth\.strategy\s*\(\s*['"]([^'"]+)['"]/g;
|
|
79
|
+
let m;
|
|
80
|
+
while ((m = strategyRe.exec(code)) !== null) {
|
|
81
|
+
strategies.push(m[1]);
|
|
82
|
+
}
|
|
83
|
+
// 路由块:server.route({ ... }) —— 从 route( 向后截 500 字符窗口
|
|
84
|
+
const routeRe = /\.route\s*\(\s*\{/g;
|
|
85
|
+
while ((m = routeRe.exec(code)) !== null) {
|
|
86
|
+
const window = code.slice(m.index, m.index + 500);
|
|
87
|
+
const methodM = window.match(/method\s*:\s*['"]([^'"]+)['"]/);
|
|
88
|
+
const pathM = window.match(/path\s*:\s*['"]([^'"]+)['"]/);
|
|
89
|
+
const authM = window.match(/auth\s*:\s*(?:['"]([^'"]+)['"]|\{\s*strategy\s*:\s*['"]([^'"]+)['"]|\s*(false|true))/);
|
|
90
|
+
if (!methodM || !pathM)
|
|
91
|
+
continue;
|
|
92
|
+
const method = methodM[1].toLowerCase();
|
|
93
|
+
const pathName = pathM[1];
|
|
94
|
+
// auth: false → 显式公开;无 auth 字段 → authM null
|
|
95
|
+
const authOption = authM
|
|
96
|
+
? (authM[3] === "false" ? "false" : authM[1] || authM[2] || authM[3] || null)
|
|
97
|
+
: null;
|
|
98
|
+
routes.push({
|
|
99
|
+
method,
|
|
100
|
+
path: pathName,
|
|
101
|
+
authOption,
|
|
102
|
+
line: code.slice(0, m.index).split("\n").length,
|
|
103
|
+
});
|
|
104
|
+
if (!MUTATION_METHODS.has(method))
|
|
105
|
+
continue;
|
|
106
|
+
if (isAuthEntryPath(pathName))
|
|
107
|
+
continue;
|
|
108
|
+
// 无 auth 字段(authOption null 且非显式 false 已涵盖)或显式 false → 报
|
|
109
|
+
if (authOption === null || authOption === "false") {
|
|
110
|
+
issues.push({
|
|
111
|
+
severity: "medium",
|
|
112
|
+
rule: "HAPI_ROUTE_NO_AUTH",
|
|
113
|
+
message: authOption === "false"
|
|
114
|
+
? `Mutation route ${method.toUpperCase()} ${pathName} is explicitly ` +
|
|
115
|
+
`public (auth: false) — any caller can reach it.`
|
|
116
|
+
: `Mutation route ${method.toUpperCase()} ${pathName} has no auth ` +
|
|
117
|
+
`option in its route config — any caller can reach it.`,
|
|
118
|
+
route: `${method.toUpperCase()} ${pathName}`,
|
|
119
|
+
line: code.slice(0, m.index).split("\n").length,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { hasHapi: true, routes, strategies, issues };
|
|
124
|
+
}
|
|
125
|
+
function analyzeHapiFile(filePath) {
|
|
126
|
+
if (!fs.existsSync(filePath))
|
|
127
|
+
return null;
|
|
128
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
129
|
+
if (!/@hapi\/hapi|@hapi\/hawk|\bHapi\.server\b/.test(code))
|
|
130
|
+
return null;
|
|
131
|
+
return analyzeHapiApp(code);
|
|
132
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* hapi-detector.test.ts — Hapi 框架适配器规则回归(纯函数,无文件 I/O)
|
|
5
|
+
*/
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const hapi_detector_1 = require("./hapi-detector");
|
|
8
|
+
const app = (routes, extra = "") => `
|
|
9
|
+
import Hapi from "@hapi/hapi";
|
|
10
|
+
const server = Hapi.server({ port: 3000 });
|
|
11
|
+
${extra}
|
|
12
|
+
${routes}
|
|
13
|
+
`;
|
|
14
|
+
(0, vitest_1.describe)("hapi-detector", () => {
|
|
15
|
+
(0, vitest_1.it)("R1:无 auth 字段的 mutation 路由 → HAPI_ROUTE_NO_AUTH", () => {
|
|
16
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
17
|
+
server.route({ method: "POST", path: "/transfer", handler: () => "ok" });
|
|
18
|
+
`));
|
|
19
|
+
(0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("HAPI_ROUTE_NO_AUTH");
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)("R1:options.auth 策略引用保护不报", () => {
|
|
22
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
23
|
+
server.route({ method: "POST", path: "/transfer", options: { auth: "jwt" }, handler: () => "ok" });
|
|
24
|
+
`));
|
|
25
|
+
(0, vitest_1.expect)(issues).toHaveLength(0);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("R1:auth 对象形态(strategy 字段)保护不报", () => {
|
|
28
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
29
|
+
server.route({ method: "PUT", path: "/update", options: { auth: { strategy: "jwt" } }, handler: () => "ok" });
|
|
30
|
+
`));
|
|
31
|
+
(0, vitest_1.expect)(issues).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)("R1:显式 auth: false → 报(显式公开 mutation)", () => {
|
|
34
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
35
|
+
server.route({ method: "POST", path: "/transfer", options: { auth: false }, handler: () => "ok" });
|
|
36
|
+
`));
|
|
37
|
+
(0, vitest_1.expect)(issues.map((i) => i.rule)).toContain("HAPI_ROUTE_NO_AUTH");
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("R1:GET 读操作不报", () => {
|
|
40
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
41
|
+
server.route({ method: "GET", path: "/articles", handler: () => [] });
|
|
42
|
+
`));
|
|
43
|
+
(0, vitest_1.expect)(issues).toHaveLength(0);
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("R1 豁免:login 认证入口路径不报", () => {
|
|
46
|
+
const { issues } = (0, hapi_detector_1.analyzeHapiApp)(app(`
|
|
47
|
+
server.route({ method: "POST", path: "/login", handler: () => "token" });
|
|
48
|
+
`));
|
|
49
|
+
(0, vitest_1.expect)(issues).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)("策略声明被记录(auth.strategy 提取)", () => {
|
|
52
|
+
const { strategies } = (0, hapi_detector_1.analyzeHapiApp)(app(`server.route({ method: "GET", path: "/me", options: { auth: "jwt" }, handler: () => "me" });`, `server.auth.strategy("jwt", "jwt", { keys: ["secret"] });`));
|
|
53
|
+
(0, vitest_1.expect)(strategies).toContain("jwt");
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.it)("非 Hapi 代码不产生任何问题", () => {
|
|
56
|
+
const { hasHapi, issues } = (0, hapi_detector_1.analyzeHapiApp)(`import express from "express"; const app = express(); app.post("/x", h);`);
|
|
57
|
+
(0, vitest_1.expect)(hasHapi).toBe(false);
|
|
58
|
+
(0, vitest_1.expect)(issues).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
});
|
package/dist/frameworks/index.js
CHANGED
|
@@ -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.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; } });
|
|
@@ -34,6 +34,18 @@ Object.defineProperty(exports, "analyzeFastifyFile", { enumerable: true, get: fu
|
|
|
34
34
|
var nextjs_detector_1 = require("./nextjs-detector");
|
|
35
35
|
Object.defineProperty(exports, "analyzeNextApp", { enumerable: true, get: function () { return nextjs_detector_1.analyzeNextApp; } });
|
|
36
36
|
Object.defineProperty(exports, "readNextMiddleware", { enumerable: true, get: function () { return nextjs_detector_1.readNextMiddleware; } });
|
|
37
|
+
var koa_detector_1 = require("./koa-detector");
|
|
38
|
+
Object.defineProperty(exports, "analyzeKoaApp", { enumerable: true, get: function () { return koa_detector_1.analyzeKoaApp; } });
|
|
39
|
+
Object.defineProperty(exports, "analyzeKoaFile", { enumerable: true, get: function () { return koa_detector_1.analyzeKoaFile; } });
|
|
40
|
+
var hapi_detector_1 = require("./hapi-detector");
|
|
41
|
+
Object.defineProperty(exports, "analyzeHapiApp", { enumerable: true, get: function () { return hapi_detector_1.analyzeHapiApp; } });
|
|
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; } });
|
|
37
49
|
var nestjs_detector_1 = require("./nestjs-detector");
|
|
38
50
|
Object.defineProperty(exports, "analyzeNestJSProject", { enumerable: true, get: function () { return nestjs_detector_1.analyzeNestJSProject; } });
|
|
39
51
|
Object.defineProperty(exports, "analyzeNestJSFile", { enumerable: true, get: function () { return nestjs_detector_1.analyzeNestJSFile; } });
|