From 304f41513ac8b6102d3e89f569a7d122a74a4d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Mon, 14 Oct 2024 17:34:47 +0200 Subject: [PATCH 1/6] Check types in test files in CI --- library/package.json | 2 +- library/tsconfig.lint.json | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 library/tsconfig.lint.json diff --git a/library/package.json b/library/package.json index 3e2696de0..ece93525c 100644 --- a/library/package.json +++ b/library/package.json @@ -90,6 +90,6 @@ "build:watch": "tsc --watch", "lint": "npm run lint-eslint && npm run lint-tsc", "lint-eslint": "eslint '**/*.ts'", - "lint-tsc": "tsc --noEmit" + "lint-tsc": "tsc -p tsconfig.lint.json" } } diff --git a/library/tsconfig.lint.json b/library/tsconfig.lint.json new file mode 100644 index 000000000..846e66c41 --- /dev/null +++ b/library/tsconfig.lint.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2019", + "lib": ["ES2019", "DOM"], + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "strict": true, + "allowJs": false, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["**/*.ts"] +} From 337445c9fdc3dbf18a7225e7a6f758491937fd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Mon, 14 Oct 2024 17:45:12 +0200 Subject: [PATCH 2/6] Do non-strict checks for tests --- library/package.json | 2 +- library/{tsconfig.lint.json => tsconfig.tests.json} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename library/{tsconfig.lint.json => tsconfig.tests.json} (82%) diff --git a/library/package.json b/library/package.json index ece93525c..8198925b5 100644 --- a/library/package.json +++ b/library/package.json @@ -90,6 +90,6 @@ "build:watch": "tsc --watch", "lint": "npm run lint-eslint && npm run lint-tsc", "lint-eslint": "eslint '**/*.ts'", - "lint-tsc": "tsc -p tsconfig.lint.json" + "lint-tsc": "tsc --noEmit && tsc -p tsconfig.tests.json" } } diff --git a/library/tsconfig.lint.json b/library/tsconfig.tests.json similarity index 82% rename from library/tsconfig.lint.json rename to library/tsconfig.tests.json index 846e66c41..3a67a45e8 100644 --- a/library/tsconfig.lint.json +++ b/library/tsconfig.tests.json @@ -5,10 +5,10 @@ "module": "commonjs", "moduleResolution": "node", "declaration": true, - "strict": true, + "strict": false, "allowJs": false, "skipLibCheck": true, "noEmit": true }, - "include": ["**/*.ts"] + "include": ["**/*.test.ts"] } From 86ce742a8c66f3267e2c108115568dd13eab032d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Fri, 18 Oct 2024 10:21:24 +0200 Subject: [PATCH 3/6] Fix some type errors in unit tests --- .../api-discovery/getApiAuthType.test.ts | 1 + library/agent/realtime/pollForChanges.test.ts | 3 +- library/package-lock.json | 11 ++ library/package.json | 3 +- library/sources/HTTP2Server.test.ts | 7 +- library/sources/HTTPServer.test.ts | 45 ++--- library/sources/Lambda.test.ts | 5 +- library/sources/Xml2js.test.ts | 3 +- library/tsconfig.tests.json | 4 +- .../ssrf/inspectDNSLookupCalls.test.ts | 164 +++++++++++------- 10 files changed, 140 insertions(+), 106 deletions(-) diff --git a/library/agent/api-discovery/getApiAuthType.test.ts b/library/agent/api-discovery/getApiAuthType.test.ts index 06a9d1bde..d6d1fa299 100644 --- a/library/agent/api-discovery/getApiAuthType.test.ts +++ b/library/agent/api-discovery/getApiAuthType.test.ts @@ -58,5 +58,6 @@ t.test("no auth", async (t) => { t.same(get(getContext()), undefined); t.same(get(getContext({})), undefined); t.same(get(getContext({ authorization: "" })), undefined); + // @ts-expect-error Testing edge case t.same(get({}), undefined); }); diff --git a/library/agent/realtime/pollForChanges.test.ts b/library/agent/realtime/pollForChanges.test.ts index 54cfe65d8..db10d848f 100644 --- a/library/agent/realtime/pollForChanges.test.ts +++ b/library/agent/realtime/pollForChanges.test.ts @@ -6,6 +6,7 @@ import { LoggerForTesting } from "../logger/LoggerForTesting"; import { LoggerNoop } from "../logger/LoggerNoop"; import { pollForChanges } from "./pollForChanges"; import * as FakeTimers from "@sinonjs/fake-timers"; +import { Config } from "../Config"; t.test("it does not start interval if no token", async (t) => { const logger = new LoggerForTesting(); @@ -74,7 +75,7 @@ t.test("it checks for config updates", async () => { }; }); - const configUpdates = []; + const configUpdates: Config[] = []; pollForChanges({ onConfigUpdate: (config) => { diff --git a/library/package-lock.json b/library/package-lock.json index 68f4c0b7a..c4ed840fd 100644 --- a/library/package-lock.json +++ b/library/package-lock.json @@ -25,6 +25,7 @@ "@types/shell-quote": "^1.7.5", "@types/sinonjs__fake-timers": "^8.1.5", "@types/supertest": "^6.0.2", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "aws-sdk": "^2.1595.0", @@ -3674,6 +3675,16 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", diff --git a/library/package.json b/library/package.json index 8198925b5..7f29902c1 100644 --- a/library/package.json +++ b/library/package.json @@ -48,6 +48,7 @@ "@types/shell-quote": "^1.7.5", "@types/sinonjs__fake-timers": "^8.1.5", "@types/supertest": "^6.0.2", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "aws-sdk": "^2.1595.0", @@ -90,6 +91,6 @@ "build:watch": "tsc --watch", "lint": "npm run lint-eslint && npm run lint-tsc", "lint-eslint": "eslint '**/*.ts'", - "lint-tsc": "tsc --noEmit && tsc -p tsconfig.tests.json" + "lint-tsc": "tsc -p tsconfig.tests.json" } } diff --git a/library/sources/HTTP2Server.test.ts b/library/sources/HTTP2Server.test.ts index 202eb82ae..55a4643a6 100644 --- a/library/sources/HTTP2Server.test.ts +++ b/library/sources/HTTP2Server.test.ts @@ -117,7 +117,7 @@ function http2Request( ); } -const http2 = require("http2"); +const http2 = require("http2") as typeof import("http2"); function createMinimalTestServer() { const server = http2.createServer((req, res) => { @@ -475,7 +475,6 @@ t.test("it wraps the createSecureServer function of http2 module", async () => { { key: readFileSync(resolve(__dirname, "fixtures/key.pem")), cert: readFileSync(resolve(__dirname, "fixtures/cert.pem")), - secureContext: {}, }, (req, res) => { res.setHeader("Content-Type", "application/json"); @@ -516,7 +515,6 @@ t.test("it wraps the createSecureServer on request event", async () => { const server = http2.createSecureServer({ key: readFileSync(resolve(__dirname, "fixtures/key.pem")), cert: readFileSync(resolve(__dirname, "fixtures/cert.pem")), - secureContext: {}, }); server.on("request", (req, res) => { @@ -557,7 +555,6 @@ t.test("it wraps the createSecureServer stream event", async () => { const server = http2.createSecureServer({ key: readFileSync(resolve(__dirname, "fixtures/key.pem")), cert: readFileSync(resolve(__dirname, "fixtures/cert.pem")), - secureContext: {}, }); server.on("stream", (stream, headers) => { @@ -647,7 +644,7 @@ t.test("real injection test", async (t) => { stream.end(file); } catch (e) { stream.respond({ ":status": 500 }); - stream.end(e.message); + stream.end(e instanceof Error ? e.message : ""); } }); diff --git a/library/sources/HTTPServer.test.ts b/library/sources/HTTPServer.test.ts index f0f5f4b31..d81436de9 100644 --- a/library/sources/HTTPServer.test.ts +++ b/library/sources/HTTPServer.test.ts @@ -45,6 +45,7 @@ const api = new ReportingAPIForTesting({ method: "GET", forceProtectionOff: false, allowedIPAddresses: ["8.8.8.8"], + // @ts-expect-error Testing rateLimiting: undefined, }, ], @@ -66,8 +67,10 @@ t.beforeEach(() => { delete process.env.NODE_ENV; }); +const http = require("http") as typeof import("http"); +const https = require("https") as typeof import("https"); + t.test("it wraps the createServer function of http module", async () => { - const http = require("http"); const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -104,7 +107,6 @@ t.test("it wraps the createServer function of http module", async () => { }); t.test("it wraps the createServer function of https module", async () => { - const https = require("https"); const { readFileSync } = require("fs"); const path = require("path"); @@ -115,7 +117,6 @@ t.test("it wraps the createServer function of https module", async () => { { key: readFileSync(path.resolve(__dirname, "fixtures/key.pem")), cert: readFileSync(path.resolve(__dirname, "fixtures/cert.pem")), - secureContext: {}, }, (req, res) => { res.setHeader("Content-Type", "application/json"); @@ -154,7 +155,6 @@ t.test("it wraps the createServer function of https module", async () => { }); t.test("it parses query parameters", async () => { - const http = require("http"); const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -178,7 +178,6 @@ t.test("it parses query parameters", async () => { }); t.test("it discovers routes", async () => { - const http = require("http"); const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -214,8 +213,7 @@ t.test("it discovers routes", async () => { t.test( "it does not discover route if server response is error code", - async () => { - const http = require("http"); + async (t) => { const server = http.createServer((req, res) => { res.statusCode = 404; res.end(); @@ -244,8 +242,7 @@ t.test( } ); -t.test("it parses cookies", async () => { - const http = require("http"); +t.test("it parses cookies", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -270,8 +267,7 @@ t.test("it parses cookies", async () => { }); }); -t.test("it parses x-forwarded-for header with proxy", async () => { - const http = require("http"); +t.test("it parses x-forwarded-for header with proxy", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -297,8 +293,7 @@ t.test("it parses x-forwarded-for header with proxy", async () => { }); }); -t.test("it uses x-forwarded-for header", async () => { - const http = require("http"); +t.test("it uses x-forwarded-for header", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -323,8 +318,7 @@ t.test("it uses x-forwarded-for header", async () => { }); }); -t.test("it sets body in context", async () => { - const http = require("http"); +t.test("it sets body in context", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -357,9 +351,7 @@ function generateJsonPayload(sizeInMb: number) { return JSON.stringify("a".repeat(sizeInBytes)); } -t.test("it sends 413 when body is larger than 20 Mb", async () => { - const http = require("http"); - +t.test("it sends 413 when body is larger than 20 Mb", async (t) => { const server = http.createServer((req, res) => { t.fail(); }); @@ -387,8 +379,7 @@ t.test("it sends 413 when body is larger than 20 Mb", async () => { }); }); -t.test("body that is not JSON is ignored", async () => { - const http = require("http"); +t.test("body that is not JSON is ignored", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(getContext())); @@ -414,9 +405,7 @@ t.test("body that is not JSON is ignored", async () => { }); }); -t.test("it uses limit from AIKIDO_MAX_BODY_SIZE_MB", async () => { - const http = require("http"); - +t.test("it uses limit from AIKIDO_MAX_BODY_SIZE_MB", async (t) => { const server = http.createServer((req, res) => { res.end(); }); @@ -459,9 +448,7 @@ t.test("it uses limit from AIKIDO_MAX_BODY_SIZE_MB", async () => { }); }); -t.test("it rate limits requests", async () => { - const http = require("http"); - +t.test("it rate limits requests", async (t) => { const server = http.createServer((req, res) => { res.end(); }); @@ -521,7 +508,6 @@ t.test("it rate limits requests", async () => { }); t.test("it wraps on request event of http", async () => { - const http = require("http"); const server = http.createServer(); server.on("request", (req, res) => { res.setHeader("Content-Type", "application/json"); @@ -559,7 +545,6 @@ t.test("it wraps on request event of http", async () => { }); t.test("it wraps on request event of https", async () => { - const https = require("https"); const { readFileSync } = require("fs"); const path = require("path"); @@ -569,7 +554,6 @@ t.test("it wraps on request event of https", async () => { const server = https.createServer({ key: readFileSync(path.resolve(__dirname, "fixtures/key.pem")), cert: readFileSync(path.resolve(__dirname, "fixtures/cert.pem")), - secureContext: {}, }); server.on("request", (req, res) => { @@ -607,8 +591,7 @@ t.test("it wraps on request event of https", async () => { }); }); -t.test("it checks if IP can access route", async () => { - const http = require("http"); +t.test("it checks if IP can access route", async (t) => { const server = http.createServer((req, res) => { res.setHeader("Content-Type", "text/plain"); res.end("OK"); diff --git a/library/sources/Lambda.test.ts b/library/sources/Lambda.test.ts index bc0d2173f..c5d5f9d90 100644 --- a/library/sources/Lambda.test.ts +++ b/library/sources/Lambda.test.ts @@ -88,7 +88,10 @@ t.test("callback handler throws error", async () => { try { await handler(gatewayEvent, lambdaContext, () => {}); } catch (error) { - t.same(error.message, "error"); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same(error.message, "error"); + } } }); diff --git a/library/sources/Xml2js.test.ts b/library/sources/Xml2js.test.ts index 3d0568361..89cc206ce 100644 --- a/library/sources/Xml2js.test.ts +++ b/library/sources/Xml2js.test.ts @@ -16,7 +16,8 @@ t.test("it works", async () => { agent.start([new Xml2js()]); - const { parseStringPromise, parseString } = require("xml2js"); + const { parseStringPromise, parseString } = + require("xml2js") as typeof import("xml2js"); const xmlString = "Hello xml2js!"; diff --git a/library/tsconfig.tests.json b/library/tsconfig.tests.json index 3a67a45e8..846e66c41 100644 --- a/library/tsconfig.tests.json +++ b/library/tsconfig.tests.json @@ -5,10 +5,10 @@ "module": "commonjs", "moduleResolution": "node", "declaration": true, - "strict": false, + "strict": true, "allowJs": false, "skipLibCheck": true, "noEmit": true }, - "include": ["**/*.test.ts"] + "include": ["**/*.ts"] } diff --git a/library/vulnerabilities/ssrf/inspectDNSLookupCalls.test.ts b/library/vulnerabilities/ssrf/inspectDNSLookupCalls.test.ts index ab186ea13..adc50b839 100644 --- a/library/vulnerabilities/ssrf/inspectDNSLookupCalls.test.ts +++ b/library/vulnerabilities/ssrf/inspectDNSLookupCalls.test.ts @@ -37,11 +37,15 @@ t.test("it resolves private IPv4 without context", (t) => { "operation" ); - wrappedLookup("localhost", { family: 4 }, (err, address) => { - t.same(err, null); - t.same(address, "127.0.0.1"); - t.end(); - }); + wrappedLookup( + "localhost", + { family: 4 }, + (err: Error | null, address: string) => { + t.same(err, null); + t.same(address, "127.0.0.1"); + t.end(); + } + ); }); t.test("it resolves private IPv6 without context", (t) => { @@ -58,7 +62,7 @@ t.test("it resolves private IPv6 without context", (t) => { "operation" ); - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err, null); t.same(address, getMajorNodeVersion() === 16 ? "127.0.0.1" : "::1"); t.end(); @@ -81,12 +85,14 @@ t.test("it blocks lookup in blocking mode", (t) => { ); runWithContext(context, () => { - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err instanceof Error, true); - t.same( - err.message, - "Zen has blocked a server-side request forgery: operation(...) originating from body.image" - ); + if (err instanceof Error) { + t.same( + err.message, + "Zen has blocked a server-side request forgery: operation(...) originating from body.image" + ); + } t.same(address, undefined); t.match(api.getEvents(), [ { @@ -123,7 +129,7 @@ t.test("it allows resolved public IP", (t) => { runWithContext( { ...context, body: { image: "http://www.google.be" } }, () => { - wrappedLookup("www.google.be", (err, address) => { + wrappedLookup("www.google.be", (err: Error | null, address: string) => { t.same(err, null); t.ok(typeof address === "string"); t.same(api.getEvents(), []); @@ -151,7 +157,7 @@ t.test( ); runWithContext({ ...context, body: undefined }, () => { - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err, null); t.same(address, getMajorNodeVersion() === 16 ? "127.0.0.1" : "::1"); t.same(api.getEvents(), []); @@ -201,7 +207,7 @@ t.test( await new Promise((resolve) => { runWithContext(context, () => { - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err, null); t.same(address, getMajorNodeVersion() === 16 ? "127.0.0.1" : "::1"); t.same(api.getEvents(), []); @@ -227,15 +233,21 @@ t.test("it blocks lookup in blocking mode with all option", (t) => { ); runWithContext(context, () => { - wrappedLookup("localhost", { all: true }, (err, addresses) => { - t.same(err instanceof Error, true); - t.same( - err.message, - "Zen has blocked a server-side request forgery: operation(...) originating from body.image" - ); - t.same(addresses, undefined); - t.end(); - }); + wrappedLookup( + "localhost", + { all: true }, + (err: Error | null, address: string) => { + t.same(err instanceof Error, true); + if (err instanceof Error) { + t.same( + err.message, + "Zen has blocked a server-side request forgery: operation(...) originating from body.image" + ); + } + t.same(address, undefined); + t.end(); + } + ); }); }); @@ -255,7 +267,7 @@ t.test("it does not block in dry mode", (t) => { ); runWithContext(context, () => { - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err, null); t.same(address, getMajorNodeVersion() === 16 ? "127.0.0.1" : "::1"); t.match(api.getEvents(), [ @@ -301,15 +313,17 @@ t.test("it ignores if lookup returns error", (t) => { agent.start([]); const wrappedLookup = inspectDNSLookupCalls( - (_, callback) => callback(new Error("lookup failed")), + (_: any, callback: Function) => callback(new Error("lookup failed")), agent, "module", "operation" ); - wrappedLookup("localhost", (err, address) => { + wrappedLookup("localhost", (err: Error | null, address: string) => { t.same(err instanceof Error, true); - t.same(err.message, "lookup failed"); + if (err instanceof Error) { + t.same(err.message, "lookup failed"); + } t.same(address, undefined); t.end(); }); @@ -350,27 +364,39 @@ t.test("Blocks IMDS SSRF with untrusted domain", async (t) => { await Promise.all([ new Promise((resolve) => { - wrappedLookup("imds.test.com", { family: 4 }, (err, addresses) => { - t.same(err instanceof Error, true); - t.same( - err.message, - "Zen has blocked a server-side request forgery: operation(...) originating from unknown source" - ); - t.same(addresses, undefined); - resolve(); - }); + wrappedLookup( + "imds.test.com", + { family: 4 }, + (err: Error | null, address: string) => { + t.same(err instanceof Error, true); + if (err instanceof Error) { + t.same( + err.message, + "Zen has blocked a server-side request forgery: operation(...) originating from unknown source" + ); + } + t.same(address, undefined); + resolve(); + } + ); }), new Promise((resolve) => { runWithContext(context, () => { - wrappedLookup("imds.test.com", { family: 4 }, (err, addresses) => { - t.same(err instanceof Error, true); - t.same( - err.message, - "Zen has blocked a server-side request forgery: operation(...) originating from unknown source" - ); - t.same(addresses, undefined); - resolve(); - }); + wrappedLookup( + "imds.test.com", + { family: 4 }, + (err: Error | null, address: string) => { + t.same(err instanceof Error, true); + if (err instanceof Error) { + t.same( + err.message, + "Zen has blocked a server-side request forgery: operation(...) originating from unknown source" + ); + } + t.same(address, undefined); + resolve(); + } + ); }); }), ]); @@ -414,11 +440,15 @@ t.test( ); runWithContext(context, () => { - wrappedLookup("imds.test.com", { family: 4 }, (err, addresses) => { - t.same(err, null); - t.same(addresses, "169.254.169.254"); - t.end(); - }); + wrappedLookup( + "imds.test.com", + { family: 4 }, + (err: Error | null, address: string) => { + t.same(err, null); + t.same(address, "169.254.169.254"); + t.end(); + } + ); }); } ); @@ -442,9 +472,9 @@ t.test("Does not block IMDS SSRF with Google metadata domain", async (t) => { wrappedLookup( "metadata.google.internal", { family: 4 }, - (err, addresses) => { + (err: Error | null, address: string) => { t.same(err, null); - t.same(addresses, "169.254.169.254"); + t.same(address, "169.254.169.254"); resolve(); } ); @@ -454,9 +484,9 @@ t.test("Does not block IMDS SSRF with Google metadata domain", async (t) => { wrappedLookup( "metadata.google.internal", { family: 4 }, - (err, addresses) => { + (err: Error | null, address: string) => { t.same(err, null); - t.same(addresses, "169.254.169.254"); + t.same(address, "169.254.169.254"); resolve(); } ); @@ -484,11 +514,14 @@ t.test("it ignores when the argument is an IP address", async (t) => { runWithContext( { ...context, routeParams: { id: "169.254.169.254" } }, () => { - wrappedLookup("169.254.169.254", (err, address) => { - t.same(err, null); - t.same(address, "169.254.169.254"); - resolve(); - }); + wrappedLookup( + "169.254.169.254", + (err: Error | null, address: string) => { + t.same(err, null); + t.same(address, "169.254.169.254"); + resolve(); + } + ); } ); }), @@ -496,11 +529,14 @@ t.test("it ignores when the argument is an IP address", async (t) => { runWithContext( { ...context, routeParams: { id: "fd00:ec2::254" } }, () => { - wrappedLookup("fd00:ec2::254", (err, address) => { - t.same(err, null); - t.same(address, "fd00:ec2::254"); - resolve(); - }); + wrappedLookup( + "fd00:ec2::254", + (err: Error | null, address: string) => { + t.same(err, null); + t.same(address, "fd00:ec2::254"); + resolve(); + } + ); } ); }), From 688f4d2ec420581be2a50e3600ac69b801f2e15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Fri, 18 Oct 2024 10:56:50 +0200 Subject: [PATCH 4/6] Switch default tsconfig, fix test types --- library/agent/Context.test.ts | 14 +-- library/agent/ServiceConfig.test.ts | 7 +- .../agent/api-discovery/getDataSchema.test.ts | 2 +- .../agent/api/ReportingAPINodeHTTP.test.ts | 4 + .../ReportingAPIRateLimitedClientSide.test.ts | 16 ++++ .../ReportingAPIRateLimitedServerSide.test.ts | 5 ++ .../ReportingAPIThatValidatesToken.test.ts | 5 ++ library/agent/applyHooks.test.ts | 1 + library/agent/realtime/pollForChanges.test.ts | 4 +- library/helpers/isPlainObject.test.ts | 3 + library/helpers/percentiles.test.ts | 4 +- library/package-lock.json | 22 +++++ library/package.json | 4 +- .../shouldRateLimitRequest.test.ts | 4 +- library/sinks/ChildProcess.test.ts | 90 +++++-------------- library/sinks/Fetch.test.ts | 26 ++++-- library/sinks/FileSystem.test.ts | 34 ++++--- .../sinks/HTTPRequest.followRedirects.test.ts | 11 +-- library/sinks/HTTPRequest.needle.test.ts | 2 +- library/sinks/HTTPRequest.redirect.test.ts | 2 +- library/sinks/HTTPRequest.test.ts | 39 ++++---- library/sinks/MySQL.test.ts | 5 +- library/sinks/Postgres.test.ts | 3 +- library/sinks/Shelljs.test.ts | 24 ++--- library/sinks/Undici.test.ts | 24 +++-- .../getUrlFromHTTPRequestArgs.test.ts | 11 +-- library/sources/Express.test.ts | 13 ++- library/sources/FunctionsFramework.test.ts | 7 +- library/sources/Hono.test.ts | 2 +- library/sources/Lambda.test.ts | 9 +- .../ipAllowedToAccessRoute.test.ts | 8 ++ ...sconfig.tests.json => tsconfig.build.json} | 7 +- library/tsconfig.json | 7 +- 33 files changed, 240 insertions(+), 179 deletions(-) rename library/{tsconfig.tests.json => tsconfig.build.json} (66%) diff --git a/library/agent/Context.test.ts b/library/agent/Context.test.ts index 546bec68b..d35aecc5c 100644 --- a/library/agent/Context.test.ts +++ b/library/agent/Context.test.ts @@ -107,26 +107,26 @@ t.test("it clears cache when context is mutated", async (t) => { const context = { ...sampleContext }; runWithContext(context, () => { - t.same(extractStringsFromUserInputCached(getContext(), "body"), undefined); + t.same(extractStringsFromUserInputCached(getContext()!, "body"), undefined); t.same( - extractStringsFromUserInputCached(getContext(), "query"), + extractStringsFromUserInputCached(getContext()!, "query"), new Map(Object.entries({ abc: ".", def: ".abc" })) ); - updateContext(getContext(), "query", {}); - t.same(extractStringsFromUserInputCached(getContext(), "body"), undefined); + updateContext(getContext()!, "query", {}); + t.same(extractStringsFromUserInputCached(getContext()!, "body"), undefined); t.same( - extractStringsFromUserInputCached(getContext(), "query"), + extractStringsFromUserInputCached(getContext()!, "query"), new Map(Object.entries({})) ); runWithContext({ ...context, body: { a: "z" }, query: { b: "y" } }, () => { t.same( - extractStringsFromUserInputCached(getContext(), "body"), + extractStringsFromUserInputCached(getContext()!, "body"), new Map(Object.entries({ a: ".", z: ".a" })) ); t.same( - extractStringsFromUserInputCached(getContext(), "query"), + extractStringsFromUserInputCached(getContext()!, "query"), new Map(Object.entries({ b: ".", y: ".b" })) ); }); diff --git a/library/agent/ServiceConfig.test.ts b/library/agent/ServiceConfig.test.ts index 908b74a2d..e6f5d78e7 100644 --- a/library/agent/ServiceConfig.test.ts +++ b/library/agent/ServiceConfig.test.ts @@ -2,7 +2,7 @@ import * as t from "tap"; import { ServiceConfig } from "./ServiceConfig"; t.test("it returns false if empty rules", async () => { - const config = new ServiceConfig([], 0, [], []); + const config = new ServiceConfig([], 0, [], [], false); t.same(config.getLastUpdatedAt(), 0); t.same(config.isUserBlocked("id"), false); t.same(config.isAllowedIP("1.2.3.4"), false); @@ -52,7 +52,8 @@ t.test("it works", async () => { ], 0, ["123"], - [] + [], + false ); t.same(config.isUserBlocked("123"), true); @@ -79,7 +80,7 @@ t.test("it works", async () => { }); t.test("it checks if IP is allowed", async () => { - const config = new ServiceConfig([], 0, [], ["1.2.3.4"]); + const config = new ServiceConfig([], 0, [], ["1.2.3.4"], false); t.same(config.isAllowedIP("1.2.3.4"), true); t.same(config.isAllowedIP("1.2.3.5"), false); }); diff --git a/library/agent/api-discovery/getDataSchema.test.ts b/library/agent/api-discovery/getDataSchema.test.ts index 9cecf86d3..e6699909f 100644 --- a/library/agent/api-discovery/getDataSchema.test.ts +++ b/library/agent/api-discovery/getDataSchema.test.ts @@ -102,7 +102,7 @@ t.test("it works", async (t) => { }); t.test("test max depth", async (t) => { - const generateTestObjectWithDepth = (depth: number) => { + const generateTestObjectWithDepth = (depth: number): object | string => { if (depth === 0) { return "testValue"; } diff --git a/library/agent/api/ReportingAPINodeHTTP.test.ts b/library/agent/api/ReportingAPINodeHTTP.test.ts index ec6d05018..97de5bec1 100644 --- a/library/agent/api/ReportingAPINodeHTTP.test.ts +++ b/library/agent/api/ReportingAPINodeHTTP.test.ts @@ -27,6 +27,10 @@ function generateStartedEvent(): Event { }, stack: [], serverless: false, + platform: { + version: "version", + arch: "arch", + }, }, }; } diff --git a/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts b/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts index fa82d82cd..abce879ad 100644 --- a/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts +++ b/library/agent/api/ReportingAPIRateLimitedClientSide.test.ts @@ -13,6 +13,7 @@ function generateAttackEvent(): Event { method: undefined, ipAddress: undefined, userAgent: undefined, + // @ts-expect-error Test headers: undefined, body: undefined, source: "express", @@ -32,6 +33,7 @@ function generateAttackEvent(): Event { }, agent: { version: "1.0.0", + library: "firewall-node", dryMode: false, hostname: "hostname", packages: {}, @@ -47,6 +49,10 @@ function generateAttackEvent(): Event { prototypePollution: {}, }, stack: [], + platform: { + version: "version", + arch: "arch", + }, }, }; } @@ -100,6 +106,11 @@ function generateStartedEvent(): Event { }, stack: [], serverless: false, + library: "firewall-node", + platform: { + version: "version", + arch: "arch", + }, }, }; } @@ -162,6 +173,11 @@ function generateHeartbeatEvent(): Event { prototypePollution: {}, }, stack: [], + library: "firewall-node", + platform: { + version: "version", + arch: "arch", + }, }, hostnames: [], routes: [], diff --git a/library/agent/api/ReportingAPIRateLimitedServerSide.test.ts b/library/agent/api/ReportingAPIRateLimitedServerSide.test.ts index 5ce783551..60f307d82 100644 --- a/library/agent/api/ReportingAPIRateLimitedServerSide.test.ts +++ b/library/agent/api/ReportingAPIRateLimitedServerSide.test.ts @@ -26,6 +26,11 @@ function generateStartedEvent(): Event { }, stack: [], serverless: false, + library: "firewall-node", + platform: { + version: "version", + arch: "arch", + }, }, }; } diff --git a/library/agent/api/ReportingAPIThatValidatesToken.test.ts b/library/agent/api/ReportingAPIThatValidatesToken.test.ts index a8f399ac2..c272c86a2 100644 --- a/library/agent/api/ReportingAPIThatValidatesToken.test.ts +++ b/library/agent/api/ReportingAPIThatValidatesToken.test.ts @@ -25,6 +25,11 @@ function generateStartedEvent(): Event { }, stack: [], serverless: false, + library: "firewall-node", + platform: { + version: "version", + arch: "arch", + }, }, }; } diff --git a/library/agent/applyHooks.test.ts b/library/agent/applyHooks.test.ts index b2b133c5a..cc9dbff25 100644 --- a/library/agent/applyHooks.test.ts +++ b/library/agent/applyHooks.test.ts @@ -257,6 +257,7 @@ t.test("it ignores route if force protection off is on", async (t) => { method: "GET", route: "/route", forceProtectionOff: true, + // @ts-expect-error Test rateLimiting: undefined, }, ], diff --git a/library/agent/realtime/pollForChanges.test.ts b/library/agent/realtime/pollForChanges.test.ts index db10d848f..7588eb4c9 100644 --- a/library/agent/realtime/pollForChanges.test.ts +++ b/library/agent/realtime/pollForChanges.test.ts @@ -45,7 +45,7 @@ t.test("it checks for config updates", async () => { let configUpdatedAt = 0; wrap(fetch, "fetch", function fetch() { - return async function fetch(params) { + return async function fetch(params: any) { calls.push({ url: params.url.toString(), method: params.method, @@ -207,7 +207,7 @@ t.test("it deals with API throwing errors", async () => { }; }); - const configUpdates = []; + const configUpdates: Config[] = []; const logger = new LoggerForTesting(); pollForChanges({ diff --git a/library/helpers/isPlainObject.test.ts b/library/helpers/isPlainObject.test.ts index 4382d1cee..169e6d431 100644 --- a/library/helpers/isPlainObject.test.ts +++ b/library/helpers/isPlainObject.test.ts @@ -16,6 +16,7 @@ t.test( "should return `false` if the object is not created by the `Object` constructor.", async (t) => { function Foo() { + // @ts-expect-error Test this.abc = {}; } @@ -24,6 +25,7 @@ t.test( t.notOk(isPlainObject(1)); t.notOk(isPlainObject(["foo", "bar"])); t.notOk(isPlainObject([])); + // @ts-expect-error Test t.notOk(isPlainObject(new Foo())); t.notOk(isPlainObject(null)); } @@ -35,6 +37,7 @@ t.test("should return `false` if prototype has been modified", async (t) => { // Directly use a built-in type's prototype, for example, Array.prototype CustomConstructor.prototype = Array.prototype; + // @ts-expect-error Test const instance = new CustomConstructor(); t.same(isPlainObject(instance), false); diff --git a/library/helpers/percentiles.test.ts b/library/helpers/percentiles.test.ts index c2599f5d5..b30263b2d 100644 --- a/library/helpers/percentiles.test.ts +++ b/library/helpers/percentiles.test.ts @@ -3,7 +3,7 @@ import { percentiles } from "./percentiles"; function generateArray( length: number, - fn: (value: number, index: number) => number + fn: (value: unknown, index: number) => number ) { return Array.from({ length: length }).map(fn); } @@ -29,7 +29,7 @@ const stubsSimple = [ { percentile: 75, list: shuffleArray( - [].concat(generateArraySimple(100), generateArraySimple(30)) + generateArraySimple(100).concat(generateArraySimple(30)) ), result: 68, }, diff --git a/library/package-lock.json b/library/package-lock.json index c4ed840fd..9087cd827 100644 --- a/library/package-lock.json +++ b/library/package-lock.json @@ -17,8 +17,10 @@ "@types/aws-lambda": "^8.10.131", "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.21", + "@types/follow-redirects": "^1.14.4", "@types/ip": "^1.1.3", "@types/mysql": "^2.15.25", + "@types/needle": "^3.3.0", "@types/node": "^20.11.5", "@types/pg": "^8.11.0", "@types/qs": "^6.9.11", @@ -3457,6 +3459,16 @@ "@types/send": "*" } }, + "node_modules/@types/follow-redirects": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -3519,6 +3531,16 @@ "@types/node": "*" } }, + "node_modules/@types/needle": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.3.0.tgz", + "integrity": "sha512-UFIuc1gdyzAqeVUYpSL+cliw2MmU/ZUhVZKE7Zo4wPbgc8hbljeKSnn6ls6iG8r5jpegPXLUIhJ+Wb2kLVs8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.3.tgz", diff --git a/library/package.json b/library/package.json index 7f29902c1..b9ef82e61 100644 --- a/library/package.json +++ b/library/package.json @@ -40,8 +40,10 @@ "@types/aws-lambda": "^8.10.131", "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.21", + "@types/follow-redirects": "^1.14.4", "@types/ip": "^1.1.3", "@types/mysql": "^2.15.25", + "@types/needle": "^3.3.0", "@types/node": "^20.11.5", "@types/pg": "^8.11.0", "@types/qs": "^6.9.11", @@ -87,7 +89,7 @@ "scripts": { "test": "node ../scripts/run-tap.js", "test:ci": "CI=true node ../scripts/run-tap.js", - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "build:watch": "tsc --watch", "lint": "npm run lint-eslint && npm run lint-tsc", "lint-eslint": "eslint '**/*.ts'", diff --git a/library/ratelimiting/shouldRateLimitRequest.test.ts b/library/ratelimiting/shouldRateLimitRequest.test.ts index 01c3e9a05..2d939ed01 100644 --- a/library/ratelimiting/shouldRateLimitRequest.test.ts +++ b/library/ratelimiting/shouldRateLimitRequest.test.ts @@ -8,8 +8,8 @@ import { LoggerNoop } from "../agent/logger/LoggerNoop"; import { shouldRateLimitRequest } from "./shouldRateLimitRequest"; function createContext( - remoteAddress: string = undefined, - userId: string = undefined, + remoteAddress: string | undefined = undefined, + userId: string | undefined = undefined, route: string = "/login", method: string = "POST" ): Context { diff --git a/library/sinks/ChildProcess.test.ts b/library/sinks/ChildProcess.test.ts index b57e72b1a..81f721e7e 100644 --- a/library/sinks/ChildProcess.test.ts +++ b/library/sinks/ChildProcess.test.ts @@ -41,25 +41,30 @@ t.test("it works", async (t) => { agent.start([new ChildProcess()]); - const { exec, execSync, spawn, spawnSync, fork } = require("child_process"); + const { exec, execSync, spawn, spawnSync, fork } = + require("child_process") as typeof import("child_process"); const runCommandsWithInvalidArgs = () => { throws( + // @ts-expect-error Testing invalid arguments () => exec().unref(), /argument must be of type string. Received undefined/ ); throws( + // @ts-expect-error Testing invalid arguments () => execSync(), /argument must be of type string. Received undefined/ ); throws( + // @ts-expect-error Testing invalid arguments () => spawn().unref(), /argument must be of type string. Received undefined/ ); throws( + // @ts-expect-error Testing invalid arguments () => spawnSync(), /argument must be of type string. Received undefined/ ); @@ -73,13 +78,13 @@ t.test("it works", async (t) => { const runSafeCommands = () => { exec("ls", (err, stdout, stderr) => {}).unref(); - execSync("ls", (err, stdout, stderr) => {}); + execSync("ls"); - spawn("ls", ["-la"], {}, (err, stdout, stderr) => {}).unref(); - spawnSync("ls", ["-la"], {}, (err, stdout, stderr) => {}); + spawn("ls", ["-la"], {}).unref(); + spawnSync("ls", ["-la"], {}); - spawn("ls", ["-la"], { shell: false }, (err, stdout, stderr) => {}).unref(); - spawnSync("ls", ["-la"], { shell: false }, (err, stdout, stderr) => {}); + spawn("ls", ["-la"], { shell: false }).unref(); + spawnSync("ls", ["-la"], { shell: false }); execFile("ls", ["-la"], {}, (err, stdout, stderr) => {}).unref(); execFileSync("ls", ["-la"], {}); @@ -100,31 +105,19 @@ t.test("it works", async (t) => { ); throws( - () => execSync("ls `echo .`", (err, stdout, stderr) => {}), + () => execSync("ls `echo .`"), "Zen has blocked a shell injection: child_process.execSync(...) originating from body.file.matches" ); }); runWithContext(unsafeContext, () => { throws( - () => - spawn( - "ls `echo .`", - [], - { shell: true }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("ls `echo .`", [], { shell: true }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); throws( - () => - spawn( - "ls", - ["`echo .`"], - { shell: "/bin/sh" }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("ls", ["`echo .`"], { shell: "/bin/sh" }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); @@ -133,78 +126,37 @@ t.test("it works", async (t) => { // While shell: false (and thus native functionality of spawning a shell is not used), we should check if the developer directly invokes // the shell via sh -c, and validate the arguments of the command. throws( - () => - spawn( - "sh", - ["-c", "`echo .`"], - { shell: false }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("sh", ["-c", "`echo .`"], { shell: false }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); throws( - () => - spawn( - "/bin/sh", - ["-c", "`echo .`"], - { shell: false }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("/bin/sh", ["-c", "`echo .`"], { shell: false }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); throws( - () => - spawn( - "bash", - ["-c", "`echo .`"], - { shell: false }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("bash", ["-c", "`echo .`"], { shell: false }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); throws( - () => - spawn( - "/bin/bash", - ["-c", "`echo .`"], - { shell: false }, - (err, stdout, stderr) => {} - ).unref(), + () => spawn("/bin/bash", ["-c", "`echo .`"], { shell: false }).unref(), "Zen has blocked a shell injection: child_process.spawn(...) originating from body.file.matches" ); throws( - () => - spawnSync( - "/bin/bash", - ["-c", "`echo .`"], - (err, stdout, stderr) => {} - ).unref(), + () => spawnSync("/bin/bash", ["-c", "`echo .`"]), "Zen has blocked a shell injection: child_process.spawnSync(...) originating from body.file.matches" ); throws( - () => - spawnSync( - "ls `echo .`", - [], - { shell: true }, - (err, stdout, stderr) => {} - ), + () => spawnSync("ls `echo .`", [], { shell: true }), "Zen has blocked a shell injection: child_process.spawnSync(...) originating from body.file.matches" ); throws( - () => - spawnSync( - "ls `echo .`", - [], - { shell: "/bin/sh" }, - (err, stdout, stderr) => {} - ), + () => spawnSync("ls `echo .`", [], { shell: "/bin/sh" }), "Zen has blocked a shell injection: child_process.spawnSync(...) originating from body.file.matches" ); }); diff --git a/library/sinks/Fetch.test.ts b/library/sinks/Fetch.test.ts index b2ed54089..a951f76bd 100644 --- a/library/sinks/Fetch.test.ts +++ b/library/sinks/Fetch.test.ts @@ -21,20 +21,26 @@ wrap(dns, "lookup", function lookup(original) { calls[hostname]++; if (hostname === "thisdomainpointstointernalip.com") { - return original.apply(this, [ - "localhost", - ...Array.from(arguments).slice(1), - ]); + return original.apply( + // @ts-expect-error We don't know the type of `this` + this, + ["localhost", ...Array.from(arguments).slice(1)] + ); } if (hostname === "example,prefix.thisdomainpointstointernalip.com") { - return original.apply(this, [ - "localhost", - ...Array.from(arguments).slice(1), - ]); + return original.apply( + // @ts-expect-error We don't know the type of `this` + this, + ["localhost", ...Array.from(arguments).slice(1)] + ); } - original.apply(this, arguments); + original.apply( + // @ts-expect-error We don't know the type of `this` + this, + arguments + ); }; }); @@ -144,6 +150,7 @@ t.test( } const error3 = await t.rejects(() => + // @ts-expect-error Test fetch(["http://localhost:4000/api/internal"]) ); if (error3 instanceof Error) { @@ -180,6 +187,7 @@ t.test( } const error2 = await t.rejects(() => + // @ts-expect-error Test fetch(["http://example", "prefix.thisdomainpointstointernalip.com"]) ); if (error2 instanceof Error) { diff --git a/library/sinks/FileSystem.test.ts b/library/sinks/FileSystem.test.ts index 3ad5d678d..142fa3802 100644 --- a/library/sinks/FileSystem.test.ts +++ b/library/sinks/FileSystem.test.ts @@ -64,7 +64,8 @@ t.test("it works", async (t) => { realpath, realpathSync, } = require("fs"); - const { writeFile: writeFilePromise } = require("fs/promises"); + const { writeFile: writeFilePromise } = + require("fs/promises") as typeof import("fs/promises"); t.ok(typeof realpath.native === "function"); t.ok(typeof realpathSync.native === "function"); @@ -85,7 +86,7 @@ t.test("it works", async (t) => { "./test.txt", "some file content to test with", { encoding: "utf-8" }, - (err) => {} + () => {} ); writeFileSync("./test.txt", "some other file content to test with", { encoding: "utf-8", @@ -95,9 +96,9 @@ t.test("it works", async (t) => { "some other file content to test with", { encoding: "utf-8" } ); - rename("./test.txt", "./test2.txt", (err) => {}); - rename(new URL("file:///test123.txt"), "test2.txt", (err) => {}); - rename(Buffer.from("./test123.txt"), "test2.txt", (err) => {}); + rename("./test.txt", "./test2.txt", () => {}); + rename(new URL("file:///test123.txt"), "test2.txt", () => {}); + rename(Buffer.from("./test123.txt"), "test2.txt", () => {}); }; await runSafeCommands(); @@ -113,7 +114,7 @@ t.test("it works", async (t) => { "../../test.txt", "some file content to test with", { encoding: "utf-8" }, - (err) => {} + () => {} ), "Zen has blocked a path traversal attack: fs.writeFile(...) originating from body.file.matches" ); @@ -144,47 +145,44 @@ t.test("it works", async (t) => { } throws( - () => rename("../../test.txt", "./test2.txt", (err) => {}), + () => rename("../../test.txt", "./test2.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( - () => rename("./test.txt", "../../test.txt", (err) => {}), + () => rename("./test.txt", "../../test.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( - () => rename(new URL("file:///../test.txt"), "../test2.txt", (err) => {}), + () => rename(new URL("file:///../test.txt"), "../test2.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( - () => - rename(new URL("file:///./../test.txt"), "../test2.txt", (err) => {}), + () => rename(new URL("file:///./../test.txt"), "../test2.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( - () => - rename(new URL("file:///../../test.txt"), "../test2.txt", (err) => {}), + () => rename(new URL("file:///../../test.txt"), "../test2.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( - () => rename(Buffer.from("../test.txt"), "../test2.txt", (err) => {}), + () => rename(Buffer.from("../test.txt"), "../test2.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); }); runWithContext(unsafeContextAbsolute, () => { throws( - () => - rename(new URL("file:///etc/passwd"), "../test123.txt", (err) => {}), + () => rename(new URL("file:///etc/passwd"), "../test123.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); throws( () => - rename(new URL("file:///../etc/passwd"), "../test123.txt", (err) => {}), + rename(new URL("file:///../etc/passwd"), "../test123.txt", () => {}), "Zen has blocked a path traversal attack: fs.rename(...) originating from body.file.matches" ); }); @@ -193,7 +191,7 @@ t.test("it works", async (t) => { runWithContext( { ...unsafeContext, body: { file: { matches: "../%" } } }, () => { - rename(new URL("file:///../../test.txt"), "../test2.txt", (err) => {}); + rename(new URL("file:///../../test.txt"), "../test2.txt", () => {}); } ); }); diff --git a/library/sinks/HTTPRequest.followRedirects.test.ts b/library/sinks/HTTPRequest.followRedirects.test.ts index 01895b2db..241318f2f 100644 --- a/library/sinks/HTTPRequest.followRedirects.test.ts +++ b/library/sinks/HTTPRequest.followRedirects.test.ts @@ -21,11 +21,11 @@ const context: Context = { route: "/posts/:id", }; -let server; +let server: import("http").Server; const port = 3000; t.before(async () => { - const { createServer } = require("http"); + const { createServer } = require("http") as typeof import("http"); server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); @@ -34,7 +34,7 @@ t.before(async () => { server.unref(); - return new Promise((resolve) => { + return new Promise((resolve) => { server.listen(port, resolve); }); }); @@ -51,7 +51,8 @@ t.test("it works", { skip: "SSRF redirect check disabled atm" }, (t) => { ); agent.start([new HTTPRequest()]); - const { http } = require("follow-redirects"); + const { http } = + require("follow-redirects") as typeof import("follow-redirects"); runWithContext( { @@ -60,7 +61,7 @@ t.test("it works", { skip: "SSRF redirect check disabled atm" }, (t) => { ...{ body: { image: `${redirectTestUrl}/ssrf-test` } }, }, () => { - const response = http.request(`${redirectTestUrl}/ssrf-test`, (res) => { + const response = http.request(`${redirectTestUrl}/ssrf-test`, () => { t.fail("should not respond"); }); response.on("error", (e) => { diff --git a/library/sinks/HTTPRequest.needle.test.ts b/library/sinks/HTTPRequest.needle.test.ts index cc1bd643d..33d886fe2 100644 --- a/library/sinks/HTTPRequest.needle.test.ts +++ b/library/sinks/HTTPRequest.needle.test.ts @@ -35,7 +35,7 @@ t.test("it works", { skip: "SSRF redirect check disabled atm" }, async (t) => { t.same(agent.getHostnames().asArray(), []); - const needle = require("needle"); + const needle = require("needle") as typeof import("needle"); await runWithContext(context, async () => { await needle("get", "https://www.aikido.dev"); diff --git a/library/sinks/HTTPRequest.redirect.test.ts b/library/sinks/HTTPRequest.redirect.test.ts index 66374adf7..9a930e9b4 100644 --- a/library/sinks/HTTPRequest.redirect.test.ts +++ b/library/sinks/HTTPRequest.redirect.test.ts @@ -43,7 +43,7 @@ t.test("it works", { skip: "SSRF redirect check disabled atm" }, (t) => { ); agent.start([new HTTPRequest()]); - const http = require("http"); + const http = require("http") as typeof import("http"); runWithContext( { diff --git a/library/sinks/HTTPRequest.test.ts b/library/sinks/HTTPRequest.test.ts index 85bb98934..1e97fa8ab 100644 --- a/library/sinks/HTTPRequest.test.ts +++ b/library/sinks/HTTPRequest.test.ts @@ -24,13 +24,18 @@ wrap(dns, "lookup", function lookup(original) { hostname === "thisdomainpointstointernalip.com" || hostname === "thisdomainpointstointernalip2.com" ) { - return original.apply(this, [ - "localhost", - ...Array.from(arguments).slice(1), - ]); + return original.apply( + // @ts-expect-error We don't know the type of `this` + this, + ["localhost", ...Array.from(arguments).slice(1)] + ); } - original.apply(this, arguments); + original.apply( + // @ts-expect-error We don't know the type of `this` + this, + arguments + ); }; }); @@ -61,8 +66,8 @@ t.test("it works", (t) => { t.same(agent.getHostnames().asArray(), []); - const http = require("http"); - const https = require("https"); + const http = require("http") as typeof import("http"); + const https = require("https") as typeof import("https"); runWithContext(context, () => { const aikido = http.request("http://aikido.dev"); @@ -240,23 +245,27 @@ t.test("it works", (t) => { } // ECONNREFUSED means that the request is not blocked - http.request("http://localhost:9876").on("error", (e) => { - t.same(e.code, "ECONNREFUSED"); - }); + http + .request("http://localhost:9876") + .on("error", (e: NodeJS.ErrnoException) => { + t.same(e.code, "ECONNREFUSED"); + }); - https.request("https://localhost:9876").on("error", (e) => { - t.same(e.code, "ECONNREFUSED"); - }); + https + .request("https://localhost:9876") + .on("error", (e: NodeJS.ErrnoException) => { + t.same(e.code, "ECONNREFUSED"); + }); https .request("https://localhost/api/internal", { port: 9876 }) - .on("error", (e) => { + .on("error", (e: NodeJS.ErrnoException) => { t.same(e.code, "ECONNREFUSED"); }); https .request("https://localhost/api/internal", { defaultPort: 9876 }) - .on("error", (e) => { + .on("error", (e: NodeJS.ErrnoException) => { t.same(e.code, "ECONNREFUSED"); }); diff --git a/library/sinks/MySQL.test.ts b/library/sinks/MySQL.test.ts index 654a4d4ef..e304758f4 100644 --- a/library/sinks/MySQL.test.ts +++ b/library/sinks/MySQL.test.ts @@ -55,8 +55,8 @@ t.test("it detects SQL injections", async () => { ); agent.start([new MySQL()]); - const mysql = require("mysql"); - const connection = await mysql.createConnection({ + const mysql = require("mysql") as typeof import("mysql"); + const connection = mysql.createConnection({ host: "localhost", user: "root", password: "mypassword", @@ -124,6 +124,7 @@ t.test("it detects SQL injections", async () => { const undefinedQueryError = await t.rejects(async () => { await runWithContext(context, () => { + // @ts-expect-error Test return query(undefined, connection); }); }); diff --git a/library/sinks/Postgres.test.ts b/library/sinks/Postgres.test.ts index cf269d66f..822db71ed 100644 --- a/library/sinks/Postgres.test.ts +++ b/library/sinks/Postgres.test.ts @@ -30,7 +30,7 @@ t.test("it inspects query method calls and blocks if needed", async (t) => { ); agent.start([new Postgres()]); - const { Client } = require("pg"); + const { Client } = require("pg") as typeof import("pg"); const client = new Client({ user: "root", host: "127.0.0.1", @@ -96,6 +96,7 @@ t.test("it inspects query method calls and blocks if needed", async (t) => { const undefinedQueryError = await t.rejects(async () => { await runWithContext(context, () => { + // @ts-expect-error Test return client.query(null); }); }); diff --git a/library/sinks/Shelljs.test.ts b/library/sinks/Shelljs.test.ts index e8902924f..56f8bee06 100644 --- a/library/sinks/Shelljs.test.ts +++ b/library/sinks/Shelljs.test.ts @@ -1,7 +1,7 @@ import * as t from "tap"; import { Agent } from "../agent/Agent"; import { ReportingAPIForTesting } from "../agent/api/ReportingAPIForTesting"; -import { getContext, runWithContext, type Context } from "../agent/Context"; +import { runWithContext, type Context } from "../agent/Context"; import { LoggerNoop } from "../agent/logger/LoggerNoop"; import { Shelljs } from "./Shelljs"; import { ChildProcess } from "./ChildProcess"; @@ -50,7 +50,7 @@ const safeContext: Context = { route: "/posts/:id", }; -t.test("it detects shell injections", async () => { +t.test("it detects shell injections", async (t) => { const agent = new Agent( true, new LoggerNoop(), @@ -119,7 +119,7 @@ t.test("it does not detect injection without context", async () => { } }); -t.test("it detects async shell injections", async () => { +t.test("it detects async shell injections", async (t) => { const agent = new Agent( true, new LoggerNoop(), @@ -174,7 +174,7 @@ t.test("it detects async shell injections", async () => { } }); -t.test("it prevents path injections using ls", async () => { +t.test("it prevents path injections using ls", async (t) => { const agent = new Agent( true, new LoggerNoop(), @@ -191,14 +191,16 @@ t.test("it prevents path injections using ls", async () => { return shelljs.ls("/etc/ssh"); }); }); - - t.same( - error.message, - "Zen has blocked a path traversal attack: fs.readdirSync(...) originating from body.myTitle" - ); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked a path traversal attack: fs.readdirSync(...) originating from body.myTitle" + ); + } }); -t.test("it prevents path injections using cat", async () => { +t.test("it prevents path injections using cat", async (t) => { const agent = new Agent( true, new LoggerNoop(), @@ -248,7 +250,7 @@ t.test( }); t.end(); } catch (error) { - t.fail(error); + t.fail(error as Error); } } ); diff --git a/library/sinks/Undici.test.ts b/library/sinks/Undici.test.ts index 4fc8ba2a9..1cec0939d 100644 --- a/library/sinks/Undici.test.ts +++ b/library/sinks/Undici.test.ts @@ -22,20 +22,26 @@ wrap(dns, "lookup", function lookup(original) { calls[hostname]++; if (hostname === "thisdomainpointstointernalip.com") { - return original.apply(this, [ - "localhost", - ...Array.from(arguments).slice(1), - ]); + return original.apply( + // @ts-expect-error We don't know the type of `this` + this, + ["localhost", ...Array.from(arguments).slice(1)] + ); } if (hostname === "example,prefix.thisdomainpointstointernalip.com") { - return original.apply(this, [ - "localhost", - ...Array.from(arguments).slice(1), - ]); + return original.apply( + // @ts-expect-error We don't know the type of `this` + this, + ["localhost", ...Array.from(arguments).slice(1)] + ); } - original.apply(this, arguments); + original.apply( + // @ts-expect-error We don't know the type of `this` + this, + arguments + ); }; }); diff --git a/library/sinks/http-request/getUrlFromHTTPRequestArgs.test.ts b/library/sinks/http-request/getUrlFromHTTPRequestArgs.test.ts index b8179f133..59a69630a 100644 --- a/library/sinks/http-request/getUrlFromHTTPRequestArgs.test.ts +++ b/library/sinks/http-request/getUrlFromHTTPRequestArgs.test.ts @@ -54,29 +54,30 @@ t.test("it works with options", async (t) => { t.test("it wraps host and hostname with square brackets", async (t) => { t.same( - getURL([{ protocol: "http:", host: "::", port: 80 }], "http").href, + getURL([{ protocol: "http:", host: "::", port: 80 }], "http")?.href, new URL("http://[::]:80").href ); t.same( - getURL([{ protocol: "http:", hostname: "::", port: 80 }], "http").href, + getURL([{ protocol: "http:", hostname: "::", port: 80 }], "http")?.href, new URL("http://[::]:80").href ); t.same( - getURL([new URL("http://domain.com"), { hostname: "::" }], "http").href, + getURL([new URL("http://domain.com"), { hostname: "::" }], "http")?.href, new URL("http://[::]:80").href ); t.same( - getURL([new URL("http://domain.com"), { host: "::" }], "http").href, + getURL([new URL("http://domain.com"), { host: "::" }], "http")?.href, new URL("http://[::]:80").href ); t.same( - getURL([new URL("http://[::]")], "http").href, + getURL([new URL("http://[::]")], "http")?.href, new URL("http://[::]:80").href ); }); t.test("it does not throw on invalid arguments", async (t) => { t.same(getURL([], "http"), undefined); + // @ts-expect-error Test t.same(getURL(["%test%"], undefined), undefined); t.same(new Date(), []); }); diff --git a/library/sources/Express.test.ts b/library/sources/Express.test.ts index 62b64a21b..174dd3bac 100644 --- a/library/sources/Express.test.ts +++ b/library/sources/Express.test.ts @@ -215,9 +215,16 @@ function getApp(userMiddleware = true) { res.send({ hello: "world" }); }); - app.use((error, req, res, next) => { - res.status(500).send({ error: error.message }); - }); + app.use( + ( + error: Error, + req: express.Request, + res: express.Response, + next: Function + ) => { + res.status(500).send({ error: error.message }); + } + ); return app; } diff --git a/library/sources/FunctionsFramework.test.ts b/library/sources/FunctionsFramework.test.ts index 4b901d439..4be97a52b 100644 --- a/library/sources/FunctionsFramework.test.ts +++ b/library/sources/FunctionsFramework.test.ts @@ -50,7 +50,9 @@ function getExpressApp() { asyncHandler( createCloudFunctionWrapper((req, res) => { const context = getContext(); - updateContext(context, "attackDetected", true); + if (context) { + updateContext(context, "attackDetected", true); + } res.send(context); }) ) @@ -172,7 +174,8 @@ t.test("it hooks into functions framework", async () => { agent.start([new FunctionsFramework()]); setInstance(agent); - const framework = require("@google-cloud/functions-framework"); + const framework = + require("@google-cloud/functions-framework") as typeof import("@google-cloud/functions-framework"); framework.http("hello", (req, res) => { res.send("Hello, Functions Framework!"); }); diff --git a/library/sources/Hono.test.ts b/library/sources/Hono.test.ts index 2ed0c5b2c..c67c69eb2 100644 --- a/library/sources/Hono.test.ts +++ b/library/sources/Hono.test.ts @@ -41,7 +41,7 @@ agent.start([new HonoInternal(), new HTTPServer()]); setInstance(agent); function getApp() { - const { Hono } = require("hono"); + const { Hono } = require("hono") as typeof import("hono"); const app = new Hono(); app.all("/", (c) => { diff --git a/library/sources/Lambda.test.ts b/library/sources/Lambda.test.ts index c5d5f9d90..263216bd6 100644 --- a/library/sources/Lambda.test.ts +++ b/library/sources/Lambda.test.ts @@ -103,7 +103,10 @@ t.test("callback handler has internal error", async () => { try { await handler(gatewayEvent, lambdaContext, () => {}); } catch (error) { - t.same(error.message, "error"); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same(error.message, "error"); + } } }); @@ -430,7 +433,9 @@ t.test("it counts attacks", async () => { const handler = createLambdaWrapper(async (event, context) => { const ctx = getContext(); - updateContext(ctx, "attackDetected", true); + if (ctx) { + updateContext(ctx, "attackDetected", true); + } return ctx; }); diff --git a/library/sources/http-server/ipAllowedToAccessRoute.test.ts b/library/sources/http-server/ipAllowedToAccessRoute.test.ts index 1e08630dd..ca42b5c91 100644 --- a/library/sources/http-server/ipAllowedToAccessRoute.test.ts +++ b/library/sources/http-server/ipAllowedToAccessRoute.test.ts @@ -33,6 +33,7 @@ t.beforeEach(async () => { endpoints: [ { route: "/posts/:id", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: ["1.2.3.4"], @@ -98,6 +99,7 @@ t.test("it allows request if configuration is broken", async () => { endpoints: [ { route: "/posts/:id", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", // @ts-expect-error We're testing a broken configuration @@ -134,6 +136,7 @@ t.test("it allows request if allowed IP addresses is empty", async () => { endpoints: [ { route: "/posts/:id", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: [], @@ -176,6 +179,7 @@ t.test("it checks every matching endpoint", async () => { endpoints: [ { route: "/posts/:id", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: ["3.4.5.6"], @@ -183,6 +187,7 @@ t.test("it checks every matching endpoint", async () => { }, { route: "/posts/*", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: ["1.2.3.4"], @@ -220,6 +225,7 @@ t.test( endpoints: [ { route: "/posts/:id", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: [], @@ -227,6 +233,7 @@ t.test( }, { route: "/posts/*", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", // @ts-expect-error We're testing a broken configuration @@ -235,6 +242,7 @@ t.test( }, { route: "/posts/*", + // @ts-expect-error Test rateLimiting: undefined, method: "POST", allowedIPAddresses: ["1.2.3.4"], diff --git a/library/tsconfig.tests.json b/library/tsconfig.build.json similarity index 66% rename from library/tsconfig.tests.json rename to library/tsconfig.build.json index 846e66c41..e9cda6dfe 100644 --- a/library/tsconfig.tests.json +++ b/library/tsconfig.build.json @@ -4,11 +4,12 @@ "lib": ["ES2019", "DOM"], "module": "commonjs", "moduleResolution": "node", + "outDir": "../build", "declaration": true, "strict": true, "allowJs": false, - "skipLibCheck": true, - "noEmit": true + "skipLibCheck": true }, - "include": ["**/*.ts"] + "include": ["**/*.ts"], + "exclude": ["**/*.test.ts"] } diff --git a/library/tsconfig.json b/library/tsconfig.json index e9cda6dfe..846e66c41 100644 --- a/library/tsconfig.json +++ b/library/tsconfig.json @@ -4,12 +4,11 @@ "lib": ["ES2019", "DOM"], "module": "commonjs", "moduleResolution": "node", - "outDir": "../build", "declaration": true, "strict": true, "allowJs": false, - "skipLibCheck": true + "skipLibCheck": true, + "noEmit": true }, - "include": ["**/*.ts"], - "exclude": ["**/*.test.ts"] + "include": ["**/*.ts"] } From 27153411f90757ffd1e5df725d2847d3880cb2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Fri, 18 Oct 2024 10:58:56 +0200 Subject: [PATCH 5/6] Fix package.json --- library/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/package.json b/library/package.json index b9ef82e61..8ebd4a11a 100644 --- a/library/package.json +++ b/library/package.json @@ -93,6 +93,6 @@ "build:watch": "tsc --watch", "lint": "npm run lint-eslint && npm run lint-tsc", "lint-eslint": "eslint '**/*.ts'", - "lint-tsc": "tsc -p tsconfig.tests.json" + "lint-tsc": "tsc" } } From 233bdcf2d98b4dc3eb4e821fe0cb78ba31795b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Fri, 18 Oct 2024 11:25:44 +0200 Subject: [PATCH 6/6] Extend base tsconfig --- library/tsconfig.build.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/library/tsconfig.build.json b/library/tsconfig.build.json index e9cda6dfe..cbe747874 100644 --- a/library/tsconfig.build.json +++ b/library/tsconfig.build.json @@ -1,14 +1,8 @@ { + "extends": "./tsconfig.json", "compilerOptions": { - "target": "ES2019", - "lib": ["ES2019", "DOM"], - "module": "commonjs", - "moduleResolution": "node", "outDir": "../build", - "declaration": true, - "strict": true, - "allowJs": false, - "skipLibCheck": true + "noEmit": false }, "include": ["**/*.ts"], "exclude": ["**/*.test.ts"]