From 98ec5c8eed44f89b220023fec155b516d61c83ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Tue, 11 Feb 2025 12:23:31 +0100 Subject: [PATCH 1/4] Add support for libsql --- library/agent/protect.ts | 2 + library/package-lock.json | 146 ++++++++++++++++++++++++++++++++ library/package.json | 1 + library/sinks/LibSQL.test.ts | 159 +++++++++++++++++++++++++++++++++++ library/sinks/LibSQL.ts | 60 +++++++++++++ 5 files changed, 368 insertions(+) create mode 100644 library/sinks/LibSQL.test.ts create mode 100644 library/sinks/LibSQL.ts diff --git a/library/agent/protect.ts b/library/agent/protect.ts index 92a853023..a9728b1a3 100644 --- a/library/agent/protect.ts +++ b/library/agent/protect.ts @@ -49,6 +49,7 @@ import { Koa } from "../sources/Koa"; import { ClickHouse } from "../sinks/ClickHouse"; import { Prisma } from "../sinks/Prisma"; import { Function } from "../sinks/Function"; +import { LibSQL } from "../sinks/LibSQL"; function getLogger(): Logger { if (isDebugging()) { @@ -140,6 +141,7 @@ export function getWrappers() { new ClickHouse(), new Prisma(), new Function(), + new LibSQL(), ]; } diff --git a/library/package-lock.json b/library/package-lock.json index 53a63adb7..fcd813965 100644 --- a/library/package-lock.json +++ b/library/package-lock.json @@ -58,6 +58,7 @@ "hono": "^4.4.2", "koa": "^2.15.3", "koa-router": "^12.0.1", + "libsql": "^0.4.7", "mariadb": "^3.3.2", "mongodb": "~6.9", "mongodb-v4": "npm:mongodb@^4.0.0", @@ -2231,6 +2232,104 @@ "node": ">= 18" } }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz", + "integrity": "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz", + "integrity": "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz", + "integrity": "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz", + "integrity": "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz", + "integrity": "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz", + "integrity": "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz", + "integrity": "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", @@ -2241,6 +2340,13 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "dev": true, + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11112,6 +11218,46 @@ "node": ">= 0.8.0" } }, + "node_modules/libsql": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.7.tgz", + "integrity": "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==", + "cpu": [ + "x64", + "arm64", + "wasm32" + ], + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.4.7", + "@libsql/darwin-x64": "0.4.7", + "@libsql/linux-arm64-gnu": "0.4.7", + "@libsql/linux-arm64-musl": "0.4.7", + "@libsql/linux-x64-gnu": "0.4.7", + "@libsql/linux-x64-musl": "0.4.7", + "@libsql/win32-x64-msvc": "0.4.7" + } + }, + "node_modules/libsql/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/light-my-request": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.3.0.tgz", diff --git a/library/package.json b/library/package.json index 1d4caf72b..4dd9b3ca9 100644 --- a/library/package.json +++ b/library/package.json @@ -91,6 +91,7 @@ "hono": "^4.4.2", "koa": "^2.15.3", "koa-router": "^12.0.1", + "libsql": "^0.4.7", "mariadb": "^3.3.2", "mongodb": "~6.9", "mongodb-v4": "npm:mongodb@^4.0.0", diff --git a/library/sinks/LibSQL.test.ts b/library/sinks/LibSQL.test.ts new file mode 100644 index 000000000..3bd175f64 --- /dev/null +++ b/library/sinks/LibSQL.test.ts @@ -0,0 +1,159 @@ +import * as t from "tap"; +import { runWithContext, type Context } from "../agent/Context"; +import { LibSQL } from "./LibSQL"; +import { createTestAgent } from "../helpers/createTestAgent"; + +const dangerousContext: Context = { + remoteAddress: "::1", + method: "POST", + url: "http://localhost:4000", + query: {}, + headers: {}, + body: { + myTitle: `-- should be blocked`, + }, + cookies: {}, + routeParams: {}, + source: "express", + route: "/posts/:id", +}; + +const safeContext: Context = { + remoteAddress: "::1", + method: "POST", + url: "http://localhost:4000/", + query: {}, + headers: {}, + body: {}, + cookies: {}, + routeParams: {}, + source: "express", + route: "/posts/:id", +}; + +t.test("it detects SQL injections", async (t) => { + const agent = createTestAgent(); + agent.start([new LibSQL()]); + + const Database = require("libsql") as typeof import("libsql"); + const db = new Database(":memory:"); + + try { + db.exec("CREATE TABLE IF NOT EXISTS cats (petname varchar(255));"); + db.exec("DELETE FROM cats;"); + const rows = db.prepare("SELECT petname FROM `cats`;").all(); + t.same(rows, []); + + runWithContext(dangerousContext, () => { + const error = t.throws(() => db.exec("SELECT 1;-- should be blocked")); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: libsql.exec(...) originating from body.myTitle" + ); + } + + const error2 = t.throws(() => + db.prepare("SELECT 1;-- should be blocked") + ); + t.ok(error2 instanceof Error); + if (error2 instanceof Error) { + t.same( + error2.message, + "Zen has blocked an SQL injection: libsql.prepare(...) originating from body.myTitle" + ); + } + + db.transaction(() => { + const error = t.throws(() => db.exec("SELECT 1;-- should be blocked")); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: libsql.exec(...) originating from body.myTitle" + ); + } + }); + }); + + await runWithContext(safeContext, async () => { + db.exec("SELECT 1;-- This is a comment"); + + // Invalid parameters are still passed to lib + // @ts-expect-error we are testing invalid parameters + const error = t.throws(() => db.exec([])); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same(error.message, "failed to downcast any to string"); + } + }); + } catch (error: any) { + t.fail(error); + } finally { + await db.close(); + } +}); + +t.test("it detects SQL injections using promises", async (t) => { + const agent = createTestAgent(); + agent.start([new LibSQL()]); + + const Database = require("libsql/promise"); + const db = new Database(":memory:"); + + try { + await db.exec("CREATE TABLE IF NOT EXISTS cats (petname varchar(255));"); + await db.exec("DELETE FROM cats;"); + const rows = await (await db.prepare("SELECT petname FROM `cats`;")).all(); + t.same(rows, []); + + runWithContext(dangerousContext, async () => { + const error = t.throws(() => db.exec("SELECT 1;-- should be blocked")); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: libsql.exec(...) originating from body.myTitle" + ); + } + + const error2 = t.throws(() => + db.prepare("SELECT 1;-- should be blocked") + ); + t.ok(error2 instanceof Error); + if (error2 instanceof Error) { + t.same( + error2.message, + "Zen has blocked an SQL injection: libsql.prepare(...) originating from body.myTitle" + ); + } + + await db.transaction(async () => { + const error = t.throws(() => db.exec("SELECT 1;-- should be blocked")); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: libsql.exec(...) originating from body.myTitle" + ); + } + }); + }); + + await runWithContext(safeContext, async () => { + db.exec("SELECT 1;-- This is a comment"); + + // Invalid parameters are still passed to lib + const error = t.throws(() => db.exec([])); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same(error.message, "failed to downcast any to string"); + } + }); + } catch (error: any) { + t.fail(error); + } finally { + await db.close(); + } +}); diff --git a/library/sinks/LibSQL.ts b/library/sinks/LibSQL.ts new file mode 100644 index 000000000..f3d54a6b8 --- /dev/null +++ b/library/sinks/LibSQL.ts @@ -0,0 +1,60 @@ +import { getContext } from "../agent/Context"; +import { Hooks } from "../agent/hooks/Hooks"; +import { InterceptorResult } from "../agent/hooks/InterceptorResult"; +import { wrapExport } from "../agent/hooks/wrapExport"; +import { Wrapper } from "../agent/Wrapper"; +import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/checkContextForSqlInjection"; +import { SQLDialect } from "../vulnerabilities/sql-injection/dialects/SQLDialect"; +import { SQLDialectSQLite } from "../vulnerabilities/sql-injection/dialects/SQLDialectSQLite"; + +export class LibSQL implements Wrapper { + private readonly dialect: SQLDialect = new SQLDialectSQLite(); + + private inspectQuery(operation: string, args: unknown[]): InterceptorResult { + const context = getContext(); + if (!context) { + return undefined; + } + + if (args.length > 0) { + if (typeof args[0] === "string" && args[0].length > 0) { + const sql = args[0]; + + return checkContextForSqlInjection({ + operation: operation, + sql: sql, + context: context, + dialect: this.dialect, + }); + } + } + + return undefined; + } + + wrap(hooks: Hooks) { + const sqlFunctions = ["prepare", "exec", "pragma"]; + + hooks + .addPackage("libsql") + .withVersion("^0.4.0") + .onRequire((exports, pkgInfo) => { + for (const func of sqlFunctions) { + wrapExport(exports.prototype, func, pkgInfo, { + inspectArgs: (args) => { + return this.inspectQuery(`libsql.${func}`, args); + }, + }); + } + }) + .onFileRequire("./promise.js", (exports, pkgInfo) => { + for (const func of sqlFunctions) { + wrapExport(exports.prototype, func, pkgInfo, { + inspectArgs: (args) => { + return this.inspectQuery(`libsql.${func}`, args); + }, + }); + } + }); + } +} From eade31df35d035d5fb915f31851e537aba08c616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Tue, 11 Feb 2025 15:52:09 +0100 Subject: [PATCH 2/4] Prepare @libsql/client support --- .github/workflows/unit-test.yml | 4 + library/agent/protect.ts | 2 + library/package-lock.json | 179 +++++++++++ library/package.json | 1 + library/sinks/LibSQL.test.ts | 9 +- library/sinks/LibSQLClient.test.ts | 279 ++++++++++++++++++ library/sinks/LibSQLClient.ts | 96 ++++++ .../checkContextForSqlInjection.ts | 2 +- sample-apps/docker-compose.yml | 7 + 9 files changed, 572 insertions(+), 7 deletions(-) create mode 100644 library/sinks/LibSQLClient.test.ts create mode 100644 library/sinks/LibSQLClient.ts diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 338214949..4ebae1d3f 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -61,6 +61,10 @@ jobs: MONGODB_REPLICA_SET_KEY: replicasetkey123 ports: - "27020:27017" + libsql: + image: ghcr.io/tursodatabase/libsql-server:v0.24.30 + ports: + - "27021:8080" strategy: fail-fast: false matrix: diff --git a/library/agent/protect.ts b/library/agent/protect.ts index a9728b1a3..af3901355 100644 --- a/library/agent/protect.ts +++ b/library/agent/protect.ts @@ -50,6 +50,7 @@ import { ClickHouse } from "../sinks/ClickHouse"; import { Prisma } from "../sinks/Prisma"; import { Function } from "../sinks/Function"; import { LibSQL } from "../sinks/LibSQL"; +import { LibSQLClient } from "../sinks/LibSQLClient"; function getLogger(): Logger { if (isDebugging()) { @@ -142,6 +143,7 @@ export function getWrappers() { new Prisma(), new Function(), new LibSQL(), + new LibSQLClient(), ]; } diff --git a/library/package-lock.json b/library/package-lock.json index fcd813965..af5b3ae1d 100644 --- a/library/package-lock.json +++ b/library/package-lock.json @@ -18,6 +18,7 @@ "@hono/node-server": "^1.12.2", "@koa/bodyparser": "^5.1.1", "@koa/router": "^13.0.0", + "@libsql/client": "^0.14.0", "@prisma/client": "^5.22.0", "@sinonjs/fake-timers": "^11.2.2", "@types/aws-lambda": "^8.10.131", @@ -2232,6 +2233,30 @@ "node": ">= 18" } }, + "node_modules/@libsql/client": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", + "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.14.0", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.4.4", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", + "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, "node_modules/@libsql/darwin-arm64": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz", @@ -2260,6 +2285,59 @@ "darwin" ] }, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/hrana-client/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, "node_modules/@libsql/linux-arm64-gnu": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz", @@ -5453,6 +5531,16 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/xml2js": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", @@ -7105,6 +7193,16 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -8965,6 +9063,30 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9197,6 +9319,19 @@ "node": ">= 0.6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", @@ -10870,6 +11005,13 @@ "node": ">= 0.6.0" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12377,6 +12519,26 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -13919,6 +14081,13 @@ "dev": true, "license": "ISC" }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "dev": true, + "license": "ISC" + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -16994,6 +17163,16 @@ "dev": true, "license": "ISC" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/library/package.json b/library/package.json index 4dd9b3ca9..a319ccf17 100644 --- a/library/package.json +++ b/library/package.json @@ -51,6 +51,7 @@ "@hono/node-server": "^1.12.2", "@koa/bodyparser": "^5.1.1", "@koa/router": "^13.0.0", + "@libsql/client": "^0.14.0", "@prisma/client": "^5.22.0", "@sinonjs/fake-timers": "^11.2.2", "@types/aws-lambda": "^8.10.131", diff --git a/library/sinks/LibSQL.test.ts b/library/sinks/LibSQL.test.ts index 3bd175f64..2fe2d9bc0 100644 --- a/library/sinks/LibSQL.test.ts +++ b/library/sinks/LibSQL.test.ts @@ -31,10 +31,10 @@ const safeContext: Context = { route: "/posts/:id", }; -t.test("it detects SQL injections", async (t) => { - const agent = createTestAgent(); - agent.start([new LibSQL()]); +const agent = createTestAgent(); +agent.start([new LibSQL()]); +t.test("it detects SQL injections", async (t) => { const Database = require("libsql") as typeof import("libsql"); const db = new Database(":memory:"); @@ -96,9 +96,6 @@ t.test("it detects SQL injections", async (t) => { }); t.test("it detects SQL injections using promises", async (t) => { - const agent = createTestAgent(); - agent.start([new LibSQL()]); - const Database = require("libsql/promise"); const db = new Database(":memory:"); diff --git a/library/sinks/LibSQLClient.test.ts b/library/sinks/LibSQLClient.test.ts new file mode 100644 index 000000000..f89cd09c6 --- /dev/null +++ b/library/sinks/LibSQLClient.test.ts @@ -0,0 +1,279 @@ +import * as t from "tap"; +import { runWithContext, type Context } from "../agent/Context"; +import { LibSQLClient } from "./LibSQLClient"; +import { createTestAgent } from "../helpers/createTestAgent"; + +const dangerousContext: Context = { + remoteAddress: "::1", + method: "POST", + url: "http://localhost:4000", + query: {}, + headers: {}, + body: { + myTitle: `-- should be blocked`, + }, + cookies: {}, + routeParams: {}, + source: "express", + route: "/posts/:id", +}; + +const safeContext: Context = { + remoteAddress: "::1", + method: "POST", + url: "http://localhost:4000/", + query: {}, + headers: {}, + body: {}, + cookies: {}, + routeParams: {}, + source: "express", + route: "/posts/:id", +}; + +const agent = createTestAgent(); +agent.start([new LibSQLClient()]); + +t.test("it works with @libsql/client: in-memory", async (t) => { + const { createClient } = + require("@libsql/client") as typeof import("@libsql/client"); + + const client = createClient({ + url: ":memory:", + }); + + try { + await client.execute( + "CREATE TABLE IF NOT EXISTS cats (petname varchar(255));" + ); + await client.executeMultiple("DELETE FROM cats;"); + t.match(await client.execute("SELECT petname FROM `cats`;"), { + columns: ["petname"], + columnTypes: ["varchar(255)"], + rows: [], + rowsAffected: 0, + }); + + await runWithContext(dangerousContext, async () => { + const error = t.throws(() => + client.execute("SELECT 1;-- should be blocked") + ); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: @libsql/client.execute(...) originating from body.myTitle" + ); + } + + const error2 = t.throws(() => + client.executeMultiple("SELECT 1;-- should be blocked") + ); + t.ok(error2 instanceof Error); + if (error2 instanceof Error) { + t.same( + error2.message, + "Zen has blocked an SQL injection: @libsql/client.executeMultiple(...) originating from body.myTitle" + ); + } + + const error3 = t.throws(() => + client.execute({ + sql: "SELECT 1;-- should be blocked", + args: [], + }) + ); + t.ok(error3 instanceof Error); + if (error3 instanceof Error) { + t.same( + error3.message, + "Zen has blocked an SQL injection: @libsql/client.execute(...) originating from body.myTitle" + ); + } + + const error4 = t.throws(() => + client.batch([ + { + sql: "SELECT 1;-- should be blocked", + args: [], + }, + { + sql: "SELECT 1+1;", + args: [], + }, + ]) + ); + t.ok(error4 instanceof Error); + if (error4 instanceof Error) { + t.same( + error4.message, + "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" + ); + } + + const error5 = t.throws(() => + client.batch([ + { + sql: "SELECT 1+1;", + args: [], + }, + { + sql: "SELECT 1;-- should be blocked", + args: [], + }, + ]) + ); + t.ok(error5 instanceof Error); + if (error5 instanceof Error) { + t.same( + error5.message, + "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" + ); + } + + const error6 = t.throws(() => + client.batch([ + { + sql: "SELECT 1+1;", + args: [], + }, + "SELECT 1;-- should be blocked", + ]) + ); + t.ok(error6 instanceof Error); + if (error6 instanceof Error) { + t.same( + error6.message, + "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" + ); + } + + client.batch([]); + + // @ts-expect-error Test with invalid parameters + const error7 = await t.rejects(() => client.batch([false])); + t.ok(error7 instanceof Error); + if (error7 instanceof Error) { + t.same(error7.message, "failed to downcast any to string"); + } + + // @ts-expect-error Test with invalid parameters + const error8 = await t.rejects(() => client.batch(null)); + t.ok(error8 instanceof Error); + if (error8 instanceof Error) { + t.same( + error8.message, + "Cannot read properties of null (reading 'map')" + ); + } + }); + } catch (error: any) { + t.fail(error); + } finally { + client.close(); + } +}); + +t.test("it works with @libsql/client: http", async (t) => { + const { createClient } = + require("@libsql/client") as typeof import("@libsql/client"); + + const client = createClient({ + url: "http://127.0.0.1:27021", + }); + + try { + await client.execute( + "CREATE TABLE IF NOT EXISTS cats (petname varchar(255));" + ); + await client.executeMultiple("DELETE FROM cats;"); + t.match(await client.execute("SELECT petname FROM `cats`;"), { + columns: ["petname"], + columnTypes: ["varchar(255)"], + rows: [], + rowsAffected: 0, + }); + + await runWithContext(dangerousContext, async () => { + const error = t.throws(() => + client.execute("SELECT 1;-- should be blocked") + ); + t.ok(error instanceof Error); + if (error instanceof Error) { + t.same( + error.message, + "Zen has blocked an SQL injection: @libsql/client.execute(...) originating from body.myTitle" + ); + } + + const error2 = t.throws(() => + client.executeMultiple("SELECT 1;-- should be blocked") + ); + t.ok(error2 instanceof Error); + if (error2 instanceof Error) { + t.same( + error2.message, + "Zen has blocked an SQL injection: @libsql/client.executeMultiple(...) originating from body.myTitle" + ); + } + + const error3 = t.throws(() => + client.execute({ + sql: "SELECT 1;-- should be blocked", + args: [], + }) + ); + t.ok(error3 instanceof Error); + if (error3 instanceof Error) { + t.same( + error3.message, + "Zen has blocked an SQL injection: @libsql/client.execute(...) originating from body.myTitle" + ); + } + + const error4 = t.throws(() => + client.batch([ + { + sql: "SELECT 1;-- should be blocked", + args: [], + }, + { + sql: "SELECT 1+1;", + args: [], + }, + ]) + ); + t.ok(error4 instanceof Error); + if (error4 instanceof Error) { + t.same( + error4.message, + "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" + ); + } + + const error5 = t.throws(() => + client.batch([ + { + sql: "SELECT 1+1;", + args: [], + }, + { + sql: "SELECT 1;-- should be blocked", + args: [], + }, + ]) + ); + t.ok(error5 instanceof Error); + if (error5 instanceof Error) { + t.same( + error5.message, + "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" + ); + } + }); + } catch (error: any) { + t.fail(error); + } finally { + client.close(); + } +}); diff --git a/library/sinks/LibSQLClient.ts b/library/sinks/LibSQLClient.ts new file mode 100644 index 000000000..4c07ea643 --- /dev/null +++ b/library/sinks/LibSQLClient.ts @@ -0,0 +1,96 @@ +import { getContext } from "../agent/Context"; +import { Hooks } from "../agent/hooks/Hooks"; +import { InterceptorResult } from "../agent/hooks/InterceptorResult"; +import { wrapExport } from "../agent/hooks/wrapExport"; +import { Wrapper } from "../agent/Wrapper"; +import { isPlainObject } from "../helpers/isPlainObject"; +import { checkContextForSqlInjection } from "../vulnerabilities/sql-injection/checkContextForSqlInjection"; +import { SQLDialect } from "../vulnerabilities/sql-injection/dialects/SQLDialect"; +import { SQLDialectSQLite } from "../vulnerabilities/sql-injection/dialects/SQLDialectSQLite"; + +export class LibSQLClient implements Wrapper { + private readonly dialect: SQLDialect = new SQLDialectSQLite(); + + private inspectQuery(operation: string, args: unknown[]): InterceptorResult { + const context = getContext(); + if (!context || args.length === 0) { + return undefined; + } + + const sqlStatements = this.extractSQLStatements(args); + + for (const sql of sqlStatements) { + const result = checkContextForSqlInjection({ + sql, + context, + operation: operation, + dialect: this.dialect, + }); + + if (result) { + return result; + } + } + + return undefined; + } + + private extractSQLStatements(args: unknown[]): string[] { + // Argument is a string + if (typeof args[0] === "string" && args[0].length > 0) { + return [args[0] as string]; + } + + // Argument is an object with a sql property that is a string + if ( + isPlainObject(args[0]) && + !Array.isArray(args[0]) && + typeof args[0].sql === "string" + ) { + return [args[0].sql]; + } + + // Argument is an array of strings or objects with a sql property + if (Array.isArray(args[0])) { + return args[0].flatMap((arg) => { + if (typeof arg === "string") { + return [arg]; + } + + if (isPlainObject(arg) && typeof arg.sql === "string") { + return [arg.sql]; + } + + return []; + }); + } + + return []; + } + + wrap(hooks: Hooks) { + const sqlFunctions = ["execute", "executeMultiple", "batch", "migrate"]; + + // Todo transactions + + hooks + .addPackage("@libsql/client") + .withVersion("^0.14.0") + .onRequire((exports, pkgInfo) => { + // Modify the return value of createClient function -> the client object + wrapExport(exports, "createClient", pkgInfo, { + modifyReturnValue: (args, returnValue, agent) => { + // Wrap all SQL functions + for (const func of sqlFunctions) { + wrapExport(returnValue, func, pkgInfo, { + inspectArgs: (args) => { + return this.inspectQuery(`@libsql/client.${func}`, args); + }, + }); + } + return returnValue; + }, + }); + }); + } +} diff --git a/library/vulnerabilities/sql-injection/checkContextForSqlInjection.ts b/library/vulnerabilities/sql-injection/checkContextForSqlInjection.ts index b5542c930..946b6151a 100644 --- a/library/vulnerabilities/sql-injection/checkContextForSqlInjection.ts +++ b/library/vulnerabilities/sql-injection/checkContextForSqlInjection.ts @@ -20,7 +20,7 @@ export function checkContextForSqlInjection({ operation: string; context: Context; dialect: SQLDialect; -}): InterceptorResult { +}): InterceptorResult | undefined { for (const source of SOURCES) { const userInput = extractStringsFromUserInputCached(context, source); if (!userInput) { diff --git a/sample-apps/docker-compose.yml b/sample-apps/docker-compose.yml index 3dd1e1121..077bba1b0 100644 --- a/sample-apps/docker-compose.yml +++ b/sample-apps/docker-compose.yml @@ -74,6 +74,13 @@ services: context: ../end2end/server ports: - "5874:3000" + libsql: + image: ghcr.io/tursodatabase/libsql-server:v0.24.30 + platform: linux/amd64 + ports: + - "27021:8080" + volumes: + - libsql:/var/lib/sqld volumes: mongodb: From 192dfb531f5c3690e6c9e4e796bb85346699d960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Tue, 11 Feb 2025 16:43:14 +0100 Subject: [PATCH 3/4] Add transaction support, update readme --- README.md | 2 + library/sinks/LibSQLClient.test.ts | 62 +++++++++++++++++++++++++++++- library/sinks/LibSQLClient.ts | 27 +++++++++++-- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 43043e4d4..022f4f0ef 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ Zen for Node.js 16+ is compatible with: * ✅ [`postgres`](https://www.npmjs.com/package/postgres) 3.x * ✅ [`@clickhouse/client`](https://www.npmjs.com/package/@clickhouse/client) 1.x * ✅ [`@prisma/client`](https://www.npmjs.com/package/@prisma/client) 5.x +* ✅ [`@libsql/client`](https://www.npmjs.com/package/@libsql/client) ^0.10.x +* ✅ [`libsql`](https://www.npmjs.com/package/libsql) ^0.4.x ### Cloud providers diff --git a/library/sinks/LibSQLClient.test.ts b/library/sinks/LibSQLClient.test.ts index f89cd09c6..df64d59b0 100644 --- a/library/sinks/LibSQLClient.test.ts +++ b/library/sinks/LibSQLClient.test.ts @@ -31,10 +31,12 @@ const safeContext: Context = { route: "/posts/:id", }; +const testOpts = { skip: !global.Request ? "fetch is not available" : false }; + const agent = createTestAgent(); agent.start([new LibSQLClient()]); -t.test("it works with @libsql/client: in-memory", async (t) => { +t.test("it works with @libsql/client: in-memory", testOpts, async (t) => { const { createClient } = require("@libsql/client") as typeof import("@libsql/client"); @@ -166,6 +168,36 @@ t.test("it works with @libsql/client: in-memory", async (t) => { "Cannot read properties of null (reading 'map')" ); } + + const transaction = await client.transaction("write"); + + const error9 = await t.rejects(() => + transaction.execute("SELECT 1;-- should be blocked") + ); + t.ok(error9 instanceof Error); + if (error9 instanceof Error) { + t.same( + error9.message, + "Zen has blocked an SQL injection: @libsql/client.transaction.execute(...) originating from body.myTitle" + ); + } + + const error10 = await t.rejects(() => + transaction.batch(["SELECT 1;-- should be blocked"]) + ); + t.ok(error10 instanceof Error); + if (error10 instanceof Error) { + t.same( + error10.message, + "Zen has blocked an SQL injection: @libsql/client.transaction.batch(...) originating from body.myTitle" + ); + } + + await transaction.commit(); + }); + + await runWithContext(safeContext, async () => { + await client.execute("SELECT 1;-- This is a comment"); }); } catch (error: any) { t.fail(error); @@ -174,7 +206,7 @@ t.test("it works with @libsql/client: in-memory", async (t) => { } }); -t.test("it works with @libsql/client: http", async (t) => { +t.test("it works with @libsql/client: http", testOpts, async (t) => { const { createClient } = require("@libsql/client") as typeof import("@libsql/client"); @@ -270,6 +302,32 @@ t.test("it works with @libsql/client: http", async (t) => { "Zen has blocked an SQL injection: @libsql/client.batch(...) originating from body.myTitle" ); } + + const transaction = await client.transaction("write"); + + const error9 = await t.rejects(() => + transaction.execute("SELECT 1;-- should be blocked") + ); + t.ok(error9 instanceof Error); + if (error9 instanceof Error) { + t.same( + error9.message, + "Zen has blocked an SQL injection: @libsql/client.transaction.execute(...) originating from body.myTitle" + ); + } + + const error10 = await t.rejects(() => + transaction.batch(["SELECT 1;-- should be blocked"]) + ); + t.ok(error10 instanceof Error); + if (error10 instanceof Error) { + t.same( + error10.message, + "Zen has blocked an SQL injection: @libsql/client.transaction.batch(...) originating from body.myTitle" + ); + } + + await transaction.commit(); }); } catch (error: any) { t.fail(error); diff --git a/library/sinks/LibSQLClient.ts b/library/sinks/LibSQLClient.ts index 4c07ea643..bf0587775 100644 --- a/library/sinks/LibSQLClient.ts +++ b/library/sinks/LibSQLClient.ts @@ -70,12 +70,11 @@ export class LibSQLClient implements Wrapper { wrap(hooks: Hooks) { const sqlFunctions = ["execute", "executeMultiple", "batch", "migrate"]; - - // Todo transactions + const transactionSqlFunctions = ["execute", "executeMultiple", "batch"]; hooks .addPackage("@libsql/client") - .withVersion("^0.14.0") + .withVersion("^0.10.0") .onRequire((exports, pkgInfo) => { // Modify the return value of createClient function -> the client object wrapExport(exports, "createClient", pkgInfo, { @@ -88,6 +87,28 @@ export class LibSQLClient implements Wrapper { }, }); } + + // Wrap transaction functions + wrapExport(returnValue, "transaction", pkgInfo, { + modifyReturnValue: async (args, returnValue, agent) => { + // Await the promise + const transaction = await returnValue; + + // Wrap all functions in the transaction object + for (const transactionFunc of transactionSqlFunctions) { + wrapExport(transaction, transactionFunc, pkgInfo, { + inspectArgs: (args) => { + return this.inspectQuery( + `@libsql/client.transaction.${transactionFunc}`, + args + ); + }, + }); + } + + return transaction; + }, + }); return returnValue; }, }); From 8201144fef6396b1b87e197328c5b3fb7473fa34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6ssler?= Date: Tue, 11 Feb 2025 17:21:01 +0100 Subject: [PATCH 4/4] Add sample app and e2e test --- end2end/tests/hapi-libsql.test.js | 109 ++++ sample-apps/hapi-libsql/Cats.js | 29 + sample-apps/hapi-libsql/README.md | 11 + sample-apps/hapi-libsql/app.js | 92 +++ sample-apps/hapi-libsql/package-lock.json | 689 ++++++++++++++++++++++ sample-apps/hapi-libsql/package.json | 12 + 6 files changed, 942 insertions(+) create mode 100644 end2end/tests/hapi-libsql.test.js create mode 100644 sample-apps/hapi-libsql/Cats.js create mode 100644 sample-apps/hapi-libsql/README.md create mode 100644 sample-apps/hapi-libsql/app.js create mode 100644 sample-apps/hapi-libsql/package-lock.json create mode 100644 sample-apps/hapi-libsql/package.json diff --git a/end2end/tests/hapi-libsql.test.js b/end2end/tests/hapi-libsql.test.js new file mode 100644 index 000000000..f44f505e9 --- /dev/null +++ b/end2end/tests/hapi-libsql.test.js @@ -0,0 +1,109 @@ +const t = require("tap"); +const { spawn } = require("child_process"); +const { resolve } = require("path"); +const timeout = require("../timeout"); + +const pathToApp = resolve(__dirname, "../../sample-apps/hapi-libsql", "app.js"); + +t.test("it blocks in blocking mode", (t) => { + const server = spawn(`node`, [pathToApp, "4000"], { + env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" }, + }); + + server.on("close", () => { + t.end(); + }); + + server.on("error", (err) => { + t.fail(err.message); + }); + + let stdout = ""; + server.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + let stderr = ""; + server.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + // Wait for the server to start + timeout(2000) + .then(() => { + return Promise.all([ + fetch( + `http://127.0.0.1:4000/?petname=${encodeURIComponent("Njuska'); DELETE FROM cats;-- H")}`, + { + signal: AbortSignal.timeout(5000), + } + ), + fetch("http://127.0.0.1:4000/?petname=Njuska", { + signal: AbortSignal.timeout(5000), + }), + ]); + }) + .then(async ([noSQLInjection, normalSearch]) => { + t.equal(noSQLInjection.status, 500); + t.equal(normalSearch.status, 200); + t.match(stdout, /Starting agent/); + t.match(await noSQLInjection.text(), /Zen has blocked an SQL injection/); + }) + .catch((error) => { + t.fail(error.message); + }) + .finally(() => { + server.kill(); + }); +}); + +t.test("it does not block in dry mode", (t) => { + const server = spawn(`node`, [pathToApp, "4001"], { + env: { ...process.env, AIKIDO_DEBUG: "true" }, + }); + + server.on("close", () => { + t.end(); + }); + + let stdout = ""; + server.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + let stderr = ""; + server.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + // Wait for the server to start + timeout(2000) + .then(() => + Promise.all([ + fetch( + `http://127.0.0.1:4001/?petname=${encodeURIComponent("Njuska'); DELETE FROM cats;-- H")}`, + { + signal: AbortSignal.timeout(5000), + } + ), + fetch("http://127.0.0.1:4001/?petname=Njuska", { + signal: AbortSignal.timeout(5000), + }), + ]) + ) + .then(async ([noSQLInjection, normalSearch]) => { + t.equal(noSQLInjection.status, 200); + t.equal(normalSearch.status, 200); + t.match(stdout, /Starting agent/); + t.notMatch( + await noSQLInjection.text(), + /Zen has blocked an SQL injection/ + ); + }) + .catch((error) => { + t.fail(error.message); + }) + .finally(() => { + server.kill(); + }); +}); diff --git a/sample-apps/hapi-libsql/Cats.js b/sample-apps/hapi-libsql/Cats.js new file mode 100644 index 000000000..eae7f9ca2 --- /dev/null +++ b/sample-apps/hapi-libsql/Cats.js @@ -0,0 +1,29 @@ +class Cats { + /** + * + * @param {import("@libsql/client").Client} db + */ + constructor(db) { + this.db = db; + } + + async add(name) { + return await this.db.executeMultiple( + `INSERT INTO cats(petname) VALUES ('${name}');` + ); + } + + async byName(name) { + const cats = await this.db.executeMultiple( + `SELECT petname FROM cats WHERE petname = '${name}';` + ); + return cats.rows.map((row) => row.petname); + } + + async getAll() { + const cats = await this.db.execute("SELECT petname FROM cats;"); + return cats.rows.map((row) => row.petname); + } +} + +module.exports = Cats; diff --git a/sample-apps/hapi-libsql/README.md b/sample-apps/hapi-libsql/README.md new file mode 100644 index 000000000..040ca74f3 --- /dev/null +++ b/sample-apps/hapi-libsql/README.md @@ -0,0 +1,11 @@ +# hapi-libsql + +WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). + +In the root directory run `npm run sample-app hapi-libsql` to start the server. + +Try the following URLs: + +- http://localhost:4000/ : List all cats +- http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" +- http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats diff --git a/sample-apps/hapi-libsql/app.js b/sample-apps/hapi-libsql/app.js new file mode 100644 index 000000000..a00a1cf34 --- /dev/null +++ b/sample-apps/hapi-libsql/app.js @@ -0,0 +1,92 @@ +const Zen = require("@aikidosec/firewall"); + +const Cats = require("./Cats"); +const Hapi = require("@hapi/hapi"); +const { createClient } = require("@libsql/client"); + +require("@aikidosec/firewall/nopp"); + +function getHTMLBody(cats) { + return ` + + +

All cats : ${cats.join(", ")}

+
+ + + +
+ Test injection / Clear table + +`; +} + +async function createConnection() { + /** @type {import("@libsql/client".Client)} */ + const client = createClient({ + url: ":memory:", + }); + + await client.execute(` + CREATE TABLE IF NOT EXISTS cats ( + petname varchar(255) + ); + `); + + return client; +} + +async function init(port) { + const db = await createConnection(); + const cats = new Cats(db); + + const server = new Hapi.Server({ + port: port, + host: "127.0.0.1", + }); + + Zen.addHapiMiddleware(server); + + server.route({ + method: "GET", + path: "/", + handler: async (request, h) => { + try { + if (request.query.petname) { + await cats.add(request.query.petname); + } + } catch (e) { + return h.response(e.message).code(500); + } + + return getHTMLBody(await cats.getAll()); + }, + }); + + server.route([ + { + method: "GET", + path: "/clear", + handler: async (request, h) => { + await db.query("DELETE FROM cats;"); + return h.redirect("/"); + }, + }, + ]); + + await server.start(); + console.log(`Server running on http://127.0.0.1:${port}`); +} + +function getPort() { + const port = parseInt(process.argv[2], 10) || 4000; + + if (isNaN(port)) { + console.error("Invalid port"); + process.exit(1); + } + + return port; +} + +init(getPort()); diff --git a/sample-apps/hapi-libsql/package-lock.json b/sample-apps/hapi-libsql/package-lock.json new file mode 100644 index 000000000..ba88dee53 --- /dev/null +++ b/sample-apps/hapi-libsql/package-lock.json @@ -0,0 +1,689 @@ +{ + "name": "hapi-libsql", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hapi-libsql", + "version": "1.0.0", + "dependencies": { + "@aikidosec/firewall": "file:../../build", + "@hapi/hapi": "^21.3.12", + "@libsql/client": "^0.14.0" + } + }, + "../../build": { + "name": "@aikidosec/firewall", + "version": "0.0.0", + "license": "AGPL-3.0-or-later", + "engines": { + "node": ">=16" + } + }, + "node_modules/@aikidosec/firewall": { + "resolved": "../../build", + "link": true + }, + "node_modules/@hapi/accept": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", + "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/ammo": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", + "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/b64": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", + "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bounce": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz", + "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/call": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", + "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/catbox": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", + "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/podium": "^5.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/catbox-memory": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz", + "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/content": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.0.tgz", + "integrity": "sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.0" + } + }, + "node_modules/@hapi/cryptiles": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.1.tgz", + "integrity": "sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", + "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hapi": { + "version": "21.3.12", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.3.12.tgz", + "integrity": "sha512-GCUP12dkb3QMjpFl+wEFO73nqKRmsnD5um/QDOn6lj2GjGBrDXPcT194mNARO+PPNXZOR4KmvIpHt/lceUncfg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/accept": "^6.0.3", + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.2", + "@hapi/call": "^9.0.1", + "@hapi/catbox": "^12.1.1", + "@hapi/catbox-memory": "^6.0.2", + "@hapi/heavy": "^8.0.1", + "@hapi/hoek": "^11.0.6", + "@hapi/mimos": "^7.0.1", + "@hapi/podium": "^5.0.1", + "@hapi/shot": "^6.0.1", + "@hapi/somever": "^4.1.1", + "@hapi/statehood": "^8.1.1", + "@hapi/subtext": "^8.1.0", + "@hapi/teamwork": "^6.0.0", + "@hapi/topo": "^6.0.2", + "@hapi/validate": "^2.0.1" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@hapi/heavy": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", + "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/iron": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", + "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/mimos": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", + "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "mime-db": "^1.52.0" + } + }, + "node_modules/@hapi/nigel": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", + "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/vise": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/pez": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.0.tgz", + "integrity": "sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/content": "^6.0.0", + "@hapi/hoek": "^11.0.2", + "@hapi/nigel": "^5.0.1" + } + }, + "node_modules/@hapi/podium": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz", + "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/teamwork": "^6.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/shot": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.1.tgz", + "integrity": "sha512-s5ynMKZXYoDd3dqPw5YTvOR/vjHvMTxc388+0qL0jZZP1+uwXuUD32o9DuuuLsmTlyXCWi02BJl1pBpwRuUrNA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/somever": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", + "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.1.1.tgz", + "integrity": "sha512-YbK7PSVUA59NArAW5Np0tKRoIZ5VNYUicOk7uJmWZF6XyH5gGL+k62w77SIJb0AoAJ0QdGQMCQ/WOGL1S3Ydow==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/iron": "^7.0.1", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/subtext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.0.tgz", + "integrity": "sha512-PyaN4oSMtqPjjVxLny1k0iYg4+fwGusIhaom9B2StinBclHs7v46mIW706Y+Wo21lcgulGyXbQrmT/w4dus6ww==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/content": "^6.0.0", + "@hapi/file": "^3.0.0", + "@hapi/hoek": "^11.0.2", + "@hapi/pez": "^6.1.0", + "@hapi/wreck": "^18.0.1" + } + }, + "node_modules/@hapi/teamwork": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.0.tgz", + "integrity": "sha512-05HumSy3LWfXpmJ9cr6HzwhAavrHkJ1ZRCmNE2qJMihdM5YcWreWPfyN0yKT2ZjCM92au3ZkuodjBxOibxM67A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/vise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", + "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/wreck": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", + "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@libsql/client": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", + "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.14.0", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.4.4", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", + "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz", + "integrity": "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz", + "integrity": "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz", + "integrity": "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz", + "integrity": "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz", + "integrity": "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz", + "integrity": "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz", + "integrity": "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "license": "BSD-3-Clause" + }, + "node_modules/libsql": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.7.tgz", + "integrity": "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==", + "cpu": [ + "x64", + "arm64", + "wasm32" + ], + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.4.7", + "@libsql/darwin-x64": "0.4.7", + "@libsql/linux-arm64-gnu": "0.4.7", + "@libsql/linux-arm64-musl": "0.4.7", + "@libsql/linux-x64-gnu": "0.4.7", + "@libsql/linux-x64-musl": "0.4.7", + "@libsql/win32-x64-msvc": "0.4.7" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/sample-apps/hapi-libsql/package.json b/sample-apps/hapi-libsql/package.json new file mode 100644 index 000000000..06774ccfe --- /dev/null +++ b/sample-apps/hapi-libsql/package.json @@ -0,0 +1,12 @@ +{ + "name": "hapi-libsql", + "version": "1.0.0", + "description": "A vulnerable app to test out SQL Injection with libsql", + "main": "app.js", + "private": true, + "dependencies": { + "@aikidosec/firewall": "file:../../build", + "@hapi/hapi": "^21.3.12", + "@libsql/client": "^0.14.0" + } +}