From 9c0feb72c5fc262d83a6e2eb02eb4f754f25b240 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 14:36:51 +0100 Subject: [PATCH 01/14] Update to latest QA tests --- .github/workflows/qa-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-tests.yml b/.github/workflows/qa-tests.yml index df4c57442..2428e0d7a 100644 --- a/.github/workflows/qa-tests.yml +++ b/.github/workflows/qa-tests.yml @@ -49,7 +49,7 @@ jobs: cp firewall-node/.github/workflows/Dockerfile.qa zen-demo-nodejs/Dockerfile - name: Run Firewall QA Tests - uses: AikidoSec/firewall-tester-action@releases/v1 + uses: AikidoSec/firewall-tester-action@v1.0.4 with: dockerfile_path: ./zen-demo-nodejs/Dockerfile app_port: 3000 From 8f62fc1bc4533d36b5668c9d1911d37a5b4b6087 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 15:05:29 +0100 Subject: [PATCH 02/14] Check bypass IPs before admin IP check (on route level) --- .../http-server/checkIfRequestIsBlocked.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/sources/http-server/checkIfRequestIsBlocked.ts b/library/sources/http-server/checkIfRequestIsBlocked.ts index 6d30dd2df..02450985d 100644 --- a/library/sources/http-server/checkIfRequestIsBlocked.ts +++ b/library/sources/http-server/checkIfRequestIsBlocked.ts @@ -40,6 +40,14 @@ export function checkIfRequestIsBlocked( // Also ensures that the statistics are only counted once res[checkedBlocks] = true; + const isBypassedIP = + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress); + + if (isBypassedIP) { + return false; + } + if (!ipAllowedToAccessRoute(context, agent)) { res.statusCode = 403; res.setHeader("Content-Type", "text/plain"); @@ -54,14 +62,6 @@ export function checkIfRequestIsBlocked( return true; } - const isBypassedIP = - context.remoteAddress && - agent.getConfig().isBypassedIP(context.remoteAddress); - - if (isBypassedIP) { - return false; - } - if ( context.remoteAddress && !agent.getConfig().isAllowedIPAddress(context.remoteAddress).allowed From 124538b15a571c6e316edb84a5a1df8012e07555 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 15:05:47 +0100 Subject: [PATCH 03/14] Check bypass IPs before stored SSRF check --- .../ssrf/inspectDNSLookupCalls.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/library/vulnerabilities/ssrf/inspectDNSLookupCalls.ts b/library/vulnerabilities/ssrf/inspectDNSLookupCalls.ts index 1642869f6..b22d34fc2 100644 --- a/library/vulnerabilities/ssrf/inspectDNSLookupCalls.ts +++ b/library/vulnerabilities/ssrf/inspectDNSLookupCalls.ts @@ -183,6 +183,17 @@ function wrapDNSLookupCallback( } } + const isBypassedIP = + context && + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress); + + if (isBypassedIP) { + // If the IP address is allowed, we don't need to block the request + // Just call the original callback to allow the DNS lookup + return callback(err, addresses, family); + } + if (!found) { if (imdsIpResult.isIMDS) { // Stored SSRF attack executed during another request (context set) @@ -211,17 +222,6 @@ function wrapDNSLookupCallback( return callback(err, addresses, family); } - const isBypassedIP = - context && - context.remoteAddress && - agent.getConfig().isBypassedIP(context.remoteAddress); - - if (isBypassedIP) { - // If the IP address is allowed, we don't need to block the request - // Just call the original callback to allow the DNS lookup - return callback(err, addresses, family); - } - // Used to get the stack trace of the calling location // We don't throw the error, we just use it to get the stack trace const stackTraceError = callingLocationStackTrace || new Error(); From e2bebeae9499b415a5aadd0530b34f9fde072885 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 15:35:25 +0100 Subject: [PATCH 04/14] Move bypass IP check to middleware check We did the check specifically for rate limiting but not yet for blocked user IDs. --- library/middleware/shouldBlockRequest.ts | 8 +++ .../shouldRateLimitRequest.test.ts | 65 ------------------- .../ratelimiting/shouldRateLimitRequest.ts | 7 +- 3 files changed, 9 insertions(+), 71 deletions(-) diff --git a/library/middleware/shouldBlockRequest.ts b/library/middleware/shouldBlockRequest.ts index f1e356b8a..634efb3c2 100644 --- a/library/middleware/shouldBlockRequest.ts +++ b/library/middleware/shouldBlockRequest.ts @@ -33,6 +33,14 @@ export function shouldBlockRequest(): Result { updateContext(context, "executedMiddleware", true); agent.onMiddlewareExecuted(); + const isBypassedIP = + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress); + + if (isBypassedIP) { + return { block: false }; + } + if (context.user && agent.getConfig().isUserBlocked(context.user.id)) { return { block: true, type: "blocked", trigger: "user" }; } diff --git a/library/ratelimiting/shouldRateLimitRequest.test.ts b/library/ratelimiting/shouldRateLimitRequest.test.ts index 773303b5b..3afec6039 100644 --- a/library/ratelimiting/shouldRateLimitRequest.test.ts +++ b/library/ratelimiting/shouldRateLimitRequest.test.ts @@ -182,37 +182,6 @@ t.test("it rate limits localhost when not in production mode", async (t) => { }); }); -t.test("it does not rate limit when the IP is allowed", async (t) => { - const agent = await createAgent( - [ - { - method: "POST", - route: "/login", - forceProtectionOff: false, - rateLimiting: { - enabled: true, - maxRequests: 3, - windowSizeInMS: 1000, - }, - }, - ], - ["1.2.3.4"] - ); - - t.same(shouldRateLimitRequest(createContext("1.2.3.4"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4"), agent), { - block: false, - }); -}); - t.test("it rate limits by user", async (t) => { const agent = await createAgent([ { @@ -437,40 +406,6 @@ t.test( } ); -t.test( - "it does not rate limit requests from allowed ip with user", - async (t) => { - const agent = await createAgent( - [ - { - method: "POST", - route: "/login", - forceProtectionOff: false, - rateLimiting: { - enabled: true, - maxRequests: 3, - windowSizeInMS: 1000, - }, - }, - ], - ["1.2.3.4"] - ); - - t.same(shouldRateLimitRequest(createContext("1.2.3.4", "123"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4", "123"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4", "123"), agent), { - block: false, - }); - t.same(shouldRateLimitRequest(createContext("1.2.3.4", "123"), agent), { - block: false, - }); - } -); - t.test( "it does not consume rate limit for user a second time (same request)", async (t) => { diff --git a/library/ratelimiting/shouldRateLimitRequest.ts b/library/ratelimiting/shouldRateLimitRequest.ts index b1b97b51d..d5b2ddc33 100644 --- a/library/ratelimiting/shouldRateLimitRequest.ts +++ b/library/ratelimiting/shouldRateLimitRequest.ts @@ -53,12 +53,7 @@ export function shouldRateLimitRequest( isLocalhostIP(context.remoteAddress) && isProduction; - // Allow requests from allowed IPs, e.g. never rate limit office IPs - const isBypassedIP = - context.remoteAddress && - agent.getConfig().isBypassedIP(context.remoteAddress); - - if (isFromLocalhostInProduction || isBypassedIP) { + if (isFromLocalhostInProduction) { return { block: false }; } From dc39085b396b517ffdace844e04fb3809cc0f231 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 17:06:09 +0100 Subject: [PATCH 05/14] Fix e2e test --- end2end/tests/hono-xml-blocklists.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/end2end/tests/hono-xml-blocklists.test.js b/end2end/tests/hono-xml-blocklists.test.js index c9a5a481a..fe34f20e9 100644 --- a/end2end/tests/hono-xml-blocklists.test.js +++ b/end2end/tests/hono-xml-blocklists.test.js @@ -300,11 +300,8 @@ t.test("it does not block bypass IP if in blocklist", (t) => { "X-Forwarded-For": "1.3.2.2", }, }); - t.same(resp3.status, 403); - t.same( - await resp3.text(), - `Your IP address is not allowed to access this resource. (Your IP: 1.3.2.2)` - ); + t.same(resp3.status, 200); + t.match(await resp3.text(), "Admin panel"); }) .catch((error) => { t.fail(error); From c12ee5241784b8fd962efa2bbe964e94f844026e Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 18:18:30 +0100 Subject: [PATCH 06/14] Still block outbound domains if protection is off for route --- library/agent/hooks/wrapExport.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/library/agent/hooks/wrapExport.ts b/library/agent/hooks/wrapExport.ts index b5cc1a745..5dce2ad8f 100644 --- a/library/agent/hooks/wrapExport.ts +++ b/library/agent/hooks/wrapExport.ts @@ -4,6 +4,7 @@ import { getInstance } from "../AgentSingleton"; import { OperationKind } from "../api/Event"; import { bindContext, getContext } from "../Context"; import type { InterceptorResult } from "./InterceptorResult"; +import { isAttackResult } from "./InterceptorResult"; import type { PartialWrapPackageInfo } from "./WrapPackageInfo"; import { wrapDefaultOrNamed } from "./wrapDefaultOrNamed"; import { onInspectionInterceptorResult } from "./onInspectionInterceptorResult"; @@ -151,14 +152,6 @@ export function inspectArgs( methodName: string, kind: OperationKind | undefined ) { - if (context) { - const matches = agent.getConfig().getEndpoints(context); - - if (matches.find((match) => match.forceProtectionOff)) { - return; - } - } - const start = performance.now(); let result: InterceptorResult = undefined; @@ -177,6 +170,16 @@ export function inspectArgs( }); } + // When forceProtectionOff is enabled, skip attack detection + // but still allow outbound connection blocking + if (context && isAttackResult(result)) { + const matches = agent.getConfig().getEndpoints(context); + + if (matches.find((match) => match.forceProtectionOff)) { + return; + } + } + onInspectionInterceptorResult( context, agent, From 7c1c58811afca42a7dfe356787354e2893c1bacb Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 18:41:45 +0100 Subject: [PATCH 07/14] Combine imports --- library/agent/hooks/wrapExport.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/agent/hooks/wrapExport.ts b/library/agent/hooks/wrapExport.ts index 5dce2ad8f..af3be4a43 100644 --- a/library/agent/hooks/wrapExport.ts +++ b/library/agent/hooks/wrapExport.ts @@ -3,8 +3,7 @@ import type { Agent } from "../Agent"; import { getInstance } from "../AgentSingleton"; import { OperationKind } from "../api/Event"; import { bindContext, getContext } from "../Context"; -import type { InterceptorResult } from "./InterceptorResult"; -import { isAttackResult } from "./InterceptorResult"; +import { type InterceptorResult, isAttackResult } from "./InterceptorResult"; import type { PartialWrapPackageInfo } from "./WrapPackageInfo"; import { wrapDefaultOrNamed } from "./wrapDefaultOrNamed"; import { onInspectionInterceptorResult } from "./onInspectionInterceptorResult"; From f26ed7cbc9164596535b721888e40417b57da927 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Tue, 23 Dec 2025 18:42:00 +0100 Subject: [PATCH 08/14] Add IPv4-mapped IPv6 addresses to bypass IPs --- library/agent/ServiceConfig.ts | 5 ++++- .../helpers/addIPv4MappedAddresses.test.ts | 21 +++++++++++++++++++ library/helpers/addIPv4MappedAddresses.ts | 10 +++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 library/helpers/addIPv4MappedAddresses.test.ts create mode 100644 library/helpers/addIPv4MappedAddresses.ts diff --git a/library/agent/ServiceConfig.ts b/library/agent/ServiceConfig.ts index ea3125dd7..0dbc63c8a 100644 --- a/library/agent/ServiceConfig.ts +++ b/library/agent/ServiceConfig.ts @@ -4,6 +4,7 @@ import { isPrivateIP } from "../vulnerabilities/ssrf/isPrivateIP"; import type { Endpoint, EndpointConfig, Domain } from "./Config"; import type { IPList, UserAgentDetails } from "./api/FetchListsAPI"; import { safeCreateRegExp } from "./safeCreateRegExp"; +import { addIPv4MappedAddresses } from "../helpers/addIPv4MappedAddresses"; export class ServiceConfig { private blockedUserIds: Map = new Map(); @@ -99,7 +100,9 @@ export class ServiceConfig { this.bypassedIPAddresses = undefined; return; } - this.bypassedIPAddresses = new IPMatcher(ipAddresses); + this.bypassedIPAddresses = new IPMatcher( + addIPv4MappedAddresses(ipAddresses) + ); } isBypassedIP(ip: string) { diff --git a/library/helpers/addIPv4MappedAddresses.test.ts b/library/helpers/addIPv4MappedAddresses.test.ts new file mode 100644 index 000000000..c2286810f --- /dev/null +++ b/library/helpers/addIPv4MappedAddresses.test.ts @@ -0,0 +1,21 @@ +import * as t from "tap"; +import { addIPv4MappedAddresses } from "./addIPv4MappedAddresses"; + +t.test("it adds IPv4-mapped IPv6 addresses", async (t) => { + t.same( + addIPv4MappedAddresses([ + "1.2.3.4", + "23.45.67.89/24", + "2606:2800:220:1:248:1893:25c8:1946", + "2001:0db9:abcd:1234::/64", + ]), + [ + "1.2.3.4", + "23.45.67.89/24", + "2606:2800:220:1:248:1893:25c8:1946", + "2001:0db9:abcd:1234::/64", + "::ffff:1.2.3.4/128", + "::ffff:23.45.67.89/120", + ] + ); +}); diff --git a/library/helpers/addIPv4MappedAddresses.ts b/library/helpers/addIPv4MappedAddresses.ts new file mode 100644 index 000000000..9e8b1c870 --- /dev/null +++ b/library/helpers/addIPv4MappedAddresses.ts @@ -0,0 +1,10 @@ +import mapIPv4ToIPv6 from "./mapIPv4ToIPv6"; + +/** + * Adds IPv4-mapped IPv6 versions for all IPv4 addresses in the array. + * e.g. ["1.2.3.4", "2001:db8::/32"] -> ["1.2.3.4", "2001:db8::/32", "::ffff:1.2.3.4/128"] + */ +export function addIPv4MappedAddresses(ips: string[]): string[] { + const ipv4Addresses = ips.filter((ip) => !ip.includes(":")); + return ips.concat(ipv4Addresses.map(mapIPv4ToIPv6)); +} From f3276888e6df17fdab22b03969cd2f95165ddd24 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Fri, 26 Dec 2025 12:42:04 +0100 Subject: [PATCH 09/14] Report hostnames in unicode version --- library/agent/Agent.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/agent/Agent.ts b/library/agent/Agent.ts index a68cb24e1..56acb5c11 100644 --- a/library/agent/Agent.ts +++ b/library/agent/Agent.ts @@ -33,6 +33,7 @@ import { isNewInstrumentationUnitTest } from "../helpers/isNewInstrumentationUni import { AttackWaveDetector } from "../vulnerabilities/attack-wave-detection/AttackWaveDetector"; import type { FetchListsAPI } from "./api/FetchListsAPI"; import { PendingEvents } from "./PendingEvents"; +import { domainToUnicode } from "node:url"; type WrappedPackage = { version: string | null; supported: boolean }; @@ -576,6 +577,14 @@ export class Agent { } onConnectHostname(hostname: string, port: number) { + try { + // new URL(...) always converts hostnames to punycode + // When reporting them in heartbeats, we want to send the unicode version + hostname = domainToUnicode(hostname); + } catch (e: any) { + this.logger.log(`Failed to convert hostname to unicode: ${e.message}`); + } + this.hostnames.add(hostname, port); } From f22feb80c1d20e5fecae6409b9db41d799d9ff2c Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Fri, 26 Dec 2025 12:59:43 +0100 Subject: [PATCH 10/14] If bypass IP, don't discover API spec nor count request --- library/sources/FunctionsFramework.ts | 50 ++++++++++------- library/sources/Lambda.ts | 53 ++++++++++++------- .../http-server/createRequestListener.ts | 13 +++-- .../http-server/http2/createStreamListener.ts | 14 ++++- 4 files changed, 86 insertions(+), 44 deletions(-) diff --git a/library/sources/FunctionsFramework.ts b/library/sources/FunctionsFramework.ts index 5d8c02c98..711cde2cd 100644 --- a/library/sources/FunctionsFramework.ts +++ b/library/sources/FunctionsFramework.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines-per-function */ +import { Agent } from "../agent/Agent"; import { getInstance } from "../agent/AgentSingleton"; -import { getContext, runWithContext } from "../agent/Context"; +import { Context, getContext, runWithContext } from "../agent/Context"; import { Hooks } from "../agent/hooks/Hooks"; import { wrapExport } from "../agent/hooks/wrapExport"; import { PartialWrapPackageInfo } from "../agent/hooks/WrapPackageInfo"; @@ -77,24 +78,7 @@ export function createCloudFunctionWrapper(fn: HttpFunction): HttpFunction { } finally { const context = getContext(); if (agent && context) { - if ( - context.route && - context.method && - Number.isInteger(res.statusCode) - ) { - const shouldDiscover = shouldDiscoverRoute({ - statusCode: res.statusCode, - method: context.method, - route: context.route, - }); - - if (shouldDiscover) { - agent.onRouteExecute(context); - } - } - - const stats = agent.getInspectionStatistics(); - stats.onRequest(); + incrementStatsAndDiscoverAPISpec(context, agent, res.statusCode); await agent.getPendingEvents().waitUntilSent(getTimeoutInMS()); @@ -112,6 +96,34 @@ export function createCloudFunctionWrapper(fn: HttpFunction): HttpFunction { }; } +function incrementStatsAndDiscoverAPISpec( + context: Context, + agent: Agent, + statusCode: number +) { + if ( + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress) + ) { + return; + } + + if (context.route && context.method && Number.isInteger(statusCode)) { + const shouldDiscover = shouldDiscoverRoute({ + statusCode: statusCode, + method: context.method, + route: context.route, + }); + + if (shouldDiscover) { + agent.onRouteExecute(context); + } + } + + const stats = agent.getInspectionStatistics(); + stats.onRequest(); +} + export class FunctionsFramework implements Wrapper { onRequire(exports: any, pkgInfo: PartialWrapPackageInfo) { wrapExport(exports, "http", pkgInfo, { diff --git a/library/sources/Lambda.ts b/library/sources/Lambda.ts index 4db74f9e9..dd1c17bb9 100644 --- a/library/sources/Lambda.ts +++ b/library/sources/Lambda.ts @@ -1,4 +1,5 @@ import type { Callback, Context, Handler } from "aws-lambda"; +import { Agent } from "../agent/Agent"; import { getInstance } from "../agent/AgentSingleton"; import { runWithContext, Context as AgentContext } from "../agent/Context"; import { envToBool } from "../helpers/envToBool"; @@ -221,26 +222,14 @@ export function createLambdaWrapper(handler: Handler): Handler { return result; } finally { if (agent) { - if ( - isGatewayEvent(event) && - isGatewayResponse(result) && - agentContext.route && - agentContext.method - ) { - const shouldDiscover = shouldDiscoverRoute({ - statusCode: result.statusCode, - method: agentContext.method, - route: agentContext.route, - }); - - if (shouldDiscover) { - agent.onRouteExecute(agentContext); - } + if (isGatewayEvent(event) && isGatewayResponse(result)) { + incrementStatsAndDiscoverAPISpec( + agentContext, + agent, + result.statusCode + ); } - const stats = agent.getInspectionStatistics(); - stats.onRequest(); - await agent.getPendingEvents().waitUntilSent(getTimeoutInMS()); if ( @@ -255,6 +244,34 @@ export function createLambdaWrapper(handler: Handler): Handler { }; } +function incrementStatsAndDiscoverAPISpec( + agentContext: AgentContext, + agent: Agent, + statusCode: number +) { + if ( + agentContext.remoteAddress && + agent.getConfig().isBypassedIP(agentContext.remoteAddress) + ) { + return; + } + + if (agentContext.route && agentContext.method) { + const shouldDiscover = shouldDiscoverRoute({ + statusCode: statusCode, + method: agentContext.method, + route: agentContext.route, + }); + + if (shouldDiscover) { + agent.onRouteExecute(agentContext); + } + } + + const stats = agent.getInspectionStatistics(); + stats.onRequest(); +} + let loggedWarningUnsupportedTrigger = false; function logWarningUnsupportedTrigger() { diff --git a/library/sources/http-server/createRequestListener.ts b/library/sources/http-server/createRequestListener.ts index 89007f7e4..8e0aee26d 100644 --- a/library/sources/http-server/createRequestListener.ts +++ b/library/sources/http-server/createRequestListener.ts @@ -93,6 +93,13 @@ function onFinishRequestHandler( // Mark the request as counted req[countedRequest] = true; + if ( + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress) + ) { + return; + } + if (context.route && context.method) { const shouldDiscover = shouldDiscoverRoute({ statusCode: res.statusCode, @@ -113,11 +120,7 @@ function onFinishRequestHandler( agent.onRouteRateLimited(context.rateLimitedEndpoint); } - if ( - context.remoteAddress && - !agent.getConfig().isBypassedIP(context.remoteAddress) && - agent.getAttackWaveDetector().check(context) - ) { + if (context.remoteAddress && agent.getAttackWaveDetector().check(context)) { agent.onDetectedAttackWave({ request: context, }); diff --git a/library/sources/http-server/http2/createStreamListener.ts b/library/sources/http-server/http2/createStreamListener.ts index cb8aba993..fff86da44 100644 --- a/library/sources/http-server/http2/createStreamListener.ts +++ b/library/sources/http-server/http2/createStreamListener.ts @@ -53,7 +53,18 @@ function discoverRouteFromStream( stream: ServerHttp2Stream, agent: Agent ) { - if (context && context.route && context.method) { + if (!context) { + return; + } + + if ( + context.remoteAddress && + agent.getConfig().isBypassedIP(context.remoteAddress) + ) { + return; + } + + if (context.route && context.method) { const statusCode = parseInt(stream.sentHeaders[":status"] as string); if (!isNaN(statusCode)) { @@ -78,7 +89,6 @@ function discoverRouteFromStream( if ( context.remoteAddress && - !agent.getConfig().isBypassedIP(context.remoteAddress) && agent.getAttackWaveDetector().check(context) ) { agent.onDetectedAttackWave({ From 184b5df3648eec10513f49741fdef8c9a97f1705 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Fri, 26 Dec 2025 13:12:10 +0100 Subject: [PATCH 11/14] Fix test for forceProtectionOff In c12ee5241784b8fd962efa2bbe964e94f844026e we've changed that we check this flag after inspecting args of an instrumented function For HTTP clients this means that we'll still call `agent.onConnectHostname(url.hostname, port);` And `agent.getConfig().shouldBlockOutgoingRequest(url.hostname)` too Blocking of new outbound connections should always work, doesn't matter if the route protection was disabled --- library/agent/applyHooks.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/agent/applyHooks.test.ts b/library/agent/applyHooks.test.ts index c07c90789..9e8582078 100644 --- a/library/agent/applyHooks.test.ts +++ b/library/agent/applyHooks.test.ts @@ -88,7 +88,7 @@ t.test( } ); -t.test("it ignores route if force protection off is on", async (t) => { +t.test("it still inspects outbound connections if force protection off is on", async (t) => { const inspectionCalls: { args: unknown[] }[] = []; const hooks = new Hooks(); @@ -140,6 +140,7 @@ t.test("it ignores route if force protection off is on", async (t) => { { args: ["www.aikido.dev"] }, ]); + // forceProtectionOff still allows outbound connection inspection await runWithContext( { ...context, @@ -154,6 +155,7 @@ t.test("it ignores route if force protection off is on", async (t) => { t.same(inspectionCalls, [ { args: ["www.google.com"] }, { args: ["www.aikido.dev"] }, + { args: ["www.times.com"] }, ]); }); From c92efb5ffbe00c666fbf8770deb0a97a6350c2b2 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Fri, 26 Dec 2025 13:18:53 +0100 Subject: [PATCH 12/14] Move isGatewayEvent(..) logic inside `incrementStatsAndDiscoverAPISpec` So that `onRequest` will always be called Mistake in f22feb80c1d20e5fecae6409b9db41d799d9ff2c --- library/sources/Lambda.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/sources/Lambda.ts b/library/sources/Lambda.ts index dd1c17bb9..04c11b7fe 100644 --- a/library/sources/Lambda.ts +++ b/library/sources/Lambda.ts @@ -222,13 +222,7 @@ export function createLambdaWrapper(handler: Handler): Handler { return result; } finally { if (agent) { - if (isGatewayEvent(event) && isGatewayResponse(result)) { - incrementStatsAndDiscoverAPISpec( - agentContext, - agent, - result.statusCode - ); - } + incrementStatsAndDiscoverAPISpec(agentContext, agent, event, result); await agent.getPendingEvents().waitUntilSent(getTimeoutInMS()); @@ -247,7 +241,8 @@ export function createLambdaWrapper(handler: Handler): Handler { function incrementStatsAndDiscoverAPISpec( agentContext: AgentContext, agent: Agent, - statusCode: number + event: unknown, + result: unknown ) { if ( agentContext.remoteAddress && @@ -256,9 +251,14 @@ function incrementStatsAndDiscoverAPISpec( return; } - if (agentContext.route && agentContext.method) { + if ( + isGatewayEvent(event) && + isGatewayResponse(result) && + agentContext.route && + agentContext.method + ) { const shouldDiscover = shouldDiscoverRoute({ - statusCode: statusCode, + statusCode: result.statusCode, method: agentContext.method, route: agentContext.route, }); From 33c85100eb30b4879905765a00e46c978e12938a Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Fri, 26 Dec 2025 13:22:21 +0100 Subject: [PATCH 13/14] Format code --- library/agent/applyHooks.test.ts | 121 ++++++++++++++++--------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/library/agent/applyHooks.test.ts b/library/agent/applyHooks.test.ts index 9e8582078..2167c70b8 100644 --- a/library/agent/applyHooks.test.ts +++ b/library/agent/applyHooks.test.ts @@ -88,76 +88,79 @@ t.test( } ); -t.test("it still inspects outbound connections if force protection off is on", async (t) => { - const inspectionCalls: { args: unknown[] }[] = []; +t.test( + "it still inspects outbound connections if force protection off is on", + async (t) => { + const inspectionCalls: { args: unknown[] }[] = []; - const hooks = new Hooks(); - hooks.addBuiltinModule("dns/promises").onRequire((exports, pkgInfo) => { - wrapExport(exports, "lookup", pkgInfo, { - kind: "outgoing_http_op", - inspectArgs: (args, agent) => { - inspectionCalls.push({ args }); - }, + const hooks = new Hooks(); + hooks.addBuiltinModule("dns/promises").onRequire((exports, pkgInfo) => { + wrapExport(exports, "lookup", pkgInfo, { + kind: "outgoing_http_op", + inspectArgs: (args, agent) => { + inspectionCalls.push({ args }); + }, + }); }); - }); - applyHooks(hooks, agent.isUsingNewInstrumentation()); + applyHooks(hooks, agent.isUsingNewInstrumentation()); - reportingAPI.setResult({ - success: true, - endpoints: [ - { - method: "GET", - route: "/route", - forceProtectionOff: true, - rateLimiting: { - enabled: false, - maxRequests: 0, - windowSizeInMS: 0, + reportingAPI.setResult({ + success: true, + endpoints: [ + { + method: "GET", + route: "/route", + forceProtectionOff: true, + rateLimiting: { + enabled: false, + maxRequests: 0, + windowSizeInMS: 0, + }, }, - }, - ], - heartbeatIntervalInMS: 10 * 60 * 1000, - blockedUserIds: [], - allowedIPAddresses: [], - configUpdatedAt: 0, - }); + ], + heartbeatIntervalInMS: 10 * 60 * 1000, + blockedUserIds: [], + allowedIPAddresses: [], + configUpdatedAt: 0, + }); - // Read rules from API - await agent.flushStats(1000); + // Read rules from API + await agent.flushStats(1000); - const { lookup } = require("dns/promises"); + const { lookup } = require("dns/promises"); - await lookup("www.google.com"); - t.same(inspectionCalls, [{ args: ["www.google.com"] }]); + await lookup("www.google.com"); + t.same(inspectionCalls, [{ args: ["www.google.com"] }]); - await runWithContext(context, async () => { - await lookup("www.aikido.dev"); - }); + await runWithContext(context, async () => { + await lookup("www.aikido.dev"); + }); - t.same(inspectionCalls, [ - { args: ["www.google.com"] }, - { args: ["www.aikido.dev"] }, - ]); - - // forceProtectionOff still allows outbound connection inspection - await runWithContext( - { - ...context, - method: "GET", - route: "/route", - }, - async () => { - await lookup("www.times.com"); - } - ); + t.same(inspectionCalls, [ + { args: ["www.google.com"] }, + { args: ["www.aikido.dev"] }, + ]); - t.same(inspectionCalls, [ - { args: ["www.google.com"] }, - { args: ["www.aikido.dev"] }, - { args: ["www.times.com"] }, - ]); -}); + // forceProtectionOff still allows outbound connection inspection + await runWithContext( + { + ...context, + method: "GET", + route: "/route", + }, + async () => { + await lookup("www.times.com"); + } + ); + + t.same(inspectionCalls, [ + { args: ["www.google.com"] }, + { args: ["www.aikido.dev"] }, + { args: ["www.times.com"] }, + ]); + } +); t.test("it does not report attack if IP is allowed", async (t) => { const hooks = new Hooks(); From 52b083a5eb7fc5dc84607e484b015cb93f877da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Fri, 9 Jan 2026 16:05:18 +0100 Subject: [PATCH 14/14] Update QA action to v1.0.5 --- .github/workflows/qa-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-tests.yml b/.github/workflows/qa-tests.yml index 2428e0d7a..e0f3a79b9 100644 --- a/.github/workflows/qa-tests.yml +++ b/.github/workflows/qa-tests.yml @@ -49,7 +49,7 @@ jobs: cp firewall-node/.github/workflows/Dockerfile.qa zen-demo-nodejs/Dockerfile - name: Run Firewall QA Tests - uses: AikidoSec/firewall-tester-action@v1.0.4 + uses: AikidoSec/firewall-tester-action@v1.0.5 with: dockerfile_path: ./zen-demo-nodejs/Dockerfile app_port: 3000