├── .nvmrc ├── sample-apps ├── n8n │ ├── .gitignore │ └── package.json ├── strapi │ ├── src │ │ ├── admin │ │ │ └── .gitkeep │ │ ├── api │ │ │ └── .gitkeep │ │ ├── extensions │ │ │ └── .gitkeep │ │ └── index.js │ ├── public │ │ └── uploads │ │ │ └── .gitkeep │ ├── database │ │ └── migrations │ │ │ └── .gitkeep │ ├── README.md │ ├── config │ │ ├── plugins.js │ │ ├── api.js │ │ ├── server.js │ │ ├── middlewares.js │ │ └── admin.js │ ├── .gitignore │ ├── jsconfig.json │ └── .env ├── hono-pg-esm │ ├── files │ │ └── test.txt │ ├── sentry.js │ ├── package.json │ └── db.js ├── nextjs-standalone │ ├── docs │ │ ├── doc.txt │ │ └── test.txt │ ├── .gitignore │ ├── jsconfig.json │ ├── next.config.mjs │ ├── README.md │ ├── src │ │ ├── app │ │ │ ├── layout.js │ │ │ └── cats │ │ │ │ └── route.js │ │ ├── db.js │ │ └── Cats.js │ └── package.json ├── express-bedrock │ ├── .gitignore │ ├── .env-example │ └── package.json ├── express-openai │ ├── .gitignore │ ├── .env-example │ └── package.json ├── react-router-pg │ ├── public │ │ └── favicon.ico │ ├── .gitignore │ ├── react-router.config.ts │ ├── app │ │ ├── routes.ts │ │ ├── routes │ │ │ ├── clear-cats.ts │ │ │ └── add-cat.ts │ │ ├── app.css │ │ └── .server │ │ │ └── db.ts │ ├── vite.config.ts │ ├── README.md │ └── tsconfig.json ├── express-mariadb │ ├── .env-example │ ├── package.json │ ├── README.md │ └── Cats.js ├── express-mongodb │ ├── .env-example │ ├── package.json │ └── README.md ├── express-mongoose │ ├── .env-example │ ├── Cat.js │ ├── package.json │ └── README.md ├── express-mysql2 │ ├── .env-example │ ├── package.json │ └── README.md ├── express-postgres │ ├── .env-example │ ├── .gitignore │ ├── esbuild-wrong.js │ ├── esbuild.js │ ├── Cats.js │ ├── package.json │ └── README.md ├── test-exports │ ├── .gitignore │ ├── package.json │ ├── tsconfig.node18.json │ ├── tsconfig.node24.json │ ├── test.js │ └── test.ts ├── express-graphql │ ├── .env-example │ ├── README.md │ └── package.json ├── restify-postgres │ ├── .env-example │ ├── .gitignore │ ├── package.json │ ├── Cats.js │ └── README.md ├── express-path-traversal │ ├── .env-example │ ├── documents │ │ └── .gitignore │ ├── package.json │ ├── Documents.js │ └── README.md ├── hono-prisma │ ├── .gitignore │ ├── prisma │ │ ├── migrations │ │ │ ├── migration_lock.toml │ │ │ └── 20241122094425_init │ │ │ │ └── migration.sql │ │ └── schema.prisma │ ├── README.md │ └── package.json ├── import-middleware-ts │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── hybrid-lambda-express │ ├── .gitignore │ ├── handler.js │ ├── server.js │ ├── package.json │ ├── app.js │ └── Dockerfile ├── express-mysql │ ├── .env-example │ ├── package.json │ ├── README.md │ └── Cats.js ├── react2shell-next │ ├── README.md │ ├── app │ │ ├── globals.css │ │ ├── favicon.ico │ │ ├── page.tsx │ │ └── layout.tsx │ ├── next.config.ts │ ├── .gitignore │ └── package.json ├── hono-mongodb │ ├── README.md │ └── package.json ├── nestjs-sentry │ ├── tsconfig.build.json │ ├── nest-cli.json │ ├── README.md │ ├── src │ │ ├── request.controller.ts │ │ ├── app.module.ts │ │ ├── main.ts │ │ └── cats.controller.ts │ └── tsconfig.json ├── node-red │ └── package.json ├── nestjs-fastify │ ├── tsconfig.build.json │ ├── src │ │ ├── app.service.ts │ │ ├── app.module.ts │ │ ├── app.controller.ts │ │ ├── app.controller.spec.ts │ │ ├── cats.controller.ts │ │ └── user.guard.ts │ ├── nest-cli.json │ ├── README.md │ └── tsconfig.json ├── adonis-sqlite │ ├── tsconfig.json │ ├── .gitignore │ ├── config │ │ ├── cors.ts │ │ ├── database.ts │ │ └── hash.ts │ ├── app │ │ └── middleware │ │ │ ├── force_json_response_middleware.ts │ │ │ └── container_bindings_middleware.ts │ └── database │ │ └── migrations │ │ └── 1760103653456_create_users_table.ts ├── http2 │ ├── package.json │ └── package-lock.json ├── lambda-mark-unsafe │ ├── README.md │ ├── submodule.js │ ├── package.json │ ├── app.js │ ├── index.js │ └── package-lock.json ├── fastify-prehandler-hook │ └── package.json ├── .mongo-replica │ └── Dockerfile ├── hono-sqlite3 │ ├── package.json │ ├── README.md │ └── db.js ├── hono-xml │ ├── README.md │ ├── package.json │ └── db.js ├── hono-sqlite3-esm │ ├── package.json │ ├── db.js │ └── Cats.js ├── lambda-mongodb │ ├── package.json │ ├── serverless.yml │ ├── README.md │ └── payloads │ │ ├── safe-request.json │ │ └── nosql-injection-request.json ├── fastify-clickhouse │ ├── README.md │ └── package.json ├── functions-framework-sqlite3 │ ├── package.json │ └── README.md ├── koa-sqlite3 │ ├── README.md │ ├── package.json │ └── db.js ├── micro │ ├── package.json │ └── index.js ├── pubsub-mongodb │ └── package.json ├── hono-mysql2-new │ ├── package.json │ └── db.js ├── cloud-functions-v1-mongodb │ └── package.json ├── cloud-functions-v2-mongodb │ └── package.json ├── hapi-postgres │ ├── package.json │ ├── Cats.js │ └── README.md ├── fastify-mysql2 │ ├── package.json │ ├── README.md │ └── db.js ├── express-http-https │ └── package.json ├── hono-pg-ts-esm │ ├── package.json │ └── db.ts └── express-postgres-esm │ ├── Cats.js │ └── package.json ├── instrumentation-wasm ├── .gitignore ├── src │ ├── lib.rs │ ├── js_transformer │ │ ├── mod.rs │ │ ├── helpers │ │ │ ├── mod.rs │ │ │ ├── code_str_includes_instrument_funcs.rs │ │ │ ├── get_arg_names.rs │ │ │ └── select_sourcetype_based_on_enum.rs │ │ └── instructions.rs │ └── wasm_bindings │ │ └── mod.rs └── Cargo.toml ├── end2end ├── server │ ├── .dockerignore │ ├── src │ │ ├── types.ts │ │ ├── handlers │ │ │ ├── listEvents.ts │ │ │ ├── getConfig.ts │ │ │ ├── realtimeConfig.ts │ │ │ └── updateConfig.ts │ │ ├── zen │ │ │ └── events.ts │ │ └── middleware │ │ │ └── checkToken.ts │ ├── Dockerfile │ ├── tsconfig.json │ └── package.json ├── tests │ ├── fixtures │ │ └── favicon.png │ ├── import-middleware-ts.test.js │ └── lambda-mark-unsafe.test.js ├── timeout.js ├── tests-new │ ├── utils │ │ ├── timeout.mjs │ │ └── get-port.mjs │ └── fixtures │ │ └── worker-thread.mjs └── package.json ├── library ├── sources │ ├── fixtures │ │ └── public │ │ │ └── test.txt │ ├── GraphQL.v16.test.ts │ ├── Express.v4.test.ts │ ├── Express.v5.test.ts │ ├── GraphQL.v15.test.ts │ ├── KoaRouter.test.ts │ ├── GraphQL.schema.v16.test.ts │ ├── KoaRouter.v14.test.ts │ ├── GraphQL.schema.v15.test.ts │ ├── KoaRouter.v10.test.ts │ ├── KoaRouter.v11.test.ts │ ├── KoaRouter.v12.test.ts │ ├── KoaRouter.v13.test.ts │ ├── FastXmlParser.v4.test.ts │ ├── FastXmlParser.v5.test.ts │ ├── Koa.v3.test.ts │ ├── Restify.v8.test.ts │ ├── Restify.v9.test.ts │ ├── Restify.v10.test.ts │ ├── Restify.v11.test.ts │ ├── xml │ │ ├── addXmlToContext.ts │ │ └── isXmlInContext.ts │ ├── http-server │ │ └── replaceRequestBody.ts │ ├── Koa.v2.test.ts │ ├── graphql │ │ └── extractInputsFromDocument.ts │ ├── koa │ │ └── isDeprecatedGenerator.ts │ ├── hono │ │ ├── wrapRequestHandler.ts │ │ └── getRemoteAddress.ts │ └── hapi │ │ └── wrapRequestHandler.ts ├── sinks │ ├── fixtures │ │ ├── .gitignore │ │ ├── helloWorld.js │ │ └── prisma │ │ │ ├── sqlite │ │ │ ├── migrations │ │ │ │ ├── migration_lock.toml │ │ │ │ └── 20241122110725_init │ │ │ │ │ └── migration.sql │ │ │ └── schema.prisma │ │ │ ├── postgres │ │ │ ├── migrations │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ │ └── mongodb │ │ │ └── schema.prisma │ ├── OpenAI.v4.test.ts │ ├── OpenAI.v5.test.ts │ ├── MySQL2.v3.10.test.ts │ ├── MySQL2.v3.12.test.ts │ ├── Undici.v4.test.ts │ ├── Undici.v5.test.ts │ ├── Undici.v6.test.ts │ ├── Undici.v7.test.ts │ ├── Undici2.v4.test.ts │ ├── Undici2.v5.test.ts │ ├── Undici2.v6.test.ts │ ├── Undici2.v7.test.ts │ ├── AiSDK.v4.test.ts │ ├── MongoDB.v4.test.ts │ ├── MongoDB.v5.test.ts │ ├── MongoDB.v6.test.ts │ ├── AiSDK.v5.test.ts │ ├── http-request │ │ └── isOptionsObject.ts │ └── undici │ │ └── RequestContextStorage.ts ├── lambda │ └── index.ts ├── agent │ ├── logger │ │ ├── Logger.ts │ │ ├── LoggerNoop.ts │ │ ├── LoggerConsole.ts │ │ └── LoggerForTesting.ts │ ├── hooks │ │ ├── instrumentation │ │ │ ├── zenHooksCheckImport.ts │ │ │ ├── checkHooks.ts │ │ │ ├── checkHooks.test.ts │ │ │ └── getPackageVersionFromPath.ts │ │ ├── RequireInterceptor.ts │ │ ├── wrapNewInstance.noAgent.test.ts │ │ ├── Global.ts │ │ ├── wrapDefaultOrNamed.ts │ │ ├── BuiltinModule.ts │ │ ├── wrapExport.noAgent.test.ts │ │ └── isBuiltinModule.ts │ ├── Wrapper.ts │ ├── context │ │ └── ContextStorage.ts │ ├── realtime │ │ ├── getRealtimeURL.ts │ │ └── getConfig.ts │ ├── Source.ts │ ├── safeCreateRegExp.ts │ ├── api-discovery │ │ ├── isPrimitiveType.ts │ │ └── helpers │ │ │ ├── isUri.ts │ │ │ ├── isUUIDString.ts │ │ │ ├── isDateString.ts │ │ │ ├── isDateString.test.ts │ │ │ └── isUUIDString.test.ts │ ├── api │ │ ├── Token.ts │ │ ├── ReportingAPIThatThrows.ts │ │ ├── ReportingAPI.ts │ │ ├── FetchListsAPIForTesting.ts │ │ └── Token.test.ts │ ├── AikidoDAST.ts │ ├── isNewHookSystemUsed.ts │ ├── extractRegionFromToken.ts │ ├── getAPIURL.ts │ ├── AgentSingleton.ts │ ├── exports.test.ts │ └── Attack.ts ├── bundler │ ├── index.ts │ ├── externals.test.ts │ └── externals.ts ├── instrument │ └── internals.ts ├── helpers │ ├── isWindows.ts │ ├── isLocalhostIP.ts │ ├── isAikidoCI.ts │ ├── isEsmUnitTest.ts │ ├── getLibraryRoot.ts │ ├── isUnitTest.ts │ ├── isPackageInstalled.ts │ ├── isDebugging.ts │ ├── isNewInstrumentationUnitTest.ts │ ├── getAgentVersion.test.ts │ ├── removeNodePrefix.ts │ ├── getCurrentAndNextSegments.ts │ ├── package-json.test.ts │ ├── tryParseURLPath.ts │ ├── limitLengthMetadata.ts │ ├── trustProxy.ts │ ├── envToBool.ts │ ├── tryParseURLParams.ts │ ├── getMaxBodySize.ts │ ├── getPackageVersion.test.ts │ ├── getPortFromURL.ts │ ├── getMaxApiDiscoverySamples.ts │ ├── getNodeVersion.ts │ ├── getFileExtension.ts │ ├── isAikidoCI.test.ts │ ├── tryParseURL.test.ts │ ├── isJsonContentType.ts │ ├── isLibBundled.ts │ ├── shouldBlock.ts │ ├── ip-matcher │ │ ├── parse.test.ts │ │ └── Network.test.ts │ ├── isRedirectStatusCode.ts │ ├── isPackageInstalled.test.ts │ ├── limitLengthMetadata.test.ts │ ├── escapeLog.test.ts │ ├── cleanError.ts │ ├── escapeLog.ts │ ├── getAgentVersion.ts │ ├── getPortFromURL.test.ts │ ├── trustProxy.test.ts │ ├── mapIPv4ToIPv6.ts │ ├── featureFlags.ts │ ├── getFileExtension.test.ts │ ├── getSourceForUserString.ts │ ├── indexImportGuard.ts │ ├── getPackageVersion.ts │ ├── featureFlags.test.ts │ ├── isHTTPAuthScheme.ts │ ├── getNodeVersion.test.ts │ ├── mapIPv4ToIPv6.test.ts │ ├── isESM.ts │ └── tryParseURL.ts ├── cloud-function │ └── index.ts ├── context │ └── index.ts ├── internals │ └── .gitignore ├── nopp │ └── index.ts ├── vulnerabilities │ ├── sql-injection │ │ ├── dialects │ │ │ ├── SQLDialect.ts │ │ │ ├── SQLDialectMySQL.ts │ │ │ ├── SQLDialectSQLite.ts │ │ │ ├── SQLDialectGeneric.ts │ │ │ ├── SQLDialectPostgres.ts │ │ │ └── SQLDialectClickHouse.ts │ │ └── payloads │ │ │ ├── README.md │ │ │ ├── mysql.txt │ │ │ └── postgres.txt │ ├── attack-wave-detection │ │ ├── isWebScanMethod.ts │ │ └── isWebScanner.ts │ └── ssrf │ │ └── getMetadataForSSRFAttack.ts ├── tsconfig.build.json ├── browser.ts ├── tsconfig.test.esm.json └── tsconfig.json ├── .gitattributes ├── .prettierignore ├── .aikido ├── .prettierrc ├── benchmarks ├── operations │ ├── fetch.js │ ├── path-traversal.js │ ├── undici.js │ ├── spawn-server.js │ ├── package.json │ ├── shelli.js │ ├── http-request.js │ ├── jsinjection.js │ ├── nosqli.js │ └── sqli.js ├── nosql-injection │ ├── getUser.js │ ├── getClient.js │ ├── package.json │ ├── withoutGuard.js │ ├── measure.js │ └── withGuard.js ├── api-discovery │ ├── package.json │ ├── package-lock.json │ └── cookies.js ├── sql-injection │ ├── package.json │ └── package-lock.json ├── shell-injection │ ├── package.json │ └── package-lock.json ├── hono-pg │ └── package.json └── express │ ├── package.json │ └── app.js ├── .editorconfig ├── docs ├── body-size.md └── proxy.md ├── .github ├── ISSUE_TEMPLATE │ └── feature_request.md ├── SECURITY.md └── workflows │ └── main.yml ├── .gitignore └── scripts ├── copyPackageJSON.js └── helpers └── asyncPool.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 24.3.0 2 | -------------------------------------------------------------------------------- /sample-apps/n8n/.gitignore: -------------------------------------------------------------------------------- 1 | .n8n -------------------------------------------------------------------------------- /sample-apps/strapi/src/admin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/strapi/src/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /instrumentation-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /sample-apps/hono-pg-esm/files/test.txt: -------------------------------------------------------------------------------- 1 | Hi -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/docs/doc.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/docs/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/strapi/public/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/strapi/src/extensions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /end2end/server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /library/sources/fixtures/public/test.txt: -------------------------------------------------------------------------------- 1 | Testfile -------------------------------------------------------------------------------- /sample-apps/express-bedrock/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /sample-apps/express-openai/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/.gitignore: -------------------------------------------------------------------------------- 1 | .next/ -------------------------------------------------------------------------------- /sample-apps/react-router-pg/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/strapi/database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-apps/strapi/README.md: -------------------------------------------------------------------------------- 1 | # Strapi sample app 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | sample-apps/* linguist-documentation 2 | -------------------------------------------------------------------------------- /sample-apps/express-mariadb/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" -------------------------------------------------------------------------------- /sample-apps/express-mongodb/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" -------------------------------------------------------------------------------- /sample-apps/express-mongoose/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" -------------------------------------------------------------------------------- /sample-apps/express-mysql2/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" -------------------------------------------------------------------------------- /sample-apps/express-postgres/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" -------------------------------------------------------------------------------- /sample-apps/test-exports/.gitignore: -------------------------------------------------------------------------------- 1 | /dist18/* 2 | /dist24/* 3 | -------------------------------------------------------------------------------- /sample-apps/express-graphql/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" 2 | -------------------------------------------------------------------------------- /sample-apps/restify-postgres/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" 2 | -------------------------------------------------------------------------------- /sample-apps/restify-postgres/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /library/sinks/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.db 3 | *.db-journal -------------------------------------------------------------------------------- /sample-apps/express-path-traversal/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" 2 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.db 3 | *.db-journal -------------------------------------------------------------------------------- /sample-apps/import-middleware-ts/README.md: -------------------------------------------------------------------------------- 1 | # import-middleware-ts 2 | -------------------------------------------------------------------------------- /sample-apps/strapi/config/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({}); 2 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/.gitignore: -------------------------------------------------------------------------------- 1 | /aikidosec-firewall-0.0.0.tgz 2 | -------------------------------------------------------------------------------- /sample-apps/express-openai/.env-example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=your_openai_api_key_here 2 | -------------------------------------------------------------------------------- /sample-apps/express-postgres/.gitignore: -------------------------------------------------------------------------------- 1 | /compiled.js 2 | /compiled-bundled.js 3 | -------------------------------------------------------------------------------- /sample-apps/express-mysql/.env-example: -------------------------------------------------------------------------------- 1 | AIKIDO_TOKEN="token" 2 | SENTRY_DSN="dsn" 3 | -------------------------------------------------------------------------------- /library/lambda/index.ts: -------------------------------------------------------------------------------- 1 | import { lambda } from "../agent/protect"; 2 | 3 | export = lambda(); 4 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/README.md: -------------------------------------------------------------------------------- 1 | This is a vulnerable application, do not deploy it. 2 | -------------------------------------------------------------------------------- /sample-apps/strapi/.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .strapi 3 | build 4 | .strapi-updater.json 5 | types 6 | -------------------------------------------------------------------------------- /library/agent/logger/Logger.ts: -------------------------------------------------------------------------------- 1 | export interface Logger { 2 | log(message: string): void; 3 | } 4 | -------------------------------------------------------------------------------- /library/bundler/index.ts: -------------------------------------------------------------------------------- 1 | import { externals } from "./externals"; 2 | 3 | export { externals }; 4 | -------------------------------------------------------------------------------- /library/instrument/internals.ts: -------------------------------------------------------------------------------- 1 | export * from "../agent/hooks/instrumentation/injectedFunctions"; 2 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod js_transformer; 2 | 3 | // Wasm bindings 4 | mod wasm_bindings; 5 | -------------------------------------------------------------------------------- /library/helpers/isWindows.ts: -------------------------------------------------------------------------------- 1 | export function isWindows() { 2 | return process.platform === "win32"; 3 | } 4 | -------------------------------------------------------------------------------- /library/sinks/fixtures/helloWorld.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log("Hello World"); 3 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/app/globals.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /library/cloud-function/index.ts: -------------------------------------------------------------------------------- 1 | import { cloudFunction } from "../agent/protect"; 2 | 3 | export = cloudFunction(); 4 | -------------------------------------------------------------------------------- /library/context/index.ts: -------------------------------------------------------------------------------- 1 | import { setUser } from "../agent/context/user"; 2 | 3 | export = { 4 | setUser, 5 | }; 6 | -------------------------------------------------------------------------------- /end2end/tests/fixtures/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-node/HEAD/end2end/tests/fixtures/favicon.png -------------------------------------------------------------------------------- /library/sinks/OpenAI.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createOpenAITests } from "./OpenAI.tests"; 2 | 3 | createOpenAITests("openai-v4"); 4 | -------------------------------------------------------------------------------- /library/sinks/OpenAI.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createOpenAITests } from "./OpenAI.tests"; 2 | 3 | createOpenAITests("openai-v5"); 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .tap/ 2 | internals/ 3 | wasm/ 4 | .next/ 5 | out/ 6 | build/ 7 | dist/ 8 | coverage/ 9 | .esm-tests/ 10 | -------------------------------------------------------------------------------- /library/agent/hooks/instrumentation/zenHooksCheckImport.ts: -------------------------------------------------------------------------------- 1 | export function test() { 2 | return "//SELF_CHECK_REPLACE"; 3 | } 4 | -------------------------------------------------------------------------------- /library/sources/GraphQL.v16.test.ts: -------------------------------------------------------------------------------- 1 | import { createGraphQLTests } from "./GraphQL.tests"; 2 | 3 | createGraphQLTests("graphql"); 4 | -------------------------------------------------------------------------------- /.aikido: -------------------------------------------------------------------------------- 1 | exclude: 2 | paths: 3 | - benchmarks/ 4 | - end2end/ 5 | - docs/ 6 | - sample-apps/ 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/sinks/MySQL2.v3.10.test.ts: -------------------------------------------------------------------------------- 1 | import { createMySQL2Tests } from "./MySQL2.tests"; 2 | 3 | createMySQL2Tests("mysql2-v3.10"); 4 | -------------------------------------------------------------------------------- /library/sinks/MySQL2.v3.12.test.ts: -------------------------------------------------------------------------------- 1 | import { createMySQL2Tests } from "./MySQL2.tests"; 2 | 3 | createMySQL2Tests("mysql2-v3.12"); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici.tests"; 2 | 3 | createUndiciTests("undici-v4", 4004); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici.tests"; 2 | 3 | createUndiciTests("undici-v5", 4005); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici.v6.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici.tests"; 2 | 3 | createUndiciTests("undici-v6", 4006); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici.v7.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici.tests"; 2 | 3 | createUndiciTests("undici-v7", 4007); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici2.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici2.tests"; 2 | 3 | createUndiciTests("undici-v4", 5004); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici2.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici2.tests"; 2 | 3 | createUndiciTests("undici-v5", 5005); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici2.v6.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici2.tests"; 2 | 3 | createUndiciTests("undici-v6", 5006); 4 | -------------------------------------------------------------------------------- /library/sinks/Undici2.v7.test.ts: -------------------------------------------------------------------------------- 1 | import { createUndiciTests } from "./Undici2.tests"; 2 | 3 | createUndiciTests("undici-v7", 5007); 4 | -------------------------------------------------------------------------------- /library/sources/Express.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createExpressTests } from "./Express.tests"; 2 | 3 | createExpressTests("express-v4"); 4 | -------------------------------------------------------------------------------- /library/sources/Express.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createExpressTests } from "./Express.tests"; 2 | 3 | createExpressTests("express-v5"); 4 | -------------------------------------------------------------------------------- /library/sources/GraphQL.v15.test.ts: -------------------------------------------------------------------------------- 1 | import { createGraphQLTests } from "./GraphQL.tests"; 2 | 3 | createGraphQLTests("graphql-v15"); 4 | -------------------------------------------------------------------------------- /sample-apps/hono-mongodb/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | open http://localhost:3000 8 | ``` 9 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | /node_modules/ 4 | 5 | # React Router 6 | /.react-router/ 7 | /build/ 8 | -------------------------------------------------------------------------------- /end2end/timeout.js: -------------------------------------------------------------------------------- 1 | module.exports = async function timeout(ms) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | }; 4 | -------------------------------------------------------------------------------- /library/agent/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import { Hooks } from "./hooks/Hooks"; 2 | 3 | export interface Wrapper { 4 | wrap(hooks: Hooks): void; 5 | } 6 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("koa-router"); 4 | -------------------------------------------------------------------------------- /sample-apps/express-path-traversal/documents/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false 7 | } 8 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helpers; 2 | pub mod instructions; 3 | pub mod transformer; 4 | mod transformer_impl; 5 | -------------------------------------------------------------------------------- /library/sinks/AiSDK.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createAiSdkTests } from "./AiSDK.tests"; 2 | 3 | createAiSdkTests("ai-v4", "@ai-sdk/google-v1", "zod"); 4 | -------------------------------------------------------------------------------- /library/sinks/MongoDB.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createMongoDBTests } from "./MongoDB.tests"; 2 | 3 | createMongoDBTests("mongodb-v4", "test_v4"); 4 | -------------------------------------------------------------------------------- /library/sinks/MongoDB.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createMongoDBTests } from "./MongoDB.tests"; 2 | 3 | createMongoDBTests("mongodb-v5", "test_v5"); 4 | -------------------------------------------------------------------------------- /library/sinks/MongoDB.v6.test.ts: -------------------------------------------------------------------------------- 1 | import { createMongoDBTests } from "./MongoDB.tests"; 2 | 3 | createMongoDBTests("mongodb-v6", "test_v6"); 4 | -------------------------------------------------------------------------------- /library/sources/GraphQL.schema.v16.test.ts: -------------------------------------------------------------------------------- 1 | import { createGraphQLTests } from "./GraphQL.schema.tests"; 2 | 3 | createGraphQLTests("graphql"); 4 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.v14.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("@koa/router"); 4 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-node/HEAD/sample-apps/react2shell-next/app/favicon.ico -------------------------------------------------------------------------------- /benchmarks/operations/fetch.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | step: async function step() { 3 | await fetch("http://localhost:10411"); 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /library/internals/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory# Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /library/sinks/AiSDK.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createAiSdkTests } from "./AiSDK.tests"; 2 | 3 | createAiSdkTests("ai-v5", "@ai-sdk/google-v2", "zod/v4"); 4 | -------------------------------------------------------------------------------- /library/sources/GraphQL.schema.v15.test.ts: -------------------------------------------------------------------------------- 1 | import { createGraphQLTests } from "./GraphQL.schema.tests"; 2 | 3 | createGraphQLTests("graphql-v15"); 4 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.v10.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("@koa/router-v10"); 4 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.v11.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("@koa/router-v11"); 4 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.v12.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("@koa/router-v12"); 4 | -------------------------------------------------------------------------------- /library/sources/KoaRouter.v13.test.ts: -------------------------------------------------------------------------------- 1 | import { createKoaRouterTests } from "./KoaRouter.tests"; 2 | 3 | createKoaRouterTests("@koa/router-v13"); 4 | -------------------------------------------------------------------------------- /library/helpers/isLocalhostIP.ts: -------------------------------------------------------------------------------- 1 | export function isLocalhostIP(ip: string) { 2 | return ["127.0.0.1", "::ffff:127.0.0.1", "::1"].includes(ip); 3 | } 4 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample-apps/node-red/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@aikidosec/firewall": "file:../../build", 4 | "node-red": "^4.0.5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /end2end/tests-new/utils/timeout.mjs: -------------------------------------------------------------------------------- 1 | import { setTimeout } from "timers/promises"; 2 | 3 | export function timeout(ms) { 4 | return setTimeout(ms); 5 | } 6 | -------------------------------------------------------------------------------- /sample-apps/express-bedrock/.env-example: -------------------------------------------------------------------------------- 1 | AWS_ACCESS_KEY_ID=your_aws_access_key_here 2 | AWS_SECRET_ACCESS_KEY=your_aws_secret_key_here 3 | AWS_REGION=us-east-1 -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample-apps/strapi/config/api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rest: { 3 | defaultLimit: 25, 4 | maxLimit: 100, 5 | withCount: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /library/sources/FastXmlParser.v4.test.ts: -------------------------------------------------------------------------------- 1 | import { createFastXmlParserTests } from "./FastXmlParser.tests"; 2 | 3 | createFastXmlParserTests("fast-xml-parser-v4"); 4 | -------------------------------------------------------------------------------- /library/sources/FastXmlParser.v5.test.ts: -------------------------------------------------------------------------------- 1 | import { createFastXmlParserTests } from "./FastXmlParser.tests"; 2 | 3 | createFastXmlParserTests("fast-xml-parser-v5"); 4 | -------------------------------------------------------------------------------- /library/nopp/index.ts: -------------------------------------------------------------------------------- 1 | import { preventPrototypePollution } from "../vulnerabilities/prototype-pollution/preventPrototypePollution"; 2 | 3 | preventPrototypePollution(); 4 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialect.ts: -------------------------------------------------------------------------------- 1 | export interface SQLDialect { 2 | getWASMDialectInt(): number; 3 | getHumanReadableName(): string; 4 | } 5 | -------------------------------------------------------------------------------- /library/helpers/isAikidoCI.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | export function isAikidoCI(): boolean { 4 | return envToBool(process.env.AIKIDO_CI); 5 | } 6 | -------------------------------------------------------------------------------- /sample-apps/n8n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@aikidosec/firewall": "file:../../build", 4 | "n8n": "^1.88.0", 5 | "sqlite3": "^5.1.7" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /library/helpers/isEsmUnitTest.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | export function isEsmUnitTest(): boolean { 4 | return envToBool(process.env.AIKIDO_ESM_TEST); 5 | } 6 | -------------------------------------------------------------------------------- /sample-apps/import-middleware-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": false, 4 | "esModuleInterop": true 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: "standalone", 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /end2end/server/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { App } from "./zen/apps.ts"; 2 | import type { Request } from "express"; 3 | 4 | export interface ZenRequest extends Request { 5 | zenApp?: App; 6 | } 7 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/sqlite/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@adonisjs/tsconfig/tsconfig.app.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample-apps/http2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http2", 3 | "main": "index.js", 4 | "private": true, 5 | "dependencies": { 6 | "@aikidosec/firewall": "file:../../build" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/strapi/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "nodenext", 4 | "target": "ES2021", 5 | "checkJs": true, 6 | "allowJs": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/postgres/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /sample-apps/hono-pg-esm/sentry.js: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/node"; 2 | 3 | // Ensure to call this before importing any other modules! 4 | Sentry.init({ 5 | tracesSampleRate: 0.0, 6 | }); 7 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/README.md: -------------------------------------------------------------------------------- 1 | # hono-prisma 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | -------------------------------------------------------------------------------- /library/agent/context/ContextStorage.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "async_hooks"; 2 | import { Context } from "../Context"; 3 | 4 | export const ContextStorage = new AsyncLocalStorage(); 5 | -------------------------------------------------------------------------------- /library/helpers/getLibraryRoot.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | 3 | const libraryRoot = resolve(__dirname, ".."); 4 | 5 | export function getLibraryRoot(): string { 6 | return libraryRoot; 7 | } 8 | -------------------------------------------------------------------------------- /library/helpers/isUnitTest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the current environment is a unit test using tap. 3 | */ 4 | export function isUnitTest() { 5 | return process.env.AIKIDO_UNIT_TESTS === "1"; 6 | } 7 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/payloads/README.md: -------------------------------------------------------------------------------- 1 | These files originate from [https://github.com/payloadbox/sql-injection-payload-list](https://github.com/payloadbox/sql-injection-payload-list). 2 | -------------------------------------------------------------------------------- /benchmarks/operations/path-traversal.js: -------------------------------------------------------------------------------- 1 | const { readFile } = require("fs/promises"); 2 | 3 | module.exports = { 4 | step: async function step() { 5 | await readFile("./package.json"); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /benchmarks/operations/undici.js: -------------------------------------------------------------------------------- 1 | const undici = require("undici"); 2 | 3 | module.exports = { 4 | step: async function step() { 5 | return await undici.request("http://localhost:10411"); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /library/agent/hooks/RequireInterceptor.ts: -------------------------------------------------------------------------------- 1 | import { WrapPackageInfo } from "./WrapPackageInfo"; 2 | 3 | export type RequireInterceptor = ( 4 | exports: any, 5 | pkgInfo: WrapPackageInfo 6 | ) => unknown; 7 | -------------------------------------------------------------------------------- /library/helpers/isPackageInstalled.ts: -------------------------------------------------------------------------------- 1 | export function isPackageInstalled(name: string): boolean { 2 | try { 3 | require.resolve(name); 4 | return true; 5 | } catch { 6 | return false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/README.md: -------------------------------------------------------------------------------- 1 | # lambda-mark-unsafe 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/README.md: -------------------------------------------------------------------------------- 1 | # Next.js Standalone 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | -------------------------------------------------------------------------------- /sample-apps/fastify-prehandler-hook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-fastify-prehandler-hook", 3 | "dependencies": { 4 | "@aikidosec/firewall": "file:../../build", 5 | "fastify": "^4.28.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return "Hello World!"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/hono-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@aikidosec/firewall": "file:../../build", 4 | "@hono/node-server": "^1.11.2", 5 | "hono": "^4.4.2", 6 | "mongodb": "^6.11.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/submodule.js: -------------------------------------------------------------------------------- 1 | const { markUnsafe } = require("@aikidosec/firewall"); 2 | 3 | function someFunction() { 4 | markUnsafe("abc"); 5 | } 6 | 7 | module.exports = { 8 | someFunction, 9 | }; 10 | -------------------------------------------------------------------------------- /library/helpers/isDebugging.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | /** 4 | * Checks if AIKIDO_DEBUG is set to true or 1 5 | */ 6 | export function isDebugging() { 7 | return envToBool(process.env.AIKIDO_DEBUG); 8 | } 9 | -------------------------------------------------------------------------------- /library/helpers/isNewInstrumentationUnitTest.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | export function isNewInstrumentationUnitTest(): boolean { 4 | return envToBool(process.env.AIKIDO_TEST_NEW_INSTRUMENTATION); 5 | } 6 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/getUser.js: -------------------------------------------------------------------------------- 1 | module.exports = async function getUser(client, { email, password }) { 2 | return await client.db("bench").collection("users").findOne({ 3 | email: email, 4 | password: password, 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /library/agent/logger/LoggerNoop.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { Logger } from "./Logger"; 3 | 4 | export class LoggerNoop implements Logger { 5 | log(message: string) { 6 | // noop 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /library/helpers/getAgentVersion.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getAgentVersion } from "./getAgentVersion"; 3 | 4 | t.test("it returns the current library version", async () => { 5 | t.equal(getAgentVersion(), "0.0.0"); 6 | }); 7 | -------------------------------------------------------------------------------- /library/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../build", 5 | "noEmit": false 6 | }, 7 | "include": ["**/*.ts"], 8 | "exclude": ["**/*.test.ts", "**/*.tests.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /sample-apps/.mongo-replica/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mongo:7 2 | 3 | RUN echo "password" > /keyfile \ 4 | && chmod 600 /keyfile \ 5 | && chown 999 /keyfile \ 6 | && chgrp 999 /keyfile 7 | 8 | CMD ["--bind_ip_all", "--keyFile", "/keyfile", "--replSet", "rs0"] -------------------------------------------------------------------------------- /library/agent/realtime/getRealtimeURL.ts: -------------------------------------------------------------------------------- 1 | export function getRealtimeURL() { 2 | if (process.env.AIKIDO_REALTIME_ENDPOINT) { 3 | return new URL(process.env.AIKIDO_REALTIME_ENDPOINT); 4 | } 5 | 6 | return new URL("https://runtime.aikido.dev"); 7 | } 8 | -------------------------------------------------------------------------------- /sample-apps/express-postgres/esbuild-wrong.js: -------------------------------------------------------------------------------- 1 | const { build } = require("esbuild"); 2 | 3 | build({ 4 | entryPoints: ["./app.js"], 5 | bundle: true, 6 | platform: "node", 7 | target: "node18", 8 | outfile: "./compiled-bundled.js", 9 | }); 10 | -------------------------------------------------------------------------------- /benchmarks/operations/spawn-server.js: -------------------------------------------------------------------------------- 1 | const { createServer } = require("http"); 2 | 3 | const server = createServer((req, res) => { 4 | res.writeHead(200, { "Content-Type": "text/plain" }); 5 | res.end("Hello Node.js!"); 6 | }); 7 | 8 | server.listen(10411); 9 | -------------------------------------------------------------------------------- /library/agent/logger/LoggerConsole.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "./Logger"; 2 | 3 | export class LoggerConsole implements Logger { 4 | log(message: string) { 5 | // eslint-disable-next-line no-console 6 | console.log(`AIKIDO: ${message}`); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-sqlite3", 3 | "dependencies": { 4 | "@aikidosec/firewall": "file:../../build", 5 | "@hono/node-server": "^1.11.2", 6 | "hono": "^4.4.2", 7 | "sqlite3": "^5.1.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /library/helpers/removeNodePrefix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes the "node:" prefix from the id of a builtin module. 3 | */ 4 | export function removeNodePrefix(id: string) { 5 | if (id.startsWith("node:")) { 6 | return id.slice(5); 7 | } 8 | return id; 9 | } 10 | -------------------------------------------------------------------------------- /sample-apps/express-mongoose/Cat.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const CatScheme = new mongoose.Schema({ 4 | name: String, 5 | createdAt: Date, 6 | }); 7 | 8 | const Cat = mongoose.model("Cat", CatScheme); 9 | 10 | module.exports = { Cat }; 11 | -------------------------------------------------------------------------------- /sample-apps/hono-xml/README.md: -------------------------------------------------------------------------------- 1 | # hono-xml 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app hono-xml` to start the server. 6 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/react-router.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@react-router/dev/config"; 2 | 3 | export default { 4 | // Config options... 5 | // Server-side render by default, to enable SPA mode set this to `false` 6 | ssr: true, 7 | } satisfies Config; 8 | -------------------------------------------------------------------------------- /benchmarks/api-discovery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-discovery-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/sql-injection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sql-injection-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-mark-unsafe", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/shell-injection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shell-injection-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /library/browser.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.warn( 3 | "AIKIDO: Zen seems to be running in a browser-like environment. Zen only runs server-side and should not be used in frontend applications. Please ensure Zen is only included in your server-side code." 4 | ); 5 | -------------------------------------------------------------------------------- /end2end/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:24-slim 2 | 3 | USER node 4 | WORKDIR /app 5 | 6 | COPY --chown=node:node package.json package-lock.json ./ 7 | RUN npm ci --ignore-scripts 8 | 9 | COPY --chown=node:node . ./ 10 | RUN node --run check:types 11 | 12 | CMD ["node" , "app.ts"] 13 | -------------------------------------------------------------------------------- /end2end/tests-new/fixtures/worker-thread.mjs: -------------------------------------------------------------------------------- 1 | import { isMainThread, Worker } from "worker_threads"; 2 | 3 | if (isMainThread) { 4 | console.log("Main thread started"); 5 | const worker = new Worker(new URL(import.meta.url)); 6 | } else { 7 | console.log("Worker thread started"); 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-sentry 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app nestjs-sentry` to start the server. 6 | -------------------------------------------------------------------------------- /sample-apps/test-exports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-exports", 3 | "private": true, 4 | "dependencies": { 5 | "@aikidosec/firewall": "file:../../build" 6 | }, 7 | "devDependencies": { 8 | "@types/node": "^22.3.0", 9 | "typescript": "^5.3.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /library/tsconfig.test.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../.esm-tests/library", 5 | "noEmit": false, 6 | "sourceMap": true 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["**/*.test.ts", "**/*.tests.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/express-graphql/README.md: -------------------------------------------------------------------------------- 1 | # express-graphql 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-graphql` to start the server. 6 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-sqlite3-esm", 3 | "type": "module", 4 | "dependencies": { 5 | "@aikidosec/firewall": "file:../../build", 6 | "@hono/node-server": "^1.11.2", 7 | "hono": "^4.4.2", 8 | "sqlite3": "^5.1.7" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-fastify 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app nestjs-fastify` to start the server. 6 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { type RouteConfig, index, route } from "@react-router/dev/routes"; 2 | 3 | export default [ 4 | index("routes/home.tsx"), 5 | route("add-cat", "routes/add-cat.ts"), 6 | route("clear-cats", "routes/clear-cats.ts"), 7 | ] satisfies RouteConfig; 8 | -------------------------------------------------------------------------------- /library/agent/Source.ts: -------------------------------------------------------------------------------- 1 | export const SOURCES = [ 2 | "query", 3 | "body", 4 | "headers", 5 | "cookies", 6 | "routeParams", 7 | "graphql", 8 | "xml", 9 | "subdomains", 10 | "markUnsafe", 11 | "url", 12 | ] as const; 13 | 14 | export type Source = (typeof SOURCES)[number]; 15 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/handler.js: -------------------------------------------------------------------------------- 1 | const protect = require("@aikidosec/firewall/lambda"); 2 | const serverless = require("serverless-http"); 3 | const createApp = require("./app"); 4 | 5 | const app = createApp({ serverless: true }); 6 | 7 | module.exports.handler = protect(serverless(app)); 8 | -------------------------------------------------------------------------------- /sample-apps/lambda-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "mongodb": "^6.3.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /library/bundler/externals.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { externals } from "./externals"; 3 | 4 | t.test("it returns externals", async (t) => { 5 | t.ok(externals().includes("@aikidosec/firewall")); 6 | t.ok(externals().includes("pg")); 7 | t.ok(externals().includes("mysql")); 8 | }); 9 | -------------------------------------------------------------------------------- /sample-apps/fastify-clickhouse/README.md: -------------------------------------------------------------------------------- 1 | # fastify-clickhouse 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app fastify-clickhouse` to start the server. 6 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/server.js: -------------------------------------------------------------------------------- 1 | require("@aikidosec/firewall"); 2 | 3 | const createApp = require("./app"); 4 | 5 | const app = createApp(); 6 | 7 | const port = process.env.PORT || 3000; 8 | app.listen(port, function () { 9 | console.log("Server started on port", port); 10 | }); 11 | -------------------------------------------------------------------------------- /library/helpers/getCurrentAndNextSegments.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentAndNextSegments( 2 | array: T[] 3 | ): { currentSegment: T; nextSegment: T }[] { 4 | return array.slice(0, -1).map((currentItem, index) => ({ 5 | currentSegment: currentItem, 6 | nextSegment: array[index + 1], 7 | })); 8 | } 9 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

Welcome to React2Shell Next.js App

5 |

6 | This is a vulnerable app for testing React2Shell. Never deploy this app. 7 |

8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/getClient.js: -------------------------------------------------------------------------------- 1 | const { MongoClient } = require("mongodb"); 2 | 3 | module.exports = async function getClient() { 4 | const url = "mongodb://root:password@127.0.0.1:27017"; 5 | const client = new MongoClient(url); 6 | await client.connect(); 7 | 8 | return client; 9 | }; 10 | -------------------------------------------------------------------------------- /library/agent/safeCreateRegExp.ts: -------------------------------------------------------------------------------- 1 | export function safeCreateRegExp( 2 | pattern: string, 3 | flags: string 4 | ): RegExp | undefined { 5 | try { 6 | return new RegExp(pattern, flags); 7 | } catch { 8 | // Don't throw errors when the regex is invalid 9 | return undefined; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample-apps/strapi/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env("HOST", "0.0.0.0"), 3 | port: env.int("PORT", 1337), 4 | app: { 5 | keys: env.array("APP_KEYS"), 6 | }, 7 | webhooks: { 8 | populateRelations: env.bool("WEBHOOKS_POPULATE_RELATIONS", false), 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialectMySQL.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./SQLDialect"; 2 | 3 | export class SQLDialectMySQL implements SQLDialect { 4 | getWASMDialectInt(): number { 5 | return 8; 6 | } 7 | getHumanReadableName(): string { 8 | return "MySQL"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialectSQLite.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./SQLDialect"; 2 | 3 | export class SQLDialectSQLite implements SQLDialect { 4 | getWASMDialectInt(): number { 5 | return 12; 6 | } 7 | getHumanReadableName(): string { 8 | return "SQLite"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/hono-xml/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-xml", 3 | "dependencies": { 4 | "@aikidosec/firewall": "file:../../build", 5 | "@hono/node-server": "^1.11.2", 6 | "fast-xml-parser": "^4.4.0", 7 | "hono": "^4.4.2", 8 | "mysql2": "^3.10.0", 9 | "xml2js": "^0.6.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample-apps/strapi/config/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | "strapi::logger", 3 | "strapi::errors", 4 | "strapi::security", 5 | "strapi::cors", 6 | "strapi::poweredBy", 7 | "strapi::query", 8 | "strapi::body", 9 | "strapi::session", 10 | "strapi::favicon", 11 | "strapi::public", 12 | ]; 13 | -------------------------------------------------------------------------------- /end2end/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "nodenext", 5 | "rewriteRelativeImportExtensions": true, 6 | "erasableSyntaxOnly": true, 7 | "verbatimModuleSyntax": true 8 | }, 9 | "include": ["**/*.ts"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialectGeneric.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./SQLDialect"; 2 | 3 | export class SQLDialectGeneric implements SQLDialect { 4 | getWASMDialectInt(): number { 5 | return 0; 6 | } 7 | getHumanReadableName(): string { 8 | return "Generic"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/app/routes/clear-cats.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from "react-router"; 2 | import { getConnection } from "~/.server/db"; 3 | 4 | export async function action() { 5 | const db = await getConnection(); 6 | 7 | await db.query(`DELETE FROM cats_5;`); 8 | 9 | return redirect("/", {}); 10 | } 11 | -------------------------------------------------------------------------------- /library/vulnerabilities/attack-wave-detection/isWebScanMethod.ts: -------------------------------------------------------------------------------- 1 | const methods = new Set([ 2 | "BADMETHOD", 3 | "BADHTTPMETHOD", 4 | "BADDATA", 5 | "BADMTHD", 6 | "BDMTHD", 7 | ]); 8 | 9 | export function isWebScanMethod(method: string): boolean { 10 | return methods.has(method.toUpperCase()); 11 | } 12 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialectPostgres.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./SQLDialect"; 2 | 3 | export class SQLDialectPostgres implements SQLDialect { 4 | getWASMDialectInt(): number { 5 | return 9; 6 | } 7 | getHumanReadableName(): string { 8 | return "PostgreSQL"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-prisma", 3 | "dependencies": { 4 | "@aikidosec/firewall": "file:../../build", 5 | "@hono/node-server": "^1.11.2", 6 | "@prisma/client": "^6.0.1", 7 | "hono": "^4.6.8" 8 | }, 9 | "devDependencies": { 10 | "prisma": "^6.0.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/dialects/SQLDialectClickHouse.ts: -------------------------------------------------------------------------------- 1 | import { SQLDialect } from "./SQLDialect"; 2 | 3 | export class SQLDialectClickHouse implements SQLDialect { 4 | getWASMDialectInt(): number { 5 | return 3; 6 | } 7 | getHumanReadableName(): string { 8 | return "ClickHouse"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/functions-framework-sqlite3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@aikidosec/firewall": "file:../../build", 4 | "@google-cloud/functions-framework": "^4.0.0", 5 | "better-sqlite3": "^11.10.0" 6 | }, 7 | "scripts": { 8 | "start": "functions-framework --target=main --port 4000" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/payloads/mysql.txt: -------------------------------------------------------------------------------- 1 | ' OR 1=1-- 2 | 'OR '' = ' Allows authentication without a valid username. 3 | '-- 4 | ' union select 1, '', '' 1-- 5 | 'OR 1=1-- 6 | create table myfile (input TEXT); load data infile '' into table myfile; select * from myfile; -------------------------------------------------------------------------------- /sample-apps/express-postgres/esbuild.js: -------------------------------------------------------------------------------- 1 | const { build } = require("esbuild"); 2 | const { externals } = require("@aikidosec/firewall/bundler"); 3 | 4 | build({ 5 | entryPoints: ["./app.js"], 6 | bundle: true, 7 | platform: "node", 8 | target: "node18", 9 | outfile: "./compiled.js", 10 | external: externals(), 11 | }); 12 | -------------------------------------------------------------------------------- /sample-apps/koa-sqlite3/README.md: -------------------------------------------------------------------------------- 1 | # koa-sqlite3 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | This example uses an in-memory SQLite database. 6 | 7 | In the root directory run `npm run sample-app koa-sqlite3` to start the server. 8 | -------------------------------------------------------------------------------- /sample-apps/lambda-mongodb/serverless.yml: -------------------------------------------------------------------------------- 1 | service: lambda-mongodb 2 | frameworkVersion: "3" 3 | 4 | useDotenv: true 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs20.x 9 | 10 | functions: 11 | login: 12 | handler: index.handler 13 | events: 14 | - httpApi: 15 | path: / 16 | method: post 17 | -------------------------------------------------------------------------------- /sample-apps/micro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micro", 3 | "main": "index.js", 4 | "private": true, 5 | "dependencies": { 6 | "@aikidosec/firewall": "file:../../build", 7 | "micro": "^10.0.1" 8 | }, 9 | "scripts": { 10 | "start": "NODE_OPTIONS='-r @aikidosec/firewall' AIKIDO_DEBUG=true micro" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { CatsController } from "./cats.controller"; 3 | import { DBService } from "./db.service"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [CatsController], 8 | providers: [DBService], 9 | }) 10 | export class AppModule {} 11 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | output: "standalone", 6 | turbopack: { 7 | root: __dirname, // Prevents issues with multiple package jsons 8 | }, 9 | }; 10 | 11 | export default nextConfig; 12 | -------------------------------------------------------------------------------- /library/helpers/package-json.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | 3 | // @esm-tests-skip - Different directory structure (not inside lib dir) 4 | 5 | const PackageJson = require("../package.json"); 6 | 7 | t.test("Check that no other dependencies are present", async (t) => { 8 | t.equal(PackageJson.dependencies, undefined); 9 | }); 10 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3/README.md: -------------------------------------------------------------------------------- 1 | # hono-sqlite3 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | This example uses an in-memory SQLite database. 6 | 7 | In the root directory run `npm run sample-app hono-sqlite3` to start the server. 8 | -------------------------------------------------------------------------------- /sample-apps/pubsub-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pubsub-mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "mongodb": "^6.3.0", 10 | "@google-cloud/pubsub": "^4.3.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nosql-injection-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "mongodb": "^6.3.0", 10 | "percentile": "^1.6.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /library/helpers/tryParseURLPath.ts: -------------------------------------------------------------------------------- 1 | import { tryParseURL } from "./tryParseURL"; 2 | 3 | export function tryParseURLPath(url: string) { 4 | const parsed = tryParseURL( 5 | url.startsWith("/") ? `http://localhost${url}` : url 6 | ); 7 | 8 | if (!parsed) { 9 | return undefined; 10 | } 11 | 12 | return parsed.pathname; 13 | } 14 | -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/app.js: -------------------------------------------------------------------------------- 1 | const { handler } = require("./index"); 2 | 3 | // mimics the AWS Lambda environment 4 | const event = { 5 | Records: [ 6 | { 7 | body: JSON.stringify({ 8 | message: "Hello from SQS", 9 | }), 10 | }, 11 | ], 12 | }; 13 | const context = {}; 14 | handler(event, context); 15 | -------------------------------------------------------------------------------- /sample-apps/lambda-mongodb/README.md: -------------------------------------------------------------------------------- 1 | # lambda-mongodb 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run: 6 | 7 | - `npm run sample-app lambda-mongodb-safe` 8 | - `npm run sample-app lambda-mongodb-nosql-injection` 9 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { reactRouter } from "@react-router/dev/vite"; 2 | import tailwindcss from "@tailwindcss/vite"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | 6 | export default defineConfig({ 7 | plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], 8 | }); 9 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod code_str_includes_instrument_funcs; 2 | pub mod get_arg_names; 3 | pub mod get_name_str_for_member_expr; 4 | pub mod insert_code; 5 | pub mod insert_import_statement; 6 | pub mod insert_instrument_method_calls; 7 | pub mod select_sourcetype_based_on_enum; 8 | pub mod transform_return_statements; 9 | -------------------------------------------------------------------------------- /library/helpers/limitLengthMetadata.ts: -------------------------------------------------------------------------------- 1 | export function limitLengthMetadata( 2 | metadata: Record, 3 | maxLength: number 4 | ) { 5 | for (const key in metadata) { 6 | if (metadata[key].length > maxLength) { 7 | metadata[key] = metadata[key].substring(0, maxLength); 8 | } 9 | } 10 | 11 | return metadata; 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/hono-pg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-pg-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "@hono/node-server": "^1.12.0", 10 | "hono": "^4.5.1", 11 | "pg": "^8.12.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /library/helpers/trustProxy.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | export function trustProxy() { 4 | if (!process.env.AIKIDO_TRUST_PROXY) { 5 | // Trust proxy by default 6 | // Most of the time, the application is behind a reverse proxy 7 | return true; 8 | } 9 | 10 | return envToBool(process.env.AIKIDO_TRUST_PROXY); 11 | } 12 | -------------------------------------------------------------------------------- /library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "lib": ["ES2019", "DOM"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "strict": true, 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "noEmit": true 12 | }, 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /sample-apps/hono-mysql2-new/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-xml", 3 | "scripts": { 4 | "start": "node --import @aikidosec/firewall/instrument ./app.js" 5 | }, 6 | "dependencies": { 7 | "@aikidosec/firewall": "file:../../build", 8 | "@hono/node-server": "^1.11.2", 9 | "hono": "^4.4.2", 10 | "mysql2": "^3.10.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/operations/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "operations-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "better-sqlite3": "^11.7.2", 10 | "mongodb": "^6.9.0", 11 | "undici": "^7.3.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /library/helpers/envToBool.ts: -------------------------------------------------------------------------------- 1 | const trueValues = ["true", "1", "yes", "y", "on"]; 2 | 3 | /** 4 | * Parses the string value of an environment variable to a boolean. 5 | */ 6 | export function envToBool(envName: string | undefined): boolean { 7 | if (!envName) { 8 | return false; 9 | } 10 | return trueValues.includes(envName.toLowerCase()); 11 | } 12 | -------------------------------------------------------------------------------- /library/helpers/tryParseURLParams.ts: -------------------------------------------------------------------------------- 1 | import { tryParseURL } from "./tryParseURL"; 2 | 3 | export function tryParseURLParams(url: string) { 4 | const parsed = tryParseURL( 5 | url.startsWith("/") ? `http://localhost${url}` : url 6 | ); 7 | 8 | if (!parsed) { 9 | return new URLSearchParams(); 10 | } 11 | 12 | return parsed.searchParams; 13 | } 14 | -------------------------------------------------------------------------------- /library/sources/Koa.v3.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 3 | import { createKoaTests } from "./Koa.tests"; 4 | 5 | t.test( 6 | "Koa", 7 | { skip: getMajorNodeVersion() < 18 ? "Koa v3 requires Node 18" : undefined }, 8 | async (t) => { 9 | createKoaTests("koa-v3"); 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from "@nestjs/common"; 2 | import { AppService } from "./app.service"; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /library/agent/api-discovery/isPrimitiveType.ts: -------------------------------------------------------------------------------- 1 | export function onlyContainsPrimitiveTypes(types: string | string[]): boolean { 2 | if (!Array.isArray(types)) { 3 | return isPrimitiveType(types); 4 | } 5 | return types.every(isPrimitiveType); 6 | } 7 | 8 | function isPrimitiveType(type: string): boolean { 9 | return !["object", "array"].includes(type); 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/cloud-functions-v1-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-functions-v1-mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "mongodb": "^6.3.0", 10 | "@google-cloud/functions-framework": "^3.3.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/cloud-functions-v2-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-functions-v2-mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "mongodb": "^6.3.0", 10 | "@google-cloud/functions-framework": "^3.3.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /end2end/server/src/handlers/listEvents.ts: -------------------------------------------------------------------------------- 1 | import type { Response } from "express"; 2 | import { listEvents as list } from "../zen/events.ts"; 3 | import type { ZenRequest } from "../types.ts"; 4 | 5 | export function listEvents(req: ZenRequest, res: Response) { 6 | if (!req.zenApp) { 7 | throw new Error("App is missing"); 8 | } 9 | res.json(list(req.zenApp)); 10 | } 11 | -------------------------------------------------------------------------------- /sample-apps/hapi-postgres/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-postgres", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with postgres", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "@hapi/hapi": "^21.3.10", 10 | "pg": "^8.11.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/fastify-mysql2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastify-mysql2", 3 | "version": "1.0.0", 4 | "description": "A vulnerable fastify app to test out SQL Injection with mysql", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "fastify": "^4.28.0", 10 | "mysql2": "^3.10.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/functions-framework-sqlite3/README.md: -------------------------------------------------------------------------------- 1 | # functions-framework-sqlite3 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | This example uses an in-memory SQLite database. 6 | 7 | In the root directory run `npm run sample-app functions-framework-sqlite3` to start the server. 8 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hybrid-lambda-express", 3 | "version": "1.0.0", 4 | "description": "Hybrid Express app that works both as AWS Lambda and on-premises server", 5 | "main": "server.js", 6 | "dependencies": { 7 | "@aikidosec/firewall": "latest", 8 | "express": "^4.19.2", 9 | "serverless-http": "^3.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample-apps/lambda-mongodb/payloads/safe-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/", 3 | "path": "/", 4 | "httpMethod": "POST", 5 | "body": "{\"username\":\"hans@aikido.dev\",\"password\":\"password\"}", 6 | "headers": { 7 | "Content-Type": "application/json" 8 | }, 9 | "requestContext": { 10 | "identity": { 11 | "sourceIp": "1.2.3.4" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 2 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /end2end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end2end", 3 | "private": true, 4 | "dependencies": { 5 | "@supercharge/promise-pool": "^3.1.1", 6 | "tap": "^20.0.3" 7 | }, 8 | "scripts": { 9 | "test": "AIKIDO_CI=true tap tests/*.js --allow-empty-coverage -j 1", 10 | "test:new": "node --test --test-concurrency 4 --test-force-exit tests-new/**/*.test.mjs" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /end2end/tests-new/utils/get-port.mjs: -------------------------------------------------------------------------------- 1 | import net from "node:net"; 2 | 3 | export function getRandomPort() { 4 | return new Promise((resolve, reject) => { 5 | const server = net.createServer(); 6 | server.listen(0, () => { 7 | const { port } = server.address(); 8 | server.close(() => resolve(port)); 9 | }); 10 | server.on("error", reject); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /library/agent/logger/LoggerForTesting.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "./Logger"; 2 | 3 | export class LoggerForTesting implements Logger { 4 | private messages: string[] = []; 5 | 6 | log(message: string) { 7 | this.messages.push(message); 8 | } 9 | 10 | getMessages() { 11 | return this.messages; 12 | } 13 | 14 | clear() { 15 | this.messages = []; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /library/helpers/getMaxBodySize.ts: -------------------------------------------------------------------------------- 1 | const MAX_BODY_SIZE_MB = 20; 2 | 3 | export function getMaxBodySize() { 4 | if (process.env.AIKIDO_MAX_BODY_SIZE_MB) { 5 | const parsed = parseInt(process.env.AIKIDO_MAX_BODY_SIZE_MB, 10); 6 | if (!isNaN(parsed) && parsed > 0) { 7 | return parsed * 1024 * 1024; 8 | } 9 | } 10 | 11 | return MAX_BODY_SIZE_MB * 1024 * 1024; 12 | } 13 | -------------------------------------------------------------------------------- /end2end/server/src/zen/events.ts: -------------------------------------------------------------------------------- 1 | import type { App } from "./apps.ts"; 2 | 3 | const events = new Map(); 4 | 5 | export function captureEvent(event: unknown, app: App) { 6 | if (!events.has(app.id)) { 7 | events.set(app.id, []); 8 | } 9 | 10 | events.get(app.id).push(event); 11 | } 12 | 13 | export function listEvents(app: App) { 14 | return events.get(app.id) || []; 15 | } 16 | -------------------------------------------------------------------------------- /benchmarks/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "benchmark.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "@opentelemetry/api": "^1.9.0", 10 | "@opentelemetry/auto-instrumentations-node": "^0.56.1", 11 | "express": "^5.0.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /library/helpers/getPackageVersion.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getPackageVersion } from "./getPackageVersion"; 3 | 4 | t.test("it resolves the version of a package", async (t) => { 5 | t.same(getPackageVersion("express"), "5.1.0"); 6 | t.same(getPackageVersion("non-existing-package"), undefined); 7 | t.same(getPackageVersion("@google-cloud/functions-framework"), "4.0.0"); 8 | }); 9 | -------------------------------------------------------------------------------- /library/helpers/getPortFromURL.ts: -------------------------------------------------------------------------------- 1 | export function getPortFromURL(url: URL): number | undefined { 2 | if (url.port && Number.isInteger(parseInt(url.port, 10))) { 3 | return parseInt(url.port, 10); 4 | } 5 | 6 | switch (url.protocol) { 7 | case "https:": 8 | return 443; 9 | case "http:": 10 | return 80; 11 | default: 12 | return undefined; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample-apps/lambda-mongodb/payloads/nosql-injection-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/", 3 | "path": "/", 4 | "httpMethod": "POST", 5 | "body": "{\"username\":\"hans@aikido.dev\",\"password\":{\"$gt\":\"\"}}", 6 | "headers": { 7 | "Content-Type": "application/json" 8 | }, 9 | "requestContext": { 10 | "identity": { 11 | "sourceIp": "1.2.3.4" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /benchmarks/operations/shelli.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | 3 | module.exports = { 4 | step: async function step() { 5 | return new Promise((resolve, reject) => { 6 | exec("echo", { cwd: __dirname }, (err) => { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(); 11 | } 12 | }); 13 | }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /library/agent/api/Token.ts: -------------------------------------------------------------------------------- 1 | export class Token { 2 | #token: string; 3 | 4 | constructor(token: string) { 5 | if (!token) { 6 | throw new Error("Token cannot be empty"); 7 | } 8 | this.#token = token.trim(); 9 | } 10 | 11 | toString() { 12 | throw new Error("Please use asString() instead"); 13 | } 14 | 15 | asString() { 16 | return this.#token; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/express-http-https/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-http-https", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SSRF", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "express": "^5.0.0", 10 | "express-async-handler": "^1.2.0", 11 | "morgan": "^1.10.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample-apps/fastify-clickhouse/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastify-clickhouse", 3 | "version": "1.0.0", 4 | "description": "A vulnerable fastify app to test out SQL Injection with clickhouse", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "fastify": "^4.28.0", 10 | "@clickhouse/client": "^1.7.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/express-openai/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-openai", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "private": true, 7 | "scripts": { 8 | "start": "node app.js" 9 | }, 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build", 12 | "dotenv": "^16.4.1", 13 | "express": "^4.19.2", 14 | "openai": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/app/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --font-sans: 5 | "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", 6 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 7 | } 8 | 9 | html, 10 | body { 11 | @apply bg-white dark:bg-gray-950; 12 | 13 | @media (prefers-color-scheme: dark) { 14 | color-scheme: dark; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /benchmarks/operations/http-request.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | 3 | module.exports = { 4 | step: async function step() { 5 | return new Promise((resolve, reject) => { 6 | const req = http.request("http://localhost:10411", (res) => { 7 | res.resume(); 8 | res.on("end", resolve); 9 | }); 10 | 11 | req.on("error", reject); 12 | req.end(); 13 | }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /library/agent/AikidoDAST.ts: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from "../helpers/isPlainObject"; 2 | import { Context } from "./Context"; 3 | 4 | const AIKIDO_DAST_HEADER = "aikido-api-test"; 5 | 6 | export function isAikidoDASTRequest(context: Context) { 7 | return ( 8 | isPlainObject(context.headers) && 9 | AIKIDO_DAST_HEADER in context.headers && 10 | context.headers[AIKIDO_DAST_HEADER] === "1" 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /library/agent/api-discovery/helpers/isUri.ts: -------------------------------------------------------------------------------- 1 | import { tryParseURL } from "../../../helpers/tryParseURL"; 2 | 3 | export default function isUriString(str: string): boolean { 4 | if (str.length > 2084) { 5 | return false; 6 | } 7 | 8 | const url = tryParseURL(str); 9 | 10 | if (!url) { 11 | return false; 12 | } 13 | if (!url.hostname) { 14 | return false; 15 | } 16 | 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /library/helpers/getMaxApiDiscoverySamples.ts: -------------------------------------------------------------------------------- 1 | const MAX_API_DISCOVERY_SAMPLES = 10; 2 | 3 | export function getMaxApiDiscoverySamples() { 4 | if (process.env.AIKIDO_MAX_API_DISCOVERY_SAMPLES) { 5 | const parsed = parseInt(process.env.AIKIDO_MAX_API_DISCOVERY_SAMPLES, 10); 6 | if (!isNaN(parsed) && parsed >= 0) { 7 | return parsed; 8 | } 9 | } 10 | 11 | return MAX_API_DISCOVERY_SAMPLES; 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/koa-sqlite3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-sqlite3", 3 | "dependencies": { 4 | "@aikidosec/firewall": "file:../../build", 5 | "@koa/router": "^13.0.0", 6 | "koa": "^2.15.3", 7 | "koa-body": "^6.0.1", 8 | "koa-helmet": "^7.0.2", 9 | "sqlite3": "^5.1.7" 10 | }, 11 | "devDependencies": { 12 | "@types/koa": "^2.15.0", 13 | "@types/koa__router": "^12.0.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample-apps/restify-postgres/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-postgres", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with postgres using Restify", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "dotenv": "^16.4.1", 10 | "restify": "^8.6.0", 11 | "pg": "^8.11.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/body-size.md: -------------------------------------------------------------------------------- 1 | # Body size 2 | 3 | If your run into `413` HTTP errors and need to accept large bodies, you can use the `AIKIDO_MAX_BODY_SIZE_MB` environment variable to increase the maximum body size that the server will accept. 4 | 5 | ```bash 6 | AIKIDO_MAX_BODY_SIZE_MB=50 node server.js 7 | ``` 8 | 9 | By default, the maximum body size is `20`. This will protect your server from being overwhelmed by large requests. 10 | -------------------------------------------------------------------------------- /library/helpers/getNodeVersion.ts: -------------------------------------------------------------------------------- 1 | export function getMajorNodeVersion(): number { 2 | return parseInt(process.version.split(".")[0].slice(1)); 3 | } 4 | 5 | export function getMinorNodeVersion() { 6 | return parseInt(process.version.split(".")[1]); 7 | } 8 | 9 | export function getSemverNodeVersion() { 10 | return process.version.startsWith("v") 11 | ? process.version.slice(1) 12 | : process.version; 13 | } 14 | -------------------------------------------------------------------------------- /sample-apps/strapi/config/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | auth: { 3 | secret: env("ADMIN_JWT_SECRET"), 4 | }, 5 | apiToken: { 6 | salt: env("API_TOKEN_SALT"), 7 | }, 8 | transfer: { 9 | token: { 10 | salt: env("TRANSFER_TOKEN_SALT"), 11 | }, 12 | }, 13 | flags: { 14 | nps: env.bool("FLAG_NPS", true), 15 | promoteEE: env.bool("FLAG_PROMOTE_EE", true), 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /library/helpers/getFileExtension.ts: -------------------------------------------------------------------------------- 1 | import { extname } from "path"; 2 | 3 | /** 4 | * Get the file extension from a path / path segment without the dot 5 | */ 6 | export function getFileExtension(segment: string) { 7 | const extension = extname(segment); 8 | if (extension && extension.startsWith(".")) { 9 | // Remove the dot from the extension 10 | return extension.slice(1); 11 | } 12 | return extension; 13 | } 14 | -------------------------------------------------------------------------------- /library/helpers/isAikidoCI.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { isAikidoCI } from "./isAikidoCI"; 3 | 4 | t.test("get aikido ci env", async (t) => { 5 | process.env.AIKIDO_CI = "true"; 6 | t.ok(isAikidoCI()); 7 | process.env.AIKIDO_CI = undefined; 8 | t.notOk(isAikidoCI()); 9 | process.env.AIKIDO_CI = "1"; 10 | t.ok(isAikidoCI()); 11 | process.env.AIKIDO_CI = "false"; 12 | t.notOk(isAikidoCI()); 13 | }); 14 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/src/request.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from "@nestjs/common"; 2 | import { RequestService } from "./request.service"; 3 | 4 | @Controller() 5 | export class RequestController { 6 | constructor(private readonly requestService: RequestService) {} 7 | 8 | @Get("/releases") 9 | async getRequest(): Promise { 10 | return await this.requestService.getReleases(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/helpers/code_str_includes_instrument_funcs.rs: -------------------------------------------------------------------------------- 1 | const INSTRUMENT_FUNC_NAMES: [&str; 4] = [ 2 | "__instrumentInspectArgs", 3 | "__instrumentModifyArgs", 4 | "__instrumentModifyReturnValue", 5 | "__instrumentAccessLocalVariables", 6 | ]; 7 | 8 | pub fn code_str_includes_instrument_funcs(code: &str) -> bool { 9 | INSTRUMENT_FUNC_NAMES.iter().any(|name| code.contains(name)) 10 | } 11 | -------------------------------------------------------------------------------- /library/helpers/tryParseURL.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { tryParseURL } from "./tryParseURL"; 3 | 4 | t.test("it returns undefined if invalid URL", async () => { 5 | const url = tryParseURL("invalid"); 6 | t.same(url, undefined); 7 | }); 8 | 9 | t.test("it returns URL if valid URL", async () => { 10 | const url = tryParseURL("https://example.com"); 11 | t.same(url, new URL("https://example.com/")); 12 | }); 13 | -------------------------------------------------------------------------------- /sample-apps/express-bedrock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-bedrock", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "private": true, 7 | "scripts": { 8 | "start": "node app.js" 9 | }, 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build", 12 | "@aws-sdk/client-bedrock-runtime": "^3.0.0", 13 | "dotenv": "^16.4.1", 14 | "express": "^4.19.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/hono-pg-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-pg-esm", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "node --require @aikidosec/firewall/instrument ./app.js" 7 | }, 8 | "dependencies": { 9 | "@aikidosec/firewall": "file:../../build", 10 | "@hono/node-server": "^1.13.8", 11 | "@sentry/node": "^10.22.0", 12 | "hono": "^4.7.2", 13 | "pg": "^8.13.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /library/helpers/isJsonContentType.ts: -------------------------------------------------------------------------------- 1 | const jsonContentTypes = [ 2 | "application/json", 3 | "application/csp-report", 4 | "application/x-json", 5 | ]; 6 | 7 | export function isJsonContentType(contentType: string) { 8 | const normalized = contentType.toLowerCase().trim(); 9 | 10 | if (jsonContentTypes.some((type) => normalized.startsWith(type))) { 11 | return true; 12 | } 13 | 14 | return normalized.includes("+json"); 15 | } 16 | -------------------------------------------------------------------------------- /library/sources/Restify.v8.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 3 | import { createRestifyTests } from "./Restify.tests"; 4 | 5 | t.test( 6 | "Restify", 7 | { 8 | skip: 9 | getMajorNodeVersion() > 16 10 | ? "Restify v8 only supports node v16" 11 | : undefined, 12 | }, 13 | async () => { 14 | createRestifyTests("restify-v8"); 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /library/sources/Restify.v9.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 3 | import { createRestifyTests } from "./Restify.tests"; 4 | 5 | t.test( 6 | "Restify", 7 | { 8 | skip: 9 | getMajorNodeVersion() > 16 10 | ? "Restify v9 only supports node v16" 11 | : undefined, 12 | }, 13 | async () => { 14 | createRestifyTests("restify-v9"); 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies and AdonisJS build 2 | node_modules 3 | build 4 | tmp 5 | 6 | # Secrets 7 | .env 8 | .env.local 9 | .env.production.local 10 | .env.development.local 11 | 12 | # Frontend assets compiled code 13 | public/assets 14 | 15 | # Build tools specific 16 | npm-debug.log 17 | yarn-error.log 18 | 19 | # Editors specific 20 | .fleet 21 | .idea 22 | .vscode 23 | 24 | # Platform specific 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/src/app/layout.js: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Next.js", 3 | description: "Generated by Next.js", 4 | }; 5 | 6 | import { Inter } from "next/font/google"; 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export default function RootLayout({ children }) { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/helpers/get_arg_names.rs: -------------------------------------------------------------------------------- 1 | use oxc_ast::ast::FormalParameters; 2 | 3 | pub fn get_function_or_method_arg_names( 4 | params: &oxc_allocator::Box, 5 | ) -> Vec { 6 | let mut arg_names = Vec::new(); 7 | 8 | params.items.iter().for_each(|param| { 9 | arg_names.push(param.pattern.get_identifier_name().unwrap().to_string()); 10 | }); 11 | 12 | arg_names 13 | } 14 | -------------------------------------------------------------------------------- /library/sinks/http-request/isOptionsObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the argument is treated as an options object by Node.js. 3 | * For checking if the argument can be used as options for a outgoing HTTP request. 4 | */ 5 | export function isOptionsObject(arg: any): arg is { [key: string]: unknown } { 6 | return ( 7 | typeof arg === "object" && 8 | !Array.isArray(arg) && 9 | arg !== null && 10 | !(arg instanceof URL) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/operations/jsinjection.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | step: async function step() { 3 | new Function( 4 | "n", 5 | ` 6 | if (n <= 0) return []; 7 | if (n === 1) return [0]; 8 | if (n === 2) return [0, 1]; 9 | 10 | const sequence = [0, 1]; 11 | for (let i = 2; i < n; i++) { 12 | sequence.push(sequence[i - 1] + sequence[i - 2]); 13 | } 14 | return sequence; 15 | ` 16 | ); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /sample-apps/express-path-traversal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-path-traversal", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test path traversal attacks", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "dotenv": "^16.4.1", 10 | "express": "^4.19.2", 11 | "express-async-handler": "^1.2.0", 12 | "morgan": "^1.10.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /end2end/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "app.ts", 5 | "private": true, 6 | "type": "module", 7 | "dependencies": { 8 | "express": "^5.1.0" 9 | }, 10 | "devDependencies": { 11 | "@types/express": "^5.0.6", 12 | "@types/node": "^24.10.1", 13 | "typescript": "^5.9.3" 14 | }, 15 | "scripts": { 16 | "start": "node ./app.ts", 17 | "check:types": "tsc --noEmit" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /library/helpers/isLibBundled.ts: -------------------------------------------------------------------------------- 1 | // Detect at runtime if the library is bundled inside an application 2 | export function isLibBundled(): boolean { 3 | // Replace Windows backslashes with forward slashes 4 | const normalizedDirName = __dirname.replace(/\\/g, "/"); 5 | 6 | return ( 7 | !normalizedDirName.includes("node_modules/@aikidosec/firewall/helpers") && 8 | !normalizedDirName.includes("/build/helpers") // In case of e2e tests 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /library/helpers/shouldBlock.ts: -------------------------------------------------------------------------------- 1 | import { envToBool } from "./envToBool"; 2 | 3 | /** 4 | * Check the environment variables to see if the firewall should block requests if an attack is detected. 5 | * - AIKIDO_BLOCKING=true or AIKIDO_BLOCKING=1 6 | * - AIKIDO_BLOCK=true or AIKIDO_BLOCK=1 7 | */ 8 | export function shouldBlock() { 9 | return ( 10 | envToBool(process.env.AIKIDO_BLOCKING) || 11 | envToBool(process.env.AIKIDO_BLOCK) 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /library/sources/Restify.v10.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 3 | import { createRestifyTests } from "./Restify.tests"; 4 | 5 | t.test( 6 | "Restify", 7 | { 8 | skip: 9 | getMajorNodeVersion() > 18 10 | ? "Restify v10 only supports node v18 and lower" 11 | : undefined, 12 | }, 13 | async () => { 14 | createRestifyTests("restify-v10"); 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /library/sources/Restify.v11.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 3 | import { createRestifyTests } from "./Restify.tests"; 4 | 5 | t.test( 6 | "Restify", 7 | { 8 | skip: 9 | getMajorNodeVersion() > 18 10 | ? "Restify v11 only supports node v18 and lower" 11 | : undefined, 12 | }, 13 | async () => { 14 | createRestifyTests("restify-v11"); 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const Zen = require("@aikidosec/firewall"); 3 | 4 | function createApp(config = {}) { 5 | const app = express(); 6 | 7 | if (!config.serverless) { 8 | Zen.addExpressMiddleware(app); 9 | } 10 | 11 | app.get("/hello", function (req, res) { 12 | res.json({ message: "Hello World!" }); 13 | }); 14 | 15 | return app; 16 | } 17 | 18 | module.exports = createApp; 19 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/wasm_bindings/mod.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use crate::js_transformer::transformer::transform_code_str; 4 | 5 | #[wasm_bindgen] 6 | pub fn wasm_transform_code_str( 7 | pkg_name: &str, 8 | pkg_version: &str, 9 | code: &str, 10 | instructions_json: &str, 11 | source_type: &str, 12 | ) -> Result { 13 | transform_code_str(pkg_name, pkg_version, code, instructions_json, source_type) 14 | } 15 | -------------------------------------------------------------------------------- /library/helpers/ip-matcher/parse.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { network } from "./parse"; 3 | 4 | t.test("it works", async (t) => { 5 | t.same(network("192.168.2.1/24"), { bytes: [192, 168, 2, 1], cidr: 24 }); 6 | t.same(network("192.168.2.1/abcde"), null); 7 | t.same(network("192.168.2.1/24/test"), null); 8 | t.same(network("::1"), { 9 | bytes: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 10 | cidr: 128, 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /library/sources/xml/addXmlToContext.ts: -------------------------------------------------------------------------------- 1 | import { Context, updateContext } from "../../agent/Context"; 2 | 3 | /** 4 | * Adds the XML to the context XML array if it exists, or creates a new array if it doesn't. 5 | */ 6 | export function addXmlToContext(xml: any, context: Context) { 7 | if (Array.isArray(context.xml)) { 8 | updateContext(context, "xml", context.xml.concat(xml)); 9 | } else { 10 | updateContext(context, "xml", [xml]); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample-apps/express-mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mongoose", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "cookie-parser": "^1.4.6", 10 | "dotenv": "^16.4.1", 11 | "express": "^4.19.2", 12 | "express-async-handler": "^1.2.0", 13 | "mongoose": "^8.1.1", 14 | "morgan": "^1.10.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/index.js: -------------------------------------------------------------------------------- 1 | const protect = require("@aikidosec/firewall/lambda"); 2 | 3 | const { someFunction } = require("./submodule"); 4 | 5 | require("@aikidosec/firewall/nopp"); 6 | 7 | async function main(client, event) { 8 | someFunction(); 9 | } 10 | 11 | exports.handler = protect(async function (event, context) { 12 | try { 13 | return await main(event, context); 14 | } catch (e) { 15 | console.error(e); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /library/helpers/isRedirectStatusCode.ts: -------------------------------------------------------------------------------- 1 | const MOVED_PERMANENTLY = 301; 2 | const FOUND = 302; 3 | const SEE_OTHER = 303; 4 | const TEMPORARY_REDIRECT = 307; 5 | const PERMANENT_REDIRECT = 308; 6 | 7 | const REDIRECT_CODES = [ 8 | MOVED_PERMANENTLY, 9 | FOUND, 10 | SEE_OTHER, 11 | TEMPORARY_REDIRECT, 12 | PERMANENT_REDIRECT, 13 | ]; 14 | 15 | export function isRedirectStatusCode(statusCode: number) { 16 | return REDIRECT_CODES.includes(statusCode); 17 | } 18 | -------------------------------------------------------------------------------- /end2end/server/src/handlers/getConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Response } from "express"; 2 | import { getAppConfig } from "../zen/config.ts"; 3 | import type { ZenRequest } from "../types.ts"; 4 | 5 | export function getConfig(req: ZenRequest, res: Response) { 6 | if (!req.zenApp) { 7 | throw new Error("App is missing"); 8 | } 9 | 10 | const config = getAppConfig(req.zenApp); 11 | delete config.failureRate; 12 | delete config.timeout; 13 | res.json(config); 14 | } 15 | -------------------------------------------------------------------------------- /sample-apps/express-mysql2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mysql2", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with mysql2", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "dotenv": "^16.4.1", 10 | "express": "^4.19.2", 11 | "express-async-handler": "^1.2.0", 12 | "morgan": "^1.10.0", 13 | "mysql2": "^3.10.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample-apps/express-path-traversal/Documents.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs/promises"); 2 | const path = require("path"); 3 | 4 | class Documents { 5 | constructor(folderName) { 6 | this.directory = folderName; 7 | } 8 | 9 | async add(filename, content) { 10 | await fs.writeFile(this.directory + filename, content, "utf8"); 11 | } 12 | 13 | async getAll() { 14 | return await fs.readdir(this.directory); 15 | } 16 | } 17 | 18 | module.exports = Documents; 19 | -------------------------------------------------------------------------------- /sample-apps/test-exports/tsconfig.node18.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 18", 4 | "_version": "18.2.0", 5 | "compilerOptions": { 6 | "lib": ["es2023"], 7 | "module": "node16", 8 | "target": "es2022", 9 | "outDir": "./dist18", 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "moduleResolution": "node16" 14 | }, 15 | "include": ["test.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /library/helpers/isPackageInstalled.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { isPackageInstalled } from "./isPackageInstalled"; 3 | 4 | t.test("it returns true if the package is installed", async (t) => { 5 | const result = isPackageInstalled("tap"); 6 | t.equal(result, true); 7 | }); 8 | 9 | t.test("it returns false if the package is not installed", async (t) => { 10 | const result = isPackageInstalled("nonexistent-package"); 11 | t.equal(result, false); 12 | }); 13 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { RequestController } from "./request.controller"; 3 | import { RequestService } from "./request.service"; 4 | import { CatsController } from "./cats.controller"; 5 | import { DBService } from "./db.service"; 6 | 7 | @Module({ 8 | imports: [], 9 | controllers: [RequestController, CatsController], 10 | providers: [RequestService, DBService], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Create Next App", 6 | description: "Generated by create next app", 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /library/sinks/undici/RequestContextStorage.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "async_hooks"; 2 | 3 | /** 4 | * This storage is used to store the port of outgoing fetch / undici requests. 5 | * This is used to check if ports match when we are inspecting the result of dns resolution. 6 | * If the port does not match, it would be a false positive ssrf detection. 7 | */ 8 | export const RequestContextStorage = new AsyncLocalStorage<{ 9 | url: URL; 10 | port?: number; 11 | }>(); 12 | -------------------------------------------------------------------------------- /library/helpers/limitLengthMetadata.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { limitLengthMetadata } from "./limitLengthMetadata"; 3 | 4 | t.test("it limits metadata length", async () => { 5 | t.same(limitLengthMetadata({}, 5), {}); 6 | t.same( 7 | limitLengthMetadata( 8 | { 9 | short: "short", 10 | long: "longer than 5", 11 | }, 12 | 5 13 | ), 14 | { 15 | short: "short", 16 | long: "longe", 17 | } 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/src/db.js: -------------------------------------------------------------------------------- 1 | import { Client } from "pg"; 2 | 3 | export async function createConnection() { 4 | const client = new Client({ 5 | user: "root", 6 | host: "127.0.0.1", 7 | database: "main_db", 8 | password: "password", 9 | port: 27016, 10 | }); 11 | 12 | await client.connect(); 13 | await client.query(` 14 | CREATE TABLE IF NOT EXISTS cats ( 15 | petname varchar(255) 16 | ); 17 | `); 18 | 19 | return client; 20 | } 21 | -------------------------------------------------------------------------------- /library/helpers/escapeLog.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { escapeLog } from "./escapeLog"; 3 | 4 | t.test("it escapes log messages", async (t) => { 5 | t.equal(escapeLog("Hello\nWorld"), "Hello World"); 6 | t.equal(escapeLog("Hello`World"), "Hello'World"); 7 | t.equal(escapeLog("Hello\n`World"), "Hello 'World"); 8 | t.equal(escapeLog("Hello`World\n"), "Hello'World "); 9 | t.equal(escapeLog('Hello "World"'), "Hello 'World'"); 10 | t.equal(escapeLog(undefined), ""); 11 | }); 12 | -------------------------------------------------------------------------------- /sample-apps/express-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-graphql", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out injections with graphql", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "dotenv": "^16.4.5", 10 | "express": "^4.19.2", 11 | "graphql": "^16.8.2", 12 | "graphql-http": "^1.22.1", 13 | "morgan": "^1.10.0", 14 | "mysql2": "^3.10.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/hono-pg-ts-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono-pg-ts-esm", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "node --require @aikidosec/firewall/instrument --experimental-strip-types ./app.ts" 7 | }, 8 | "dependencies": { 9 | "@aikidosec/firewall": "file:../../build", 10 | "@hono/node-server": "^1.13.8", 11 | "hono": "^4.7.2", 12 | "pg": "^8.13.3" 13 | }, 14 | "devDependencies": { 15 | "@types/pg": "^8.11.10" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /library/helpers/cleanError.ts: -------------------------------------------------------------------------------- 1 | import { cleanupStackTrace } from "./cleanupStackTrace"; 2 | import { getLibraryRoot } from "./getLibraryRoot"; 3 | 4 | // Cleans up the error stack trace by removing all the lines that are part of the library. 5 | // e.g. useful to hide the module patching if we throw an error on a detected attack. 6 | export function cleanError(err: Error) { 7 | if (err.stack) { 8 | err.stack = cleanupStackTrace(err.stack, getLibraryRoot()); 9 | } 10 | 11 | return err; 12 | } 13 | -------------------------------------------------------------------------------- /library/helpers/escapeLog.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove new line characters and escape backticks from a log message. 3 | * 4 | * Also truncates the log message to a maximum length. 5 | */ 6 | export function escapeLog(log: string | undefined, maxLength = 256): string { 7 | if (!log || typeof log !== "string") { 8 | return ""; 9 | } 10 | 11 | if (log.length > maxLength) { 12 | log = log.slice(0, maxLength) + "..."; 13 | } 14 | 15 | return log.replace(/\n/g, " ").replace(/[`"]/g, "'"); 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/hybrid-lambda-express/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/nodejs:18 2 | 3 | COPY package.json ${LAMBDA_TASK_ROOT} 4 | COPY package-lock.json ${LAMBDA_TASK_ROOT} 5 | COPY handler.js ${LAMBDA_TASK_ROOT} 6 | COPY app.js ${LAMBDA_TASK_ROOT} 7 | 8 | # Copy the dev firewall package 9 | COPY aikidosec-firewall-*.tgz ${LAMBDA_TASK_ROOT} 10 | 11 | RUN npm ci && \ 12 | npm uninstall @aikidosec/firewall && \ 13 | npm install ./aikidosec-firewall-*.tgz 14 | 15 | CMD [ "handler.handler" ] 16 | -------------------------------------------------------------------------------- /library/agent/isNewHookSystemUsed.ts: -------------------------------------------------------------------------------- 1 | // Set to true if the new hook system was imported (also if no agent) 2 | // Prevents that the following import has side effects if it's still inside the codebase / used for middleware 3 | // import Zen from "@aikidosec/firewall" 4 | 5 | let _isNewHookSystemUsed = false; 6 | 7 | export function setIsNewHookSystemUsed(value: boolean) { 8 | _isNewHookSystemUsed = value; 9 | } 10 | 11 | export function isNewHookSystemUsed() { 12 | return _isNewHookSystemUsed; 13 | } 14 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/withoutGuard.js: -------------------------------------------------------------------------------- 1 | const measure = require("./measure"); 2 | const getUser = require("./getUser"); 3 | const getClient = require("./getClient"); 4 | 5 | async function main() { 6 | const client = await getClient(); 7 | const timings = await measure(async () => { 8 | await getUser(client, { 9 | email: "email", 10 | password: "password", 11 | }); 12 | }); 13 | 14 | await client.close(); 15 | 16 | console.log(JSON.stringify(timings)); 17 | } 18 | 19 | main(); 20 | -------------------------------------------------------------------------------- /end2end/tests/import-middleware-ts.test.js: -------------------------------------------------------------------------------- 1 | const t = require("tap"); 2 | const { execSync } = require("child_process"); 3 | const { resolve } = require("path"); 4 | 5 | const pathToApp = resolve(__dirname, "../../sample-apps/import-middleware-ts"); 6 | 7 | t.test("it passes type check when skipLibCheck is set to false", async (t) => { 8 | try { 9 | execSync(`npm run type-check`, { 10 | cwd: pathToApp, 11 | }); 12 | } catch (error) { 13 | t.fail(error.stdout.toString()); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /sample-apps/express-mariadb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mariadb", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with MariaDB", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "cookie-parser": "^1.4.6", 10 | "dotenv": "^16.4.1", 11 | "express": "^4.19.2", 12 | "mariadb": "^3.2.3", 13 | "express-async-handler": "^1.2.0", 14 | "morgan": "^1.10.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/express-postgres-esm/Cats.js: -------------------------------------------------------------------------------- 1 | export default class Cats { 2 | constructor(db) { 3 | this.db = db; 4 | } 5 | 6 | async add(name) { 7 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 8 | await this.db.query(`INSERT INTO cats_4 (petname) VALUES ('${name}');`); 9 | } 10 | 11 | async getAll() { 12 | const cats = await this.db.query("SELECT petname FROM cats_4;"); 13 | 14 | return cats.rows.map((row) => row.petname); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /end2end/server/src/handlers/realtimeConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Response } from "express"; 2 | import { getAppConfig } from "../zen/config.ts"; 3 | import type { ZenRequest } from "../types.ts"; 4 | 5 | export function realtimeConfig(req: ZenRequest, res: Response) { 6 | if (!req.zenApp) { 7 | throw new Error("App is missing"); 8 | } 9 | 10 | const config = getAppConfig(req.zenApp); 11 | 12 | res.json({ 13 | serviceId: req.zenApp.id, 14 | configUpdatedAt: config.configUpdatedAt, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /library/helpers/getAgentVersion.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | 3 | export function getAgentVersion(): string { 4 | try { 5 | const json = require(resolve(__dirname, "../package.json")); 6 | 7 | /* c8 ignore start */ 8 | if (!json.version) { 9 | throw new Error("Missing version in package.json"); 10 | } 11 | /* c8 ignore stop */ 12 | 13 | return json.version; 14 | /* c8 ignore start */ 15 | } catch { 16 | return "0.0.0"; 17 | } 18 | /* c8 ignore stop */ 19 | } 20 | -------------------------------------------------------------------------------- /library/helpers/getPortFromURL.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getPortFromURL } from "./getPortFromURL"; 3 | 4 | t.test("it works", async (t) => { 5 | t.same(getPortFromURL(new URL("http://localhost:4000")), 4000); 6 | t.same(getPortFromURL(new URL("http://localhost")), 80); 7 | t.same(getPortFromURL(new URL("https://localhost")), 443); 8 | t.same(getPortFromURL(new URL("ftp://localhost")), undefined); 9 | t.same(getPortFromURL(new URL("https://test.com:8080/test?abc=123")), 8080); 10 | }); 11 | -------------------------------------------------------------------------------- /sample-apps/hapi-postgres/Cats.js: -------------------------------------------------------------------------------- 1 | class Cats { 2 | constructor(db) { 3 | this.db = db; 4 | } 5 | 6 | async add(name) { 7 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 8 | await this.db.query(`INSERT INTO cats (petname) VALUES ('${name}');`); 9 | } 10 | 11 | async getAll() { 12 | const cats = await this.db.query("SELECT petname FROM cats;"); 13 | 14 | return cats.rows.map((row) => row.petname); 15 | } 16 | } 17 | 18 | module.exports = Cats; 19 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/app/routes/add-cat.ts: -------------------------------------------------------------------------------- 1 | import { redirect, type ActionFunctionArgs } from "react-router"; 2 | import { getConnection } from "~/.server/db"; 3 | 4 | export async function action({ request }: ActionFunctionArgs) { 5 | const formData = await request.formData(); 6 | const catName = formData.get("catname"); 7 | 8 | const db = await getConnection(); 9 | 10 | // Insecure 11 | await db.query(`INSERT INTO cats_5 (petname) VALUES ('${catName}');`); 12 | 13 | return redirect("/", {}); 14 | } 15 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/measure.js: -------------------------------------------------------------------------------- 1 | module.exports = async function measureFunctionPerformance( 2 | func, 3 | warmupIterations = 100, 4 | measuredIterations = 1000 5 | ) { 6 | for (let i = 0; i < warmupIterations; i++) { 7 | await func(); 8 | } 9 | 10 | const timings = []; 11 | for (let i = 0; i < measuredIterations; i++) { 12 | const start = performance.now(); 13 | await func(); 14 | const end = performance.now(); 15 | timings.push(end - start); 16 | } 17 | 18 | return timings; 19 | }; 20 | -------------------------------------------------------------------------------- /library/bundler/externals.ts: -------------------------------------------------------------------------------- 1 | import { Hooks } from "../agent/hooks/Hooks"; 2 | import { getWrappers } from "../agent/protect"; 3 | 4 | export function externals() { 5 | const wrappers = getWrappers(); 6 | const hooks = new Hooks(); 7 | 8 | wrappers.forEach((wrapper) => { 9 | wrapper.wrap(hooks); 10 | }); 11 | 12 | const packages = ["@aikidosec/firewall"].concat( 13 | hooks.getPackages().map((pkg) => pkg.getName()) 14 | ); 15 | 16 | // Remove duplicates 17 | return Array.from(new Set(packages)); 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/express-postgres/Cats.js: -------------------------------------------------------------------------------- 1 | class Cats { 2 | constructor(db) { 3 | this.db = db; 4 | } 5 | 6 | async add(name) { 7 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 8 | await this.db.query(`INSERT INTO cats_2 (petname) VALUES ('${name}');`); 9 | } 10 | 11 | async getAll() { 12 | const cats = await this.db.query("SELECT petname FROM cats_2;"); 13 | 14 | return cats.rows.map((row) => row.petname); 15 | } 16 | } 17 | 18 | module.exports = Cats; 19 | -------------------------------------------------------------------------------- /sample-apps/restify-postgres/Cats.js: -------------------------------------------------------------------------------- 1 | class Cats { 2 | constructor(db) { 3 | this.db = db; 4 | } 5 | 6 | async add(name) { 7 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 8 | await this.db.query(`INSERT INTO cats_2 (petname) VALUES ('${name}');`); 9 | } 10 | 11 | async getAll() { 12 | const cats = await this.db.query("SELECT petname FROM cats_2;"); 13 | 14 | return cats.rows.map((row) => row.petname); 15 | } 16 | } 17 | 18 | module.exports = Cats; 19 | -------------------------------------------------------------------------------- /library/agent/api-discovery/helpers/isUUIDString.ts: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/uuidjs/uuid/blob/main/src/regex.ts 2 | // MIT License - Copyright (c) 2010-2020 Robert Kieffer and other contributors 3 | const regex = 4 | /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i; 5 | 6 | export default function isUUIDString(str: string) { 7 | if (str.length !== 36) { 8 | return false; 9 | } 10 | return regex.test(str); 11 | } 12 | -------------------------------------------------------------------------------- /sample-apps/express-mysql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mysql", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with mysql", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "@sentry/node": "^7", 10 | "dotenv": "^16.4.1", 11 | "express": "^5.0.0", 12 | "express-async-handler": "^1.2.0", 13 | "morgan": "^1.10.0", 14 | "mysql": "^2.18.1", 15 | "xml-js": "^1.6.11" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /end2end/tests/lambda-mark-unsafe.test.js: -------------------------------------------------------------------------------- 1 | const t = require("tap"); 2 | const { execSync } = require("child_process"); 3 | const { resolve } = require("path"); 4 | 5 | const pathToApp = resolve( 6 | __dirname, 7 | "../../sample-apps/lambda-mark-unsafe", 8 | "app.js" 9 | ); 10 | 11 | t.test( 12 | "it does not crash if markUnsafe is used in lambda wrapper", 13 | async (t) => { 14 | execSync(`node ${pathToApp}`, { 15 | env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" }, 16 | }); 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /sample-apps/express-postgres/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-postgres", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with postgres", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "dd-trace": "^4.49.0", 10 | "dotenv": "^16.4.1", 11 | "esbuild": "^0.25.2", 12 | "express": "^4.19.2", 13 | "express-async-handler": "^1.2.0", 14 | "morgan": "^1.10.0", 15 | "pg": "^8.11.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample-apps/hono-pg-esm/db.js: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | const { Client } = pg; 3 | 4 | export async function createConnection() { 5 | const client = new Client({ 6 | user: "root", 7 | host: "127.0.0.1", 8 | database: "main_db", 9 | password: "password", 10 | port: 27016, 11 | }); 12 | 13 | await client.connect(); 14 | await client.query(` 15 | CREATE TABLE IF NOT EXISTS cats_3 ( 16 | petname varchar(255), 17 | comment varchar(255) 18 | ); 19 | `); 20 | 21 | return client; 22 | } 23 | -------------------------------------------------------------------------------- /sample-apps/import-middleware-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "import-middleware-ts", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "private": true, 6 | "dependencies": { 7 | "@aikidosec/firewall": "file:../../build", 8 | "express": "^5.1.0", 9 | "morgan": "^1.10.0", 10 | "typescript": "^5.8.3" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^22.15.19", 14 | "@types/express": "^5.0.2", 15 | "@types/morgan": "^1.9.9" 16 | }, 17 | "scripts": { 18 | "type-check": "tsc --noEmit" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/agent/hooks/wrapNewInstance.noAgent.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { wrapNewInstance } from "./wrapNewInstance"; 3 | 4 | t.test("Agent is not initialized", async (t) => { 5 | try { 6 | wrapNewInstance({}, "test", { name: "test", type: "external" }, () => {}); 7 | t.fail(); 8 | } catch (e: unknown) { 9 | t.ok(e instanceof Error); 10 | if (e instanceof Error) { 11 | t.same( 12 | e.message, 13 | "Can not wrap new instance if agent is not initialized" 14 | ); 15 | } 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /sample-apps/express-mongoose/README.md: -------------------------------------------------------------------------------- 1 | # express-mongoose 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-mongoose` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ add a few cats 10 | - http://localhost:4000/?search=name search for cats with name 11 | - http://localhost:4000/?search[$ne]=null will abuse the vulnerability parameter to return all cats 12 | -------------------------------------------------------------------------------- /sample-apps/express-mysql/README.md: -------------------------------------------------------------------------------- 1 | # express-mysql 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-mysql` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /sample-apps/hapi-postgres/README.md: -------------------------------------------------------------------------------- 1 | # hapi-postgres 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app hapi-postgres` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | --- 6 | 7 | ## Feature request 8 | 9 | **Describe the problem** 10 | What problem or limitation are you facing? 11 | 12 | **Proposed solution** 13 | Describe the feature or improvement you’d like to see. 14 | 15 | **Alternatives considered** 16 | Have you tried any workarounds or other approaches? 17 | 18 | **Additional context** 19 | Add any other information, links, or examples that help explain the request. 20 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/helpers/select_sourcetype_based_on_enum.rs: -------------------------------------------------------------------------------- 1 | use oxc_span::SourceType; 2 | 3 | pub fn select_sourcetype_based_on_enum(src_type: &str) -> SourceType { 4 | // 0 is generic type. 5 | match src_type { 6 | "unambiguous" => SourceType::unambiguous(), 7 | "ts" => SourceType::ts(), 8 | "cjs" => SourceType::cjs(), 9 | "mjs" => SourceType::mjs(), 10 | "tsx" => SourceType::tsx(), 11 | "jsx" => SourceType::jsx(), 12 | _ => SourceType::unambiguous(), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample-apps/express-mariadb/README.md: -------------------------------------------------------------------------------- 1 | # express-mariadb 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-mariadb` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /sample-apps/express-mysql2/README.md: -------------------------------------------------------------------------------- 1 | # express-mysql2 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-mysql2` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /sample-apps/fastify-mysql2/README.md: -------------------------------------------------------------------------------- 1 | # fastify-mysql2 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app fastify-mysql2` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /sample-apps/hono-pg-ts-esm/db.ts: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | const { Client } = pg; 3 | 4 | export async function createConnection(): Promise { 5 | const client = new Client({ 6 | user: "root", 7 | host: "127.0.0.1", 8 | database: "main_db", 9 | password: "password", 10 | port: 27016, 11 | }); 12 | 13 | await client.connect(); 14 | await client.query(` 15 | CREATE TABLE IF NOT EXISTS cats_3 ( 16 | petname varchar(255), 17 | comment varchar(255) 18 | ); 19 | `); 20 | 21 | return client; 22 | } 23 | -------------------------------------------------------------------------------- /library/agent/api/ReportingAPIThatThrows.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { ReportingAPI, ReportingAPIResponse } from "./ReportingAPI"; 3 | import { Token } from "./Token"; 4 | import { Event } from "./Event"; 5 | 6 | export class ReportingAPIThatThrows implements ReportingAPI { 7 | // oxlint-disable-next-line require-await 8 | async report( 9 | token: Token, 10 | event: Event, 11 | timeoutInMS: number 12 | ): Promise { 13 | throw new Error("Failed to report event"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /library/agent/extractRegionFromToken.ts: -------------------------------------------------------------------------------- 1 | export function extractRegionFromToken(token: string): string { 2 | if (!token || !token.startsWith("AIK_RUNTIME_")) { 3 | return "EU"; 4 | } 5 | 6 | const tokenWithoutPrefix = token.replace("AIK_RUNTIME_", ""); 7 | const parts = tokenWithoutPrefix.split("_"); 8 | 9 | // New format: AIK_RUNTIME_{sys_group_id}_{service_id}_{region}_{random} 10 | // Old format: AIK_RUNTIME_{sys_group_id}_{service_id}_{random} 11 | if (parts.length === 4) { 12 | return parts[2]; 13 | } 14 | 15 | return "EU"; 16 | } 17 | -------------------------------------------------------------------------------- /library/sources/http-server/replaceRequestBody.ts: -------------------------------------------------------------------------------- 1 | import type { IncomingMessage } from "http"; 2 | import type { Readable } from "stream"; 3 | 4 | // Copies all properties from the stream to the base object 5 | export function replaceRequestBody( 6 | base: T, 7 | stream: Readable 8 | ): T { 9 | for (const key in stream) { 10 | let v = stream[key as keyof Readable] as any; 11 | if (typeof v === "function") { 12 | v = v.bind(base); 13 | } 14 | base[key as keyof T] = v; 15 | } 16 | 17 | return base; 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/express-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mongodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "private": true, 7 | "dependencies": { 8 | "@aikidosec/firewall": "file:../../build", 9 | "@opentelemetry/api": "^1.8.0", 10 | "@opentelemetry/auto-instrumentations-node": "^0.44.0", 11 | "cookie-parser": "^1.4.6", 12 | "dotenv": "^16.4.1", 13 | "express": "^4.19.2", 14 | "express-async-handler": "^1.2.0", 15 | "mongodb": "~6.3.0", 16 | "morgan": "^1.10.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/express-postgres/README.md: -------------------------------------------------------------------------------- 1 | # express-postgres 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-postgres` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | -------------------------------------------------------------------------------- /benchmarks/express/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | function getPort() { 4 | const port = parseInt(process.env.PORT, 10) || 4000; 5 | 6 | if (isNaN(port)) { 7 | console.error("Invalid port"); 8 | process.exit(1); 9 | } 10 | 11 | return port; 12 | } 13 | 14 | function start() { 15 | const app = express(); 16 | 17 | app.get("/empty", (req, res) => { 18 | res.send(""); 19 | }); 20 | 21 | app.listen(getPort(), () => { 22 | console.log(`Server listening on port ${getPort()}`); 23 | }); 24 | } 25 | 26 | start(); 27 | -------------------------------------------------------------------------------- /library/agent/hooks/instrumentation/checkHooks.ts: -------------------------------------------------------------------------------- 1 | export async function checkHooks() { 2 | let success = false; 3 | 4 | try { 5 | const imported = await import("./zenHooksCheckImport"); 6 | 7 | if (imported.test() === ":)") { 8 | success = true; 9 | } 10 | } catch { 11 | // 12 | } 13 | 14 | if (!success) { 15 | // eslint-disable-next-line no-console 16 | console.warn( 17 | `AIKIDO: A self check of the code instrumentation failed. This means that the protection might not work as expected.` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/sources/Koa.v2.test.ts: -------------------------------------------------------------------------------- 1 | import { getMajorNodeVersion } from "../helpers/getNodeVersion"; 2 | import { isNewInstrumentationUnitTest } from "../helpers/isNewInstrumentationUnitTest"; 3 | import { createKoaTests } from "./Koa.tests"; 4 | import * as t from "tap"; 5 | 6 | t.test( 7 | "Koa", 8 | { 9 | skip: 10 | getMajorNodeVersion() < 25 && isNewInstrumentationUnitTest() 11 | ? "require(esm) triggers ERR_INVALID_RETURN_PROPERTY_VALUE" 12 | : undefined, 13 | }, 14 | async () => { 15 | createKoaTests("koa-v2"); 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /library/vulnerabilities/ssrf/getMetadataForSSRFAttack.ts: -------------------------------------------------------------------------------- 1 | export function getMetadataForSSRFAttack({ 2 | hostname, 3 | port, 4 | privateIP, 5 | }: { 6 | hostname: string; 7 | port: number | undefined; 8 | privateIP?: string; 9 | }): Record { 10 | const metadata: Record = { 11 | hostname: hostname, 12 | }; 13 | 14 | if (typeof port === "number") { 15 | metadata.port = port.toString(); 16 | } 17 | 18 | if (privateIP) { 19 | metadata.privateIP = privateIP; 20 | } 21 | 22 | return metadata; 23 | } 24 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # tests 12 | .tap 13 | 14 | # Dependency directories 15 | node_modules 16 | ../package-lock.json 17 | 18 | # Built files 19 | build/ 20 | generated_docs 21 | 22 | # IDE's 23 | .idea/ 24 | .vscode/ 25 | 26 | # Credentials 27 | .env 28 | credentials.json 29 | 30 | # Test files 31 | library/test.txt 32 | library/test2.txt 33 | 34 | # Rust build files 35 | instrumentation-wasm/target/ 36 | 37 | # Temp esm test directory 38 | .esm-tests/ -------------------------------------------------------------------------------- /library/agent/hooks/Global.ts: -------------------------------------------------------------------------------- 1 | import { InterceptorObject } from "./wrapExport"; 2 | 3 | export class Global { 4 | constructor( 5 | private readonly name: string, 6 | private readonly interceptors: InterceptorObject 7 | ) { 8 | if (!this.name) { 9 | throw new Error("Name is required"); 10 | } 11 | if (!this.interceptors) { 12 | throw new Error("Interceptors are required"); 13 | } 14 | } 15 | 16 | getName() { 17 | return this.name; 18 | } 19 | 20 | getInterceptors() { 21 | return this.interceptors; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /library/agent/getAPIURL.ts: -------------------------------------------------------------------------------- 1 | import { extractRegionFromToken } from "./extractRegionFromToken"; 2 | 3 | export function getAPIURL() { 4 | if (process.env.AIKIDO_ENDPOINT) { 5 | return new URL(process.env.AIKIDO_ENDPOINT); 6 | } 7 | 8 | const region = extractRegionFromToken(process.env.AIKIDO_TOKEN || ""); 9 | 10 | if (region === "US") { 11 | return new URL("https://guard.us.aikido.dev"); 12 | } 13 | 14 | if (region === "ME") { 15 | return new URL("https://guard.me.aikido.dev"); 16 | } 17 | 18 | return new URL("https://guard.aikido.dev"); 19 | } 20 | -------------------------------------------------------------------------------- /library/agent/hooks/wrapDefaultOrNamed.ts: -------------------------------------------------------------------------------- 1 | import { createWrappedFunction, wrap } from "../../helpers/wrap"; 2 | 3 | /** 4 | * This function allows to wrap a default export or a named export. 5 | * If the name is undefined, it will wrap the default export of a module. 6 | */ 7 | export function wrapDefaultOrNamed( 8 | module: any, 9 | name: string | undefined, 10 | wrapper: (original: Function) => Function 11 | ) { 12 | if (typeof name === "undefined") { 13 | return createWrappedFunction(module, wrapper); 14 | } 15 | return wrap(module, name, wrapper); 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/config/cors.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@adonisjs/cors"; 2 | 3 | /** 4 | * Configuration options to tweak the CORS policy. The following 5 | * options are documented on the official documentation website. 6 | * 7 | * https://docs.adonisjs.com/guides/security/cors 8 | */ 9 | const corsConfig = defineConfig({ 10 | enabled: true, 11 | origin: true, 12 | methods: ["GET", "HEAD", "POST", "PUT", "DELETE"], 13 | headers: true, 14 | exposeHeaders: [], 15 | credentials: true, 16 | maxAge: 90, 17 | }); 18 | 19 | export default corsConfig; 20 | -------------------------------------------------------------------------------- /sample-apps/express-postgres-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-postgres-esm", 3 | "version": "1.0.0", 4 | "description": "A vulnerable app to test out SQL Injection with postgres", 5 | "main": "app.js", 6 | "private": true, 7 | "type": "module", 8 | "scripts": { 9 | "start": "node --import @aikidosec/firewall/instrument ./app.js" 10 | }, 11 | "dependencies": { 12 | "@aikidosec/firewall": "file:../../build", 13 | "dotenv": "^16.4.1", 14 | "express": "^4.19.2", 15 | "express-async-handler": "^1.2.0", 16 | "pg": "^8.11.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /library/helpers/trustProxy.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { trustProxy } from "./trustProxy"; 3 | 4 | t.beforeEach(() => { 5 | delete process.env.AIKIDO_TRUST_PROXY; 6 | }); 7 | 8 | t.test("the default is true", async () => { 9 | t.equal(trustProxy(), true); 10 | }); 11 | 12 | t.test("trust proxy set to false", async () => { 13 | process.env.AIKIDO_TRUST_PROXY = "false"; 14 | t.equal(trustProxy(), false); 15 | }); 16 | 17 | t.test("trust proxy set to true", async () => { 18 | process.env.AIKIDO_TRUST_PROXY = "true"; 19 | t.equal(trustProxy(), true); 20 | }); 21 | -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/config/database.ts: -------------------------------------------------------------------------------- 1 | import app from "@adonisjs/core/services/app"; 2 | import { defineConfig } from "@adonisjs/lucid"; 3 | 4 | const dbConfig = defineConfig({ 5 | connection: "sqlite", 6 | connections: { 7 | sqlite: { 8 | client: "better-sqlite3", 9 | connection: { 10 | filename: app.tmpPath("db.sqlite3"), 11 | }, 12 | useNullAsDefault: true, 13 | migrations: { 14 | naturalSort: true, 15 | paths: ["database/migrations"], 16 | }, 17 | }, 18 | }, 19 | }); 20 | 21 | export default dbConfig; 22 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3-esm/db.js: -------------------------------------------------------------------------------- 1 | import sqlite3 from "sqlite3"; 2 | 3 | /** @type {sqlite3.Database} */ 4 | let db; 5 | 6 | export async function getDB() { 7 | if (db) { 8 | return db; 9 | } 10 | 11 | db = new sqlite3.Database(":memory:"); 12 | 13 | await new Promise((resolve, reject) => { 14 | db.run( 15 | `CREATE TABLE cats ( 16 | petname TEXT 17 | );`, 18 | (err) => { 19 | if (err) { 20 | return reject(err); 21 | } 22 | resolve(); 23 | } 24 | ); 25 | }); 26 | 27 | return db; 28 | } 29 | -------------------------------------------------------------------------------- /library/agent/hooks/instrumentation/checkHooks.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { checkHooks } from "./checkHooks"; 3 | import { wrap } from "../../../helpers/wrap"; 4 | 5 | const logs: string[] = []; 6 | wrap(console, "warn", function warn() { 7 | return function warn(...args: string[]) { 8 | logs.push(...args); 9 | }; 10 | }); 11 | 12 | t.test("it works", async (t) => { 13 | await checkHooks(); 14 | 15 | t.same(logs, [ 16 | "AIKIDO: A self check of the code instrumentation failed. This means that the protection might not work as expected.", 17 | ]); 18 | }); 19 | -------------------------------------------------------------------------------- /library/helpers/mapIPv4ToIPv6.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Maps an IPv4 address to an IPv6 address. 3 | * e.g. 127.0.0.0/8 -> ::ffff:127.0.0.0/104 4 | */ 5 | export default function mapIPv4ToIPv6(ip: string): string { 6 | if (!ip.includes("/")) { 7 | // No CIDR suffix, assume /32 8 | return `::ffff:${ip}/128`; 9 | } 10 | 11 | const parts = ip.split("/"); 12 | const suffix = Number.parseInt(parts[1], 10); 13 | // we add 96 to the suffix, since ::ffff: already is 96 bits, so the 32 remaining bits are decided by the IPv4 address 14 | return `::ffff:${parts[0]}/${suffix + 96}`; 15 | } 16 | -------------------------------------------------------------------------------- /sample-apps/strapi/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | /** 5 | * An asynchronous register function that runs before 6 | * your application is initialized. 7 | * 8 | * This gives you an opportunity to extend code. 9 | */ 10 | register(/*{ strapi }*/) {}, 11 | 12 | /** 13 | * An asynchronous bootstrap function that runs before 14 | * your application gets started. 15 | * 16 | * This gives you an opportunity to set up your data model, 17 | * run jobs, or perform some special logic. 18 | */ 19 | bootstrap(/*{ strapi }*/) {}, 20 | }; 21 | -------------------------------------------------------------------------------- /sample-apps/react2shell-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint" 10 | }, 11 | "dependencies": { 12 | "@aikidosec/firewall": "file:../../build", 13 | "next": "16.0.6", 14 | "react": "19.2.1", 15 | "react-dom": "19.2.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^19", 20 | "@types/react-dom": "^19", 21 | "typescript": "^5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /library/agent/AgentSingleton.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from "./Agent"; 2 | 3 | let instance: Agent | undefined = undefined; 4 | 5 | /** 6 | * This module is a storage unit, this function retrieves the stored value 7 | * @returns The current instance stored in the instance variable 8 | */ 9 | export function getInstance() { 10 | return instance; 11 | } 12 | 13 | /** 14 | * This module is a storage unit, this function sets the stored value 15 | * @param agent The agent you want stored as the current instance. 16 | */ 17 | export function setInstance(agent: Agent) { 18 | instance = agent; 19 | } 20 | -------------------------------------------------------------------------------- /library/agent/api-discovery/helpers/isDateString.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the string is a date according to RFC3339 3 | * https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 4 | */ 5 | export default function isDateString(str: string): boolean { 6 | if (str.length !== 10) { 7 | return false; 8 | } 9 | 10 | if (!/^\d{4}-\d{2}-\d{2}$/.test(str)) { 11 | return false; 12 | } 13 | 14 | const [year, month, day] = str.split("-").map(Number); 15 | 16 | if (month < 1 || month > 12 || day < 1 || day > 31 || year < 0) { 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | -------------------------------------------------------------------------------- /library/sources/graphql/extractInputsFromDocument.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode, StringValueNode } from "graphql"; 2 | 3 | /** 4 | * This function extracts user inputs (that could be harmful) from a GraphQL document. 5 | * @returns An array of user inputs. 6 | */ 7 | export function extractInputsFromDocument( 8 | document: DocumentNode, 9 | visitFn: typeof import("graphql").visit 10 | ): string[] { 11 | const inputs: string[] = []; 12 | visitFn(document, { 13 | StringValue(node: StringValueNode) { 14 | inputs.push(node.value); 15 | }, 16 | }); 17 | 18 | return inputs; 19 | } 20 | -------------------------------------------------------------------------------- /sample-apps/micro/index.js: -------------------------------------------------------------------------------- 1 | const { send, json } = require("micro"); 2 | 3 | module.exports = async (req, res) => { 4 | if (req.method !== "POST") { 5 | return send(res, 405, { 6 | message: "Method Not Allowed", 7 | }); 8 | } 9 | 10 | const body = await json(req); 11 | 12 | if (!body.url) { 13 | return send(res, 400, { 14 | message: "Missing URL in request body", 15 | }); 16 | } 17 | 18 | const data = await (await fetch(body.url)).arrayBuffer(); 19 | res.setHeader("Content-Type", "image/jpeg"); 20 | res.statusCode = 200; 21 | res.end(Buffer.from(data)); 22 | }; 23 | -------------------------------------------------------------------------------- /library/agent/hooks/BuiltinModule.ts: -------------------------------------------------------------------------------- 1 | import { RequireInterceptor } from "./RequireInterceptor"; 2 | 3 | export class BuiltinModule { 4 | private requireInterceptors: RequireInterceptor[] = []; 5 | 6 | constructor(private readonly name: string) { 7 | if (!this.name) { 8 | throw new Error("Name is required"); 9 | } 10 | } 11 | 12 | getName() { 13 | return this.name; 14 | } 15 | 16 | onRequire(interceptor: RequireInterceptor) { 17 | this.requireInterceptors.push(interceptor); 18 | } 19 | 20 | getRequireInterceptors() { 21 | return this.requireInterceptors; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample-apps/hono-xml/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require("mysql2/promise"); 2 | 3 | async function createConnection() { 4 | // Normally you'd use environment variables for this 5 | const connection = await mysql.createConnection({ 6 | host: "localhost", 7 | user: "root", 8 | password: "mypassword", 9 | database: "catsdb", 10 | port: 27015, 11 | multipleStatements: true, 12 | }); 13 | 14 | await connection.execute( 15 | `CREATE TABLE IF NOT EXISTS cats (petname varchar(255));` 16 | ); 17 | 18 | return connection; 19 | } 20 | 21 | module.exports = { 22 | createConnection, 23 | }; 24 | -------------------------------------------------------------------------------- /sample-apps/test-exports/tsconfig.node24.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 24", 4 | "_version": "24.0.0", 5 | "compilerOptions": { 6 | "lib": [ 7 | "es2024", 8 | "ESNext.Array", 9 | "ESNext.Collection", 10 | "ESNext.Iterator", 11 | "ESNext.Promise" 12 | ], 13 | "module": "nodenext", 14 | "target": "es2024", 15 | "outDir": "./dist24", 16 | "strict": true, 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "moduleResolution": "nodenext" 20 | }, 21 | "include": ["test.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /library/agent/hooks/wrapExport.noAgent.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { wrapExport } from "./wrapExport"; 3 | 4 | t.test("Agent is not initialized", async (t) => { 5 | try { 6 | wrapExport( 7 | {}, 8 | "test", 9 | { name: "test", type: "external" }, 10 | { 11 | kind: "outgoing_http_op", 12 | inspectArgs: () => {}, 13 | } 14 | ); 15 | t.fail(); 16 | } catch (e: unknown) { 17 | t.ok(e instanceof Error); 18 | if (e instanceof Error) { 19 | t.same(e.message, "Can not wrap exports if agent is not initialized"); 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /sample-apps/express-path-traversal/README.md: -------------------------------------------------------------------------------- 1 | # express-path-traversal 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-path-traversal` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all documents 10 | - http://localhost:4000/?filename=/Test.txt&content=asdf : This will add a new file named Test.txt 11 | - http://localhost:4000/?filename=/../NotAllowed.txt&content=naughty This will add a file in an unsafe file location 12 | -------------------------------------------------------------------------------- /sample-apps/hono-mysql2-new/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require("mysql2/promise"); 2 | 3 | async function createConnection() { 4 | // Normally you'd use environment variables for this 5 | const connection = await mysql.createConnection({ 6 | host: "localhost", 7 | user: "root", 8 | password: "mypassword", 9 | database: "catsdb", 10 | port: 27015, 11 | multipleStatements: true, 12 | }); 13 | 14 | await connection.execute( 15 | `CREATE TABLE IF NOT EXISTS cats_2 (petname varchar(255));` 16 | ); 17 | 18 | return connection; 19 | } 20 | 21 | module.exports = { 22 | createConnection, 23 | }; 24 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3/db.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require("sqlite3"); 2 | 3 | /** @type {sqlite3.Database} */ 4 | let db; 5 | 6 | async function getDB() { 7 | if (db) { 8 | return db; 9 | } 10 | 11 | db = new sqlite3.Database(":memory:"); 12 | 13 | await new Promise((resolve, reject) => { 14 | db.run( 15 | `CREATE TABLE cats ( 16 | petname TEXT 17 | );`, 18 | (err) => { 19 | if (err) { 20 | return reject(err); 21 | } 22 | resolve(); 23 | } 24 | ); 25 | }); 26 | 27 | return db; 28 | } 29 | 30 | module.exports = { 31 | getDB, 32 | }; 33 | -------------------------------------------------------------------------------- /sample-apps/http2/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http2", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "http2", 8 | "dependencies": { 9 | "@aikidosec/firewall": "file:../../build" 10 | } 11 | }, 12 | "../../build": { 13 | "name": "@aikidosec/firewall", 14 | "version": "0.0.0", 15 | "license": "AGPL-3.0-or-later", 16 | "engines": { 17 | "node": ">=16" 18 | } 19 | }, 20 | "node_modules/@aikidosec/firewall": { 21 | "resolved": "../../build", 22 | "link": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample-apps/koa-sqlite3/db.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require("sqlite3"); 2 | 3 | /** @type {sqlite3.Database} */ 4 | let db; 5 | 6 | async function getDB() { 7 | if (db) { 8 | return db; 9 | } 10 | 11 | db = new sqlite3.Database(":memory:"); 12 | 13 | await new Promise((resolve, reject) => { 14 | db.run( 15 | `CREATE TABLE cats ( 16 | petname TEXT 17 | );`, 18 | (err) => { 19 | if (err) { 20 | return reject(err); 21 | } 22 | resolve(); 23 | } 24 | ); 25 | }); 26 | 27 | return db; 28 | } 29 | 30 | module.exports = { 31 | getDB, 32 | }; 33 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/src/Cats.js: -------------------------------------------------------------------------------- 1 | import { createConnection } from "@/db"; 2 | 3 | export class Cats { 4 | async add(name) { 5 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 6 | const conn = await createConnection(); 7 | await conn.query(`INSERT INTO cats (petname) VALUES ('${name}');`); 8 | await conn.end(); 9 | } 10 | 11 | async getAll() { 12 | const conn = await createConnection(); 13 | const cats = await conn.query("SELECT petname FROM cats;"); 14 | await conn.end(); 15 | 16 | return cats.rows.map((row) => row.petname); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/src/app/cats/route.js: -------------------------------------------------------------------------------- 1 | import { Cats } from "@/Cats"; 2 | 3 | export async function GET(request) { 4 | const cats = new Cats(); 5 | 6 | return Response.json(await cats.getAll()); 7 | } 8 | 9 | export async function POST(request) { 10 | const cats = new Cats(); 11 | const json = await request.json(); 12 | 13 | if (!json.name) { 14 | return Response.json({ success: false, error: "Missing name" }); 15 | } 16 | 17 | // This is a SQL injection vulnerability. Do not use this in production. 18 | await cats.add(json.name); 19 | 20 | return Response.json({ success: true }); 21 | } 22 | -------------------------------------------------------------------------------- /library/agent/api-discovery/helpers/isDateString.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import isDateString from "./isDateString"; 3 | 4 | t.test("it is a date string", async (t) => { 5 | t.same(isDateString("2021-01-01"), true); 6 | t.same(isDateString("2021-12-31"), true); 7 | }); 8 | 9 | t.test("it is not a date string", async (t) => { 10 | t.same(isDateString("2021-01-32"), false); 11 | t.same(isDateString("-2021-01-20"), false); 12 | t.same(isDateString(""), false); 13 | t.same(isDateString("2021-01-01T00:00:00Z"), false); 14 | t.same(isDateString("01"), false); 15 | t.same(isDateString("2001-ab-02"), false); 16 | }); 17 | -------------------------------------------------------------------------------- /sample-apps/strapi/.env: -------------------------------------------------------------------------------- 1 | 2 | # Server 3 | HOST=0.0.0.0 4 | PORT=1337 5 | 6 | # Secrets 7 | APP_KEYS=VNeiRYbjO59wTIBiQjxojw==,8VwQJ8/OPgQcBIM/nFwotA==,b4EdXjheFY/lpKobm8/dWQ==,OISHRQRX0SbbMCoojcCmoA== 8 | API_TOKEN_SALT=2tO5RuOGrckUxWvTM99IAg== 9 | ADMIN_JWT_SECRET=D/fmwAwHkRbpJl/2P1cukw== 10 | TRANSFER_TOKEN_SALT=HPBo2W9yp5gVgKbGGsFRHg== 11 | 12 | # Database 13 | DATABASE_CLIENT=sqlite 14 | DATABASE_HOST= 15 | DATABASE_PORT= 16 | DATABASE_NAME= 17 | DATABASE_USERNAME= 18 | DATABASE_PASSWORD= 19 | DATABASE_SSL=false 20 | DATABASE_FILENAME=.tmp/data.db 21 | JWT_SECRET=cloUcmCbXwbgcHcCbMA02g== 22 | 23 | # Disable browser 24 | BROWSER=false 25 | -------------------------------------------------------------------------------- /library/helpers/featureFlags.ts: -------------------------------------------------------------------------------- 1 | import { isUnitTest } from "./isUnitTest"; 2 | 3 | const envPrefix = "AIKIDO_FEATURE_"; 4 | 5 | /** 6 | * Check if a feature that is behind a feature flag is enabled 7 | * This function is case-insensitive. 8 | * All feature flags are enabled by default in unit tests (using tap). 9 | */ 10 | export function isFeatureEnabled(feature: string): boolean { 11 | // Always enable features in tests / ci 12 | if (isUnitTest()) { 13 | return true; 14 | } 15 | 16 | const envVar = `${envPrefix}${feature.toUpperCase()}`; 17 | 18 | return process.env[envVar] === "true" || process.env[envVar] === "1"; 19 | } 20 | -------------------------------------------------------------------------------- /library/helpers/getFileExtension.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { getFileExtension } from "./getFileExtension"; 3 | 4 | t.test("it works", async () => { 5 | t.equal(getFileExtension("file.txt"), "txt"); 6 | t.equal(getFileExtension("file.tar.gz"), "gz"); 7 | t.equal(getFileExtension(".file.txt"), "txt"); 8 | t.equal(getFileExtension("font.woff2"), "woff2"); 9 | t.equal(getFileExtension("/path/to/file.txt"), "txt"); 10 | 11 | t.equal(getFileExtension("file"), ""); 12 | t.equal(getFileExtension("file."), ""); 13 | t.equal(getFileExtension("file.."), ""); 14 | t.equal(getFileExtension("file.txt."), ""); 15 | }); 16 | -------------------------------------------------------------------------------- /library/helpers/getSourceForUserString.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../agent/Context"; 2 | import { Source, SOURCES } from "../agent/Source"; 3 | import { extractStringsFromUserInput } from "./extractStringsFromUserInput"; 4 | 5 | export function getSourceForUserString( 6 | context: Context, 7 | str: string 8 | ): Source | undefined { 9 | for (const source of SOURCES) { 10 | if (!context[source]) { 11 | continue; 12 | } 13 | 14 | const userStrings = extractStringsFromUserInput(context[source]); 15 | 16 | if (userStrings.has(str)) { 17 | return source; 18 | } 19 | } 20 | 21 | return undefined; 22 | } 23 | -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/config/hash.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, drivers } from "@adonisjs/core/hash"; 2 | 3 | const hashConfig = defineConfig({ 4 | default: "scrypt", 5 | 6 | list: { 7 | scrypt: drivers.scrypt({ 8 | cost: 16384, 9 | blockSize: 8, 10 | parallelization: 1, 11 | maxMemory: 33554432, 12 | }), 13 | }, 14 | }); 15 | 16 | export default hashConfig; 17 | 18 | /** 19 | * Inferring types for the list of hashers you have configured 20 | * in your application. 21 | */ 22 | declare module "@adonisjs/core/types" { 23 | export interface HashersList extends InferHashers {} 24 | } 25 | -------------------------------------------------------------------------------- /sample-apps/fastify-mysql2/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require("mysql2/promise"); 2 | 3 | async function createConnection() { 4 | // Normally you'd use environment variables for this 5 | const connection = await mysql.createConnection({ 6 | host: "localhost", 7 | user: "root", 8 | password: "mypassword", 9 | database: "catsdb", 10 | port: 27015, 11 | multipleStatements: true, 12 | }); 13 | 14 | await connection.execute(` 15 | CREATE TABLE IF NOT EXISTS cats ( 16 | petname varchar(255) 17 | ); 18 | `); 19 | 20 | return connection; 21 | } 22 | 23 | module.exports = { 24 | createConnection, 25 | }; 26 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/app/.server/db.ts: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | const { Client } = pg; 3 | 4 | let client: pg.Client | null = null; 5 | 6 | export async function getConnection() { 7 | if (client) { 8 | return client; 9 | } 10 | 11 | client = new Client({ 12 | user: "root", 13 | host: "127.0.0.1", 14 | database: "main_db", 15 | password: "password", 16 | port: 27016, 17 | }); 18 | 19 | await client.connect(); 20 | await client.query(` 21 | CREATE TABLE IF NOT EXISTS cats_5 ( 22 | petname varchar(255), 23 | comment varchar(255) 24 | ); 25 | `); 26 | 27 | return client; 28 | } 29 | -------------------------------------------------------------------------------- /benchmarks/operations/nosqli.js: -------------------------------------------------------------------------------- 1 | const { MongoClient } = require("mongodb"); 2 | 3 | const client = new MongoClient("mongodb://root:password@127.0.0.1:27017"); 4 | 5 | let users; 6 | 7 | module.exports = { 8 | setup: async function setup() { 9 | await client.connect(); 10 | const db = client.db("test"); 11 | users = db.collection("users"); 12 | await users.insertOne({ email: "john@acme.com", password: "password" }); 13 | }, 14 | step: async function step() { 15 | await users.find({ email: "john@acme.com", password: "password" }); 16 | }, 17 | teardown: async function teardown() { 18 | await client.close(); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /library/sources/koa/isDeprecatedGenerator.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | /** 3 | * Checks if a function is a generator function. 4 | * They are deprecated in koa v2 and are already removed in the main branch (will be completely removed in v3). 5 | */ 6 | export function isDeprecatedGenerator(fn: Function): boolean { 7 | const GeneratorFunction = function* () {}.constructor; 8 | 9 | if (fn instanceof GeneratorFunction) { 10 | return true; 11 | } 12 | 13 | const AsyncGeneratorFunction = async function* () {}.constructor; 14 | if (fn instanceof AsyncGeneratorFunction) { 15 | return true; 16 | } 17 | 18 | return false; 19 | } 20 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | The Aikido team and community take security bugs in Zen seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, register on Intigriti and navigate to https://app.intigriti.com/researcher/programs/aikido/aikidoruntime. 6 | 7 | The Intigriti team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 8 | -------------------------------------------------------------------------------- /library/agent/api/ReportingAPI.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "../Config"; 2 | import { Event } from "./Event"; 3 | import { Token } from "./Token"; 4 | 5 | type ReportingAPIError = 6 | | "timeout" 7 | | "unknown_error" 8 | | "rate_limited" 9 | | "max_attacks_reached" 10 | | "invalid_token"; 11 | 12 | export type ReportingAPIResponse = 13 | | ({ 14 | success: true; 15 | } & Config) 16 | | { 17 | success: false; 18 | error: ReportingAPIError; 19 | }; 20 | 21 | export interface ReportingAPI { 22 | report( 23 | token: Token, 24 | event: Event, 25 | timeoutInMS: number 26 | ): Promise; 27 | } 28 | -------------------------------------------------------------------------------- /library/agent/hooks/isBuiltinModule.ts: -------------------------------------------------------------------------------- 1 | import * as mod from "module"; 2 | import { removeNodePrefix } from "../../helpers/removeNodePrefix"; 3 | 4 | // Added in Node.js v9.3.0, v8.10.0, v6.13.0 5 | const moduleList = mod.builtinModules; 6 | 7 | /** 8 | * Returns true if the module is a builtin module, otherwise false. 9 | */ 10 | export function isBuiltinModule(moduleName: string) { 11 | // Added in Node.js v18.6.0, v16.17.0 12 | if (typeof mod.isBuiltin === "function") { 13 | return mod.isBuiltin(moduleName); 14 | } 15 | 16 | // The modulelist does not include the node: prefix 17 | return moduleList.includes(removeNodePrefix(moduleName)); 18 | } 19 | -------------------------------------------------------------------------------- /library/helpers/indexImportGuard.ts: -------------------------------------------------------------------------------- 1 | const symbol = Symbol.for("zen.nodejs.index.run.once"); 2 | 3 | export function checkIndexImportGuard(): boolean { 4 | const globalState = globalThis as typeof globalThis & { 5 | [symbol]: boolean; 6 | }; 7 | 8 | if (globalState[symbol]) { 9 | // eslint-disable-next-line no-console 10 | console.error( 11 | "AIKIDO: Zen has already been initialized. This can lead to unexpected behavior and may be caused by cleaning the require cache or using multiple installations of Zen at the same time." 12 | ); 13 | 14 | return false; 15 | } 16 | 17 | globalState[symbol] = true; 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /library/sources/hono/wrapRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import type { Handler, MiddlewareHandler } from "hono"; 2 | import { runWithContext } from "../../agent/Context"; 3 | import { contextFromRequest } from "./contextFromRequest"; 4 | import { wrapRequestBodyParsing } from "./wrapRequestBodyParsing"; 5 | 6 | export function wrapRequestHandler( 7 | handler: Handler | MiddlewareHandler 8 | ): MiddlewareHandler { 9 | return async (c, next) => { 10 | const context = contextFromRequest(c); 11 | 12 | return await runWithContext(context, async () => { 13 | wrapRequestBodyParsing(c.req); 14 | 15 | return await handler(c, next); 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /sample-apps/express-mariadb/Cats.js: -------------------------------------------------------------------------------- 1 | class Cats { 2 | constructor(pool) { 3 | this.pool = pool; 4 | } 5 | 6 | async add(name) { 7 | const conn = await this.pool.getConnection(); 8 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 9 | await conn.query(`INSERT INTO cats (petname) VALUES ('${name}');`); 10 | conn.end(); 11 | } 12 | 13 | async getAll() { 14 | const conn = await this.pool.getConnection(); 15 | const cats = await conn.query("SELECT petname FROM cats;"); 16 | conn.end(); 17 | 18 | return cats.map((row) => row.petname); 19 | } 20 | } 21 | 22 | module.exports = Cats; 23 | -------------------------------------------------------------------------------- /sample-apps/nextjs-standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-standalone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "AIKIDO_BLOCKING=true AIKIDO_DEBUG=true NODE_OPTIONS='-r @aikidosec/firewall' next dev -p 4000", 7 | "build": "next build", 8 | "start": "AIKIDO_BLOCKING=true AIKIDO_DEBUG=true NODE_OPTIONS='-r @aikidosec/firewall' next start -p 4000", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@aikidosec/firewall": "file:../../build", 13 | "next": "14.2.33", 14 | "pg": "^8.11.3", 15 | "react": "^18", 16 | "react-dom": "^18", 17 | "swr": "^2.2.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample-apps/restify-postgres/README.md: -------------------------------------------------------------------------------- 1 | # restify-postgres 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app restify-postgres` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | 13 | This sample app demonstrates SQL injection vulnerabilities using Restify as the web framework. 14 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": false, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /library/agent/exports.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import * as pkg from "../index"; 3 | 4 | t.test("has named and default exports", async (t) => { 5 | t.same(typeof pkg, "object"); 6 | t.same(typeof pkg.default, "object"); 7 | 8 | t.same( 9 | Object.keys(pkg).length - 1, 10 | Object.keys(pkg.default).length, 11 | "same number of named exports and default exports" 12 | ); 13 | 14 | for (const key of Object.keys(pkg)) { 15 | if (key === "default") continue; 16 | t.same( 17 | pkg[key as keyof typeof pkg], 18 | pkg.default[key as keyof typeof pkg.default], 19 | `export ${key} is the same` 20 | ); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /library/helpers/getPackageVersion.ts: -------------------------------------------------------------------------------- 1 | import { getPackageVersionFromPath } from "../agent/hooks/instrumentation/getPackageVersionFromPath"; 2 | import { getModuleInfoFromPath } from "../agent/hooks/getModuleInfoFromPath"; 3 | 4 | /** 5 | * Get the installed version of a package 6 | */ 7 | export function getPackageVersion(pkg: string): string | undefined { 8 | try { 9 | const entrypoint = require.resolve(pkg); 10 | 11 | const moduleInfo = getModuleInfoFromPath(entrypoint); 12 | if (!moduleInfo) { 13 | return undefined; 14 | } 15 | 16 | return getPackageVersionFromPath(moduleInfo.base); 17 | } catch { 18 | return undefined; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample-apps/express-mongodb/README.md: -------------------------------------------------------------------------------- 1 | # express-mongodb 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app express-mongodb` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ add a few posts 10 | - http://localhost:4000/?search=title search for posts with title 11 | - http://localhost:4000/?search[$ne]=null will abuse the vulnerable parameter to return all posts 12 | - http://localhost:4000/images?url=http://localhost:80 will vulnerable parameter to fetch an image from a private server 13 | -------------------------------------------------------------------------------- /library/helpers/featureFlags.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { isFeatureEnabled } from "./featureFlags"; 3 | 4 | t.test("isFeatureEnabled", async (t) => { 5 | t.equal(isFeatureEnabled("foo"), true); 6 | process.env.AIKIDO_UNIT_TESTS = "0"; 7 | t.equal(isFeatureEnabled("foo"), false); 8 | process.env.AIKIDO_FEATURE_FOO = "true"; 9 | t.equal(isFeatureEnabled("foo"), true); 10 | process.env.AIKIDO_FEATURE_FOO = "1"; 11 | t.equal(isFeatureEnabled("foo"), true); 12 | process.env.AIKIDO_FEATURE_FOO = "false"; 13 | t.equal(isFeatureEnabled("foo"), false); 14 | process.env.AIKIDO_UNIT_TESTS = "1"; 15 | t.equal(isFeatureEnabled("foo"), true); 16 | }); 17 | -------------------------------------------------------------------------------- /library/helpers/isHTTPAuthScheme.ts: -------------------------------------------------------------------------------- 1 | const AUTH_SCHEMES = [ 2 | "basic", 3 | "bearer", 4 | "digest", 5 | "dpop", 6 | "gnap", 7 | "hoba", 8 | "mutal", 9 | "negotiate", 10 | "privatetoken", 11 | "scram-sha-1", 12 | "scram-sha-256", 13 | "vapid", 14 | ] as const; 15 | export type HTTPAuthScheme = (typeof AUTH_SCHEMES)[number]; 16 | 17 | /** 18 | * Checks if a string is a valid HTTP authentication scheme. 19 | * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml 20 | */ 21 | export function isHTTPAuthScheme(scheme: string): scheme is HTTPAuthScheme { 22 | return AUTH_SCHEMES.includes(scheme.toLowerCase() as HTTPAuthScheme); 23 | } 24 | -------------------------------------------------------------------------------- /library/sources/hapi/wrapRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import type { Lifecycle } from "@hapi/hapi"; 2 | import { runWithContext } from "../../agent/Context"; 3 | import { contextFromRequest } from "./contextFromRequest"; 4 | 5 | export function wrapRequestHandler( 6 | handler: Lifecycle.Method 7 | ): Lifecycle.Method { 8 | // oxlint-disable-next-line require-await 9 | return async (request, h) => { 10 | const context = contextFromRequest(request); 11 | 12 | return runWithContext(context, () => { 13 | return handler.apply( 14 | // @ts-expect-error We don't now the type of this 15 | this, 16 | [request, h] 17 | ); 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/app/middleware/force_json_response_middleware.ts: -------------------------------------------------------------------------------- 1 | import type { HttpContext } from "@adonisjs/core/http"; 2 | import type { NextFn } from "@adonisjs/core/types/http"; 3 | 4 | /** 5 | * Updating the "Accept" header to always accept "application/json" response 6 | * from the server. This will force the internals of the framework like 7 | * validator errors or auth errors to return a JSON response. 8 | */ 9 | export default class ForceJsonResponseMiddleware { 10 | async handle({ request }: HttpContext, next: NextFn) { 11 | const headers = request.headers(); 12 | headers.accept = "application/json"; 13 | 14 | return next(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/README.md: -------------------------------------------------------------------------------- 1 | # react-router-pg Sample App 2 | 3 | WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code). 4 | 5 | In the root directory run `npm run sample-app react-router-pg` to start the server. 6 | 7 | Try the following URLs: 8 | 9 | - http://localhost:4000/ : List all cats 10 | - http://localhost:4000/?petname=Kitty : This will add a new cat named "Kitty" 11 | - http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H : This will delete all cats 12 | 13 | This sample app demonstrates SQL injection vulnerabilities using React Router as the web framework. 14 | -------------------------------------------------------------------------------- /library/agent/api-discovery/helpers/isUUIDString.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import isUUIDString from "./isUUIDString"; 3 | 4 | t.test("it is a uuid", async (t) => { 5 | t.same(isUUIDString("550e8400-e29b-41d4-a716-446655440000"), true); 6 | t.same(isUUIDString("00000000-0000-0000-0000-000000000000"), true); 7 | t.same(isUUIDString("ffffffff-ffff-ffff-ffff-ffffffffffff"), true); 8 | }); 9 | 10 | t.test("it is not a uuid", async (t) => { 11 | t.same(isUUIDString(""), false); 12 | t.same(isUUIDString("abc"), false); 13 | t.same(isUUIDString("550e8400-e29b-41d4-a716-44665544000"), false); 14 | t.same(isUUIDString("550e8400-e29b-41d4-a716-4466554400000"), false); 15 | }); 16 | -------------------------------------------------------------------------------- /library/agent/realtime/getConfig.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from "../../helpers/fetch"; 2 | import { Token } from "../api/Token"; 3 | import { Config } from "../Config"; 4 | import { getAPIURL } from "../getAPIURL"; 5 | 6 | export async function getConfig(token: Token): Promise { 7 | const { body, statusCode } = await fetch({ 8 | url: new URL(`${getAPIURL().toString()}api/runtime/config`), 9 | method: "GET", 10 | headers: { 11 | Authorization: token.asString(), 12 | }, 13 | timeoutInMS: 3000, 14 | }); 15 | 16 | if (statusCode !== 200) { 17 | throw new Error(`Invalid response (${statusCode}): ${body}`); 18 | } 19 | 20 | return JSON.parse(body); 21 | } 22 | -------------------------------------------------------------------------------- /library/vulnerabilities/attack-wave-detection/isWebScanner.ts: -------------------------------------------------------------------------------- 1 | import { type Context } from "../../agent/Context"; 2 | import { queryParamsContainDangerousPayload } from "./queryParamsContainDangerousPayload"; 3 | import { isWebScanMethod } from "./isWebScanMethod"; 4 | import { isWebScanPath } from "./isWebScanPath"; 5 | 6 | export function isWebScanner(context: Context): boolean { 7 | if (context.method && isWebScanMethod(context.method)) { 8 | return true; 9 | } 10 | 11 | if (context.route && isWebScanPath(context.route)) { 12 | return true; 13 | } 14 | 15 | if (queryParamsContainDangerousPayload(context)) { 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | -------------------------------------------------------------------------------- /sample-apps/test-exports/test.js: -------------------------------------------------------------------------------- 1 | const assert = require("node:assert"); 2 | 3 | const Zen = require("@aikidosec/firewall"); 4 | const context = require("@aikidosec/firewall/context"); 5 | const lambda = require("@aikidosec/firewall/lambda"); 6 | const cloudFunction = require("@aikidosec/firewall/cloud-function"); 7 | const bundler = require("@aikidosec/firewall/bundler"); 8 | 9 | assert.ok(typeof Zen.addExpressMiddleware === "function"); 10 | assert.ok(typeof Zen.setUser === "function"); 11 | assert.ok(typeof context.setUser === "function"); 12 | assert.ok(typeof lambda === "function"); 13 | assert.ok(typeof cloudFunction === "function"); 14 | assert.ok(Array.isArray(bundler.externals())); 15 | -------------------------------------------------------------------------------- /end2end/server/src/middleware/checkToken.ts: -------------------------------------------------------------------------------- 1 | import type { Response, NextFunction } from "express"; 2 | import { getByToken } from "../zen/apps.ts"; 3 | import type { ZenRequest } from "../types.ts"; 4 | 5 | export function checkToken(req: ZenRequest, res: Response, next: NextFunction) { 6 | const token = req.headers["authorization"] as string | undefined; 7 | 8 | if (!token) { 9 | return res.status(401).json({ 10 | message: "Token is required", 11 | }); 12 | } 13 | 14 | const app = getByToken(token); 15 | if (!app) { 16 | return res.status(401).json({ 17 | message: "Invalid token", 18 | }); 19 | } 20 | 21 | req.zenApp = app; 22 | 23 | next(); 24 | } 25 | -------------------------------------------------------------------------------- /library/agent/api/FetchListsAPIForTesting.ts: -------------------------------------------------------------------------------- 1 | import { FetchListsAPI, FetchListsAPIResponse } from "./FetchListsAPI"; 2 | import type { Token } from "./Token"; 3 | 4 | export class FetchListsAPIForTesting implements FetchListsAPI { 5 | constructor( 6 | private response: FetchListsAPIResponse = { 7 | blockedIPAddresses: [], 8 | allowedIPAddresses: [], 9 | monitoredIPAddresses: [], 10 | blockedUserAgents: "", 11 | monitoredUserAgents: "", 12 | userAgentDetails: [], 13 | } 14 | ) {} 15 | 16 | // oxlint-disable-next-line require-await 17 | async getLists(_token: Token): Promise { 18 | return this.response; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/helpers/getNodeVersion.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { 3 | getMajorNodeVersion, 4 | getMinorNodeVersion, 5 | getSemverNodeVersion, 6 | } from "./getNodeVersion"; 7 | 8 | t.test("getMajorNodeVersion", async (t) => { 9 | t.ok(typeof getMajorNodeVersion() === "number"); 10 | }); 11 | 12 | t.test("getMinorNodeVersion", async (t) => { 13 | t.ok(typeof getMinorNodeVersion() === "number"); 14 | }); 15 | 16 | t.test("getSemverNodeVersion", async (t) => { 17 | const parts = getSemverNodeVersion().split("."); 18 | t.ok(parts.length === 3); 19 | t.notOk(parts.includes("v")); 20 | parts.forEach((part) => { 21 | t.ok(parseInt(part, 10) >= 0); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /sample-apps/test-exports/test.ts: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | 3 | import Zen from "@aikidosec/firewall"; 4 | import context from "@aikidosec/firewall/context"; 5 | import lambda from "@aikidosec/firewall/lambda"; 6 | import cloudFunction from "@aikidosec/firewall/cloud-function"; 7 | import { externals } from "@aikidosec/firewall/bundler"; 8 | import "@aikidosec/firewall/nopp"; 9 | 10 | assert.ok(typeof Zen.addExpressMiddleware === "function"); 11 | assert.ok(typeof Zen.setUser === "function"); 12 | assert.ok(typeof context.setUser === "function"); 13 | assert.ok(typeof lambda === "function"); 14 | assert.ok(typeof cloudFunction === "function"); 15 | assert.ok(Array.isArray(externals())); 16 | -------------------------------------------------------------------------------- /library/agent/api/Token.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { Token } from "./Token"; 3 | 4 | t.test("it throws error if token is empty", async (t) => { 5 | t.throws(() => new Token("")); 6 | }); 7 | 8 | t.test("it returns the token as string", async (t) => { 9 | const token = new Token("token"); 10 | t.same(token.asString(), "token"); 11 | }); 12 | 13 | t.test("it throws error if toString() is called", async (t) => { 14 | // oxlint-disable-next-line restrict-template-expressions 15 | t.throws(() => `${new Token("token")}`); 16 | }); 17 | 18 | t.test("it trims the token", async (t) => { 19 | const token = new Token(" token "); 20 | t.same(token.asString(), "token"); 21 | }); 22 | -------------------------------------------------------------------------------- /library/helpers/mapIPv4ToIPv6.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import mapIPv4ToIPv6 from "./mapIPv4ToIPv6"; 3 | 4 | t.test("it works", async (t) => { 5 | t.equal(mapIPv4ToIPv6("127.0.0.0"), "::ffff:127.0.0.0/128"); 6 | t.equal(mapIPv4ToIPv6("127.0.0.0/8"), "::ffff:127.0.0.0/104"); 7 | t.equal(mapIPv4ToIPv6("10.0.0.0"), "::ffff:10.0.0.0/128"); 8 | t.equal(mapIPv4ToIPv6("10.0.0.0/8"), "::ffff:10.0.0.0/104"); 9 | t.equal(mapIPv4ToIPv6("10.0.0.1"), "::ffff:10.0.0.1/128"); 10 | t.equal(mapIPv4ToIPv6("10.0.0.1/8"), "::ffff:10.0.0.1/104"); 11 | t.equal(mapIPv4ToIPv6("192.168.0.0/16"), "::ffff:192.168.0.0/112"); 12 | t.equal(mapIPv4ToIPv6("172.16.0.0/12"), "::ffff:172.16.0.0/108"); 13 | }); 14 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/prisma/migrations/20241122094425_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT 6 | ); 7 | 8 | -- CreateTable 9 | CREATE TABLE "Post" ( 10 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 11 | "title" TEXT NOT NULL, 12 | "content" TEXT, 13 | "published" BOOLEAN NOT NULL DEFAULT false, 14 | "authorId" INTEGER NOT NULL, 15 | CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 16 | ); 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 20 | -------------------------------------------------------------------------------- /scripts/copyPackageJSON.js: -------------------------------------------------------------------------------- 1 | const { writeFile } = require("fs/promises"); 2 | const { join } = require("path"); 3 | 4 | async function main() { 5 | const pkg = require(join(__dirname, "../library/package.json")); 6 | 7 | // We're going to remove the devDependencies from the package.json 8 | // Otherwise they will show up in every lock file 9 | // whenever we add a new dev dependency to the library 10 | delete pkg.devDependencies; 11 | 12 | await writeFile( 13 | join(__dirname, "../build/package.json"), 14 | JSON.stringify(pkg, null, 2) 15 | ); 16 | } 17 | 18 | (async () => { 19 | try { 20 | await main(); 21 | } catch (e) { 22 | console.error(e); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/sqlite/migrations/20241122110725_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT 6 | ); 7 | 8 | -- CreateTable 9 | CREATE TABLE "Post" ( 10 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 11 | "title" TEXT NOT NULL, 12 | "content" TEXT, 13 | "published" BOOLEAN NOT NULL DEFAULT false, 14 | "authorId" INTEGER NOT NULL, 15 | CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 16 | ); 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 20 | -------------------------------------------------------------------------------- /library/helpers/isESM.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks at runtime if the Node.js application is using ESM. 3 | * As it depends on the stack trace, it should be used directly after the file got imported / at top level of the library. 4 | */ 5 | export function isESM() { 6 | // Save current stack trace limit and increase it a bit, to make sure we don't get too few frames 7 | const currentStackTraceLimit = Error.stackTraceLimit; 8 | Error.stackTraceLimit = 15; 9 | 10 | // Capture the current stack trace 11 | const stack = new Error().stack || ""; 12 | 13 | // Reset stack trace limit 14 | Error.stackTraceLimit = currentStackTraceLimit; 15 | 16 | return stack.includes("node:internal/modules/esm/loader:"); 17 | } 18 | -------------------------------------------------------------------------------- /sample-apps/hono-prisma/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | email String @unique 16 | name String? 17 | posts Post[] 18 | } 19 | 20 | model Post { 21 | id Int @id @default(autoincrement()) 22 | title String 23 | content String? 24 | published Boolean @default(false) 25 | author User @relation(fields: [authorId], references: [id]) 26 | authorId Int 27 | } -------------------------------------------------------------------------------- /sample-apps/lambda-mark-unsafe/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-mark-unsafe", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lambda-mark-unsafe", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build" 12 | } 13 | }, 14 | "../../build": { 15 | "name": "@aikidosec/firewall", 16 | "version": "0.0.0", 17 | "license": "AGPL-3.0-or-later", 18 | "engines": { 19 | "node": ">=16" 20 | } 21 | }, 22 | "node_modules/@aikidosec/firewall": { 23 | "resolved": "../../build", 24 | "link": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /benchmarks/api-discovery/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-discovery-benchmark", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "api-discovery-benchmark", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build" 12 | } 13 | }, 14 | "../../build": { 15 | "name": "@aikidosec/firewall", 16 | "version": "0.0.0", 17 | "license": "AGPL-3.0-or-later", 18 | "engines": { 19 | "node": ">=16" 20 | } 21 | }, 22 | "node_modules/@aikidosec/firewall": { 23 | "resolved": "../../build", 24 | "link": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /benchmarks/sql-injection/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sql-injection-benchmark", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "sql-injection-benchmark", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build" 12 | } 13 | }, 14 | "../../build": { 15 | "name": "@aikidosec/firewall", 16 | "version": "0.0.0", 17 | "license": "AGPL-3.0-or-later", 18 | "engines": { 19 | "node": ">=16" 20 | } 21 | }, 22 | "node_modules/@aikidosec/firewall": { 23 | "resolved": "../../build", 24 | "link": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /benchmarks/shell-injection/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shell-injection-benchmark", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "shell-injection-benchmark", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "@aikidosec/firewall": "file:../../build" 12 | } 13 | }, 14 | "../../build": { 15 | "name": "@aikidosec/firewall", 16 | "version": "0.0.0", 17 | "license": "AGPL-3.0-or-later", 18 | "engines": { 19 | "node": ">=16" 20 | } 21 | }, 22 | "node_modules/@aikidosec/firewall": { 23 | "resolved": "../../build", 24 | "link": true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/sqlite/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | email String @unique 16 | name String? 17 | posts Post[] 18 | } 19 | 20 | model Post { 21 | id Int @id @default(autoincrement()) 22 | title String 23 | content String? 24 | published Boolean @default(false) 25 | author User @relation(fields: [authorId], references: [id]) 26 | authorId Int 27 | } -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/database/migrations/1760103653456_create_users_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from "@adonisjs/lucid/schema"; 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = "users"; 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments("id").notNullable(); 9 | table.string("full_name").nullable(); 10 | table.string("email", 254).notNullable().unique(); 11 | table.string("password").notNullable(); 12 | 13 | table.timestamp("created_at").notNullable(); 14 | table.timestamp("updated_at").nullable(); 15 | }); 16 | } 17 | 18 | async down() { 19 | this.schema.dropTable(this.tableName); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/postgres/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model AppUser { 14 | id Int @id @default(autoincrement()) 15 | email String @unique 16 | name String? 17 | posts Post[] 18 | } 19 | 20 | model Post { 21 | id Int @id @default(autoincrement()) 22 | title String 23 | content String? 24 | published Boolean @default(false) 25 | author AppUser @relation(fields: [authorId], references: [id]) 26 | authorId Int 27 | } -------------------------------------------------------------------------------- /scripts/helpers/asyncPool.js: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/rxaviers/async-pool/blob/1.x/lib/es7.js 2 | // Copyright 2017 Rafael Xavier de Souza http://rafael.xavier.blog.br 3 | // MIT License 4 | 5 | async function asyncPool(poolLimit, iterable, iteratorFn) { 6 | const ret = []; 7 | const executing = new Set(); 8 | for (const item of iterable) { 9 | const p = Promise.resolve().then(() => iteratorFn(item, iterable)); 10 | ret.push(p); 11 | executing.add(p); 12 | const clean = () => executing.delete(p); 13 | p.then(clean).catch(clean); 14 | if (executing.size >= poolLimit) { 15 | await Promise.race(executing); 16 | } 17 | } 18 | return Promise.all(ret); 19 | } 20 | 21 | module.exports = asyncPool; 22 | -------------------------------------------------------------------------------- /benchmarks/operations/sqli.js: -------------------------------------------------------------------------------- 1 | const Database = require("better-sqlite3"); 2 | 3 | const db = new Database(":memory:"); 4 | 5 | module.exports = { 6 | setup: async function setup() { 7 | db.exec( 8 | "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT, password TEXT)" 9 | ); 10 | const stmt = db.prepare( 11 | "INSERT INTO users (email, password) VALUES (?, ?)" 12 | ); 13 | stmt.run("john@acme.com", "password"); 14 | }, 15 | step: async function step() { 16 | const stmt = db.prepare( 17 | "SELECT * FROM users WHERE email = ? AND password = ?" 18 | ); 19 | stmt.all("john@acme.com", "password"); 20 | }, 21 | teardown: async function teardown() { 22 | db.close(); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /end2end/server/src/handlers/updateConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Response } from "express"; 2 | import { updateAppConfig } from "../zen/config.ts"; 3 | import type { ZenRequest } from "../types.ts"; 4 | 5 | export function updateConfig(req: ZenRequest, res: Response) { 6 | if (!req.zenApp) { 7 | throw new Error("App is missing"); 8 | } 9 | 10 | // Insecure input validation - but this is only a mock server 11 | if ( 12 | !req.body || 13 | typeof req.body !== "object" || 14 | Array.isArray(req.body) || 15 | !Object.keys(req.body).length 16 | ) { 17 | return res.status(400).json({ 18 | message: "Request body is missing or invalid", 19 | }); 20 | } 21 | res.json({ success: updateAppConfig(req.zenApp, req.body) }); 22 | } 23 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from "@nestjs/testing"; 2 | import { AppController } from "./app.controller"; 3 | import { AppService } from "./app.service"; 4 | 5 | describe("AppController", () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe("root", () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe("Hello World!"); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /sample-apps/react-router-pg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*", 4 | "**/.server/**/*", 5 | "**/.client/**/*", 6 | ".react-router/types/**/*" 7 | ], 8 | "compilerOptions": { 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "types": ["node", "vite/client"], 11 | "target": "ES2022", 12 | "module": "ES2022", 13 | "moduleResolution": "bundler", 14 | "jsx": "react-jsx", 15 | "rootDirs": [".", "./.react-router/types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": ["./app/*"] 19 | }, 20 | "esModuleInterop": true, 21 | "verbatimModuleSyntax": true, 22 | "noEmit": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/helpers/tryParseURL.ts: -------------------------------------------------------------------------------- 1 | type ParseFunction = (url: string) => URL | undefined; 2 | 3 | let tryParseURL: ParseFunction; 4 | 5 | tryParseURL = parseAndCatchError; 6 | 7 | function parseAndCatchError(url: string): URL | undefined { 8 | try { 9 | return new URL(url); 10 | } catch { 11 | return undefined; 12 | } 13 | } 14 | 15 | function canParse(url: string): URL | undefined { 16 | if (URL.canParse(url)) { 17 | return new URL(url); 18 | } 19 | 20 | return undefined; 21 | } 22 | 23 | // URL.canParse(...) is a lot faster than using the constructor and catching the error 24 | // Added in Node.js: v19.9.0, v18.17.0 25 | if (typeof URL.canParse === "function") { 26 | tryParseURL = canParse; 27 | } 28 | 29 | export { tryParseURL }; 30 | -------------------------------------------------------------------------------- /instrumentation-wasm/src/js_transformer/instructions.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | #[serde(rename_all = "camelCase")] 5 | pub struct FileInstructions { 6 | pub path: String, 7 | pub identifier: String, 8 | pub version_range: String, 9 | pub functions: Vec, 10 | pub access_local_variables: Vec, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct FunctionInstructions { 16 | pub node_type: String, 17 | pub name: String, 18 | pub identifier: String, 19 | pub inspect_args: bool, 20 | pub modify_args: bool, 21 | pub modify_return_value: bool, 22 | pub modify_arguments_object: bool, 23 | } 24 | -------------------------------------------------------------------------------- /library/sources/xml/isXmlInContext.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../../agent/Context"; 2 | import { SOURCES } from "../../agent/Source"; 3 | import { extractStringsFromUserInput } from "../../helpers/extractStringsFromUserInput"; 4 | 5 | /** 6 | * Checks if the XML string can be found in the context. 7 | */ 8 | export function isXmlInContext(xml: string, context: Context): boolean { 9 | for (const source of SOURCES) { 10 | if (source === "xml") { 11 | // Skip parsed XML 12 | continue; 13 | } 14 | 15 | if (!context[source]) { 16 | continue; 17 | } 18 | 19 | const userInput = extractStringsFromUserInput(context[source]); 20 | if (userInput.has(xml)) { 21 | return true; 22 | } 23 | } 24 | 25 | return false; 26 | } 27 | -------------------------------------------------------------------------------- /library/vulnerabilities/sql-injection/payloads/postgres.txt: -------------------------------------------------------------------------------- 1 | select version(); 2 | select current_database(); 3 | select current_user; 4 | select session_user; 5 | select current_setting('log_connections'); 6 | select current_setting('log_statement'); 7 | select current_setting('port'); 8 | select current_setting('password_encryption'); 9 | select current_setting('krb_server_keyfile'); 10 | select current_setting('virtual_host'); 11 | select current_setting('port'); 12 | select current_setting('config_file'); 13 | select current_setting('hba_file'); 14 | select current_setting('data_directory'); 15 | select * from pg_shadow; 16 | select * from pg_group; 17 | create table myfile (input TEXT); 18 | copy myfile from '/etc/passwd'; 19 | select * from myfile;copy myfile to /tmp/test; -------------------------------------------------------------------------------- /sample-apps/adonis-sqlite/app/middleware/container_bindings_middleware.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@adonisjs/core/logger"; 2 | import { HttpContext } from "@adonisjs/core/http"; 3 | import type { NextFn } from "@adonisjs/core/types/http"; 4 | 5 | /** 6 | * The container bindings middleware binds classes to their request 7 | * specific value using the container resolver. 8 | * 9 | * - We bind "HttpContext" class to the "ctx" object 10 | * - And bind "Logger" class to the "ctx.logger" object 11 | */ 12 | export default class ContainerBindingsMiddleware { 13 | handle(ctx: HttpContext, next: NextFn) { 14 | ctx.containerResolver.bindValue(HttpContext, ctx); 15 | ctx.containerResolver.bindValue(Logger, ctx.logger); 16 | 17 | return next(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /library/sinks/fixtures/prisma/mongodb/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "mongodb" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id String @id @default(auto()) @map("_id") @db.ObjectId 15 | email String @unique 16 | name String? 17 | posts Post[] 18 | } 19 | 20 | model Post { 21 | id String @id @default(auto()) @map("_id") @db.ObjectId 22 | slug String @unique 23 | title String 24 | body String 25 | author User @relation(fields: [authorId], references: [id]) 26 | authorId String @db.ObjectId 27 | } -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/src/main.ts: -------------------------------------------------------------------------------- 1 | import "@aikidosec/firewall"; 2 | import * as Sentry from "@sentry/nestjs"; 3 | 4 | Sentry.init({ 5 | dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", 6 | }); 7 | 8 | import { NestFactory } from "@nestjs/core"; 9 | import { AppModule } from "./app.module"; 10 | import { ZenGuard } from "./zen.guard"; 11 | 12 | function getPort() { 13 | const port = parseInt(process.env.PORT, 10) || 4000; 14 | 15 | if (isNaN(port)) { 16 | console.error("Invalid port"); 17 | process.exit(1); 18 | } 19 | 20 | return port; 21 | } 22 | 23 | async function bootstrap() { 24 | const app = await NestFactory.create(AppModule); 25 | 26 | app.useGlobalGuards(new ZenGuard()); 27 | await app.listen(getPort()); 28 | } 29 | 30 | bootstrap(); 31 | -------------------------------------------------------------------------------- /benchmarks/api-discovery/cookies.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | sessionId: "abc123xyz", 4 | userPreferences: "dark-mode=true;language=nl", 5 | authToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", 6 | }, 7 | { 8 | userId: "user456", 9 | cartId: "cart789", 10 | theme: "light", 11 | recentViewed: "item001,item002,item003", 12 | }, 13 | { 14 | auth: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", 15 | csrfToken: "def456ghi", 16 | rememberMe: "true", 17 | }, 18 | { 19 | visitCount: "5", 20 | lastVisit: "2024-10-28T15:30:00Z", 21 | preferences: "text-size=large;color=blue", 22 | }, 23 | { 24 | analyticsId: "GA1.2.3456789.1234567890", 25 | trackingOptOut: "false", 26 | experimentGroup: "B", 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /library/helpers/ip-matcher/Network.test.ts: -------------------------------------------------------------------------------- 1 | import * as t from "tap"; 2 | import { Network } from "./Network"; 3 | 4 | t.test("it works", async (t) => { 5 | t.same(new Network().cidr(), Number.NaN); 6 | t.same(new Network().setCIDR(0).isValid(), false); 7 | t.same(new Network("192.168.2.1/24").isValid(), true); 8 | t.same(new Network("192.168.2.1/24").setCIDR(-1).isValid(), false); 9 | t.same(new Network().compare(new Network()), null); 10 | t.same(new Network("192.168.2.1/24").compare(new Network()), null); 11 | 12 | t.same(new Network().contains(new Network()), false); 13 | t.same(new Network("192.168.2.1/24").contains(new Network()), false); 14 | t.same( 15 | new Network("192.168.2.1/24").contains(new Network("192.168.2.1/32")), 16 | true 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /library/sources/hono/getRemoteAddress.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from "hono"; 2 | 3 | /** 4 | * Tries to get the remote address (ip) from the context of a Hono request. 5 | */ 6 | export function getRemoteAddress(c: Context): string | undefined { 7 | // Node.js server 8 | // https://github.com/honojs/node-server/blob/fc749268c411bfdd7babd781cee5bdfed244f1c0/src/conninfo.ts#L10 9 | if (c.env) { 10 | try { 11 | const bindings = c.env.server ? c.env.server : c.env; 12 | const addressInfo = bindings.incoming.socket.address(); 13 | 14 | if ("address" in addressInfo && typeof addressInfo.address === "string") { 15 | return addressInfo.address; 16 | } 17 | } catch { 18 | // Ignore 19 | } 20 | } 21 | 22 | return undefined; 23 | } 24 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/cats.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Query, 6 | Body, 7 | HttpException, 8 | } from "@nestjs/common"; 9 | import { DBService } from "./db.service"; 10 | 11 | @Controller() 12 | export class CatsController { 13 | constructor(private readonly dbService: DBService) {} 14 | 15 | @Get("/cats") 16 | async getRequest(@Query("name") name: string): Promise { 17 | return await this.dbService.getCats(name); 18 | } 19 | 20 | @Post("/cats") 21 | async postRequest(@Body() body): Promise { 22 | if (typeof body.name !== "string") { 23 | throw new HttpException("Invalid name", 400); 24 | } 25 | await this.dbService.addCat(body.name); 26 | return "Added cat"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample-apps/nestjs-sentry/src/cats.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Query, 6 | Body, 7 | HttpException, 8 | } from "@nestjs/common"; 9 | import { DBService } from "./db.service"; 10 | 11 | @Controller() 12 | export class CatsController { 13 | constructor(private readonly dbService: DBService) {} 14 | 15 | @Get("/cats") 16 | async getRequest(@Query("name") name: string): Promise { 17 | return await this.dbService.getCats(name); 18 | } 19 | 20 | @Post("/cats") 21 | async postRequest(@Body() body): Promise { 22 | if (typeof body.name !== "string") { 23 | throw new HttpException("Invalid name", 400); 24 | } 25 | await this.dbService.addCat(body.name); 26 | return "Added cat"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample-apps/express-mysql/Cats.js: -------------------------------------------------------------------------------- 1 | class Cats { 2 | constructor(db) { 3 | this.db = db; 4 | } 5 | 6 | async query(sql) { 7 | return new Promise((resolve, reject) => { 8 | this.db.query(sql, (err, result) => { 9 | if (err) { 10 | reject(err); 11 | } else { 12 | resolve(result); 13 | } 14 | }); 15 | }); 16 | } 17 | 18 | async add(name) { 19 | // This is unsafe! This is for demo purposes only, you should use parameterized queries. 20 | await this.query(`INSERT INTO cats(petname) VALUES ('${name}');`); 21 | } 22 | 23 | async getAll() { 24 | const cats = await this.query("SELECT petname FROM `cats`;"); 25 | 26 | return cats.map((row) => row.petname); 27 | } 28 | } 29 | 30 | module.exports = Cats; 31 | -------------------------------------------------------------------------------- /sample-apps/nestjs-fastify/src/user.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common"; 2 | import { Observable } from "rxjs"; 3 | 4 | import Zen from "@aikidosec/firewall"; 5 | 6 | // This Guard is used for testing rate limiting and should never be used in production 7 | 8 | @Injectable() 9 | export class UserGuard implements CanActivate { 10 | canActivate( 11 | context: ExecutionContext 12 | ): boolean | Promise | Observable { 13 | const req = context.switchToHttp().getRequest(); 14 | 15 | // If header X-User-Id is set in the request, set the user ID 16 | if (req.headers["x-user-id"]) { 17 | Zen.setUser({ 18 | id: req.headers["x-user-id"], 19 | }); 20 | } 21 | 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/proxy.md: -------------------------------------------------------------------------------- 1 | # Proxy settings 2 | 3 | We'll automatically use the `x-forwarded-for` header to determine the client's IP address when behind a proxy. 4 | 5 | If you're publicly exposing your server without a load balancer in front of it, you should set the `AIKIDO_TRUST_PROXY` env var to `false` to ensure that the correct IP address is used. Otherwise, someone could potentially spoof their IP address by adding the above header and thus bypassing the rate limiting. 6 | 7 | If you need to use a different header to determine the client's IP address, you can set the `AIKIDO_CLIENT_IP_HEADER` environment variable to the name of that header. This will override the default `x-forwarded-for` header. 8 | 9 | ```bash 10 | # For DigitalOcean App Platform 11 | AIKIDO_CLIENT_IP_HEADER=do-connecting-ip node app.js 12 | ``` 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main Workflow 2 | 3 | on: 4 | push: {} 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | build: 11 | name: ⚙️ Build library 12 | uses: ./.github/workflows/build.yml 13 | lint-code: 14 | name: 🧹 Lint code 15 | uses: ./.github/workflows/lint-code.yml 16 | needs: build 17 | unit-tests: 18 | name: 🧪 Unit tests 19 | uses: ./.github/workflows/unit-test.yml 20 | needs: build 21 | end-to-end-tests: 22 | name: 🕵️ End to end tests 23 | uses: ./.github/workflows/end-to-end-tests.yml 24 | needs: build 25 | benchmark: 26 | name: 📊 Benchmark 27 | uses: ./.github/workflows/benchmark.yml 28 | needs: build 29 | qa-tests: 30 | name: 🧪 QA Tests 31 | uses: ./.github/workflows/qa-tests.yml 32 | needs: build 33 | -------------------------------------------------------------------------------- /benchmarks/nosql-injection/withGuard.js: -------------------------------------------------------------------------------- 1 | require("../../build"); 2 | 3 | const measure = require("./measure"); 4 | const getUser = require("./getUser"); 5 | const getClient = require("./getClient"); 6 | const { runWithContext } = require("../../build/agent/Context"); 7 | 8 | async function main() { 9 | const client = await getClient(); 10 | const timings = await runWithContext( 11 | { 12 | body: { 13 | email: "email", 14 | password: "password", 15 | }, 16 | }, 17 | () => { 18 | return measure(async () => { 19 | await getUser(client, { 20 | email: "email", 21 | password: "password", 22 | }); 23 | }); 24 | } 25 | ); 26 | 27 | await client.close(); 28 | 29 | console.log(JSON.stringify(timings)); 30 | } 31 | 32 | main(); 33 | -------------------------------------------------------------------------------- /library/agent/Attack.ts: -------------------------------------------------------------------------------- 1 | export type Kind = 2 | | "nosql_injection" 3 | | "sql_injection" 4 | | "shell_injection" 5 | | "path_traversal" 6 | | "ssrf" 7 | | "stored_ssrf" 8 | | "code_injection"; 9 | 10 | export function attackKindHumanName(kind: Kind) { 11 | switch (kind) { 12 | case "nosql_injection": 13 | return "a NoSQL injection"; 14 | case "sql_injection": 15 | return "an SQL injection"; 16 | case "shell_injection": 17 | return "a shell injection"; 18 | case "path_traversal": 19 | return "a path traversal attack"; 20 | case "ssrf": 21 | return "a server-side request forgery"; 22 | case "stored_ssrf": 23 | return "a stored server-side request forgery"; 24 | case "code_injection": 25 | return "a JavaScript injection"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/agent/hooks/instrumentation/getPackageVersionFromPath.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | 3 | const cache = new Map(); 4 | 5 | /** 6 | * Get the installed version of a package 7 | */ 8 | export function getPackageVersionFromPath( 9 | basePath: string 10 | ): string | undefined { 11 | // This function is called for every file of an imported package, so we cache the result 12 | if (cache.has(basePath)) { 13 | return cache.get(basePath); 14 | } 15 | 16 | try { 17 | const version = JSON.parse( 18 | readFileSync(`${basePath}/package.json`, "utf8") 19 | ).version; 20 | cache.set(basePath, version); 21 | return version; 22 | } catch { 23 | // Return undefined if the package is not found 24 | cache.set(basePath, undefined); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample-apps/hono-sqlite3-esm/Cats.js: -------------------------------------------------------------------------------- 1 | import { promisify } from "util"; 2 | 3 | export class Cats { 4 | /** 5 | * 6 | * @param {import("sqlite3").Database} db 7 | */ 8 | constructor(db) { 9 | this.db = db; 10 | this.all = promisify(this.db.all).bind(this.db); 11 | } 12 | 13 | async add(name) { 14 | const result = await this.all( 15 | `INSERT INTO cats(petname) VALUES ('${name}');` 16 | ); 17 | return result; 18 | } 19 | 20 | async byName(name) { 21 | const cats = await this.all( 22 | `SELECT petname FROM cats WHERE petname = '${name}';` 23 | ); 24 | return cats.map((row) => row.petname); 25 | } 26 | 27 | async getAll() { 28 | const cats = await this.all("SELECT petname FROM cats;"); 29 | return cats.map((row) => row.petname); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /instrumentation-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node_code_instrumentation" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["Aikido Security"] 6 | description = "Internal library for JS code transformation" 7 | 8 | [lib] 9 | name = "node_code_instrumentation" 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | oxc_allocator = "0.102.0" 14 | oxc_ast = "0.102.0" 15 | oxc_codegen = "0.102.0" 16 | oxc_parser = "0.102.0" 17 | oxc_semantic = "0.102.0" 18 | oxc_span = "0.102.0" 19 | oxc_traverse = "0.102.0" 20 | serde = "1.0.228" 21 | serde_json = "1.0.145" 22 | wasm-bindgen = "0.2.105" 23 | 24 | [profile.release] 25 | strip = true 26 | opt-level = "s" 27 | lto = true 28 | 29 | [package.metadata.wasm-pack.profile.release] 30 | wasm-opt = ["-O", "--enable-bulk-memory", "--enable-nontrapping-float-to-int"] --------------------------------------------------------------------------------