fix(frontend): 升级 Next→15.5.20 + React 19.2.7 修 RCE/补丁 + 版本下界守卫(CR-C1)
- next/eslint-config-next 15.1.3→15.5.20(backport 安全维护线),react/react-dom 19.0.0→19.2.7 - @types/react(-dom) →^19.2.0;pnpm audit --prod 无 next/react/react-dom 高危/严重告警 - 新增 lib/security/dependencyFloors 守卫 + 回归测试锁定安全下界 - 连带修 useDraftStream.test noUncheckedIndexedAccess(重锁失效增量缓存后暴露,对齐 useReviewStream 既有 ! 约定)
This commit is contained in:
70
apps/web/lib/security/dependencyFloors.test.ts
Normal file
70
apps/web/lib/security/dependencyFloors.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { isAtLeast, SECURITY_FLOORS } from "./dependencyFloors";
|
||||
|
||||
// 读取本包 package.json 声明版本(相对本测试文件 ../../ → apps/web/package.json)。
|
||||
function declaredVersions(): Record<string, string> {
|
||||
const raw = readFileSync(
|
||||
new URL("../../package.json", import.meta.url),
|
||||
"utf-8",
|
||||
);
|
||||
const pkg = JSON.parse(raw) as {
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
};
|
||||
return { ...pkg.devDependencies, ...pkg.dependencies };
|
||||
}
|
||||
|
||||
describe("SECURITY_FLOORS 锁定安全下界(CR-C1)", () => {
|
||||
const declared = declaredVersions();
|
||||
|
||||
it.each(Object.keys(SECURITY_FLOORS))(
|
||||
"%s 声明版本不得低于安全下界",
|
||||
(name) => {
|
||||
const version = declared[name];
|
||||
if (version === undefined) {
|
||||
throw new Error(`${name} 未在 package.json 声明`);
|
||||
}
|
||||
const floor = SECURITY_FLOORS[name as keyof typeof SECURITY_FLOORS];
|
||||
expect(
|
||||
isAtLeast(version, floor),
|
||||
`${name}@${version} 低于安全下界 ${floor}`,
|
||||
).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("isAtLeast 语义版本比较", () => {
|
||||
it("相等即满足", () => {
|
||||
expect(isAtLeast("15.5.20", "15.5.20")).toBe(true);
|
||||
});
|
||||
|
||||
it("patch 更高满足、更低不满足", () => {
|
||||
expect(isAtLeast("19.2.7", "19.2.0")).toBe(true);
|
||||
expect(isAtLeast("19.2.0", "19.2.7")).toBe(false);
|
||||
});
|
||||
|
||||
it("minor 边界", () => {
|
||||
expect(isAtLeast("15.5.0", "15.1.3")).toBe(true);
|
||||
expect(isAtLeast("15.1.3", "15.5.20")).toBe(false);
|
||||
});
|
||||
|
||||
it("major 边界", () => {
|
||||
expect(isAtLeast("19.0.0", "18.9.9")).toBe(true);
|
||||
expect(isAtLeast("18.9.9", "19.0.0")).toBe(false);
|
||||
});
|
||||
|
||||
it("去除前导 ^ / ~ 范围符", () => {
|
||||
expect(isAtLeast("^19.2.7", "19.2.7")).toBe(true);
|
||||
expect(isAtLeast("~19.2.7", "19.2.7")).toBe(true);
|
||||
expect(isAtLeast("19.2.0", "^19.2.7")).toBe(false);
|
||||
});
|
||||
|
||||
it("缺省段按 0 计", () => {
|
||||
expect(isAtLeast("15", "15.0.0")).toBe(true);
|
||||
expect(isAtLeast("15.5", "15.5.0")).toBe(true);
|
||||
expect(isAtLeast("15", "15.5.0")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user