Skip to content

Commit 599ff4f

Browse files
authored
fix: ReDos regex vulnerability, reported by @dayshift (#515)
1 parent 0a80e82 commit 599ff4f

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

src/parse.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export function parse(options: EndpointDefaults): RequestOptions {
5959
if (url.endsWith("/graphql")) {
6060
if (options.mediaType.previews?.length) {
6161
const previewsFromAcceptHeader =
62-
headers.accept.match(/[\w-]+(?=-preview)/g) || ([] as string[]);
62+
headers.accept.match(/(?<![\w-])[\w-]+(?=-preview)/g) ||
63+
([] as string[]);
6364
headers.accept = previewsFromAcceptHeader
6465
.concat(options.mediaType.previews!)
6566
.map((preview) => {

src/util/extract-url-variable-names.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const urlVariableRegex = /\{[^}]+\}/g;
1+
const urlVariableRegex = /\{[^{}}]+\}/g;
22

33
function removeNonChars(variableName: string) {
4-
return variableName.replace(/^\W+|\W+$/g, "").split(/,/);
4+
return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
55
}
66

77
export function extractUrlVariableNames(url: string) {

test/parse.test.ts

+102
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,106 @@ describe("endpoint.parse()", () => {
5151

5252
expect(input.headers.accept).toEqual("application/vnd.github.v3+json");
5353
});
54+
55+
it("Test ReDoS - attack string #1", async () => {
56+
const startTime = performance.now();
57+
try {
58+
endpoint.parse({
59+
method: "POST",
60+
url: "/graphql", // Ensure that the URL ends with "/graphql"
61+
headers: {
62+
accept: "" + "A".repeat(100000) + "-", // Pass in the attack string
63+
"content-type": "text/plain",
64+
"user-agent": "Your User Agent String Here",
65+
},
66+
mediaType: {
67+
previews: ["test-preview"], // Ensure that mediaType.previews exists and has values
68+
format: "raw", // Optional media format
69+
},
70+
baseUrl: "https://api.github.com",
71+
});
72+
} catch (error) {
73+
// pass
74+
}
75+
const endTime = performance.now();
76+
const elapsedTime = endTime - startTime;
77+
const reDosThreshold = 2000;
78+
79+
expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold);
80+
if (elapsedTime > reDosThreshold) {
81+
console.warn(
82+
`🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed(
83+
2,
84+
)} ms, exceeding threshold of ${reDosThreshold} ms.`,
85+
);
86+
}
87+
});
88+
89+
it("Test ReDoS - attack string #2", async () => {
90+
const startTime = performance.now();
91+
try {
92+
endpoint.parse({
93+
method: "POST",
94+
url: "{".repeat(100000) + "@", // Pass in the attack string
95+
headers: {
96+
accept: "application/vnd.github.v3+json",
97+
"content-type": "text/plain",
98+
"user-agent": "Your User Agent String Here",
99+
},
100+
mediaType: {
101+
previews: ["test-preview"], // Ensure that mediaType.previews exists and has values
102+
format: "raw", // Optional media format
103+
},
104+
baseUrl: "https://api.github.com",
105+
});
106+
} catch (error) {
107+
// pass
108+
}
109+
const endTime = performance.now();
110+
const elapsedTime = endTime - startTime;
111+
const reDosThreshold = 2000;
112+
113+
expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold);
114+
if (elapsedTime > reDosThreshold) {
115+
console.warn(
116+
`🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed(
117+
2,
118+
)} ms, exceeding threshold of ${reDosThreshold} ms.`,
119+
);
120+
}
121+
});
122+
123+
it("Test ReDoS - attack string #3", async () => {
124+
const startTime = performance.now();
125+
try {
126+
endpoint.parse({
127+
method: "POST",
128+
url: "{" + "00" + "\u0000".repeat(100000) + "a!a" + "}", // Pass in the attack string
129+
headers: {
130+
accept: "application/vnd.github.v3+json",
131+
"content-type": "text/plain",
132+
"user-agent": "Your User Agent String Here",
133+
},
134+
mediaType: {
135+
previews: ["test-preview"],
136+
format: "raw",
137+
},
138+
baseUrl: "https://api.github.com",
139+
});
140+
} catch (error) {
141+
// pass
142+
}
143+
const endTime = performance.now();
144+
const elapsedTime = endTime - startTime;
145+
const reDosThreshold = 2000;
146+
147+
expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold);
148+
if (elapsedTime > reDosThreshold) {
149+
console.warn(
150+
`🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed(
151+
2,
152+
)} ms, exceeding threshold of ${reDosThreshold} ms.`,
153+
);
154+
}
155+
});
54156
});

0 commit comments

Comments
 (0)