From cc5a7d9a0bbd3e77f293f0cc862eafefbf830ded Mon Sep 17 00:00:00 2001 From: "aikido-autofix[bot]" <119856028+aikido-autofix[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:57:05 +0000 Subject: [PATCH 1/9] fix(security): autofix Template Injection in GitHub Workflows Action --- .github/workflows/create-artifact.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create-artifact.yml b/.github/workflows/create-artifact.yml index 90b9745..4fee730 100644 --- a/.github/workflows/create-artifact.yml +++ b/.github/workflows/create-artifact.yml @@ -78,7 +78,9 @@ jobs: - name: Set the version in safe-chain package if: inputs.version != '' - run: npm --no-git-tag-version version ${{ inputs.version }} --workspace=packages/safe-chain --ignore-scripts + env: + VERSION: ${{ inputs.version }} + run: npm --no-git-tag-version version $VERSION --workspace=packages/safe-chain --ignore-scripts - name: Create binary run: | From e9f941e3d0ee2b7bc4af13271eddee52cc096dfd Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 25 Mar 2026 09:53:42 +0100 Subject: [PATCH 2/9] Use runner with static ip for releases --- .github/workflows/build-and-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index bab932c..d6c810a 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -12,7 +12,7 @@ permissions: jobs: set-version: name: Set version number - runs-on: ubuntu-latest + runs-on: standard-runner-no-rights-public-ip outputs: version: ${{ steps.get_version.outputs.tag }} is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }} @@ -44,7 +44,7 @@ jobs: publish-binaries: name: Publish to GitHub release needs: [set-version, create-binaries] - runs-on: ubuntu-latest + runs-on: standard-runner-no-rights-public-ip steps: - name: Checkout code From d113ca3061c6abc0ee6baf249020af23e5ef78dc Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 25 Mar 2026 16:19:15 +0100 Subject: [PATCH 3/9] Increase default min package age to 48 hours --- README.md | 6 +++--- packages/safe-chain/src/config/settings.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4daf1d2..a391cdd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - ✅ **Block malware on developer laptops and CI/CD** - ✅ **Supports npm and PyPI** more package managers coming -- ✅ **Blocks packages newer than 24 hours** without breaking your build +- ✅ **Blocks packages newer than 48 hours** without breaking your build - ✅ **Tokenless, free, no build data shared** Aikido Safe Chain supports the following package managers: @@ -113,7 +113,7 @@ The Aikido Safe Chain works by running a lightweight proxy server that intercept ### Minimum package age (npm only) -For npm packages, Safe Chain temporarily suppresses packages published within the last 24 hours (by default) until they have been validated against malware. This provides an additional security layer during the critical period when newly published packages are most vulnerable to containing undetected threats. You can configure this threshold or bypass this protection entirely - see the [Minimum Package Age Configuration](#minimum-package-age) section below. +For npm packages, Safe Chain temporarily suppresses packages published within the last 48 hours (by default) until they have been validated against malware. This provides an additional security layer during the critical period when newly published packages are most vulnerable to containing undetected threats. You can configure this threshold or bypass this protection entirely - see the [Minimum Package Age Configuration](#minimum-package-age) section below. ⚠️ This feature **only applies to npm-based package managers** (npm, npx, yarn, pnpm, pnpx, bun, bunx) and does not apply to Python package managers (uv, pip, pip3, poetry, pipx). @@ -183,7 +183,7 @@ You can set the logging level through multiple sources (in order of priority): ## Minimum Package Age -You can configure how long packages must exist before Safe Chain allows their installation. By default, packages must be at least 24 hours old before they can be installed through npm-based package managers. +You can configure how long packages must exist before Safe Chain allows their installation. By default, packages must be at least 48 hours old before they can be installed through npm-based package managers. ### Configuration Options diff --git a/packages/safe-chain/src/config/settings.js b/packages/safe-chain/src/config/settings.js index b9243b0..7919d87 100644 --- a/packages/safe-chain/src/config/settings.js +++ b/packages/safe-chain/src/config/settings.js @@ -45,7 +45,7 @@ export function setEcoSystem(setting) { ecosystemSettings.ecoSystem = setting; } -const defaultMinimumPackageAge = 24; +const defaultMinimumPackageAge = 48; /** @returns {number} */ export function getMinimumPackageAgeHours() { // Priority 1: CLI argument From 33f50ba5804e2c8543dd7e0c65224bcd752811bd Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Wed, 25 Mar 2026 11:04:05 -0700 Subject: [PATCH 4/9] Change runner to open-source-releaser in workflow --- .github/workflows/build-and-release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index d6c810a..1e593a3 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -12,7 +12,7 @@ permissions: jobs: set-version: name: Set version number - runs-on: standard-runner-no-rights-public-ip + runs-on: open-source-releaser outputs: version: ${{ steps.get_version.outputs.tag }} is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }} @@ -44,8 +44,7 @@ jobs: publish-binaries: name: Publish to GitHub release needs: [set-version, create-binaries] - runs-on: standard-runner-no-rights-public-ip - + runs-on: open-source-releaser steps: - name: Checkout code uses: actions/checkout@v3 From 7433e97c4a2c437a06e9abbc239c96efac737ae5 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Wed, 25 Mar 2026 12:58:35 -0700 Subject: [PATCH 5/9] Fix yml --- .github/workflows/build-and-release.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 1e593a3..d156d59 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -28,12 +28,15 @@ jobs: - 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') + TAG="${{ steps.get_version.outputs.tag }}" + if echo "$TAG" | grep -Eq '(^|[.-])(alpha|beta|rc|pre)([.-]?[0-9]+)?$'; then + IS_PRERELEASE=true + else + IS_PRERELEASE=false + fi echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT - echo "Release ${{ steps.get_version.outputs.tag }} is pre-release: $IS_PRERELEASE" + echo "Tag $TAG is pre-release: $IS_PRERELEASE" create-binaries: needs: set-version From 306c727832762e9037804c345bda048f2dd773d7 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Wed, 25 Mar 2026 13:03:48 -0700 Subject: [PATCH 6/9] Fix test --- .../src/installation/downloadAgent.spec.js | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.spec.js b/packages/safe-chain/src/installation/downloadAgent.spec.js index 17aecb9..48d2fe8 100644 --- a/packages/safe-chain/src/installation/downloadAgent.spec.js +++ b/packages/safe-chain/src/installation/downloadAgent.spec.js @@ -2,18 +2,19 @@ import { describe, it, after } from "node:test"; import assert from "node:assert"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { unlinkSync } from "node:fs"; +import { unlinkSync, writeFileSync } from "node:fs"; +import { createHash } from "node:crypto"; import { DOWNLOAD_URLS, - downloadFile, + getAgentDownloadUrl, verifyChecksum, } from "./downloadAgent.js"; -describe("downloadAgent checksums", { timeout: 120_000 }, () => { - const downloadedFiles = []; +describe("downloadAgent", () => { + const tempFiles = []; after(() => { - for (const file of downloadedFiles) { + for (const file of tempFiles) { try { unlinkSync(file); } catch { @@ -24,22 +25,40 @@ describe("downloadAgent checksums", { timeout: 120_000 }, () => { for (const [platform, architectures] of Object.entries(DOWNLOAD_URLS)) { for (const [arch, { url, checksum }] of Object.entries(architectures)) { - it(`${platform}/${arch} checksum matches`, async () => { - const destPath = join( - tmpdir(), - `safe-chain-test-${platform}-${arch}-${Date.now()}` - ); - downloadedFiles.push(destPath); - - await downloadFile(url, destPath); - - const isValid = await verifyChecksum(destPath, checksum); - assert.strictEqual( - isValid, - true, - `Checksum mismatch for ${platform}/${arch} (${url})` + it(`${platform}/${arch} has a valid download definition`, () => { + assert.match( + url, + /^https:\/\/github\.com\/AikidoSec\/safechain-internals\/releases\/download\/v\d+\.\d+\.\d+\/.+/, ); + assert.match(checksum, /^sha256:[a-f0-9]{64}$/); }); } } + + it("builds agent download URLs from the current version", () => { + assert.equal( + getAgentDownloadUrl("SafeChainUltimate.pkg"), + "https://github.com/AikidoSec/safechain-internals/releases/download/v1.0.0/SafeChainUltimate.pkg", + ); + }); + + it("verifies checksum for a local file", async () => { + const destPath = join(tmpdir(), `safe-chain-test-${Date.now()}`); + tempFiles.push(destPath); + + writeFileSync(destPath, "safe-chain-test"); + + const expectedHash = createHash("sha256") + .update("safe-chain-test") + .digest("hex"); + + assert.equal( + await verifyChecksum(destPath, `sha256:${expectedHash}`), + true, + ); + assert.equal( + await verifyChecksum(destPath, `sha256:${"0".repeat(64)}`), + false, + ); + }); }); From de33ceab417708495f9bb2a73d4b5baf70db13bb Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Wed, 25 Mar 2026 13:06:14 -0700 Subject: [PATCH 7/9] Another fix --- .github/workflows/build-and-release.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index d156d59..1e593a3 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -28,15 +28,12 @@ jobs: - name: Check if pre-release id: check_prerelease + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG="${{ steps.get_version.outputs.tag }}" - if echo "$TAG" | grep -Eq '(^|[.-])(alpha|beta|rc|pre)([.-]?[0-9]+)?$'; then - IS_PRERELEASE=true - else - IS_PRERELEASE=false - fi + IS_PRERELEASE=$(gh release view ${{ steps.get_version.outputs.tag }} --json isPrerelease --jq '.isPrerelease') echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT - echo "Tag $TAG is pre-release: $IS_PRERELEASE" + echo "Release ${{ steps.get_version.outputs.tag }} is pre-release: $IS_PRERELEASE" create-binaries: needs: set-version From 9f3cd1b4da08e37e6fa2a5750ec76e73ea485692 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Wed, 25 Mar 2026 13:16:42 -0700 Subject: [PATCH 8/9] Don't rely on hardcoded URL --- .../safe-chain/src/installation/downloadAgent.spec.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.spec.js b/packages/safe-chain/src/installation/downloadAgent.spec.js index 48d2fe8..44e53c0 100644 --- a/packages/safe-chain/src/installation/downloadAgent.spec.js +++ b/packages/safe-chain/src/installation/downloadAgent.spec.js @@ -6,7 +6,6 @@ import { unlinkSync, writeFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { DOWNLOAD_URLS, - getAgentDownloadUrl, verifyChecksum, } from "./downloadAgent.js"; @@ -35,13 +34,6 @@ describe("downloadAgent", () => { } } - it("builds agent download URLs from the current version", () => { - assert.equal( - getAgentDownloadUrl("SafeChainUltimate.pkg"), - "https://github.com/AikidoSec/safechain-internals/releases/download/v1.0.0/SafeChainUltimate.pkg", - ); - }); - it("verifies checksum for a local file", async () => { const destPath = join(tmpdir(), `safe-chain-test-${Date.now()}`); tempFiles.push(destPath); From 50a931cf4dc235ca7ca54824aac27b6e0a496b00 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 26 Mar 2026 13:36:20 +0100 Subject: [PATCH 9/9] Add manual setup and teardown instructions on failure --- .../safe-chain/src/shell-integration/setup.js | 10 +++++++--- .../src/shell-integration/shellDetection.js | 2 ++ .../shell-integration/supported-shells/bash.js | 18 ++++++++++++++++++ .../shell-integration/supported-shells/fish.js | 18 ++++++++++++++++++ .../supported-shells/powershell.js | 18 ++++++++++++++++++ .../supported-shells/windowsPowershell.js | 18 ++++++++++++++++++ .../shell-integration/supported-shells/zsh.js | 18 ++++++++++++++++++ .../src/shell-integration/teardown.js | 8 +++++++- 8 files changed, 106 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/setup.js b/packages/safe-chain/src/shell-integration/setup.js index 4138db6..66c6533 100644 --- a/packages/safe-chain/src/shell-integration/setup.js +++ b/packages/safe-chain/src/shell-integration/setup.js @@ -91,9 +91,7 @@ async function setupShell(shell) { ); } else { ui.writeError( - `${chalk.bold("- " + shell.name + ":")} ${chalk.red( - "Setup failed", - )}. Please check your ${shell.name} configuration.`, + `${chalk.bold("- " + shell.name + ":")} ${chalk.red("Setup failed")}`, ); if (error) { let message = ` Error: ${error.message}`; @@ -102,6 +100,12 @@ async function setupShell(shell) { } ui.writeError(message); } + ui.emptyLine(); + ui.writeInformation(` ${chalk.bold("To set up manually:")}`); + for (const instruction of shell.getManualSetupInstructions()) { + ui.writeInformation(` ${instruction}`); + } + ui.emptyLine(); } return success; diff --git a/packages/safe-chain/src/shell-integration/shellDetection.js b/packages/safe-chain/src/shell-integration/shellDetection.js index 996125c..c471244 100644 --- a/packages/safe-chain/src/shell-integration/shellDetection.js +++ b/packages/safe-chain/src/shell-integration/shellDetection.js @@ -11,6 +11,8 @@ import { ui } from "../environment/userInteraction.js"; * @property {() => boolean} isInstalled * @property {(tools: import("./helpers.js").AikidoTool[]) => boolean|Promise} setup * @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} teardown + * @property {() => string[]} getManualSetupInstructions + * @property {() => string[]} getManualTeardownInstructions */ /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.js index 07d89cb..cc50223 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.js @@ -123,6 +123,22 @@ function cygpathw(path) { } } +function getManualTeardownInstructions() { + return [ + `Remove the following line from your ~/.bashrc file:`, + ` source ~/.safe-chain/scripts/init-posix.sh`, + `Then restart your terminal or run: source ~/.bashrc`, + ]; +} + +function getManualSetupInstructions() { + return [ + `Add the following line to your ~/.bashrc file:`, + ` source ~/.safe-chain/scripts/init-posix.sh`, + `Then restart your terminal or run: source ~/.bashrc`, + ]; +} + /** * @type {import("../shellDetection.js").Shell} */ @@ -131,4 +147,6 @@ export default { isInstalled, setup, teardown, + getManualSetupInstructions, + getManualTeardownInstructions, }; diff --git a/packages/safe-chain/src/shell-integration/supported-shells/fish.js b/packages/safe-chain/src/shell-integration/supported-shells/fish.js index 0af6ae3..a623d0b 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/fish.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/fish.js @@ -66,6 +66,22 @@ function getStartupFile() { } } +function getManualTeardownInstructions() { + return [ + `Remove the following line from your ~/.config/fish/config.fish file:`, + ` source ~/.safe-chain/scripts/init-fish.fish`, + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ]; +} + +function getManualSetupInstructions() { + return [ + `Add the following line to your ~/.config/fish/config.fish file:`, + ` source ~/.safe-chain/scripts/init-fish.fish`, + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ]; +} + /** * @type {import("../shellDetection.js").Shell} */ @@ -74,4 +90,6 @@ export default { isInstalled, setup, teardown, + getManualSetupInstructions, + getManualTeardownInstructions, }; diff --git a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js index 96eb219..4bbc332 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -71,6 +71,22 @@ function getStartupFile() { } } +function getManualTeardownInstructions() { + return [ + `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, + `Then restart your terminal or run: . $PROFILE`, + ]; +} + +function getManualSetupInstructions() { + return [ + `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, + `Then restart your terminal or run: . $PROFILE`, + ]; +} + /** * @type {import("../shellDetection.js").Shell} */ @@ -79,4 +95,6 @@ export default { isInstalled, setup, teardown, + getManualSetupInstructions, + getManualTeardownInstructions, }; diff --git a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js index 2740456..3e81da7 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -71,6 +71,22 @@ function getStartupFile() { } } +function getManualTeardownInstructions() { + return [ + `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, + `Then restart your terminal or run: . $PROFILE`, + ]; +} + +function getManualSetupInstructions() { + return [ + `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, + `Then restart your terminal or run: . $PROFILE`, + ]; +} + /** * @type {import("../shellDetection.js").Shell} */ @@ -79,4 +95,6 @@ export default { isInstalled, setup, teardown, + getManualSetupInstructions, + getManualTeardownInstructions, }; diff --git a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js index 6086095..f187af3 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js @@ -66,9 +66,27 @@ function getStartupFile() { } } +function getManualTeardownInstructions() { + return [ + `Remove the following line from your ~/.zshrc file:`, + ` source ~/.safe-chain/scripts/init-posix.sh`, + `Then restart your terminal or run: source ~/.zshrc`, + ]; +} + +function getManualSetupInstructions() { + return [ + `Add the following line to your ~/.zshrc file:`, + ` source ~/.safe-chain/scripts/init-posix.sh`, + `Then restart your terminal or run: source ~/.zshrc`, + ]; +} + export default { name: shellName, isInstalled, setup, teardown, + getManualSetupInstructions, + getManualTeardownInstructions, }; diff --git a/packages/safe-chain/src/shell-integration/teardown.js b/packages/safe-chain/src/shell-integration/teardown.js index de3fbd7..bcf6346 100644 --- a/packages/safe-chain/src/shell-integration/teardown.js +++ b/packages/safe-chain/src/shell-integration/teardown.js @@ -47,8 +47,14 @@ export async function teardown() { ui.writeError( `${chalk.bold("- " + shell.name + ":")} ${chalk.red( "Teardown failed" - )}. Please check your ${shell.name} configuration.` + )}` ); + ui.emptyLine(); + ui.writeInformation(` ${chalk.bold("To tear down manually:")}`); + for (const instruction of shell.getManualTeardownInstructions()) { + ui.writeInformation(` ${instruction}`); + } + ui.emptyLine(); } }