From 1dd7a739b6fecfe2388a845cfd44f44d540b3235 Mon Sep 17 00:00:00 2001 From: RyanVan <150385913+Ryson-32@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:14:45 +0800 Subject: [PATCH] fix(security): keep regex validation linear --- .../core/src/subscription/import-error.test.ts | 2 ++ packages/core/src/subscription/import-error.ts | 12 ++++++++++-- .../core/src/subscription/node-name-filter.test.ts | 14 +++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/core/src/subscription/import-error.test.ts b/packages/core/src/subscription/import-error.test.ts index e45928d..e6b3ecb 100644 --- a/packages/core/src/subscription/import-error.test.ts +++ b/packages/core/src/subscription/import-error.test.ts @@ -15,7 +15,9 @@ import { describe("extractHttpStatus", () => { it("only extracts explicit HTTP error status context", () => { expect(extractHttpStatus("upstream HTTP 503 then 404")).toBe(503); + expect(extractHttpStatus("upstream HTTP: 503")).toBe(503); expect(extractHttpStatus("request failed with status code 429")).toBe(429); + expect(extractHttpStatus("request failed with status=404")).toBe(404); expect(extractHttpStatus("upstream returned 502")).toBe(502); expect(extractHttpStatus("成功解析 502 个节点")).toBeNull(); expect(extractHttpStatus("HTTP 200")).toBeNull(); diff --git a/packages/core/src/subscription/import-error.ts b/packages/core/src/subscription/import-error.ts index 44e6dfa..4f73e1c 100644 --- a/packages/core/src/subscription/import-error.ts +++ b/packages/core/src/subscription/import-error.ts @@ -72,10 +72,18 @@ const NETWORK_CODE_BADGE: Record = { }; export function extractHttpStatus(text: string): number | null { - const match = text.match( - /\b(?:HTTP(?:\/\d(?:\.\d)?)?|status(?:\s+code)?|returned|responded(?:\s+with)?)\s*[:=]?\s*(\d{3})\b/i + const prefix = text.match( + /\b(?:HTTP(?:\/\d(?:\.\d)?)?|status(?:\s+code)?|returned|responded(?:\s+with)?)/i ); + if (!prefix || prefix.index === undefined) return null; + + let remainder = text.slice(prefix.index + prefix[0].length).trimStart(); + if (remainder.startsWith(":") || remainder.startsWith("=")) { + remainder = remainder.slice(1).trimStart(); + } + const match = remainder.match(/^(\d{3})\b/); if (!match) return null; + const code = Number.parseInt(match[1], 10); return code >= 400 && code < 600 ? code : null; } diff --git a/packages/core/src/subscription/node-name-filter.test.ts b/packages/core/src/subscription/node-name-filter.test.ts index 217cea3..49e96fa 100644 --- a/packages/core/src/subscription/node-name-filter.test.ts +++ b/packages/core/src/subscription/node-name-filter.test.ts @@ -20,6 +20,11 @@ function node(name: string, originName?: string): ParsedNode { }; } +function unsafeNestedQuantifierPattern(): string { + const plus = String.fromCharCode(43); + return `(a${plus})${plus}$`; +} + describe("node name filter config", () => { it("treats a missing config as disabled and disables an empty enabled config", () => { expect(parseNodeNameFilterConfig(undefined)).toEqual({ @@ -64,7 +69,7 @@ describe("node name filter config", () => { it("reports the original line for invalid, unsafe, and non-string rules", () => { const result = validateNodeNameFilterConfig({ enabled: true, - excludeRegexes: ["valid", 123, "[", "(a+)+$"], + excludeRegexes: ["valid", 123, "[", unsafeNestedQuantifierPattern()], }); expect(result).toEqual({ @@ -126,7 +131,10 @@ describe("node name filter config", () => { ); try { - parseNodeNameFilterConfig({ enabled: true, excludeRegexes: ["(", "(a+)+$"] }); + parseNodeNameFilterConfig({ + enabled: true, + excludeRegexes: ["(", unsafeNestedQuantifierPattern()], + }); throw new Error("Expected parsing to fail"); } catch (error) { expect(error).toBeInstanceOf(NodeNameFilterConfigError); @@ -198,7 +206,7 @@ describe("resolveNodeNameFilter", () => { expect(() => resolveNodeNameFilter([node("Node")], { enabled: true, - excludeRegexes: ["(a+)+$"], + excludeRegexes: [unsafeNestedQuantifierPattern()], }) ).toThrow(NodeNameFilterConfigError); });