-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttp-method-validation.test.ts
More file actions
170 lines (145 loc) · 5.51 KB
/
Copy pathhttp-method-validation.test.ts
File metadata and controls
170 lines (145 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { describe, it, expect, beforeEach, spyOn, afterEach } from "bun:test"
import { validateHttpMethod } from "../src/utils"
import { FetchProxy } from "../src/proxy"
describe("HTTP Method Validation Security Tests", () => {
let fetchSpy: ReturnType<typeof spyOn>
afterEach(() => {
fetchSpy?.mockRestore()
})
describe("Direct Method Validation", () => {
it("should reject CONNECT method", () => {
expect(() => {
validateHttpMethod("CONNECT")
}).toThrow(/HTTP method CONNECT is not allowed/)
})
it("should reject TRACE method", () => {
expect(() => {
validateHttpMethod("TRACE")
}).toThrow(/HTTP method TRACE is not allowed/)
})
it("should reject arbitrary custom methods", () => {
expect(() => {
validateHttpMethod("CUSTOM_DANGEROUS_METHOD")
}).toThrow(/HTTP method CUSTOM_DANGEROUS_METHOD is not allowed/)
})
it("should allow GET method", () => {
expect(() => {
validateHttpMethod("GET")
}).not.toThrow()
})
it("should allow POST method", () => {
expect(() => {
validateHttpMethod("POST")
}).not.toThrow()
})
it("should handle case sensitivity correctly", () => {
expect(() => {
validateHttpMethod("connect")
}).toThrow(/HTTP method.*is not allowed/)
expect(() => {
validateHttpMethod("Trace")
}).toThrow(/HTTP method.*is not allowed/)
})
})
describe("Native Request Constructor Security", () => {
it("should silently normalize invalid method injection attempts (runtime protection)", () => {
// The native Request constructor in Bun normalizes invalid methods
const req1 = new Request("http://example.com/test", {
method: "GET\r\nHost: evil.com",
})
expect(req1.method).toBe("GET") // Runtime normalizes to GET
})
it("should silently normalize methods with null bytes (runtime protection)", () => {
// The native Request constructor in Bun normalizes invalid methods
const req2 = new Request("http://example.com/test", {
method: "GET\x00",
})
expect(req2.method).toBe("GET") // Runtime normalizes to GET
})
})
describe("Proxy Integration Tests", () => {
let proxy: FetchProxy
beforeEach(() => {
proxy = new FetchProxy({
base: "http://httpbin.org", // Use a real service for testing
circuitBreaker: { enabled: false },
})
// Mock fetch to return a successful response
fetchSpy = spyOn(global, "fetch").mockResolvedValue(
new Response("", {
status: 200,
statusText: "OK",
headers: new Headers({ "content-type": "text/plain" }),
}),
)
})
it("should reject CONNECT method in proxy (if runtime allows it)", async () => {
// Note: The native Request constructor may normalize some methods
const request = new Request("http://httpbin.org/status/200", {
method: "CONNECT",
})
// If the runtime allows CONNECT through, our validation should catch it
if (request.method === "CONNECT") {
const response = await proxy.proxy(request)
expect(response.status).toBe(400)
const text = await response.text()
expect(text).toMatch(/HTTP method CONNECT is not allowed/)
} else {
// If runtime normalizes it, verify the normalization happened
expect(request.method).toBe("GET") // Most runtimes normalize invalid methods to GET
}
})
it("should handle runtime method normalization correctly", async () => {
// Test that runtime normalizes invalid methods to GET
const request = new Request("http://httpbin.org/status/200", {
method: "CUSTOM_DANGEROUS_METHOD",
})
// The runtime should normalize the invalid method to GET
expect(request.method).toBe("GET")
// The normalized request should work fine
const response = await proxy.proxy(request)
expect(response.status).toBe(200)
// Verify fetch was called
expect(fetchSpy).toHaveBeenCalledTimes(1)
})
it("should allow safe methods in proxy", async () => {
const request = new Request("http://httpbin.org/status/200", {
method: "GET",
})
const response = await proxy.proxy(request)
expect(response.status).toBe(200)
// Verify fetch was called
expect(fetchSpy).toHaveBeenCalledTimes(1)
})
it("should validate methods when passed through request options", async () => {
// Test direct method validation by bypassing Request constructor
const request = new Request("http://httpbin.org/status/200", {
method: "GET",
})
// Simulate a scenario where we manually override the method (for testing purposes)
// This tests our validation logic directly
const originalMethod = request.method
try {
// Override the method property to simulate an invalid method reaching our code
Object.defineProperty(request, "method", {
value: "CUSTOM_DANGEROUS_METHOD",
writable: false,
configurable: true,
})
const response = await proxy.proxy(request)
expect(response.status).toBe(400)
const text = await response.text()
expect(text).toMatch(
/HTTP method CUSTOM_DANGEROUS_METHOD is not allowed/,
)
} finally {
// Restore the original method
Object.defineProperty(request, "method", {
value: originalMethod,
writable: false,
configurable: true,
})
}
})
})
})