Skip to content

Commit 75ee2b3

Browse files
authored
Support ip ranges in bypass list (#530)
* Support ip ranges in bypass list * Add more unit tests
1 parent 873e8e7 commit 75ee2b3

File tree

4 files changed

+130
-8
lines changed

4 files changed

+130
-8
lines changed

end2end/tests/hono-xml-blocklists.test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ t.beforeEach(async () => {
2121
Authorization: token,
2222
},
2323
body: JSON.stringify({
24-
allowedIPAddresses: ["1.3.2.1", "1.3.2.2"],
24+
allowedIPAddresses: ["1.3.2.1", "1.3.2.2", "123.4.0.0/16"], // bypass list
2525
endpoints: [
2626
{
2727
route: "/admin",
@@ -209,6 +209,17 @@ t.test("it blocks bots", (t) => {
209209
"You are not allowed to access this resource because you have been identified as a bot."
210210
);
211211

212+
// Do not block if on allowlist
213+
const resp3 = await fetch("http://127.0.0.1:4003/", {
214+
headers: {
215+
"User-Agent":
216+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.1; +https://openai.com/gptbot",
217+
"X-Forwarded-For": "123.4.5.6",
218+
},
219+
signal: AbortSignal.timeout(5000),
220+
});
221+
t.same(resp3.status, 200);
222+
212223
// Does not block allowed user agents
213224
const allowedUserAgents = [
214225
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",

library/agent/ServiceConfig.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,57 @@ t.test("only allow some ips: empty list", async () => {
200200
t.same(config.isAllowedIPAddress("1.2.3.4").allowed, true);
201201
t.same(config.isAllowedIPAddress("4.3.2.1").allowed, true);
202202
});
203+
204+
t.test("bypassed ips support cidr", async () => {
205+
const config = new ServiceConfig(
206+
[],
207+
0,
208+
[],
209+
["192.168.2.0/24", "::1"],
210+
false,
211+
[],
212+
[]
213+
);
214+
215+
t.same(config.isBypassedIP("192.168.2.32"), true);
216+
t.same(config.isBypassedIP("::1"), true);
217+
t.same(config.isBypassedIP("::2"), false);
218+
t.same(config.isBypassedIP("10.0.0.1"), false);
219+
220+
config.updateConfig(
221+
[],
222+
0,
223+
[],
224+
["invalid", "2002::1/124", "127.0.0.1"],
225+
false
226+
);
227+
228+
t.same(config.isBypassedIP("2002::6"), true);
229+
t.same(config.isBypassedIP("2002::f"), true);
230+
t.same(config.isBypassedIP("2002::10"), false);
231+
t.same(config.isBypassedIP("127.0.0.1"), true);
232+
t.same(config.isBypassedIP("192.168.2.1"), false);
233+
t.same(config.isBypassedIP("::1"), false);
234+
235+
config.updateConfig(
236+
[],
237+
0,
238+
[],
239+
["0", "123.123.123.1/32", "234.0.0.0/8", "999.999.999.999", "::1/128"],
240+
false
241+
);
242+
243+
t.same(config.isBypassedIP("123.123.123.1"), true);
244+
t.same(config.isBypassedIP("124.123.123.1"), false);
245+
t.same(config.isBypassedIP("234.1.2.3"), true);
246+
t.same(config.isBypassedIP("235.1.2.3"), false);
247+
t.same(config.isBypassedIP("127.0.0.1"), false);
248+
t.same(config.isBypassedIP("999.999.999.999"), false);
249+
t.same(config.isBypassedIP("::1"), true);
250+
t.same(config.isBypassedIP("::2"), false);
251+
252+
config.updateConfig([], 0, [], [], false);
253+
254+
t.same(config.isBypassedIP("123.123.123.1"), false);
255+
t.same(config.isBypassedIP("999.999.999.999"), false);
256+
});

library/agent/ServiceConfig.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IPList } from "./api/fetchBlockedLists";
77
export class ServiceConfig {
88
private blockedUserIds: Map<string, string> = new Map();
99
// IP addresses that are allowed to bypass rate limiting, attack blocking, etc.
10-
private bypassedIPAddresses: Set<string> = new Set();
10+
private bypassedIPAddresses: IPMatcher | undefined;
1111
private nonGraphQLEndpoints: Endpoint[] = [];
1212
private graphqlFields: Endpoint[] = [];
1313
private blockedIPAddresses: { blocklist: IPMatcher; description: string }[] =
@@ -71,14 +71,15 @@ export class ServiceConfig {
7171
}
7272

7373
private setBypassedIPAddresses(ipAddresses: string[]) {
74-
this.bypassedIPAddresses = new Set();
75-
ipAddresses.forEach((ip) => {
76-
this.bypassedIPAddresses.add(ip);
77-
});
74+
if (ipAddresses.length === 0) {
75+
this.bypassedIPAddresses = undefined;
76+
return;
77+
}
78+
this.bypassedIPAddresses = new IPMatcher(ipAddresses);
7879
}
7980

8081
isBypassedIP(ip: string) {
81-
return this.bypassedIPAddresses.has(ip);
82+
return this.bypassedIPAddresses ? this.bypassedIPAddresses.has(ip) : false;
8283
}
8384

8485
private setBlockedUserIds(blockedUserIds: string[]) {

library/sources/Hono.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const agent = createTestAgent({
5959
blockedUserIds: ["567"],
6060
configUpdatedAt: 0,
6161
heartbeatIntervalInMS: 10 * 60 * 1000,
62-
allowedIPAddresses: ["4.3.2.1"],
62+
allowedIPAddresses: ["4.3.2.1", "123.1.2.0/24"],
6363
}),
6464
});
6565
agent.start([new HonoInternal(), new HTTPServer()]);
@@ -507,3 +507,59 @@ t.test("invalid json body", opts, async (t) => {
507507
t.same(response.status, 400);
508508
t.same(await response.text(), "Invalid JSON");
509509
});
510+
511+
t.test("bypass list works", opts, async (t) => {
512+
// Start a server with a real socket
513+
// The blocking is implemented in the HTTPServer source
514+
const { serve } =
515+
require("@hono/node-server") as typeof import("@hono/node-server");
516+
const server = serve({
517+
fetch: getApp().fetch,
518+
port: 8769,
519+
});
520+
521+
// It blocks bot
522+
const response = await fetch.fetch({
523+
url: new URL("http://127.0.0.1:8769/"),
524+
headers: {
525+
"X-Forwarded-For": "123.2.2.2",
526+
"User-Agent": "hacker",
527+
},
528+
});
529+
t.equal(response.statusCode, 403);
530+
t.equal(
531+
response.body,
532+
"You are not allowed to access this resource because you have been identified as a bot."
533+
);
534+
535+
// It does not block bypassed IP
536+
const response2 = await fetch.fetch({
537+
url: new URL("http://127.0.0.1:8769/"),
538+
headers: {
539+
"X-Forwarded-For": "4.3.2.1",
540+
"User-Agent": "hacker",
541+
},
542+
});
543+
t.equal(response2.statusCode, 200);
544+
545+
const response3 = await fetch.fetch({
546+
url: new URL("http://127.0.0.1:8769/"),
547+
headers: {
548+
"X-Forwarded-For": "123.1.2.2",
549+
"User-Agent": "hacker",
550+
},
551+
});
552+
t.equal(response3.statusCode, 200);
553+
554+
const response4 = await fetch.fetch({
555+
url: new URL("http://127.0.0.1:8769/"),
556+
headers: {
557+
"X-Forwarded-For": "123.1.2.254",
558+
"User-Agent": "hacker",
559+
},
560+
});
561+
t.equal(response4.statusCode, 200);
562+
563+
// Cleanup server
564+
server.close();
565+
});

0 commit comments

Comments
 (0)