Skip to content

Commit cf35e65

Browse files
committed
Prevent double counting monitored lists
1 parent 8580d8e commit cf35e65

File tree

2 files changed

+86
-26
lines changed

2 files changed

+86
-26
lines changed

library/sources/HTTPServer.stats.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,59 @@ t.test("it tracks monitored IP addresses", async () => {
154154
});
155155
});
156156
});
157+
158+
t.test("it only counts once if multiple listeners", async () => {
159+
const server = http.createServer((req, res) => {
160+
res.setHeader("Content-Type", "text/plain");
161+
res.end("OK");
162+
});
163+
164+
server.on("request", (req, res) => {
165+
if (res.headersSent) {
166+
return;
167+
}
168+
169+
res.setHeader("Content-Type", "text/plain");
170+
res.end("OK");
171+
});
172+
173+
await new Promise<void>((resolve) => {
174+
server.listen(3329, () => {
175+
Promise.all([
176+
fetch({
177+
url: new URL("http://localhost:3329/test"),
178+
method: "GET",
179+
headers: {
180+
"user-agent": "GPTBot",
181+
},
182+
timeoutInMS: 500,
183+
}),
184+
fetch({
185+
url: new URL("http://localhost:3329/test"),
186+
method: "GET",
187+
headers: {
188+
"x-forwarded-for": "1.2.3.4",
189+
},
190+
timeoutInMS: 500,
191+
}),
192+
]).then(() => {
193+
const { userAgents, ipAddresses } = agent
194+
.getInspectionStatistics()
195+
.getStats();
196+
t.same(userAgents, {
197+
breakdown: {
198+
// eslint-disable-next-line camelcase
199+
ai_data_scrapers: { total: 1, blocked: 0 },
200+
},
201+
});
202+
t.same(ipAddresses, {
203+
breakdown: {
204+
"known_threat_actors/public_scanners": { total: 1, blocked: 0 },
205+
},
206+
});
207+
server.close();
208+
resolve();
209+
});
210+
});
211+
});
212+
});

library/sources/http-server/checkIfRequestIsBlocked.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { getContext } from "../../agent/Context";
55
import { escapeHTML } from "../../helpers/escapeHTML";
66
import { ipAllowedToAccessRoute } from "./ipAllowedToAccessRoute";
77

8+
const checkedBlocks = Symbol("__zen_checked_blocks__");
9+
810
/**
911
* Inspects the IP address of the request:
1012
* - Whether the IP address is blocked by an IP blocklist (e.g. Geo restrictions)
1113
* - Whether the IP address is allowed to access the current route (e.g. Admin panel)
1214
*/
1315
export function checkIfRequestIsBlocked(
14-
res: ServerResponse,
16+
res: ServerResponse & { [checkedBlocks]?: boolean },
1517
agent: Agent
1618
): boolean {
1719
if (res.headersSent) {
@@ -26,6 +28,14 @@ export function checkIfRequestIsBlocked(
2628
return false;
2729
}
2830

31+
if (res[checkedBlocks]) {
32+
return false;
33+
}
34+
35+
// We don't need to check again if the request has already been checked
36+
// Also ensures that the statistics are only counted once
37+
// res[checkedBlocklist] = true;
38+
2939
if (!ipAllowedToAccessRoute(context, agent)) {
3040
res.statusCode = 403;
3141
res.setHeader("Content-Type", "text/plain");
@@ -69,43 +79,37 @@ export function checkIfRequestIsBlocked(
6979
? agent.getConfig().getBlockedIPAddresses(context.remoteAddress)
7080
: [];
7181

72-
if (blockedIPs.length > 0) {
73-
// The same IP address can be blocked by multiple lists
74-
agent.getInspectionStatistics().onIPAddressMatches(blockedIPs);
82+
agent.getInspectionStatistics().onIPAddressMatches(blockedIPs);
83+
const blockingMatch = blockedIPs.find((match) => !match.monitor);
7584

76-
const blockingMatch = blockedIPs.find((match) => !match.monitor);
77-
if (blockingMatch) {
78-
res.statusCode = 403;
79-
res.setHeader("Content-Type", "text/plain");
80-
81-
let message = `Your IP address is blocked due to ${escapeHTML(blockingMatch.reason)}.`;
82-
if (context.remoteAddress) {
83-
message += ` (Your IP: ${escapeHTML(context.remoteAddress)})`;
84-
}
85+
if (blockingMatch) {
86+
res.statusCode = 403;
87+
res.setHeader("Content-Type", "text/plain");
8588

86-
res.end(message);
87-
return true;
89+
let message = `Your IP address is blocked due to ${escapeHTML(blockingMatch.reason)}.`;
90+
if (context.remoteAddress) {
91+
message += ` (Your IP: ${escapeHTML(context.remoteAddress)})`;
8892
}
93+
94+
res.end(message);
95+
return true;
8996
}
9097

9198
const blockedUserAgents =
9299
context.headers && typeof context.headers["user-agent"] === "string"
93100
? agent.getConfig().getBlockedUserAgents(context.headers["user-agent"])
94101
: [];
95102

96-
if (blockedUserAgents.length > 0) {
97-
// The same user agent can be blocked by multiple lists
98-
agent.getInspectionStatistics().onUserAgentMatches(blockedUserAgents);
103+
agent.getInspectionStatistics().onUserAgentMatches(blockedUserAgents);
99104

100-
if (blockedUserAgents.find((match) => !match.monitor)) {
101-
res.statusCode = 403;
102-
res.setHeader("Content-Type", "text/plain");
105+
if (blockedUserAgents.find((match) => !match.monitor)) {
106+
res.statusCode = 403;
107+
res.setHeader("Content-Type", "text/plain");
103108

104-
res.end(
105-
"You are not allowed to access this resource because you have been identified as a bot."
106-
);
107-
return true;
108-
}
109+
res.end(
110+
"You are not allowed to access this resource because you have been identified as a bot."
111+
);
112+
return true;
109113
}
110114

111115
return false;

0 commit comments

Comments
 (0)