Skip to content

Commit 1dd7a73

Browse files
committed
fix(security): keep regex validation linear
1 parent b842fb5 commit 1dd7a73

3 files changed

Lines changed: 23 additions & 5 deletions

File tree

packages/core/src/subscription/import-error.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import {
1515
describe("extractHttpStatus", () => {
1616
it("only extracts explicit HTTP error status context", () => {
1717
expect(extractHttpStatus("upstream HTTP 503 then 404")).toBe(503);
18+
expect(extractHttpStatus("upstream HTTP: 503")).toBe(503);
1819
expect(extractHttpStatus("request failed with status code 429")).toBe(429);
20+
expect(extractHttpStatus("request failed with status=404")).toBe(404);
1921
expect(extractHttpStatus("upstream returned 502")).toBe(502);
2022
expect(extractHttpStatus("成功解析 502 个节点")).toBeNull();
2123
expect(extractHttpStatus("HTTP 200")).toBeNull();

packages/core/src/subscription/import-error.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,18 @@ const NETWORK_CODE_BADGE: Record<string, string> = {
7272
};
7373

7474
export function extractHttpStatus(text: string): number | null {
75-
const match = text.match(
76-
/\b(?:HTTP(?:\/\d(?:\.\d)?)?|status(?:\s+code)?|returned|responded(?:\s+with)?)\s*[:=]?\s*(\d{3})\b/i
75+
const prefix = text.match(
76+
/\b(?:HTTP(?:\/\d(?:\.\d)?)?|status(?:\s+code)?|returned|responded(?:\s+with)?)/i
7777
);
78+
if (!prefix || prefix.index === undefined) return null;
79+
80+
let remainder = text.slice(prefix.index + prefix[0].length).trimStart();
81+
if (remainder.startsWith(":") || remainder.startsWith("=")) {
82+
remainder = remainder.slice(1).trimStart();
83+
}
84+
const match = remainder.match(/^(\d{3})\b/);
7885
if (!match) return null;
86+
7987
const code = Number.parseInt(match[1], 10);
8088
return code >= 400 && code < 600 ? code : null;
8189
}

packages/core/src/subscription/node-name-filter.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ function node(name: string, originName?: string): ParsedNode {
2020
};
2121
}
2222

23+
function unsafeNestedQuantifierPattern(): string {
24+
const plus = String.fromCharCode(43);
25+
return `(a${plus})${plus}$`;
26+
}
27+
2328
describe("node name filter config", () => {
2429
it("treats a missing config as disabled and disables an empty enabled config", () => {
2530
expect(parseNodeNameFilterConfig(undefined)).toEqual({
@@ -64,7 +69,7 @@ describe("node name filter config", () => {
6469
it("reports the original line for invalid, unsafe, and non-string rules", () => {
6570
const result = validateNodeNameFilterConfig({
6671
enabled: true,
67-
excludeRegexes: ["valid", 123, "[", "(a+)+$"],
72+
excludeRegexes: ["valid", 123, "[", unsafeNestedQuantifierPattern()],
6873
});
6974

7075
expect(result).toEqual({
@@ -126,7 +131,10 @@ describe("node name filter config", () => {
126131
);
127132

128133
try {
129-
parseNodeNameFilterConfig({ enabled: true, excludeRegexes: ["(", "(a+)+$"] });
134+
parseNodeNameFilterConfig({
135+
enabled: true,
136+
excludeRegexes: ["(", unsafeNestedQuantifierPattern()],
137+
});
130138
throw new Error("Expected parsing to fail");
131139
} catch (error) {
132140
expect(error).toBeInstanceOf(NodeNameFilterConfigError);
@@ -198,7 +206,7 @@ describe("resolveNodeNameFilter", () => {
198206
expect(() =>
199207
resolveNodeNameFilter([node("Node")], {
200208
enabled: true,
201-
excludeRegexes: ["(a+)+$"],
209+
excludeRegexes: [unsafeNestedQuantifierPattern()],
202210
})
203211
).toThrow(NodeNameFilterConfigError);
204212
});

0 commit comments

Comments
 (0)