diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cd915dcbb..41962115b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,12 +1,15 @@ name: 📊 Benchmark + on: - push: {} workflow_call: {} + permissions: contents: read + jobs: benchmark: runs-on: ubuntu-latest + timeout-minutes: 10 services: mongodb: image: mongo:5 @@ -23,54 +26,68 @@ jobs: "POSTGRES_DB": "main_db" ports: - "27016:5432" - timeout-minutes: 10 strategy: fail-fast: false matrix: node-version: [20.x, 24.x] + steps: - - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" cache-dependency-path: "**/package-lock.json" + - name: Setup Aikido safe-chain run: | npm i -g @aikidosec/safe-chain safe-chain setup-ci + - name: Downgrade npm for v24 and v25 # https://github.com/npm/cli/issues/8669 if: ${{ matrix.node-version == '24.x' || matrix.node-version == '25.x' }} run: npm i -g npm@11.6.0 + - name: Install K6 uses: grafana/setup-k6-action@ffe7d7290dfa715e48c2ccc924d068444c94bde2 # v1 + - name: Install wrk run: | sudo apt-get update sudo apt-get install -y wrk - - name: Set up Rust - run: | - rustup toolchain install stable - rustup default stable - cargo install wasm-pack - - run: npm install - - run: npm run build + + - name: Download build artifacts + uses: actions/download-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + + - name: Install dependencies for benchmarks + run: npm run install-benchmarks-only + - name: Run NoSQL Injection Benchmark run: cd benchmarks/nosql-injection && AIKIDO_CI=true node benchmark.js + - name: Run SQL Injection Benchmark run: cd benchmarks/sql-injection && node benchmark.js + - name: Run shell injection Benchmark run: cd benchmarks/shell-injection && node benchmark.js + - name: Run Hono with Postgres Benchmark run: cd benchmarks/hono-pg && node benchmark.js + - name: Run API Discovery Benchmark run: cd benchmarks/api-discovery && node benchmark.js + - name: Run Express Benchmark # Skip on Node 24.x because benchmark currently fails. # Big performance improve in comparison to older Node.js versions, but higher difference between usage with and without Zen if: matrix.node-version != '24.x' run: cd benchmarks/express && node benchmark.js + - name: Check Rate Limiter memory usage run: cd benchmarks/rate-limiting && node --expose-gc memory.js diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index c697e1711..f23db08f4 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -26,8 +26,8 @@ jobs: id-token: write timeout-minutes: 15 steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: "24.x" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..ed5df0c0f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,61 @@ +name: ⚙️ Build library + +on: + workflow_call: + +permissions: + contents: read + +env: + node_version: 24.x + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Use Node.js ${{ env.node_version }} + + uses: actions/setup-node@v6 + with: + node-version: ${{ env.node_version }} + cache: "npm" + cache-dependency-path: "**/package-lock.json" + + - name: Setup Aikido safe-chain + run: | + npm i -g @aikidosec/safe-chain + safe-chain setup-ci + + - name: Downgrade npm for v24 and v25 + # https://github.com/npm/cli/issues/8669 + if: ${{ env.node_version == '24.x' || env.node_version == '25.x' }} + run: npm i -g npm@11.6.0 + + - name: Set up Rust + run: | + rustup toolchain install stable + rustup default stable + + - name: Install wasm-pack + run: bash ./.github/workflows/utils/install-wasm-pack.sh + + - name: Install dependencies (library only) + run: npm run install-lib-only + + - name: Build complete library + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + if-no-files-found: error + retention-days: 7 + path: | + build/ + library/internals/ + library/agent/hooks/instrumentation/wasm/ diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 39db0c0c7..8d09706db 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -1,52 +1,14 @@ name: 🕵️ End to end tests + on: - push: {} workflow_call: {} + permissions: contents: read + jobs: test: runs-on: ubuntu-latest - services: - mongodb: - image: mongo:5 - env: - "MONGO_INITDB_ROOT_USERNAME": "root" - "MONGO_INITDB_ROOT_PASSWORD": "password" - ports: - - 27017:27017 - postgres: - image: postgres:14-alpine - env: - "POSTGRES_PASSWORD": "password" - "POSTGRES_USER": "root" - "POSTGRES_DB": "main_db" - ports: - - "27016:5432" - mysql: - image: mysql:8.0 - # NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password - # We need to use this long command in order to execute the last part : mysql_native_password - # https://stackoverflow.com/questions/60902904/how-to-pass-mysql-native-password-to-mysql-service-in-github-actions - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=mypassword -e MYSQL_DATABASE=catsdb --entrypoint sh mysql:8.0 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - ports: - - "27015:3306" - mariadb: - image: mariadb:11 - env: - MARIADB_ROOT_PASSWORD: mypassword - MARIADB_DATABASE: catsdb - ports: - - "27018:3306" - clickhouse: - image: clickhouse/clickhouse-server:24 - env: - "CLICKHOUSE_USER": "clickhouse" - "CLICKHOUSE_PASSWORD": "clickhouse" - "CLICKHOUSE_DB": "main_db" - "CLICKHOUSE_DEFAULT_ACCESS": "MANAGEMENT=1" - ports: - - "27019:8123" timeout-minutes: 15 strategy: fail-fast: false @@ -60,36 +22,47 @@ jobs: mode: "new" - node-version: 25.x mode: "new" + steps: - - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" cache-dependency-path: "**/package-lock.json" + - name: Setup Aikido safe-chain run: | npm i -g @aikidosec/safe-chain safe-chain setup-ci + - name: Downgrade npm for v24 and v25 # https://github.com/npm/cli/issues/8669 if: ${{ matrix.node-version == '24.x' || matrix.node-version == '25.x' }} run: npm i -g npm@11.6.0 + - name: Add local.aikido.io to /etc/hosts run: | sudo echo "127.0.0.1 local.aikido.io" | sudo tee -a /etc/hosts - - name: Build and run server - run: | - cd end2end/server && docker build -t server . && docker run -d -p 5874:3000 server - - name: Set up Rust - run: | - rustup toolchain install stable - rustup default stable - cargo install wasm-pack - - run: npm install - - run: npm run build - - if: matrix.mode == 'old' + + - name: Run Containers + run: npm run containers + + - name: Install dependencies (end-to-end only) + run: npm run install-e2e-only + + - name: Download build artifacts + uses: actions/download-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + + - name: Run end-to-end tests + if: matrix.mode == 'old' run: npm run end2end - - if: matrix.mode == 'new' + + - name: Run new end-to-end tests + if: matrix.mode == 'new' run: npm run end2end:new diff --git a/.github/workflows/lint-code.yml b/.github/workflows/lint-code.yml index 532482938..b4b382802 100644 --- a/.github/workflows/lint-code.yml +++ b/.github/workflows/lint-code.yml @@ -1,7 +1,11 @@ name: 🧹 Lint code -on: push + +on: + workflow_call: {} + permissions: contents: read + jobs: lint: runs-on: ubuntu-latest @@ -10,36 +14,50 @@ jobs: matrix: node-version: [24.x] steps: - - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" cache-dependency-path: "**/package-lock.json" + - name: Set up Rust run: | rustup toolchain install stable rustup default stable rustup component add rustfmt clippy - cargo install wasm-pack + - name: Setup Aikido safe-chain run: | npm i -g @aikidosec/safe-chain safe-chain setup-ci + - name: Downgrade npm for v24 and v25 # https://github.com/npm/cli/issues/8669 if: ${{ matrix.node-version == '24.x' || matrix.node-version == '25.x' }} run: npm i -g npm@11.6.0 - - run: npm run install-lib-only - - run: npm run build + + - name: Install dependencies (library only) + run: npm run install-lib-only + + - name: Download build artifacts + uses: actions/download-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + - name: Run Linter for JavaScript/TypeScript run: npm run lint + - name: Check formatting run: npm run format:check + - name: Check Rust formatting run: cargo fmt --check working-directory: ./instrumentation-wasm + - name: Run Rust Linter run: cargo clippy -- -D warnings working-directory: ./instrumentation-wasm diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..148488c95 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: Main Workflow + +on: + push: {} + +permissions: + contents: read + +jobs: + build: + name: ⚙️ Build library + uses: ./.github/workflows/build.yml + lint-code: + name: 🧹 Lint code + uses: ./.github/workflows/lint-code.yml + needs: build + unit-tests: + name: 🧪 Unit tests + uses: ./.github/workflows/unit-test.yml + needs: build + end-to-end-tests: + name: 🕵️ End to end tests + uses: ./.github/workflows/end-to-end-tests.yml + needs: build + benchmark: + name: 📊 Benchmark + uses: ./.github/workflows/benchmark.yml + needs: build + qa-tests: + name: 🧪 QA Tests + uses: ./.github/workflows/qa-tests.yml + needs: build diff --git a/.github/workflows/qa-tests.yml b/.github/workflows/qa-tests.yml index 8e9bc5b68..df4c57442 100644 --- a/.github/workflows/qa-tests.yml +++ b/.github/workflows/qa-tests.yml @@ -1,8 +1,9 @@ name: 🧪 QA Tests + permissions: contents: read + on: - push: {} workflow_call: {} jobs: @@ -11,41 +12,33 @@ jobs: timeout-minutes: 30 steps: - name: Checkout firewall-node - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: firewall-node - name: Checkout zen-demo-nodejs - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: Aikido-demo-apps/zen-demo-nodejs path: zen-demo-nodejs submodules: true - - name: Use Node.js 22.x - uses: actions/setup-node@v5 + - name: Use Node.js 24.x + uses: actions/setup-node@v6 with: - node-version: "22.x" - - - name: Setup Aikido safe-chain - run: | - npm i -g @aikidosec/safe-chain - safe-chain setup-ci + node-version: "24.x" - - name: Set up Rust - run: | - rustup toolchain install stable - rustup default stable - cargo install wasm-pack + - name: Download build artifacts + uses: actions/download-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + path: firewall-node - - name: Build firewall-node dev package + - name: Pack firewall-node library run: | - cd firewall-node - npm run install-lib-only - npm run build + cd firewall-node/build # Pack the built package for local installation - cd build npm pack # Move the packed tarball to zen-demo-nodejs directory diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 96ecca708..b69ffd64f 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,14 +1,18 @@ name: 🧪 Unit tests + on: - push: {} workflow_call: {} + permissions: contents: read + jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 strategy: fail-fast: false + matrix: include: - node-version: 16.x @@ -41,50 +45,57 @@ jobs: - node-version: 25.x new-instrumentation: "new" mode: "esm" - timeout-minutes: 15 + steps: - - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" cache-dependency-path: "**/package-lock.json" + - name: Setup Aikido safe-chain run: | npm i -g @aikidosec/safe-chain safe-chain setup-ci + - name: Downgrade npm for v24 and v25 # https://github.com/npm/cli/issues/8669 if: ${{ matrix.node-version == '24.x' || matrix.node-version == '25.x' }} run: npm i -g npm@11.6.0 + - name: Add local.aikido.io to /etc/hosts run: | sudo echo "127.0.0.1 local.aikido.io" | sudo tee -a /etc/hosts - - name: Set up Rust - run: | - rustup toolchain install stable - rustup default stable - cargo install wasm-pack - - run: npm run install-lib-only + + - name: Install dependencies (library only) + run: npm run install-lib-only + - name: Start containers run: npm run containers - - name: Prepare WASM components - # When running tests with tap (CJS), we don't need to build our lib, just need the WASM files - run: npm run build -- --only-wasm - if: ${{ matrix.mode != 'esm' }} - - name: Build complete library - run: npm run build - if: ${{ matrix.new-instrumentation == 'new' && matrix.mode == 'esm' }} - - run: npm run test:ci + + - name: Download build artifacts + uses: actions/download-artifact@v5 + with: + name: firewall-node-library-${{ github.sha }} + + - name: Run tests with current instrumentation + run: npm run test:ci if: ${{ matrix.new-instrumentation == 'current' }} - - run: npm run test:ci:new + + - name: Run tests with new instrumentation in CJS mode + run: npm run test:ci:new if: ${{ matrix.new-instrumentation == 'new' && matrix.mode == 'cjs' }} + - name: Run tests in ESM mode run: npm run test:esm if: ${{ matrix.new-instrumentation == 'new' && matrix.mode == 'esm' }} + - name: "Upload coverage" - uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: files: ./library/.tap/report/lcov.info,./.esm-tests/tests/lcov.info env: diff --git a/.github/workflows/utils/install-wasm-pack.sh b/.github/workflows/utils/install-wasm-pack.sh new file mode 100755 index 000000000..f560cfa27 --- /dev/null +++ b/.github/workflows/utils/install-wasm-pack.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# Copyright 2016 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +# This is just a little script that can be downloaded from the internet to +# install wasm-pack. It just does platform detection, downloads the installer +# and runs it. + +# This file has been modified from the original content +# to fix issues and enhance security. + +set -u + +UPDATE_ROOT="https://github.com/drager/wasm-pack/releases/download/v0.13.1" + +main() { + downloader --check + need_cmd uname + need_cmd mktemp + need_cmd chmod + need_cmd mkdir + need_cmd rm + need_cmd rmdir + need_cmd tar + need_cmd which + need_cmd dirname + + get_architecture || return 1 + local _arch="$RETVAL" + assert_nz "$_arch" "arch" + + local _ext="" + case "$_arch" in + *windows*) + _ext=".exe" + ;; + esac + + which rustup > /dev/null 2>&1 + need_ok "failed to find Rust installation, is rustup installed?" + local _rustup=$(which rustup) + local _tardir="wasm-pack-v0.13.1-${_arch}" + local _url="$UPDATE_ROOT/${_tardir}.tar.gz" + local _dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t wasm-pack)" + local _file="$_dir/input.tar.gz" + local _wasmpack="$_dir/wasm-pack$_ext" + local _wasmpackinit="$_dir/wasm-pack-init$_ext" + + printf '%s\n' 'info: downloading wasm-pack' 1>&2 + + ensure mkdir -p "$_dir" + downloader "$_url" "$_file" + if [ $? != 0 ]; then + say "failed to download $_url" + say "this may be a standard network error, but it may also indicate" + say "that wasm-pack's release process is not working. When in doubt" + say "please feel free to open an issue!" + exit 1 + fi + ensure tar xf "$_file" --strip-components 1 -C "$_dir" + mv "$_wasmpack" "$_wasmpackinit" + + # The installer may want to ask for confirmation on stdin for various + # operations. We were piped through `sh` though so we probably don't have + # access to a tty naturally. If it looks like we're attached to a terminal + # (`-t 1`) then pass the tty down to the installer explicitly. + if [ -t 1 ]; then + "$_wasmpackinit" "$@" < /dev/tty + else + "$_wasmpackinit" "$@" + fi + + local _retval=$? + + ignore rm -rf "$_dir" + + return "$_retval" +} + +get_architecture() { + local _ostype="$(uname -s)" + local _cputype="$(uname -m)" + + # This is when installing inside docker, or can be useful to side-step + # the script's built-in platform detection heuristic (if it drifts again in the future) + set +u + if [ -n "$TARGETOS" ]; then + _ostype="$TARGETOS" # probably always linux + fi + + if [ -n "$TARGETARCH" ]; then + _cputype="$TARGETARCH" + fi + set -u + + + if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then + # Darwin `uname -s` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + local _cputype=x86_64 + fi + fi + + case "$_ostype" in + Linux | linux) + local _ostype=unknown-linux-musl + ;; + + Darwin) + local _ostype=apple-darwin + ;; + + MINGW* | MSYS* | CYGWIN*) + local _ostype=pc-windows-msvc + ;; + + *) + err "no precompiled binaries available for OS: $_ostype" + ;; + esac + + case "$_cputype" in + x86_64 | x86-64 | x64 | amd64) + local _cputype=x86_64 + ;; + arm64 | aarch64) + local _cputype=aarch64 + ;; + *) + err "no precompiled binaries available for CPU architecture: $_cputype" + + esac + + # See https://github.com/drager/wasm-pack/pull/1088 + if [ "$_cputype" = "aarch64" ] && [ "$_ostype" = "apple-darwin" ]; then + _cputype="x86_64" + fi + + local _arch="$_cputype-$_ostype" + + RETVAL="$_arch" +} + +say() { + echo "wasm-pack-init: $1" +} + +err() { + say "$1" >&2 + exit 1 +} + +need_cmd() { + if ! check_cmd "$1" + then err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" > /dev/null 2>&1 + return $? +} + +need_ok() { + if [ $? != 0 ]; then err "$1"; fi +} + +assert_nz() { + if [ -z "$1" ]; then err "assert_nz $2"; fi +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + "$@" + need_ok "command failed: $*" +} + +# This is just for indicating that commands' results are being +# intentionally ignored. Usually, because it's being executed +# as part of error handling. +ignore() { + "$@" +} + +# This wraps curl or wget. Try curl first, if not installed, +# use wget instead. +downloader() { + if check_cmd curl + then _dld=curl + elif check_cmd wget + then _dld=wget + else _dld='curl or wget' # to be used in error message of need_cmd + fi + + if [ "$1" = --check ] + then need_cmd "$_dld" + elif [ "$_dld" = curl ] + then curl -sSfL "$1" -o "$2" + elif [ "$_dld" = wget ] + then wget "$1" -O "$2" + else err "Unknown downloader" # should not reach here + fi +} + +main "$@" || exit 1 diff --git a/library/.oxlintrc.json b/library/.oxlintrc.json index 122b441d9..48cc5dcda 100644 --- a/library/.oxlintrc.json +++ b/library/.oxlintrc.json @@ -30,5 +30,6 @@ "no-unused-vars": "off" } } - ] + ], + "ignorePatterns": ["**/wasm/**"] } diff --git a/library/helpers/isLibBundled.ts b/library/helpers/isLibBundled.ts index 93028e434..d22974c5a 100644 --- a/library/helpers/isLibBundled.ts +++ b/library/helpers/isLibBundled.ts @@ -5,6 +5,6 @@ export function isLibBundled(): boolean { return ( !normalizedDirName.includes("node_modules/@aikidosec/firewall/helpers") && - !normalizedDirName.includes("firewall-node/build/helpers") // In case of e2e tests + !normalizedDirName.includes("/build/helpers") // In case of e2e tests ); } diff --git a/package.json b/package.json index 56251ddc2..6f257b524 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "scripts": { "install": "node scripts/install.js", "install-lib-only": "node scripts/install.js --lib-only", + "install-e2e-only": "node scripts/install.js --end2end-only", + "install-benchmarks-only": "node scripts/install.js --benchmarks-only", "containers": "cd sample-apps && docker compose up -d --remove-orphans --build", "build": "node scripts/build.js", "watch": "cd library && npm run build:watch", diff --git a/scripts/install.js b/scripts/install.js index 7596f46eb..b9346ee64 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -28,10 +28,25 @@ async function main() { await prepareBuildDir(); const libOnly = process.argv.includes("--lib-only"); - - // . is the root directory, npm install is automatically run in the root directory if npm install is used, but not if npm run install-lib-only is executed - const installDirs = libOnly ? ["library", "."] : ["library", "end2end"]; - const scanForSubDirs = libOnly ? [] : ["sample-apps", "benchmarks"]; + const e2eOnly = process.argv.includes("--end2end-only"); + const benchmarksOnly = process.argv.includes("--benchmarks-only"); + const installAll = !libOnly && !e2eOnly && !benchmarksOnly; + + const installDirs = []; + const scanForSubDirs = []; + + if (installAll) { + installDirs.push("library", "end2end"); + scanForSubDirs.push("sample-apps", "benchmarks"); + } else { + // . is the root directory, npm install is automatically run in the root directory if npm install is used, + // but not if npm run install-xxx is used + installDirs.push("."); + if (libOnly) installDirs.push("library"); + if (e2eOnly) installDirs.push("end2end"); + if (benchmarksOnly) scanForSubDirs.push("benchmarks"); + if (e2eOnly) scanForSubDirs.push("sample-apps"); + } for (const dir of scanForSubDirs) { const subDirs = await scanForSubDirsWithPackageJson(dir);