diff --git a/end2end/server/src/handlers/lists.js b/end2end/server/src/handlers/lists.js index 9334a1f2e..23c2d2321 100644 --- a/end2end/server/src/handlers/lists.js +++ b/end2end/server/src/handlers/lists.js @@ -24,6 +24,8 @@ module.exports = function lists(req, res) { }, ] : [], - blockedUserAgents: blockedUserAgents, + blockedUserAgentsV2: Array.isArray(blockedUserAgents) + ? blockedUserAgents + : [], }); }; diff --git a/end2end/server/src/handlers/updateLists.js b/end2end/server/src/handlers/updateLists.js index e0c7d9080..c0935ff5f 100644 --- a/end2end/server/src/handlers/updateLists.js +++ b/end2end/server/src/handlers/updateLists.js @@ -32,10 +32,10 @@ module.exports = function updateIPLists(req, res) { updateBlockedIPAddresses(req.app, req.body.blockedIPAddresses); if ( - req.body.blockedUserAgents && - typeof req.body.blockedUserAgents === "string" + req.body.blockedUserAgentsV2 && + Array.isArray(req.body.blockedUserAgentsV2) ) { - updateBlockedUserAgents(req.app, req.body.blockedUserAgents); + updateBlockedUserAgents(req.app, req.body.blockedUserAgentsV2); } res.json({ success: true }); diff --git a/end2end/tests/hono-xml-blocklists.test.js b/end2end/tests/hono-xml-blocklists.test.js index 35de0c378..446a9257e 100644 --- a/end2end/tests/hono-xml-blocklists.test.js +++ b/end2end/tests/hono-xml-blocklists.test.js @@ -45,7 +45,12 @@ t.beforeEach(async () => { }, body: JSON.stringify({ blockedIPAddresses: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"], - blockedUserAgents: "hacker|attacker|GPTBot", + blockedUserAgentsV2: [ + { + key: "some/key", + pattern: "hacker|attacker|GPTBot", + }, + ], }), }); t.same(lists.status, 200); diff --git a/library/agent/Agent.test.ts b/library/agent/Agent.test.ts index 358b9fc6b..602f7660e 100644 --- a/library/agent/Agent.test.ts +++ b/library/agent/Agent.test.ts @@ -18,6 +18,7 @@ import { Wrapper } from "./Wrapper"; import { Context } from "./Context"; import { createTestAgent } from "../helpers/createTestAgent"; import { setTimeout } from "node:timers/promises"; +import type { Response } from "./api/fetchBlockedLists"; wrap(fetch, "fetch", function mock() { return async function mock() { @@ -26,13 +27,23 @@ wrap(fetch, "fetch", function mock() { body: JSON.stringify({ blockedIPAddresses: [ { + key: "some/key", source: "name", description: "Description", ips: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"], }, ], - blockedUserAgents: "AI2Bot|Bytespider", - }), + blockedUserAgentsV2: [ + { + key: "ai", + pattern: "AI2Bot|SomethingElse", + }, + { + key: "spider", + pattern: "Bytespider", + }, + ], + } satisfies Response), }; }; }); @@ -1055,10 +1066,12 @@ t.test("it fetches blocked lists", async () => { t.same(agent.getConfig().isIPAddressBlocked("1.3.2.4"), { blocked: true, reason: "Description", + key: "some/key", }); t.same(agent.getConfig().isIPAddressBlocked("fe80::1234:5678:abcd:ef12"), { blocked: true, reason: "Description", + key: "some/key", }); t.same( @@ -1068,6 +1081,7 @@ t.test("it fetches blocked lists", async () => { "Mozilla/5.0 (compatible) AI2Bot (+https://www.allenai.org/crawler)" ), { + key: "ai", blocked: true, } ); @@ -1075,6 +1089,7 @@ t.test("it fetches blocked lists", async () => { t.same( agent.getConfig().isUserAgentBlocked("Mozilla/5.0 (compatible) Bytespider"), { + key: "spider", blocked: true, } ); diff --git a/library/agent/InspectionStatistics.test.ts b/library/agent/InspectionStatistics.test.ts index fc218b3a0..d2d531f9d 100644 --- a/library/agent/InspectionStatistics.test.ts +++ b/library/agent/InspectionStatistics.test.ts @@ -39,6 +39,11 @@ t.test("it resets stats", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -54,6 +59,11 @@ t.test("it resets stats", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -80,6 +90,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -112,6 +127,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -144,6 +164,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -170,6 +195,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -202,6 +232,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -234,6 +269,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -285,6 +325,11 @@ t.test("it keeps track of amount of calls", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -332,6 +377,11 @@ t.test("it keeps track of requests", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -347,6 +397,11 @@ t.test("it keeps track of requests", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -363,6 +418,11 @@ t.test("it keeps track of requests", async () => { total: 1, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -379,6 +439,11 @@ t.test("it keeps track of requests", async () => { total: 2, blocked: 1, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -396,6 +461,11 @@ t.test("it keeps track of requests", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -420,6 +490,11 @@ t.test("it force compresses stats", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); @@ -462,6 +537,54 @@ t.test("it keeps track of aborted requests", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); + + clock.uninstall(); +}); + +t.test("it keeps track of blocked requests", async () => { + const clock = FakeTimers.install(); + + const stats = new InspectionStatistics({ + maxPerfSamplesInMemory: 50, + maxCompressedStatsInMemory: 5, + }); + + stats.onBlockedRequest({ + match: "ipBlocklist", + key: "known_threat_actors/public_scanners", + }); + stats.onBlockedRequest({ match: "userAgentList", key: "ai_data_scrapers" }); + + t.same(stats.getStats(), { + sinks: {}, + startedAt: 0, + requests: { + total: 0, + aborted: 0, + attacksDetected: { + total: 0, + blocked: 0, + }, + blocked: { + total: 2, + ipBlocklist: { + // eslint-disable-next-line camelcase + "known_threat_actors/public_scanners": 1, + }, + userAgentList: { + // eslint-disable-next-line camelcase + ai_data_scrapers: 1, + }, + }, + }, + }); + + clock.uninstall(); }); diff --git a/library/agent/InspectionStatistics.ts b/library/agent/InspectionStatistics.ts index ad2076bd5..7f38b7cb2 100644 --- a/library/agent/InspectionStatistics.ts +++ b/library/agent/InspectionStatistics.ts @@ -22,6 +22,16 @@ type SinkStats = { type SinkStatsWithoutTimings = Omit; +type RequestBlocked = + | { + match: "userAgentList"; + key: string; + } + | { + match: "ipBlocklist"; + key: string; + }; + export class InspectionStatistics { private startedAt = Date.now(); private stats: Record = {}; @@ -34,7 +44,21 @@ export class InspectionStatistics { total: number; blocked: number; }; - } = { total: 0, aborted: 0, attacksDetected: { total: 0, blocked: 0 } }; + blocked: { + total: number; + userAgentList: Record; + ipBlocklist: Record; + }; + } = { + total: 0, + aborted: 0, + attacksDetected: { total: 0, blocked: 0 }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, + }; constructor({ maxPerfSamplesInMemory, @@ -67,6 +91,11 @@ export class InspectionStatistics { total: 0, aborted: 0, attacksDetected: { total: 0, blocked: 0 }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }; this.startedAt = Date.now(); } @@ -81,6 +110,11 @@ export class InspectionStatistics { total: number; blocked: number; }; + blocked: { + total: number; + userAgentList: Record; + ipBlocklist: Record; + }; }; } { const sinks: Record = {}; @@ -176,6 +210,27 @@ export class InspectionStatistics { } } + onBlockedRequest({ match, key }: RequestBlocked) { + this.requests.blocked.total += 1; + + switch (match) { + case "userAgentList": { + if (!this.requests.blocked.userAgentList[key]) { + this.requests.blocked.userAgentList[key] = 0; + } + this.requests.blocked.userAgentList[key] += 1; + break; + } + case "ipBlocklist": { + if (!this.requests.blocked.ipBlocklist[key]) { + this.requests.blocked.ipBlocklist[key] = 0; + } + this.requests.blocked.ipBlocklist[key] += 1; + break; + } + } + } + onAbortedRequest() { this.requests.aborted += 1; } diff --git a/library/agent/ServiceConfig.test.ts b/library/agent/ServiceConfig.test.ts index b2fc4abaf..b7377589a 100644 --- a/library/agent/ServiceConfig.test.ts +++ b/library/agent/ServiceConfig.test.ts @@ -89,6 +89,7 @@ t.test("it checks if IP is allowed", async () => { t.test("ip blocking works", async () => { const config = new ServiceConfig([], 0, [], [], false, [ { + key: "geoip/Belgium;BE", source: "geoip", description: "description", ips: [ @@ -103,15 +104,18 @@ t.test("ip blocking works", async () => { t.same(config.isIPAddressBlocked("1.2.3.4"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("2.3.4.5"), { blocked: false }); t.same(config.isIPAddressBlocked("192.168.2.2"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("fd00:1234:5678:9abc::1"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("fd00:1234:5678:9abc::2"), { blocked: false, @@ -119,27 +123,41 @@ t.test("ip blocking works", async () => { t.same(config.isIPAddressBlocked("fd00:3234:5678:9abc::1"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("fd00:3234:5678:9abc::2"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("5.6.7.8"), { blocked: true, reason: "description", + key: "geoip/Belgium;BE", }); t.same(config.isIPAddressBlocked("1.2"), { blocked: false }); }); t.test("it blocks bots", async () => { const config = new ServiceConfig([], 0, [], [], true, []); - config.updateBlockedUserAgents("googlebot|bingbot"); + config.updateBlockedUserAgents([ + { + key: "search", + pattern: "googlebot|bingbot", + }, + ]); - t.same(config.isUserAgentBlocked("googlebot"), { blocked: true }); - t.same(config.isUserAgentBlocked("123 bingbot abc"), { blocked: true }); + t.same(config.isUserAgentBlocked("googlebot"), { + blocked: true, + key: "search", + }); + t.same(config.isUserAgentBlocked("123 bingbot abc"), { + blocked: true, + key: "search", + }); t.same(config.isUserAgentBlocked("bing"), { blocked: false }); - config.updateBlockedUserAgents(""); + config.updateBlockedUserAgents([]); t.same(config.isUserAgentBlocked("googlebot"), { blocked: false }); }); diff --git a/library/agent/ServiceConfig.ts b/library/agent/ServiceConfig.ts index 7d15e2306..5a9921053 100644 --- a/library/agent/ServiceConfig.ts +++ b/library/agent/ServiceConfig.ts @@ -1,16 +1,25 @@ import { IPMatcher } from "../helpers/ip-matcher/IPMatcher"; import { LimitedContext, matchEndpoints } from "../helpers/matchEndpoints"; import { Endpoint } from "./Config"; -import { Blocklist as BlocklistType } from "./api/fetchBlockedLists"; +import { + IPBlocklist as BlocklistType, + AgentBlockList, +} from "./api/fetchBlockedLists"; export class ServiceConfig { private blockedUserIds: Map = new Map(); private allowedIPAddresses: Map = new Map(); private nonGraphQLEndpoints: Endpoint[] = []; private graphqlFields: Endpoint[] = []; - private blockedIPAddresses: { blocklist: IPMatcher; description: string }[] = - []; - private blockedUserAgentRegex: RegExp | undefined; + private blockedIPAddresses: { + key: string; + blocklist: IPMatcher; + description: string; + }[] = []; + private blockedUserAgentRegex: { + key: string; + pattern: RegExp; + }[] = []; constructor( endpoints: Endpoint[], @@ -84,13 +93,17 @@ export class ServiceConfig { isIPAddressBlocked( ip: string - ): { blocked: true; reason: string } | { blocked: false } { + ): { blocked: true; reason: string; key: string } | { blocked: false } { const blocklist = this.blockedIPAddresses.find((blocklist) => blocklist.blocklist.has(ip) ); if (blocklist) { - return { blocked: true, reason: blocklist.description }; + return { + blocked: true, + reason: blocklist.description, + key: blocklist.key, + }; } return { blocked: false }; @@ -101,6 +114,7 @@ export class ServiceConfig { for (const source of blockedIPAddresses) { this.blockedIPAddresses.push({ + key: source.key, blocklist: new IPMatcher(source.ips), description: source.description, }); @@ -111,18 +125,32 @@ export class ServiceConfig { this.setBlockedIPAddresses(blockedIPAddresses); } - updateBlockedUserAgents(blockedUserAgents: string) { - if (!blockedUserAgents) { - this.blockedUserAgentRegex = undefined; - return; - } - this.blockedUserAgentRegex = new RegExp(blockedUserAgents, "i"); + private setBlockedUserAgents(blockedUserAgents: AgentBlockList[]) { + this.blockedUserAgentRegex = blockedUserAgents + .filter( + (list) => typeof list.pattern === "string" && list.pattern.length > 0 + ) + .map((list) => { + return { + key: list.key, + pattern: new RegExp(list.pattern, "i"), + }; + }); } - isUserAgentBlocked(ua: string): { blocked: boolean } { - if (this.blockedUserAgentRegex) { - return { blocked: this.blockedUserAgentRegex.test(ua) }; + updateBlockedUserAgents(blockedUserAgents: AgentBlockList[]) { + this.setBlockedUserAgents(blockedUserAgents); + } + + isUserAgentBlocked( + ua: string + ): { blocked: false } | { blocked: true; key: string } { + for (const blocklist of this.blockedUserAgentRegex) { + if (blocklist.pattern.test(ua)) { + return { blocked: true, key: blocklist.key }; + } } + return { blocked: false }; } diff --git a/library/agent/api/Event.ts b/library/agent/api/Event.ts index f06c842f3..bcf3e0c30 100644 --- a/library/agent/api/Event.ts +++ b/library/agent/api/Event.ts @@ -92,6 +92,11 @@ type Heartbeat = { total: number; blocked: number; }; + blocked: { + total: number; + userAgentList: Record; + ipBlocklist: Record; + }; }; }; hostnames: { hostname: string; port: number | undefined; hits: number }[]; diff --git a/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts b/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts index abce879ad..e45d61cdb 100644 --- a/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts +++ b/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts @@ -154,6 +154,11 @@ function generateHeartbeatEvent(): Event { blocked: 0, total: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }, agent: { diff --git a/library/agent/api/fetchBlockedLists.ts b/library/agent/api/fetchBlockedLists.ts index 3e2db2719..8e9048e40 100644 --- a/library/agent/api/fetchBlockedLists.ts +++ b/library/agent/api/fetchBlockedLists.ts @@ -2,15 +2,26 @@ import { fetch } from "../../helpers/fetch"; import { getAPIURL } from "../getAPIURL"; import { Token } from "./Token"; -export type Blocklist = { +export type IPBlocklist = { + key: string; source: string; description: string; ips: string[]; }; +export type AgentBlockList = { + key: string; + pattern: string; // e.g. "Googlebot|Bingbot" +}; + +export type Response = { + blockedIPAddresses: IPBlocklist[]; + blockedUserAgentsV2: AgentBlockList[]; +}; + export async function fetchBlockedLists(token: Token): Promise<{ - blockedIPAddresses: Blocklist[]; - blockedUserAgents: string; + blockedIPAddresses: IPBlocklist[]; + blockedUserAgents: AgentBlockList[]; }> { const baseUrl = getAPIURL(); const { body, statusCode } = await fetch({ @@ -33,20 +44,16 @@ export async function fetchBlockedLists(token: Token): Promise<{ throw new Error(`Failed to fetch blocked lists: ${statusCode}`); } - const result: { - blockedIPAddresses: Blocklist[]; - blockedUserAgents: string; - } = JSON.parse(body); + const result: Response = JSON.parse(body); return { blockedIPAddresses: result && Array.isArray(result.blockedIPAddresses) ? result.blockedIPAddresses : [], - // Blocked user agents are stored as a string pattern for usage in a regex (e.g. "Googlebot|Bingbot") blockedUserAgents: - result && typeof result.blockedUserAgents === "string" - ? result.blockedUserAgents - : "", + result && Array.isArray(result.blockedUserAgentsV2) + ? result.blockedUserAgentsV2 + : [], }; } diff --git a/library/sources/FunctionsFramework.test.ts b/library/sources/FunctionsFramework.test.ts index ea5fb23c5..4a3c13757 100644 --- a/library/sources/FunctionsFramework.test.ts +++ b/library/sources/FunctionsFramework.test.ts @@ -91,6 +91,11 @@ t.test("it counts requests", async (t) => { total: 1, aborted: 0, attacksDetected: { total: 0, blocked: 0 }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }); }); @@ -107,6 +112,11 @@ t.test("it counts attacks", async (t) => { total: 1, aborted: 0, attacksDetected: { total: 1, blocked: 1 }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }); }); @@ -123,6 +133,11 @@ t.test("it counts request if error", async (t) => { total: 1, aborted: 0, attacksDetected: { total: 0, blocked: 0 }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }); }); diff --git a/library/sources/HTTPServer.test.ts b/library/sources/HTTPServer.test.ts index 743728010..a376e9a79 100644 --- a/library/sources/HTTPServer.test.ts +++ b/library/sources/HTTPServer.test.ts @@ -8,7 +8,7 @@ import { wrap } from "../helpers/wrap"; import { HTTPServer } from "./HTTPServer"; import { join } from "path"; import { createTestAgent } from "../helpers/createTestAgent"; -import type { Blocklist } from "../agent/api/fetchBlockedLists"; +import type { Response } from "../agent/api/fetchBlockedLists"; import * as fetchBlockedLists from "../agent/api/fetchBlockedLists"; import { mkdtemp, writeFile, unlink } from "fs/promises"; import { exec } from "child_process"; @@ -51,19 +51,17 @@ const agent = createTestAgent({ agent.start([new HTTPServer()]); wrap(fetchBlockedLists, "fetchBlockedLists", function fetchBlockedLists() { - return async function fetchBlockedLists(): Promise<{ - blockedIPAddresses: Blocklist[]; - blockedUserAgents: string; - }> { + return async function fetchBlockedLists(): Promise { return { blockedIPAddresses: [ { + key: "geoip/Belgium;BE", source: "geoip", ips: ["9.9.9.9"], description: "geo restrictions", }, ], - blockedUserAgents: "", + blockedUserAgentsV2: [], }; }; }); diff --git a/library/sources/Hono.test.ts b/library/sources/Hono.test.ts index 015d1d7bd..26c50091b 100644 --- a/library/sources/Hono.test.ts +++ b/library/sources/Hono.test.ts @@ -1,5 +1,6 @@ /* eslint-disable prefer-rest-params */ import * as t from "tap"; +import type { Response } from "../agent/api/fetchBlockedLists"; import { ReportingAPIForTesting } from "../agent/api/ReportingAPIForTesting"; import { Token } from "../agent/api/Token"; import { setUser } from "../agent/context/user"; @@ -25,13 +26,19 @@ wrap(fetch, "fetch", function mock(original) { body: JSON.stringify({ blockedIPAddresses: [ { + key: "geoip/Belgium;BE", source: "geoip", description: "geo restrictions", ips: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"], }, ], - blockedUserAgents: "hacker|attacker", - }), + blockedUserAgentsV2: [ + { + key: "key", + pattern: "hacker|attacker", + }, + ], + } satisfies Response), }; } diff --git a/library/sources/Lambda.test.ts b/library/sources/Lambda.test.ts index becc6370a..2b3dfa5c9 100644 --- a/library/sources/Lambda.test.ts +++ b/library/sources/Lambda.test.ts @@ -303,6 +303,11 @@ t.test("it sends heartbeat after first and every 10 minutes", async () => { total: 0, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }, middlewareInstalled: false, @@ -460,6 +465,11 @@ t.test("it counts attacks", async () => { total: 1, blocked: 0, }, + blocked: { + total: 0, + userAgentList: {}, + ipBlocklist: {}, + }, }, }); }); diff --git a/library/sources/http-server/checkIfRequestIsBlocked.ts b/library/sources/http-server/checkIfRequestIsBlocked.ts index 140ac0c4c..aa3a5e1bd 100644 --- a/library/sources/http-server/checkIfRequestIsBlocked.ts +++ b/library/sources/http-server/checkIfRequestIsBlocked.ts @@ -63,6 +63,10 @@ export function checkIfRequestIsBlocked( res.end(message); + agent + .getInspectionStatistics() + .onBlockedRequest({ match: "ipBlocklist", key: result.key }); + return true; } @@ -79,6 +83,11 @@ export function checkIfRequestIsBlocked( "You are not allowed to access this resource because you have been identified as a bot." ); + agent.getInspectionStatistics().onBlockedRequest({ + match: "userAgentList", + key: isUserAgentBlocked.key, + }); + return true; } diff --git a/library/sources/http-server/createRequestListener.ts b/library/sources/http-server/createRequestListener.ts index 4f74448a8..4c95569ef 100644 --- a/library/sources/http-server/createRequestListener.ts +++ b/library/sources/http-server/createRequestListener.ts @@ -75,19 +75,19 @@ function callListenerWithContext( const countedRequest = Symbol("__zen_request_counted__"); function createOnFinishRequestHandler( - req: IncomingMessage, + req: IncomingMessage & { [countedRequest]?: boolean }, res: ServerResponse, agent: Agent ) { return function onFinishRequest() { - if ((req as any)[countedRequest]) { + if (req[countedRequest]) { // The request has already been counted // This might happen if the server has multiple listeners return; } // Mark the request as counted - (req as any)[countedRequest] = true; + req[countedRequest] = true; const context = getContext();