Skip to content

Commit 618ccf4

Browse files
committed
fix(readRawBody): fix case-sensitive Transfer-Encoding check causing request smuggling risk
1 parent 401c9b8 commit 618ccf4

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

src/utils/body.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,9 @@ export function readRawBody<E extends Encoding = "utf8">(
109109

110110
if (
111111
!Number.parseInt(event.node.req.headers["content-length"] || "") &&
112-
!String(event.node.req.headers["transfer-encoding"] ?? "")
113-
.split(",")
114-
.map((e) => e.trim())
115-
.filter(Boolean)
116-
.includes("chunked")
112+
!/\bchunked\b/i.test(
113+
String(event.node.req.headers["transfer-encoding"] ?? ""),
114+
)
117115
) {
118116
return Promise.resolve(undefined);
119117
}

test/body.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,42 @@ describe("body", () => {
9494
expect(await result.body.text()).toBe("200");
9595
});
9696

97+
it("handles case-insensitive Transfer-Encoding: chunked header (CVE)", async () => {
98+
// Test that Transfer-Encoding header check is case-insensitive per RFC 7230
99+
// This prevents HTTP Request Smuggling via TE.TE desynchronization attacks
100+
101+
const testCases = [
102+
{ encoding: "ChunKed", expected: '{"test":"data"}' },
103+
{ encoding: "CHUNKED", expected: '{"test":"data"}' },
104+
{ encoding: "Chunked", expected: '{"test":"data"}' },
105+
];
106+
107+
for (const testCase of testCases) {
108+
// Simulate a raw HTTP request with mixed-case Transfer-Encoding
109+
const mockReq = {
110+
method: "POST",
111+
headers: { "transfer-encoding": testCase.encoding },
112+
on(event: string, handler: (chunk?: Buffer) => void) {
113+
if (event === "data") {
114+
// Simulate chunked data arrival
115+
handler(Buffer.from(testCase.expected));
116+
} else if (event === "end") {
117+
handler();
118+
}
119+
return this;
120+
},
121+
};
122+
123+
const mockEvent = { method: "POST", node: { req: mockReq } };
124+
125+
const result = await readRawBody(mockEvent as any);
126+
127+
// Should properly read the body regardless of Transfer-Encoding case
128+
// If the check is case-sensitive, this will fail and return undefined
129+
expect(result).toEqual(testCase.expected);
130+
}
131+
});
132+
97133
it("returns an empty string if body is empty", async () => {
98134
let _body: string | undefined = "initial";
99135
app.use(

0 commit comments

Comments
 (0)