From 24230da4a7a9e706a3b625b4baf8132689524b59 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:05:52 +0100 Subject: [PATCH 01/10] Add nvm safe-chain uninstallation in install script --- install-scripts/install-safe-chain.sh | 57 ++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/install-scripts/install-safe-chain.sh b/install-scripts/install-safe-chain.sh index 94a9b55..6f0dd26 100755 --- a/install-scripts/install-safe-chain.sh +++ b/install-scripts/install-safe-chain.sh @@ -159,6 +159,57 @@ remove_volta_installation() { fi } +# Check and uninstall nvm-managed package if present across all Node versions +remove_nvm_installation() { + # Check if nvm is available as a command + if ! command_exists nvm; then + return + fi + + # Get list of installed Node versions + nvm_versions=$(nvm list 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "") + + if [ -z "$nvm_versions" ]; then + return + fi + + # Track if we found any installations + found_installation=false + uninstall_failed=false + current_version=$(nvm current 2>/dev/null || echo "") + + # Check each version for safe-chain installation + for version in $nvm_versions; do + # Check if this version has safe-chain installed + # Use nvm exec to run npm list in the context of that Node version + if nvm exec "$version" npm list -g @aikidosec/safe-chain >/dev/null 2>&1; then + if [ "$found_installation" = false ]; then + info "Detected nvm installation(s) of @aikidosec/safe-chain" + info "Uninstalling from all Node versions..." + found_installation=true + fi + + info " Removing from Node $version..." + if nvm exec "$version" npm uninstall -g @aikidosec/safe-chain >/dev/null 2>&1; then + info " Successfully uninstalled from Node $version" + else + warn " Failed to uninstall from Node $version" + uninstall_failed=true + fi + fi + done + + # Restore original Node version if it was set + if [ -n "$current_version" ] && [ "$current_version" != "none" ] && [ "$current_version" != "system" ]; then + nvm use "$current_version" >/dev/null 2>&1 || true + fi + + # If any uninstall failed, error out instead of continuing + if [ "$uninstall_failed" = true ]; then + error "Failed to uninstall @aikidosec/safe-chain from all nvm Node versions. Please uninstall manually and try again." + fi +} + # Parse command-line arguments parse_arguments() { for arg in "$@"; do @@ -204,9 +255,11 @@ main() { info "$INSTALL_MSG" - # Check for existing safe-chain installation through npm or volta - remove_npm_installation + # Check for existing safe-chain installation through nvm, volta, or npm + # nvm must be checked first as it manages multiple Node versions + remove_nvm_installation remove_volta_installation + remove_npm_installation # Detect platform OS=$(detect_os) From efe3b24ab9906482fb36982ef7cdb1e1745ac8ff Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:07:40 +0100 Subject: [PATCH 02/10] Comment npm publish step --- .github/workflows/build-and-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 83c11d9..1c05824 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -61,10 +61,10 @@ jobs: cp LICENSE packages/safe-chain/ cp -r docs packages/safe-chain/ - - name: Publish to npm - run: | - echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" - npm publish --workspace=packages/safe-chain --access public --provenance + # - name: Publish to npm + # run: | + # echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" + # npm publish --workspace=packages/safe-chain --access public --provenance - name: Download all binary artifacts uses: actions/download-artifact@v4 From 6bbd3f59558b1ccfddb10854a75161c969d6cd9f Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:35:10 +0100 Subject: [PATCH 03/10] Add nvm detection to uninstall script --- install-scripts/uninstall-safe-chain.sh | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/install-scripts/uninstall-safe-chain.sh b/install-scripts/uninstall-safe-chain.sh index 4b2d7ec..8d1fbdf 100755 --- a/install-scripts/uninstall-safe-chain.sh +++ b/install-scripts/uninstall-safe-chain.sh @@ -75,6 +75,58 @@ remove_volta_installation() { fi } +# Check and uninstall nvm-managed package if present across all Node versions +remove_nvm_installation() { + # Check if nvm is available as a command + if ! command_exists nvm; then + return + fi + + # Get list of installed Node versions + nvm_versions=$(nvm list 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "") + + if [ -z "$nvm_versions" ]; then + return + fi + + # Track if we found any installations + found_installation=false + uninstall_failed=false + current_version=$(nvm current 2>/dev/null || echo "") + + # Check each version for safe-chain installation + for version in $nvm_versions; do + # Check if this version has safe-chain installed + # Use nvm exec to run npm list in the context of that Node version + if nvm exec "$version" npm list -g @aikidosec/safe-chain >/dev/null 2>&1; then + if [ "$found_installation" = false ]; then + info "Detected nvm installation(s) of @aikidosec/safe-chain" + info "Uninstalling from all Node versions..." + found_installation=true + fi + + info " Removing from Node $version..." + if nvm exec "$version" npm uninstall -g @aikidosec/safe-chain >/dev/null 2>&1; then + info " Successfully uninstalled from Node $version" + else + warn " Failed to uninstall from Node $version" + uninstall_failed=true + fi + fi + done + + # Restore original Node version if it was set + if [ -n "$current_version" ] && [ "$current_version" != "none" ] && [ "$current_version" != "system" ]; then + nvm use "$current_version" >/dev/null 2>&1 || true + fi + + # Show warning if any uninstall failed (but don't error out during uninstall) + if [ "$uninstall_failed" = true ]; then + warn "Failed to uninstall @aikidosec/safe-chain from some nvm Node versions" + warn "You may need to manually run: nvm exec npm uninstall -g @aikidosec/safe-chain" + fi +} + # Main uninstallation main() { SAFE_CHAIN_LOCATION="$INSTALL_DIR/safe-chain" @@ -89,8 +141,10 @@ main() { warn "safe-chain command not found. Proceeding with uninstallation." fi - remove_npm_installation + # Remove npm-based installations (nvm must be checked first) + remove_nvm_installation remove_volta_installation + remove_npm_installation # Remove install dir recursively if it exists if [ -d "$INSTALL_DIR" ]; then From 10a2407b3227a67c9cd9ec36e85037a154b9fad4 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:43:15 +0100 Subject: [PATCH 04/10] Source nvm in script --- install-scripts/install-safe-chain.sh | 10 +++++++++- install-scripts/uninstall-safe-chain.sh | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/install-scripts/install-safe-chain.sh b/install-scripts/install-safe-chain.sh index 6f0dd26..63e622e 100755 --- a/install-scripts/install-safe-chain.sh +++ b/install-scripts/install-safe-chain.sh @@ -161,7 +161,15 @@ remove_volta_installation() { # Check and uninstall nvm-managed package if present across all Node versions remove_nvm_installation() { - # Check if nvm is available as a command + # nvm is a shell function, not a binary, so we need to source it first + if [ -s "$HOME/.nvm/nvm.sh" ]; then + # Source nvm to make it available in this script + . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 + elif [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" >/dev/null 2>&1 + fi + + # Check if nvm is now available if ! command_exists nvm; then return fi diff --git a/install-scripts/uninstall-safe-chain.sh b/install-scripts/uninstall-safe-chain.sh index 8d1fbdf..15c4f96 100755 --- a/install-scripts/uninstall-safe-chain.sh +++ b/install-scripts/uninstall-safe-chain.sh @@ -77,7 +77,15 @@ remove_volta_installation() { # Check and uninstall nvm-managed package if present across all Node versions remove_nvm_installation() { - # Check if nvm is available as a command + # nvm is a shell function, not a binary, so we need to source it first + if [ -s "$HOME/.nvm/nvm.sh" ]; then + # Source nvm to make it available in this script + . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 + elif [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" >/dev/null 2>&1 + fi + + # Check if nvm is now available if ! command_exists nvm; then return fi From 5a28d6646f28394eb1018d345b4c158f41cb639f Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:53:24 +0100 Subject: [PATCH 05/10] Update comments --- install-scripts/install-safe-chain.sh | 5 +++-- install-scripts/uninstall-safe-chain.sh | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/install-scripts/install-safe-chain.sh b/install-scripts/install-safe-chain.sh index 63e622e..8e184da 100755 --- a/install-scripts/install-safe-chain.sh +++ b/install-scripts/install-safe-chain.sh @@ -161,7 +161,9 @@ remove_volta_installation() { # Check and uninstall nvm-managed package if present across all Node versions remove_nvm_installation() { - # nvm is a shell function, not a binary, so we need to source it first + # This script is run in sh shell for greatest compatibility. + # Because nvm is usually setup in bash/zsh/fish startup scripts, we need to source it. + # Otherwise it won't be available in sh. if [ -s "$HOME/.nvm/nvm.sh" ]; then # Source nvm to make it available in this script . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 @@ -174,7 +176,6 @@ remove_nvm_installation() { return fi - # Get list of installed Node versions nvm_versions=$(nvm list 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "") if [ -z "$nvm_versions" ]; then diff --git a/install-scripts/uninstall-safe-chain.sh b/install-scripts/uninstall-safe-chain.sh index 15c4f96..7b226a5 100755 --- a/install-scripts/uninstall-safe-chain.sh +++ b/install-scripts/uninstall-safe-chain.sh @@ -77,7 +77,9 @@ remove_volta_installation() { # Check and uninstall nvm-managed package if present across all Node versions remove_nvm_installation() { - # nvm is a shell function, not a binary, so we need to source it first + # This script is run in sh shell for greatest compatibility. + # Because nvm is usually setup in bash/zsh/fish startup scripts, we need to source it. + # Otherwise it won't be available in sh. if [ -s "$HOME/.nvm/nvm.sh" ]; then # Source nvm to make it available in this script . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 From d7d5bacd2158ffed87171519148d7cb54915419e Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:53:32 +0100 Subject: [PATCH 06/10] Remove warning from readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a13395c..f08daad 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,6 @@ Aikido Safe Chain supports the following package managers: Installing the Aikido Safe Chain is easy with our one-line installer. -> ⚠️ **Already installed via npm?** See the [migration guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/npm-to-binary-migration.md) to switch to the binary version. - ### Unix/Linux/macOS ```shell @@ -206,6 +204,7 @@ You can set the minimum package age through multiple sources (in order of priori Configure Safe Chain to scan packages from custom or private registries. Supported ecosystems: + - Node.js - Python @@ -348,5 +347,4 @@ pipeline { } ``` - After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection. From 4aca6ef86a9f564c7bf0e18b44079e0cac4f9180 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 10:54:34 +0100 Subject: [PATCH 07/10] Restore publish script --- .github/workflows/build-and-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 1c05824..83c11d9 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -61,10 +61,10 @@ jobs: cp LICENSE packages/safe-chain/ cp -r docs packages/safe-chain/ - # - name: Publish to npm - # run: | - # echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" - # npm publish --workspace=packages/safe-chain --access public --provenance + - name: Publish to npm + run: | + echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" + npm publish --workspace=packages/safe-chain --access public --provenance - name: Download all binary artifacts uses: actions/download-artifact@v4 From 4e098bcff746f3ed0c0904e357be5671dd88ea16 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 11:23:47 +0100 Subject: [PATCH 08/10] Change order of removal for npm-based installations --- install-scripts/install-safe-chain.sh | 5 ++--- install-scripts/uninstall-safe-chain.sh | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/install-scripts/install-safe-chain.sh b/install-scripts/install-safe-chain.sh index 8e184da..80e4493 100755 --- a/install-scripts/install-safe-chain.sh +++ b/install-scripts/install-safe-chain.sh @@ -265,10 +265,9 @@ main() { info "$INSTALL_MSG" # Check for existing safe-chain installation through nvm, volta, or npm - # nvm must be checked first as it manages multiple Node versions - remove_nvm_installation - remove_volta_installation remove_npm_installation + remove_volta_installation + remove_nvm_installation # Detect platform OS=$(detect_os) diff --git a/install-scripts/uninstall-safe-chain.sh b/install-scripts/uninstall-safe-chain.sh index 7b226a5..e208319 100755 --- a/install-scripts/uninstall-safe-chain.sh +++ b/install-scripts/uninstall-safe-chain.sh @@ -151,10 +151,10 @@ main() { warn "safe-chain command not found. Proceeding with uninstallation." fi - # Remove npm-based installations (nvm must be checked first) - remove_nvm_installation - remove_volta_installation + # Check for existing safe-chain installation through nvm, volta, or npm remove_npm_installation + remove_volta_installation + remove_nvm_installation # Remove install dir recursively if it exists if [ -d "$INSTALL_DIR" ]; then From 66c1da0f1e36ebe1845db9ca7e54816f5c788092 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 11:48:06 +0100 Subject: [PATCH 09/10] Rework release workflow (split npm and github release), and skip npm publish for prereleases --- .github/workflows/build-and-release.yml | 86 ++++++++++++++++--------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 83c11d9..c0256a9 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -11,9 +11,11 @@ permissions: jobs: set-version: + name: Set version number runs-on: ubuntu-latest outputs: version: ${{ steps.get_version.outputs.tag }} + is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }} steps: - name: Set version number id: get_version @@ -21,13 +23,23 @@ jobs: version="${{ github.ref_name }}" echo "tag=$version" >> $GITHUB_OUTPUT + - name: Check if pre-release + id: check_prerelease + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + IS_PRERELEASE=$(gh release view ${{ steps.get_version.outputs.tag }} --json isPrerelease --jq '.isPrerelease') + echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT + echo "Release ${{ steps.get_version.outputs.tag }} is pre-release: $IS_PRERELEASE" + create-binaries: needs: set-version uses: ./.github/workflows/create-artifact.yml with: version: ${{ needs.set-version.outputs.version }} - build: + publish-binaries: + name: Publish to GitHub release needs: [set-version, create-binaries] runs-on: ubuntu-latest @@ -35,37 +47,6 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "lts/*" - registry-url: "https://registry.npmjs.org/" - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} - - - name: Setup safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - - - name: Set the version in safe-chain package - run: npm --no-git-tag-version version ${{ needs.set-version.outputs.version }} --workspace=packages/safe-chain - - - name: Install dependencies - run: npm ci - - - name: Run tests - run: npm run test - - - name: Copy documentation files to package - run: | - cp README.md packages/safe-chain/ - cp LICENSE packages/safe-chain/ - cp -r docs packages/safe-chain/ - - - name: Publish to npm - run: | - echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" - npm publish --workspace=packages/safe-chain --access public --provenance - - name: Download all binary artifacts uses: actions/download-artifact@v4 with: @@ -107,3 +88,44 @@ jobs: release-artifacts/install-safe-chain.ps1 \ release-artifacts/uninstall-safe-chain.sh \ release-artifacts/uninstall-safe-chain.ps1 + + publish-npm: + name: Publish to npm + needs: [set-version, create-binaries] + if: needs.set-version.outputs.is_prerelease != 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "lts/*" + registry-url: "https://registry.npmjs.org/" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + + - name: Setup safe-chain + run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci + + - name: Set the version in safe-chain package + run: npm --no-git-tag-version version ${{ needs.set-version.outputs.version }} --workspace=packages/safe-chain + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test + + - name: Copy documentation files to package + run: | + cp README.md packages/safe-chain/ + cp LICENSE packages/safe-chain/ + cp -r docs packages/safe-chain/ + + - name: Publish to npm + run: | + echo "Publishing version ${{ needs.set-version.outputs.version }} to NPM" + npm publish --workspace=packages/safe-chain --access public --provenance From 1f4e50df9db9dbf63aa5f9182b10a99a6f01d8e9 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 6 Jan 2026 11:51:01 +0100 Subject: [PATCH 10/10] Checkout code in set version --- .github/workflows/build-and-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index c0256a9..a372e1e 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -17,6 +17,9 @@ jobs: version: ${{ steps.get_version.outputs.tag }} is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }} steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set version number id: get_version run: |