From 3bfca9e296470c6429dfa46d86d0be7827a5804a Mon Sep 17 00:00:00 2001 From: Uriel Corfa Date: Wed, 7 Jan 2026 17:18:48 +0100 Subject: [PATCH 01/97] Propagate command-not-found errors when invoking wrapped commands Before this change, if a package manager was not installed, safe-chain still sets the function and when invoked, the wrapper will invoke safe-chain, which will exit with error code 127 when it fails to invoke the wrapped command. As an example (with a shell prompt that shows $? when non-zero): ``` $ type -f pip bash: type: pip: not found 1$ pip 127$ ``` With this patch, the wrapper first checks for the existence of the wrapped command (ignoring functions), and if no such command exists, it instructs the shell to invoke it anyway. This results in the shell failing to find the command, and reporting an error as if the wrapper function wasn't there: ``` $ source init-posix.sh $ type -f pip bash: type: pip: not found 1$ pip Command 'pip' not found, but can be installed with: sudo apt install python3-pip 127$ ``` --- .../src/shell-integration/startup-scripts/init-posix.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh index e649909..b9eebeb 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh @@ -76,6 +76,14 @@ function printSafeChainWarning() { function wrapSafeChainCommand() { local original_cmd="$1" + if ! type -f "${original_cmd}" > /dev/null 2>&1; then + # If the original command is not available, don't try to wrap it: invoke it + # transparently, so the shell can report errors as if this wrapper didn't + # exist. + command $@ + return $? + fi + if command -v safe-chain > /dev/null 2>&1; then # If the aikido command is available, just run it with the provided arguments safe-chain "$@" From 4e894dd0fdcfdea1fb731c3562adbf6be7b86c04 Mon Sep 17 00:00:00 2001 From: Uriel Corfa Date: Thu, 8 Jan 2026 09:56:59 +0100 Subject: [PATCH 02/97] init-posix: preserve arguments when exec'ing the original_cmd --- .../src/shell-integration/startup-scripts/init-posix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh index b9eebeb..ebaaf3c 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh @@ -80,7 +80,7 @@ function wrapSafeChainCommand() { # If the original command is not available, don't try to wrap it: invoke it # transparently, so the shell can report errors as if this wrapper didn't # exist. - command $@ + command "$@" return $? fi From 0ce0a875575a6d6ca9485e2f63cd54d44cea51e1 Mon Sep 17 00:00:00 2001 From: Uriel Corfa Date: Thu, 8 Jan 2026 10:01:13 +0100 Subject: [PATCH 03/97] Add the same handler for fish --- .../startup-scripts/init-fish.fish | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish b/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish index ec58c8b..13463f6 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish @@ -71,13 +71,13 @@ end function printSafeChainWarning set original_cmd $argv[1] - + # Fish equivalent of ANSI color codes: yellow background, black text for "Warning:" set_color -b yellow black printf "Warning:" set_color normal printf " safe-chain is not available to protect you from installing malware. %s will run without it.\n" $original_cmd - + # Cyan text for the install command printf "Install safe-chain by using " set_color cyan @@ -90,6 +90,20 @@ function wrapSafeChainCommand set original_cmd $argv[1] set cmd_args $argv[2..-1] + if not type -fq $original_cmd + # If the original command is not available, don't try to wrap it: invoke + # it transparently, so the shell can report errors as if this wrapper + # didn't exist. fish always adds extra debug information when executing + # missing commands from within a function, so after the "command not + # found" handler, there will be information about how the + # wrapSafeChainCommand function errored out. To avoid users assuming this + # is a safe-chain bug, display an explicit error message afterwards. + command $original_cmd $cmd_args + set oldstatus $status + echo "safe-chain tried to run $original_cmd but it doesn't seem to be installed in your \$PATH." >&2 + return $oldstatus + end + if type -q safe-chain # If the safe-chain command is available, just run it with the provided arguments safe-chain $original_cmd $cmd_args From 19652c49c9e48ec96d84b40739b275831b8c8b90 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Mon, 12 Jan 2026 14:53:23 -0800 Subject: [PATCH 04/97] Attempted fix for powershell swallowing '--' --- .../startup-scripts/init-pwsh.ps1 | 66 ++++++++++++++----- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 index 7fabcad..f02b900 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -6,27 +6,27 @@ $safeChainBin = Join-Path (Join-Path $HOME '.safe-chain') 'bin' $env:PATH = "$env:PATH$pathSeparator$safeChainBin" function npx { - Invoke-WrappedCommand "npx" $args + Invoke-WrappedCommand "npx" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function yarn { - Invoke-WrappedCommand "yarn" $args + Invoke-WrappedCommand "yarn" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function pnpm { - Invoke-WrappedCommand "pnpm" $args + Invoke-WrappedCommand "pnpm" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function pnpx { - Invoke-WrappedCommand "pnpx" $args + Invoke-WrappedCommand "pnpx" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function bun { - Invoke-WrappedCommand "bun" $args + Invoke-WrappedCommand "bun" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function bunx { - Invoke-WrappedCommand "bunx" $args + Invoke-WrappedCommand "bunx" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function npm { @@ -37,37 +37,37 @@ function npm { return } - Invoke-WrappedCommand "npm" $args + Invoke-WrappedCommand "npm" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function pip { - Invoke-WrappedCommand "pip" $args + Invoke-WrappedCommand "pip" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function pip3 { - Invoke-WrappedCommand "pip3" $args + Invoke-WrappedCommand "pip3" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function uv { - Invoke-WrappedCommand "uv" $args + Invoke-WrappedCommand "uv" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function poetry { - Invoke-WrappedCommand "poetry" $args + Invoke-WrappedCommand "poetry" $args $MyInvocation.Line $MyInvocation.OffsetInLine } # `python -m pip`, `python -m pip3`. function python { - Invoke-WrappedCommand 'python' $args + Invoke-WrappedCommand 'python' $args $MyInvocation.Line $MyInvocation.OffsetInLine } # `python3 -m pip`, `python3 -m pip3'. function python3 { - Invoke-WrappedCommand 'python3' $args + Invoke-WrappedCommand 'python3' $args $MyInvocation.Line $MyInvocation.OffsetInLine } function pipx { - Invoke-WrappedCommand "pipx" $args + Invoke-WrappedCommand "pipx" $args $MyInvocation.Line $MyInvocation.OffsetInLine } function Write-SafeChainWarning { @@ -111,10 +111,44 @@ function Invoke-RealCommand { function Invoke-WrappedCommand { param( [string]$OriginalCmd, - [string[]]$Arguments + [string[]]$Arguments, + [string]$RawLine = $null, + [int]$RawOffset = 0 ) - if (Test-CommandAvailable "safe-chain") { + # Use raw line parsing to recover arguments like '--' that PowerShell consumes + if ($RawLine) { + $tokens = [System.Management.Automation.PSParser]::Tokenize($RawLine, [ref]$null) + $newArgs = @() + $foundCommand = $false + $canUseRaw = $true + + foreach ($t in $tokens) { + # Find the command token based on offset + if (-not $foundCommand) { + if ($t.Start -eq ($RawOffset - 1)) { $foundCommand = $true } + continue + } + # Stop at command separators + if ($t.Type -eq 'Operator' -and $t.Content -match '[|;&]') { break } + # Stop if complex variable expansion is used + if ($t.Type -eq 'Variable' -or $t.Type -eq 'Group' -or $t.Type -eq 'SubExpression') { + $canUseRaw = $false + break + } + $newArgs += $t.Content + } + + if ($foundCommand -and $canUseRaw) { + $Arguments = $newArgs + Write-Host "Safe-chain Powershell Wrapper: Reconstructed args: $($Arguments -join ' ')" + } + } + + if ($isWindowsPlatform -and (Test-CommandAvailable "safe-chain.cmd")) { + & safe-chain.cmd $OriginalCmd @Arguments + } + elseif (Test-CommandAvailable "safe-chain") { & safe-chain $OriginalCmd @Arguments } else { From 9a902af917fce9920a71a34235cd8fc5ec39977c Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Mon, 12 Jan 2026 15:12:19 -0800 Subject: [PATCH 05/97] Fix some logic --- .../startup-scripts/init-pwsh.ps1 | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 index f02b900..e1ed660 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -108,6 +108,40 @@ function Invoke-RealCommand { } } +function Get-ReconstructedArguments { + param( + [string]$RawLine, + [int]$RawOffset + ) + + if (-not $RawLine) { return $null } + + $tokens = [System.Management.Automation.PSParser]::Tokenize($RawLine, [ref]$null) + $newArgs = @() + $foundCommand = $false + + foreach ($t in $tokens) { + if (-not $foundCommand) { + if ($t.Start -eq ($RawOffset - 1)) { $foundCommand = $true } + continue + } + + if ($t.Type -eq 'Operator' -and $t.Content -match '[|;&]') { break } + + # Stop if complex variable expansion is used + if ($t.Type -eq 'Variable' -or $t.Type -eq 'Group' -or $t.Type -eq 'SubExpression') { + return $null + } + + $newArgs += $t.Content + } + + if ($foundCommand) { + return ,$newArgs + } + return $null +} + function Invoke-WrappedCommand { param( [string]$OriginalCmd, @@ -118,29 +152,9 @@ function Invoke-WrappedCommand { # Use raw line parsing to recover arguments like '--' that PowerShell consumes if ($RawLine) { - $tokens = [System.Management.Automation.PSParser]::Tokenize($RawLine, [ref]$null) - $newArgs = @() - $foundCommand = $false - $canUseRaw = $true - - foreach ($t in $tokens) { - # Find the command token based on offset - if (-not $foundCommand) { - if ($t.Start -eq ($RawOffset - 1)) { $foundCommand = $true } - continue - } - # Stop at command separators - if ($t.Type -eq 'Operator' -and $t.Content -match '[|;&]') { break } - # Stop if complex variable expansion is used - if ($t.Type -eq 'Variable' -or $t.Type -eq 'Group' -or $t.Type -eq 'SubExpression') { - $canUseRaw = $false - break - } - $newArgs += $t.Content - } - - if ($foundCommand -and $canUseRaw) { - $Arguments = $newArgs + $reconstructedArgs = Get-ReconstructedArguments $RawLine $RawOffset + if ($null -ne $reconstructedArgs) { + $Arguments = $reconstructedArgs Write-Host "Safe-chain Powershell Wrapper: Reconstructed args: $($Arguments -join ' ')" } } From 340e9a90a5f47c6725852c4b61e057ccb017cf97 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Mon, 12 Jan 2026 15:13:34 -0800 Subject: [PATCH 06/97] Remove comment --- .../src/shell-integration/startup-scripts/init-pwsh.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 index e1ed660..f82d0fc 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -155,7 +155,6 @@ function Invoke-WrappedCommand { $reconstructedArgs = Get-ReconstructedArguments $RawLine $RawOffset if ($null -ne $reconstructedArgs) { $Arguments = $reconstructedArgs - Write-Host "Safe-chain Powershell Wrapper: Reconstructed args: $($Arguments -join ' ')" } } From b25d405972d433cf30f8cf86dcb9cece4c74bf37 Mon Sep 17 00:00:00 2001 From: Robert Slootjes Date: Tue, 13 Jan 2026 08:19:10 +0100 Subject: [PATCH 07/97] Add Bitbucket Pipelines example --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 57d1bf4..17d2515 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,7 @@ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download - ✅ **Azure Pipelines** - ✅ **CircleCI** - ✅ **Jenkins** +- ✅ **Bitbucket Pipelines** ## GitHub Actions Example @@ -360,6 +361,21 @@ pipeline { } ``` +## Bitbucket Pipelines Example + +```yaml +image: node:22 + +steps: + - step: + name: Install + script: + - npm install -g @aikidosec/safe-chain + - safe-chain setup-ci + - export PATH=~/.safe-chain/shims:$PATH + - npm ci +``` + After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection. # Troubleshooting From f678ff8dd1d62d8274ae093b23c010647903248c Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Tue, 13 Jan 2026 10:09:59 -0800 Subject: [PATCH 08/97] Include package name in logging when minimum package age is not met --- .../src/registryProxy/interceptors/npm/modifyNpmInfo.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js index 2ee4eb8..ae71cb3 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js @@ -116,8 +116,10 @@ export function modifyNpmInfoResponse(body, headers) { function deleteVersionFromJson(json, version) { state.hasSuppressedVersions = true; - ui.writeVerbose( - `Safe-chain: ${version} is newer than ${getMinimumPackageAgeHours()} hours and was removed (minimumPackageAgeInHours setting).` + const packageName = typeof json?.name === "string" ? json.name : "(unknown)"; + + ui.writeInformation( + `Safe-chain: ${packageName}@${version} is newer than ${getMinimumPackageAgeHours()} hours and was removed (minimumPackageAgeInHours setting).` ); delete json.time[version]; From c38f1bcb3e752985b040e6669bbc5e0932bad621 Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Tue, 13 Jan 2026 19:33:00 +0100 Subject: [PATCH 09/97] Update packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js --- .../src/registryProxy/interceptors/npm/modifyNpmInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js index ae71cb3..421666a 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js @@ -118,7 +118,7 @@ function deleteVersionFromJson(json, version) { const packageName = typeof json?.name === "string" ? json.name : "(unknown)"; - ui.writeInformation( + ui.writeVerbose( `Safe-chain: ${packageName}@${version} is newer than ${getMinimumPackageAgeHours()} hours and was removed (minimumPackageAgeInHours setting).` ); From d83a381231a84af8d1766eb1e4e1f3de3de6107c Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 14:02:27 +0100 Subject: [PATCH 10/97] Retry downloading the malware database 3 times --- packages/safe-chain/src/api/aikido.js | 97 +++++++++++++------ .../src/scanning/malwareDatabase.js | 14 +-- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/packages/safe-chain/src/api/aikido.js b/packages/safe-chain/src/api/aikido.js index 5c04360..26c88ea 100644 --- a/packages/safe-chain/src/api/aikido.js +++ b/packages/safe-chain/src/api/aikido.js @@ -1,5 +1,9 @@ import fetch from "make-fetch-happen"; -import { getEcoSystem, ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js"; +import { + getEcoSystem, + ECOSYSTEM_JS, + ECOSYSTEM_PY, +} from "../config/settings.js"; const malwareDatabaseUrls = { [ECOSYSTEM_JS]: "https://malware-list.aikido.dev/malware_predictions.json", @@ -17,38 +21,77 @@ const malwareDatabaseUrls = { * @returns {Promise<{malwareDatabase: MalwarePackage[], version: string | undefined}>} */ export async function fetchMalwareDatabase() { - const ecosystem = getEcoSystem(); - const malwareDatabaseUrl = malwareDatabaseUrls[/** @type {keyof typeof malwareDatabaseUrls} */ (ecosystem)]; - const response = await fetch(malwareDatabaseUrl); - if (!response.ok) { - throw new Error(`Error fetching ${ecosystem} malware database: ${response.statusText}`); - } + return retry(async () => { + const ecosystem = getEcoSystem(); + const malwareDatabaseUrl = + malwareDatabaseUrls[ + /** @type {keyof typeof malwareDatabaseUrls} */ (ecosystem) + ]; + const response = await fetch(malwareDatabaseUrl); + if (!response.ok) { + throw new Error( + `Error fetching ${ecosystem} malware database: ${response.statusText}` + ); + } - try { - let malwareDatabase = await response.json(); - return { - malwareDatabase: malwareDatabase, - version: response.headers.get("etag") || undefined, - }; - } catch (/** @type {any} */ error) { - throw new Error(`Error parsing malware database: ${error.message}`); - } + try { + let malwareDatabase = await response.json(); + return { + malwareDatabase: malwareDatabase, + version: response.headers.get("etag") || undefined, + }; + } catch (/** @type {any} */ error) { + throw new Error(`Error parsing malware database: ${error.message}`); + } + }, 3); } /** * @returns {Promise} */ export async function fetchMalwareDatabaseVersion() { - const ecosystem = getEcoSystem(); - const malwareDatabaseUrl = malwareDatabaseUrls[/** @type {keyof typeof malwareDatabaseUrls} */ (ecosystem)]; - const response = await fetch(malwareDatabaseUrl, { - method: "HEAD", - }); + return retry(async () => { + const ecosystem = getEcoSystem(); + const malwareDatabaseUrl = + malwareDatabaseUrls[ + /** @type {keyof typeof malwareDatabaseUrls} */ (ecosystem) + ]; + const response = await fetch(malwareDatabaseUrl, { + method: "HEAD", + }); - if (!response.ok) { - throw new Error( - `Error fetching ${ecosystem} malware database version: ${response.statusText}` - ); - } - return response.headers.get("etag") || undefined; + if (!response.ok) { + throw new Error( + `Error fetching ${ecosystem} malware database version: ${response.statusText}` + ); + } + return response.headers.get("etag") || undefined; + }, 3); +} + +/** + * Retries an asynchronous function multiple times until it succeeds or exhausts all attempts. + * + * @template T + * @param {() => Promise} func - The asynchronous function to retry + * @param {number} times - The number of retry attempts (will execute times + 1 total attempts) + * @returns {Promise} The return value of the function if successful + * @throws {Error} The last error encountered if all retry attempts fail + */ +async function retry(func, times) { + let lastError; + + for (let i = 0; i <= times; i++) { + try { + return await func(); + } catch (error) { + lastError = error; + } + + if (i < times) { + await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 500)); + } + } + + throw lastError; } diff --git a/packages/safe-chain/src/scanning/malwareDatabase.js b/packages/safe-chain/src/scanning/malwareDatabase.js index 4aba43c..120c438 100644 --- a/packages/safe-chain/src/scanning/malwareDatabase.js +++ b/packages/safe-chain/src/scanning/malwareDatabase.js @@ -48,13 +48,13 @@ export async function openMalwareDatabase() { */ function getPackageStatus(name, version) { const normalizedName = normalizePackageName(name); - const packageData = malwareDatabase.find( - (pkg) => { - const normalizedPkgName = normalizePackageName(pkg.package_name); - return normalizedPkgName === normalizedName && - (pkg.version === version || pkg.version === "*"); - } - ); + const packageData = malwareDatabase.find((pkg) => { + const normalizedPkgName = normalizePackageName(pkg.package_name); + return ( + normalizedPkgName === normalizedName && + (pkg.version === version || pkg.version === "*") + ); + }); if (!packageData) { return MALWARE_STATUS_OK; From 8d2655a4bf1b59d61c34d06d28b0b4f1992a0f48 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 14:41:06 +0100 Subject: [PATCH 11/97] Add tests for malware db retry --- packages/safe-chain/src/api/aikido.spec.js | 125 +++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 packages/safe-chain/src/api/aikido.spec.js diff --git a/packages/safe-chain/src/api/aikido.spec.js b/packages/safe-chain/src/api/aikido.spec.js new file mode 100644 index 0000000..2191d42 --- /dev/null +++ b/packages/safe-chain/src/api/aikido.spec.js @@ -0,0 +1,125 @@ +import { describe, it, mock, beforeEach } from "node:test"; +import assert from "node:assert"; + +describe("aikido API", async () => { + const mockFetch = mock.fn(); + + mock.module("make-fetch-happen", { + defaultExport: mockFetch, + }); + + mock.module("../config/settings.js", { + namedExports: { + getEcoSystem: () => "js", + ECOSYSTEM_JS: "js", + ECOSYSTEM_PY: "py", + }, + }); + + const { fetchMalwareDatabase, fetchMalwareDatabaseVersion } = + await import("./aikido.js"); + + beforeEach(() => { + mockFetch.mock.resetCalls(); + }); + + describe("fetchMalwareDatabase", () => { + it("should succeed immediately when fetch succeeds on first try", async () => { + const malwareData = [ + { package_name: "malicious-pkg", version: "1.0.0", reason: "test" }, + ]; + mockFetch.mock.mockImplementationOnce(() => ({ + ok: true, + json: async () => malwareData, + headers: { get: () => '"etag-123"' }, + })); + + const result = await fetchMalwareDatabase(); + + assert.strictEqual(mockFetch.mock.calls.length, 1); + assert.deepStrictEqual(result.malwareDatabase, malwareData); + assert.strictEqual(result.version, '"etag-123"'); + }); + + it("should throw error after exhausting all retries", async () => { + mockFetch.mock.mockImplementation(() => { + throw new Error("Network error"); + }); + + await assert.rejects(() => fetchMalwareDatabase(), { + message: "Network error", + }); + + assert.strictEqual(mockFetch.mock.calls.length, 4); + }); + + it("should succeed after failing 3 times and succeeding on 4th attempt", async () => { + const malwareData = [ + { package_name: "bad-pkg", version: "2.0.0", reason: "malware" }, + ]; + let callCount = 0; + mockFetch.mock.mockImplementation(() => { + callCount++; + if (callCount < 4) { + throw new Error("Network error"); + } + return { + ok: true, + json: async () => malwareData, + headers: { get: () => '"etag-456"' }, + }; + }); + + const result = await fetchMalwareDatabase(); + + assert.strictEqual(mockFetch.mock.calls.length, 4); + assert.deepStrictEqual(result.malwareDatabase, malwareData); + assert.strictEqual(result.version, '"etag-456"'); + }); + }); + + describe("fetchMalwareDatabaseVersion", () => { + it("should succeed immediately when fetch succeeds on first try", async () => { + mockFetch.mock.mockImplementationOnce(() => ({ + ok: true, + headers: { get: () => '"version-etag"' }, + })); + + const result = await fetchMalwareDatabaseVersion(); + + assert.strictEqual(mockFetch.mock.calls.length, 1); + assert.strictEqual(result, '"version-etag"'); + }); + + it("should throw error after exhausting all retries", async () => { + mockFetch.mock.mockImplementation(() => { + throw new Error("Connection refused"); + }); + + await assert.rejects(() => fetchMalwareDatabaseVersion(), { + message: "Connection refused", + }); + + assert.strictEqual(mockFetch.mock.calls.length, 4); + }); + + it("should succeed after failing 3 times and succeeding on 4th attempt", async () => { + let callCount = 0; + mockFetch.mock.mockImplementation(() => { + callCount++; + if (callCount < 4) { + throw new Error("Timeout"); + } + return { + ok: true, + headers: { get: () => '"final-etag"' }, + }; + }); + + const result = await fetchMalwareDatabaseVersion(); + + assert.strictEqual(mockFetch.mock.calls.length, 4); + assert.strictEqual(result, '"final-etag"'); + }); + }); +}); From a5d545f29b2ad95e47c0029b038b7bdfb6a01ac3 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 14:55:11 +0100 Subject: [PATCH 12/97] Handle pr comments --- packages/safe-chain/src/api/aikido.js | 22 ++++++++++++++----- .../src/scanning/malwareDatabase.js | 14 ++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/safe-chain/src/api/aikido.js b/packages/safe-chain/src/api/aikido.js index 26c88ea..88dffb2 100644 --- a/packages/safe-chain/src/api/aikido.js +++ b/packages/safe-chain/src/api/aikido.js @@ -21,6 +21,8 @@ const malwareDatabaseUrls = { * @returns {Promise<{malwareDatabase: MalwarePackage[], version: string | undefined}>} */ export async function fetchMalwareDatabase() { + const numberOfAttempts = 4; + return retry(async () => { const ecosystem = getEcoSystem(); const malwareDatabaseUrl = @@ -43,13 +45,15 @@ export async function fetchMalwareDatabase() { } catch (/** @type {any} */ error) { throw new Error(`Error parsing malware database: ${error.message}`); } - }, 3); + }, numberOfAttempts); } /** * @returns {Promise} */ export async function fetchMalwareDatabaseVersion() { + const numberOfAttempts = 4; + return retry(async () => { const ecosystem = getEcoSystem(); const malwareDatabaseUrl = @@ -66,7 +70,7 @@ export async function fetchMalwareDatabaseVersion() { ); } return response.headers.get("etag") || undefined; - }, 3); + }, numberOfAttempts); } /** @@ -74,21 +78,27 @@ export async function fetchMalwareDatabaseVersion() { * * @template T * @param {() => Promise} func - The asynchronous function to retry - * @param {number} times - The number of retry attempts (will execute times + 1 total attempts) + * @param {number} attempts - The number of attempts * @returns {Promise} The return value of the function if successful * @throws {Error} The last error encountered if all retry attempts fail */ -async function retry(func, times) { +async function retry(func, attempts) { let lastError; - for (let i = 0; i <= times; i++) { + for (let i = 0; i < attempts; i++) { try { return await func(); } catch (error) { lastError = error; } - if (i < times) { + if (i < attempts - 1) { + // When this is not the last try, back-off expenentially: + // 1st attempt - 500ms delay + // 2nd attempt - 1000ms delay + // 3rd attempt - 2000ms delay + // 4th attempt - 4000ms delay + // ... await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 500)); } } diff --git a/packages/safe-chain/src/scanning/malwareDatabase.js b/packages/safe-chain/src/scanning/malwareDatabase.js index 120c438..4aba43c 100644 --- a/packages/safe-chain/src/scanning/malwareDatabase.js +++ b/packages/safe-chain/src/scanning/malwareDatabase.js @@ -48,13 +48,13 @@ export async function openMalwareDatabase() { */ function getPackageStatus(name, version) { const normalizedName = normalizePackageName(name); - const packageData = malwareDatabase.find((pkg) => { - const normalizedPkgName = normalizePackageName(pkg.package_name); - return ( - normalizedPkgName === normalizedName && - (pkg.version === version || pkg.version === "*") - ); - }); + const packageData = malwareDatabase.find( + (pkg) => { + const normalizedPkgName = normalizePackageName(pkg.package_name); + return normalizedPkgName === normalizedName && + (pkg.version === version || pkg.version === "*"); + } + ); if (!packageData) { return MALWARE_STATUS_OK; From 6f4eaf5234447948397e6e021e009aa51ea7370b Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 15:31:37 +0100 Subject: [PATCH 13/97] Don't swallow error on retry --- packages/safe-chain/src/api/aikido.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/safe-chain/src/api/aikido.js b/packages/safe-chain/src/api/aikido.js index 88dffb2..be01518 100644 --- a/packages/safe-chain/src/api/aikido.js +++ b/packages/safe-chain/src/api/aikido.js @@ -4,6 +4,7 @@ import { ECOSYSTEM_JS, ECOSYSTEM_PY, } from "../config/settings.js"; +import { ui } from "../environment/userInteraction.js"; const malwareDatabaseUrls = { [ECOSYSTEM_JS]: "https://malware-list.aikido.dev/malware_predictions.json", @@ -89,6 +90,10 @@ async function retry(func, attempts) { try { return await func(); } catch (error) { + ui.writeVerbose( + "An error occurred while trying to download the Aikido Malware database", + error + ); lastError = error; } From 9d55afbf857a29f402c7e4234cb651a361124b5e Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Wed, 14 Jan 2026 15:33:09 +0100 Subject: [PATCH 14/97] Update packages/safe-chain/src/api/aikido.js --- packages/safe-chain/src/api/aikido.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/api/aikido.js b/packages/safe-chain/src/api/aikido.js index be01518..abb2135 100644 --- a/packages/safe-chain/src/api/aikido.js +++ b/packages/safe-chain/src/api/aikido.js @@ -98,7 +98,7 @@ async function retry(func, attempts) { } if (i < attempts - 1) { - // When this is not the last try, back-off expenentially: + // When this is not the last try, back-off exponentially: // 1st attempt - 500ms delay // 2nd attempt - 1000ms delay // 3rd attempt - 2000ms delay From 6815b620199d4ef8ce48202bcb7a0ebfe5f66f55 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 17:41:23 +0100 Subject: [PATCH 15/97] Allow to exclude packages from the minimum package age --- README.md | 16 ++ packages/safe-chain/src/api/aikido.spec.js | 8 + packages/safe-chain/src/config/configFile.js | 22 +++ .../src/config/environmentVariables.js | 10 ++ packages/safe-chain/src/config/settings.js | 31 ++++ .../safe-chain/src/config/settings.spec.js | 135 ++++++++++++++++ .../interceptors/npm/modifyNpmInfo.js | 12 +- .../npm/npmInterceptor.minPackageAge.spec.js | 153 ++++++++++++++++++ .../npmInterceptor.packageDownload.spec.js | 1 + 9 files changed, 387 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 17d2515..bc61787 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,22 @@ You can set the minimum package age through multiple sources (in order of priori } ``` +### Excluding Packages + +Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged): + +```shell +export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="react,@aikidosec/safe-chain" +``` + +```json +{ + "npm": { + "minimumPackageAgeExclusions": ["react", "@aikidosec/safe-chain"] + } +} +``` + ## Custom Registries Configure Safe Chain to scan packages from custom or private registries. diff --git a/packages/safe-chain/src/api/aikido.spec.js b/packages/safe-chain/src/api/aikido.spec.js index 2191d42..2e7cecb 100644 --- a/packages/safe-chain/src/api/aikido.spec.js +++ b/packages/safe-chain/src/api/aikido.spec.js @@ -8,6 +8,14 @@ describe("aikido API", async () => { defaultExport: mockFetch, }); + mock.module("../environment/userInteraction.js", { + namedExports: { + ui: { + writeVerbose: () => {}, + }, + }, + }); + mock.module("../config/settings.js", { namedExports: { getEcoSystem: () => "js", diff --git a/packages/safe-chain/src/config/configFile.js b/packages/safe-chain/src/config/configFile.js index a98304e..fd6ac26 100644 --- a/packages/safe-chain/src/config/configFile.js +++ b/packages/safe-chain/src/config/configFile.js @@ -16,6 +16,7 @@ import { getEcoSystem } from "./settings.js"; * @typedef {Object} SafeChainRegistryConfiguration * We cannot trust the input and should add the necessary validations. * @property {unknown | string[]} customRegistries + * @property {unknown | string[]} minimumPackageAgeExclusions */ /** @@ -127,6 +128,27 @@ export function getPipCustomRegistries() { return customRegistries.filter((item) => typeof item === "string"); } +/** + * Gets the minimum package age exclusions from the config file + * @returns {string[]} + */ +export function getNpmMinimumPackageAgeExclusions() { + const config = readConfigFile(); + + if (!config || !config.npm) { + return []; + } + + const npmConfig = /** @type {SafeChainRegistryConfiguration} */ (config.npm); + const exclusions = npmConfig.minimumPackageAgeExclusions; + + if (!Array.isArray(exclusions)) { + return []; + } + + return exclusions.filter((item) => typeof item === "string"); +} + /** * @param {import("../api/aikido.js").MalwarePackage[]} data * @param {string | number} version diff --git a/packages/safe-chain/src/config/environmentVariables.js b/packages/safe-chain/src/config/environmentVariables.js index 1b85ed7..8a44841 100644 --- a/packages/safe-chain/src/config/environmentVariables.js +++ b/packages/safe-chain/src/config/environmentVariables.js @@ -34,3 +34,13 @@ export function getPipCustomRegistries() { export function getLoggingLevel() { return process.env.SAFE_CHAIN_LOGGING; } + +/** + * Gets the minimum package age exclusions from environment variable + * Expected format: comma-separated list of package names + * Example: "react,@aikidosec/safe-chain,lodash" + * @returns {string | undefined} + */ +export function getNpmMinimumPackageAgeExclusions() { + return process.env.SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS; +} diff --git a/packages/safe-chain/src/config/settings.js b/packages/safe-chain/src/config/settings.js index 6910fe3..b9243b0 100644 --- a/packages/safe-chain/src/config/settings.js +++ b/packages/safe-chain/src/config/settings.js @@ -167,3 +167,34 @@ export function getPipCustomRegistries() { // Normalize each registry (remove protocol if any) return uniqueRegistries.map(normalizeRegistry); } + +/** + * Parses comma-separated exclusions from environment variable + * @param {string | undefined} envValue + * @returns {string[]} + */ +function parseExclusionsFromEnv(envValue) { + if (!envValue || typeof envValue !== "string") { + return []; + } + + return envValue + .split(",") + .map((exclusion) => exclusion.trim()) + .filter((exclusion) => exclusion.length > 0); +} + +/** + * Gets the minimum package age exclusions from both environment variable and config file (merged) + * @returns {string[]} + */ +export function getNpmMinimumPackageAgeExclusions() { + const envExclusions = parseExclusionsFromEnv( + environmentVariables.getNpmMinimumPackageAgeExclusions() + ); + const configExclusions = configFile.getNpmMinimumPackageAgeExclusions(); + + // Merge both sources and remove duplicates + const allExclusions = [...envExclusions, ...configExclusions]; + return [...new Set(allExclusions)]; +} diff --git a/packages/safe-chain/src/config/settings.spec.js b/packages/safe-chain/src/config/settings.spec.js index 314fac0..8db5b83 100644 --- a/packages/safe-chain/src/config/settings.spec.js +++ b/packages/safe-chain/src/config/settings.spec.js @@ -14,6 +14,7 @@ mock.module("fs", { const { getNpmCustomRegistries, getPipCustomRegistries, + getNpmMinimumPackageAgeExclusions, getLoggingLevel, LOGGING_SILENT, LOGGING_NORMAL, @@ -365,3 +366,137 @@ describe("getLoggingLevel", () => { assert.strictEqual(level, LOGGING_NORMAL); }); }); + +describe("getNpmMinimumPackageAgeExclusions", () => { + let originalEnv; + const envVarName = "SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS"; + + beforeEach(() => { + originalEnv = process.env[envVarName]; + delete process.env[envVarName]; + }); + + afterEach(() => { + if (originalEnv !== undefined) { + process.env[envVarName] = originalEnv; + } else { + delete process.env[envVarName]; + } + configFileContent = undefined; + }); + + it("should return empty array when no exclusions configured", () => { + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, []); + }); + + it("should return exclusions from config file", () => { + configFileContent = JSON.stringify({ + npm: { + minimumPackageAgeExclusions: ["react", "@aikidosec/safe-chain"], + }, + }); + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["react", "@aikidosec/safe-chain"]); + }); + + it("should parse comma-separated exclusions from environment variable", () => { + process.env[envVarName] = "lodash,express,@types/node"; + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["lodash", "express", "@types/node"]); + }); + + it("should merge environment variable and config file exclusions", () => { + process.env[envVarName] = "lodash"; + configFileContent = JSON.stringify({ + npm: { + minimumPackageAgeExclusions: ["react"], + }, + }); + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["lodash", "react"]); + }); + + it("should remove duplicate exclusions when merging", () => { + process.env[envVarName] = "lodash,react"; + configFileContent = JSON.stringify({ + npm: { + minimumPackageAgeExclusions: ["react", "express"], + }, + }); + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["lodash", "react", "express"]); + }); + + it("should trim whitespace from environment variable exclusions", () => { + process.env[envVarName] = " lodash , react "; + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["lodash", "react"]); + }); + + it("should handle scoped packages", () => { + configFileContent = JSON.stringify({ + npm: { + minimumPackageAgeExclusions: ["@babel/core", "@types/react"], + }, + }); + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["@babel/core", "@types/react"]); + }); + + it("should handle empty strings in comma-separated list", () => { + process.env[envVarName] = "lodash,,react,"; + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["lodash", "react"]); + }); + + it("should return empty array for empty environment variable", () => { + process.env[envVarName] = ""; + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, []); + }); + + it("should return empty array for whitespace-only environment variable", () => { + process.env[envVarName] = " , , "; + configFileContent = undefined; + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, []); + }); + + it("should filter non-string values from config file", () => { + configFileContent = JSON.stringify({ + npm: { + minimumPackageAgeExclusions: ["react", 123, null, "lodash", undefined], + }, + }); + + const exclusions = getNpmMinimumPackageAgeExclusions(); + + assert.deepStrictEqual(exclusions, ["react", "lodash"]); + }); +}); diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js index 421666a..3407397 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js @@ -1,4 +1,4 @@ -import { getMinimumPackageAgeHours } from "../../../config/settings.js"; +import { getMinimumPackageAgeHours, getNpmMinimumPackageAgeExclusions } from "../../../config/settings.js"; import { ui } from "../../../environment/userInteraction.js"; import { getHeaderValueAsString } from "../../http-utils.js"; @@ -65,6 +65,16 @@ export function modifyNpmInfoResponse(body, headers) { return body; } + // Check if this package is excluded from minimum age filtering + const packageName = bodyJson.name; + const exclusions = getNpmMinimumPackageAgeExclusions(); + if (packageName && exclusions.includes(packageName)) { + ui.writeVerbose( + `Safe-chain: ${packageName} is excluded from minimum package age filtering (minimumPackageAgeExclusions setting).` + ); + return body; + } + const cutOff = new Date( new Date().getTime() - getMinimumPackageAgeHours() * 3600 * 1000 ); diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js index fb7ae56..ed00909 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js @@ -4,12 +4,14 @@ import assert from "node:assert"; describe("npmInterceptor minimum package age", async () => { let minimumPackageAgeSettings = 48; let skipMinimumPackageAgeSetting = false; + let minimumPackageAgeExclusionsSetting = []; mock.module("../../../config/settings.js", { namedExports: { getMinimumPackageAgeHours: () => minimumPackageAgeSettings, skipMinimumPackageAge: () => skipMinimumPackageAgeSetting, getNpmCustomRegistries: () => [], + getNpmMinimumPackageAgeExclusions: () => minimumPackageAgeExclusionsSetting, }, }); @@ -357,6 +359,157 @@ describe("npmInterceptor minimum package age", async () => { assert.equal(modifiedJson["dist-tags"]["latest"], "2.0.0"); }); + it("Should not filter packages when package is in exclusion list", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["lodash"]; + + const packageUrl = "https://registry.npmjs.org/lodash"; + + const originalBody = JSON.stringify({ + name: "lodash", + ["dist-tags"]: { + latest: "3.0.0", + }, + versions: { + ["1.0.0"]: {}, + ["2.0.0"]: {}, + ["3.0.0"]: {}, + }, + time: { + created: getDate(-365 * 24), + modified: getDate(-3), + ["1.0.0"]: getDate(-7), + // cutoff-date here + ["2.0.0"]: getDate(-4), + ["3.0.0"]: getDate(-3), // Would normally be filtered + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // All versions should remain unchanged since lodash is excluded + assert.equal(Object.keys(modifiedJson.versions).length, 3); + assert.ok(Object.keys(modifiedJson.versions).includes("1.0.0")); + assert.ok(Object.keys(modifiedJson.versions).includes("2.0.0")); + assert.ok(Object.keys(modifiedJson.versions).includes("3.0.0")); + assert.equal(modifiedJson["dist-tags"]["latest"], "3.0.0"); + }); + + it("Should filter packages when package is NOT in exclusion list", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["react"]; // Different package + + const packageUrl = "https://registry.npmjs.org/lodash"; + + const modifiedBody = await runModifyNpmInfoRequest( + packageUrl, + JSON.stringify({ + name: "lodash", + ["dist-tags"]: { latest: "3.0.0" }, + versions: { ["1.0.0"]: {}, ["3.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-3), + ["1.0.0"]: getDate(-7), + ["3.0.0"]: getDate(-3), + }, + }) + ); + + const modifiedJson = JSON.parse(modifiedBody); + + // lodash should still be filtered since it's not in exclusions + assert.equal(Object.keys(modifiedJson.versions).length, 1); + assert.ok(Object.keys(modifiedJson.versions).includes("1.0.0")); + assert.ok(!Object.keys(modifiedJson.versions).includes("3.0.0")); + }); + + it("Should handle scoped packages in exclusion list", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["@babel/core"]; + + const packageUrl = "https://registry.npmjs.org/@babel/core"; + + const originalBody = JSON.stringify({ + name: "@babel/core", + ["dist-tags"]: { latest: "7.0.0" }, + versions: { ["6.0.0"]: {}, ["7.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["6.0.0"]: getDate(-100), + ["7.0.0"]: getDate(-1), // Would normally be filtered + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // All versions should remain for excluded scoped package + assert.equal(Object.keys(modifiedJson.versions).length, 2); + assert.ok(Object.keys(modifiedJson.versions).includes("6.0.0")); + assert.ok(Object.keys(modifiedJson.versions).includes("7.0.0")); + }); + + it("Should handle multiple packages in exclusion list", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["react", "lodash", "@types/node"]; + + const packageUrl = "https://registry.npmjs.org/lodash"; + + const originalBody = JSON.stringify({ + name: "lodash", + ["dist-tags"]: { latest: "2.0.0" }, + versions: { ["1.0.0"]: {}, ["2.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["1.0.0"]: getDate(-100), + ["2.0.0"]: getDate(-1), + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // All versions should remain since lodash is in the exclusion list + assert.equal(Object.keys(modifiedJson.versions).length, 2); + }); + + it("Should reset exclusions between tests", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = []; // Reset to empty + + const packageUrl = "https://registry.npmjs.org/lodash"; + + const modifiedBody = await runModifyNpmInfoRequest( + packageUrl, + JSON.stringify({ + name: "lodash", + ["dist-tags"]: { latest: "2.0.0" }, + versions: { ["1.0.0"]: {}, ["2.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["1.0.0"]: getDate(-100), + ["2.0.0"]: getDate(-1), + }, + }) + ); + + const modifiedJson = JSON.parse(modifiedBody); + + // Version 2.0.0 should be filtered since exclusions are empty + assert.equal(Object.keys(modifiedJson.versions).length, 1); + assert.ok(Object.keys(modifiedJson.versions).includes("1.0.0")); + }); + function getDate(plusHours) { const date = new Date(); date.setHours(date.getHours() + plusHours); diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.packageDownload.spec.js b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.packageDownload.spec.js index 88fcbd0..e1b7c79 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.packageDownload.spec.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.packageDownload.spec.js @@ -26,6 +26,7 @@ mock.module("../../../config/settings.js", { setEcoSystem: () => {}, getMinimumPackageAgeHours: () => 24, getNpmCustomRegistries: () => customRegistries, + getNpmMinimumPackageAgeExclusions: () => [], skipMinimumPackageAge: () => false, }, }); From 884cb6e02622f3b3f747e4163ac809ec24eb1eca Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 14 Jan 2026 17:51:41 +0100 Subject: [PATCH 16/97] Allow trailing * for wildcard matching --- README.md | 6 +- .../interceptors/npm/modifyNpmInfo.js | 16 +++- .../npm/npmInterceptor.minPackageAge.spec.js | 81 +++++++++++++++++++ 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bc61787..290304f 100644 --- a/README.md +++ b/README.md @@ -214,16 +214,16 @@ You can set the minimum package age through multiple sources (in order of priori ### Excluding Packages -Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged): +Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged). Supports wildcard patterns with trailing `*`: ```shell -export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="react,@aikidosec/safe-chain" +export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*,react-*,lodash" ``` ```json { "npm": { - "minimumPackageAgeExclusions": ["react", "@aikidosec/safe-chain"] + "minimumPackageAgeExclusions": ["@aikidosec/*", "react-*", "lodash"] } } ``` diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js index 3407397..9a36207 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js @@ -68,7 +68,7 @@ export function modifyNpmInfoResponse(body, headers) { // Check if this package is excluded from minimum age filtering const packageName = bodyJson.name; const exclusions = getNpmMinimumPackageAgeExclusions(); - if (packageName && exclusions.includes(packageName)) { + if (packageName && exclusions.some((pattern) => matchesExclusionPattern(packageName, pattern))) { ui.writeVerbose( `Safe-chain: ${packageName} is excluded from minimum package age filtering (minimumPackageAgeExclusions setting).` ); @@ -187,3 +187,17 @@ function getMostRecentTag(tagList) { export function getHasSuppressedVersions() { return state.hasSuppressedVersions; } + +/** + * Checks if a package name matches an exclusion pattern. + * Supports trailing wildcard (*) for prefix matching. + * @param {string} packageName + * @param {string} pattern + * @returns {boolean} + */ +function matchesExclusionPattern(packageName, pattern) { + if (pattern.endsWith("*")) { + return packageName.startsWith(pattern.slice(0, -1)); + } + return packageName === pattern; +} diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js index ed00909..82fed71 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js @@ -481,6 +481,87 @@ describe("npmInterceptor minimum package age", async () => { assert.equal(Object.keys(modifiedJson.versions).length, 2); }); + it("Should exclude packages matching wildcard pattern @scope/*", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["@aikidosec/*"]; + + const packageUrl = "https://registry.npmjs.org/@aikidosec/safe-chain"; + + const originalBody = JSON.stringify({ + name: "@aikidosec/safe-chain", + ["dist-tags"]: { latest: "2.0.0" }, + versions: { ["1.0.0"]: {}, ["2.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["1.0.0"]: getDate(-100), + ["2.0.0"]: getDate(-1), // Would normally be filtered + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // All versions should remain since @aikidosec/* matches @aikidosec/safe-chain + assert.equal(Object.keys(modifiedJson.versions).length, 2); + assert.ok(Object.keys(modifiedJson.versions).includes("1.0.0")); + assert.ok(Object.keys(modifiedJson.versions).includes("2.0.0")); + }); + + it("Should exclude packages matching wildcard pattern prefix-*", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["react-*"]; + + const packageUrl = "https://registry.npmjs.org/react-dom"; + + const originalBody = JSON.stringify({ + name: "react-dom", + ["dist-tags"]: { latest: "18.0.0" }, + versions: { ["17.0.0"]: {}, ["18.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["17.0.0"]: getDate(-100), + ["18.0.0"]: getDate(-1), // Would normally be filtered + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // All versions should remain since react-* matches react-dom + assert.equal(Object.keys(modifiedJson.versions).length, 2); + }); + + it("Should NOT exclude packages that don't match wildcard pattern", async () => { + minimumPackageAgeSettings = 5; + skipMinimumPackageAgeSetting = false; + minimumPackageAgeExclusionsSetting = ["@aikidosec/*"]; + + const packageUrl = "https://registry.npmjs.org/@other/package"; + + const originalBody = JSON.stringify({ + name: "@other/package", + ["dist-tags"]: { latest: "2.0.0" }, + versions: { ["1.0.0"]: {}, ["2.0.0"]: {} }, + time: { + created: getDate(-365 * 24), + modified: getDate(-1), + ["1.0.0"]: getDate(-100), + ["2.0.0"]: getDate(-1), + }, + }); + + const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); + const modifiedJson = JSON.parse(modifiedBody); + + // Version 2.0.0 should be filtered since @other/package doesn't match @aikidosec/* + assert.equal(Object.keys(modifiedJson.versions).length, 1); + assert.ok(Object.keys(modifiedJson.versions).includes("1.0.0")); + }); + it("Should reset exclusions between tests", async () => { minimumPackageAgeSettings = 5; skipMinimumPackageAgeSetting = false; From 6c814ff82fd7183a5c8a0d33645d6d492fc31151 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 15 Jan 2026 15:13:00 +0100 Subject: [PATCH 17/97] Only allow wildcards for scoped packages (@scope/*) --- README.md | 6 ++--- .../interceptors/npm/modifyNpmInfo.js | 2 +- .../npm/npmInterceptor.minPackageAge.spec.js | 26 ------------------- 3 files changed, 4 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 290304f..128d662 100644 --- a/README.md +++ b/README.md @@ -214,16 +214,16 @@ You can set the minimum package age through multiple sources (in order of priori ### Excluding Packages -Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged). Supports wildcard patterns with trailing `*`: +Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged). Use `@scope/*` to trust all packages from an organization: ```shell -export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*,react-*,lodash" +export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*" ``` ```json { "npm": { - "minimumPackageAgeExclusions": ["@aikidosec/*", "react-*", "lodash"] + "minimumPackageAgeExclusions": ["@aikidosec/*"] } } ``` diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js index 9a36207..14e3ba7 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/modifyNpmInfo.js @@ -196,7 +196,7 @@ export function getHasSuppressedVersions() { * @returns {boolean} */ function matchesExclusionPattern(packageName, pattern) { - if (pattern.endsWith("*")) { + if (pattern.endsWith("/*")) { return packageName.startsWith(pattern.slice(0, -1)); } return packageName === pattern; diff --git a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js index 82fed71..834a2ad 100644 --- a/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js +++ b/packages/safe-chain/src/registryProxy/interceptors/npm/npmInterceptor.minPackageAge.spec.js @@ -509,32 +509,6 @@ describe("npmInterceptor minimum package age", async () => { assert.ok(Object.keys(modifiedJson.versions).includes("2.0.0")); }); - it("Should exclude packages matching wildcard pattern prefix-*", async () => { - minimumPackageAgeSettings = 5; - skipMinimumPackageAgeSetting = false; - minimumPackageAgeExclusionsSetting = ["react-*"]; - - const packageUrl = "https://registry.npmjs.org/react-dom"; - - const originalBody = JSON.stringify({ - name: "react-dom", - ["dist-tags"]: { latest: "18.0.0" }, - versions: { ["17.0.0"]: {}, ["18.0.0"]: {} }, - time: { - created: getDate(-365 * 24), - modified: getDate(-1), - ["17.0.0"]: getDate(-100), - ["18.0.0"]: getDate(-1), // Would normally be filtered - }, - }); - - const modifiedBody = await runModifyNpmInfoRequest(packageUrl, originalBody); - const modifiedJson = JSON.parse(modifiedBody); - - // All versions should remain since react-* matches react-dom - assert.equal(Object.keys(modifiedJson.versions).length, 2); - }); - it("Should NOT exclude packages that don't match wildcard pattern", async () => { minimumPackageAgeSettings = 5; skipMinimumPackageAgeSetting = false; From 879b37e164d0a264a9f129a57adad29232685684 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 12:47:57 +0100 Subject: [PATCH 18/97] Add ultimate installer for Windows --- packages/safe-chain/bin/safe-chain.js | 32 +++--- .../src/installation/installUltimate.js | 101 ++++++++++++++++++ 2 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 packages/safe-chain/src/installation/installUltimate.js diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 841ccee..e33ad9f 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -16,6 +16,7 @@ import path from "path"; import { fileURLToPath } from "url"; import fs from "fs"; import { knownAikidoTools } from "../src/shell-integration/helpers.js"; +import { installUltimate } from "../src/installation/installUltimate.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -62,6 +63,8 @@ if (tool) { process.exit(0); } else if (command === "setup") { setup(); +} else if (command === "--ultimate") { + installUltimate(); } else if (command === "teardown") { teardownDirectories(); teardown(); @@ -82,36 +85,41 @@ if (tool) { function writeHelp() { ui.writeInformation( - chalk.bold("Usage: ") + chalk.cyan("safe-chain ") + chalk.bold("Usage: ") + chalk.cyan("safe-chain "), ); ui.emptyLine(); ui.writeInformation( `Available commands: ${chalk.cyan("setup")}, ${chalk.cyan( - "teardown" + "teardown", )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan( - "--version" - )}` + "--version", + )}`, ); ui.emptyLine(); ui.writeInformation( `- ${chalk.cyan( - "safe-chain setup" - )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.` + "safe-chain setup", + )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`, ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain teardown" - )}: This will remove safe-chain aliases from your shell configuration.` + "safe-chain --ultimate", + )}: This installs the ultimate version of safe-chain, enabling protection for more eco-systems (vscode).`, ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain setup-ci" - )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.` + "safe-chain teardown", + )}: This will remove safe-chain aliases from your shell configuration.`, + ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain setup-ci", + )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.`, ); ui.writeInformation( `- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan( - "-v" - )}): Display the current version of safe-chain.` + "-v", + )}): Display the current version of safe-chain.`, ); ui.emptyLine(); } diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js new file mode 100644 index 0000000..e1323db --- /dev/null +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -0,0 +1,101 @@ +import { platform, arch, tmpdir } from "os"; +import { createWriteStream, unlinkSync } from "fs"; +import { join } from "path"; +import { execSync } from "child_process"; +import { pipeline } from "stream/promises"; +import fetch from "make-fetch-happen"; +import { ui } from "../environment/userInteraction.js"; + +const ULTIMATE_VERSION = "v0.2.0"; + +export function installUltimate() { + const operatingSystem = platform(); + + if (operatingSystem === "win32") { + installOnWindows(); + } else { + ui.writeInformation( + `${operatingSystem} is not supported yet by safe-chain's ultimate version.`, + ); + } +} + +async function installOnWindows() { + if (!isRunningAsAdmin()) { + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator)." + ); + return; + } + + const architecture = getWindowsArchitecture(); + const downloadUrl = buildDownloadUrl(architecture); + const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + + ui.writeInformation(`Downloading SafeChain Agent ${ULTIMATE_VERSION} for ${architecture}...`); + ui.writeVerbose(`Download URL: ${downloadUrl}`); + ui.writeVerbose(`Destination: ${msiPath}`); + await downloadFile(downloadUrl, msiPath); + + ui.writeInformation("Installing SafeChain Agent..."); + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + runMsiInstaller(msiPath); + + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + ui.writeInformation("SafeChain Agent installed successfully!"); +} + +function isRunningAsAdmin() { + try { + execSync("net session", { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +function getWindowsArchitecture() { + const nodeArch = arch(); + if (nodeArch === "x64") return "amd64"; + if (nodeArch === "arm64") return "arm64"; + throw new Error(`Unsupported architecture: ${nodeArch}`); +} + +/** + * @param {string} architecture + */ +function buildDownloadUrl(architecture) { + return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-${architecture}.msi`; +} + +/** + * @param {string} url + * @param {string} destPath + */ +async function downloadFile(url, destPath) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Download failed: ${response.statusText}`); + } + await pipeline(response.body, createWriteStream(destPath)); +} + +/** + * @param {string} msiPath + */ +function runMsiInstaller(msiPath) { + execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); +} + +/** + * @param {string} msiPath + */ +function cleanup(msiPath) { + try { + unlinkSync(msiPath); + } catch { + // Ignore cleanup errors + } +} From 2c0245b020ecbed7ee4c28212d7653970458e288 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 13:28:16 +0100 Subject: [PATCH 19/97] Start and stop safe-chain agent's Windows service. --- .../src/installation/installUltimate.js | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index e1323db..a638407 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -38,13 +38,18 @@ async function installOnWindows() { ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); + stopServiceIfRunning(); + ui.writeInformation("Installing SafeChain Agent..."); ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); runMsiInstaller(msiPath); + ui.writeInformation("Starting SafeChain Agent service..."); + startService(); + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); cleanup(msiPath); - ui.writeInformation("SafeChain Agent installed successfully!"); + ui.writeInformation("SafeChain Agent installed and started successfully!"); } function isRunningAsAdmin() { @@ -89,6 +94,22 @@ function runMsiInstaller(msiPath) { execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); } +function stopServiceIfRunning() { + try { + ui.writeInformation("Stopping existing SafeChain Agent service..."); + ui.writeVerbose('Running: net stop "SafeChainAgent"'); + execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); + } catch { + // Service is not running or doesn't exist, which is fine + ui.writeVerbose("SafeChain Agent service not running or not installed."); + } +} + +function startService() { + ui.writeVerbose('Running: net start "SafeChainAgent"'); + execSync('net start "SafeChainAgent"', { stdio: "inherit" }); +} + /** * @param {string} msiPath */ From 6a3c7b938b9ac12d35924c30cf4c2159c901a7a8 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 13:48:33 +0100 Subject: [PATCH 20/97] Overwrite the agent if it's already installed. --- packages/safe-chain/src/installation/installUltimate.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index a638407..5d38eb0 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -41,7 +41,7 @@ async function installOnWindows() { stopServiceIfRunning(); ui.writeInformation("Installing SafeChain Agent..."); - ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn REINSTALL=ALL REINSTALLMODE=vomus`); runMsiInstaller(msiPath); ui.writeInformation("Starting SafeChain Agent service..."); @@ -91,7 +91,10 @@ async function downloadFile(url, destPath) { * @param {string} msiPath */ function runMsiInstaller(msiPath) { - execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); + // Use /i for install/upgrade with REINSTALL=ALL REINSTALLMODE=vomus + // This forces a reinstall of all features if the product is already installed + // /qn = quiet mode (no UI) + execSync(`msiexec /i "${msiPath}" /qn REINSTALL=ALL REINSTALLMODE=vomus`, { stdio: "inherit" }); } function stopServiceIfRunning() { From 4851e582f69a89ddc6ae24420002105cd5b7f467 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 13:54:32 +0100 Subject: [PATCH 21/97] Improve updating existing agent install --- .../src/installation/installUltimate.js | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 5d38eb0..1860b80 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -24,7 +24,7 @@ async function installOnWindows() { if (!isRunningAsAdmin()) { ui.writeError("Administrator privileges required."); ui.writeInformation( - "Please run this command in an elevated terminal (Run as Administrator)." + "Please run this command in an elevated terminal (Run as Administrator).", ); return; } @@ -33,15 +33,20 @@ async function installOnWindows() { const downloadUrl = buildDownloadUrl(architecture); const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); - ui.writeInformation(`Downloading SafeChain Agent ${ULTIMATE_VERSION} for ${architecture}...`); + ui.writeInformation( + `Downloading SafeChain Agent ${ULTIMATE_VERSION} for ${architecture}...`, + ); ui.writeVerbose(`Download URL: ${downloadUrl}`); ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); stopServiceIfRunning(); + // Wait a moment for the service to fully stop before installing + await new Promise((resolve) => setTimeout(resolve, 10000)); + ui.writeInformation("Installing SafeChain Agent..."); - ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn REINSTALL=ALL REINSTALLMODE=vomus`); + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn /norestart`); runMsiInstaller(msiPath); ui.writeInformation("Starting SafeChain Agent service..."); @@ -91,10 +96,23 @@ async function downloadFile(url, destPath) { * @param {string} msiPath */ function runMsiInstaller(msiPath) { - // Use /i for install/upgrade with REINSTALL=ALL REINSTALLMODE=vomus - // This forces a reinstall of all features if the product is already installed + // Try to install/upgrade + // /i = install (will upgrade if product code matches) // /qn = quiet mode (no UI) - execSync(`msiexec /i "${msiPath}" /qn REINSTALL=ALL REINSTALLMODE=vomus`, { stdio: "inherit" }); + // /norestart = suppress restarts + try { + execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); + } catch (error) { + // If installation fails, it might be because it's already installed + // Try to force a reinstall + ui.writeVerbose( + "Initial installation failed, attempting to force reinstall...", + ); + execSync( + `msiexec /i "${msiPath}" /qn /norestart REINSTALL=ALL REINSTALLMODE=vomus`, + { stdio: "inherit" }, + ); + } } function stopServiceIfRunning() { From c4941e25ed5ca304e684b921a8e20590c81f8486 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 13:55:41 +0100 Subject: [PATCH 22/97] Fix linting --- packages/safe-chain/src/installation/installUltimate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 1860b80..278aab4 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -102,7 +102,7 @@ function runMsiInstaller(msiPath) { // /norestart = suppress restarts try { execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); - } catch (error) { + } catch { // If installation fails, it might be because it's already installed // Try to force a reinstall ui.writeVerbose( From 673783ceabffac02ec684e3f661b1e90672669f6 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:00:09 +0100 Subject: [PATCH 23/97] Uninstall safe-chain agent if it's there, before re-installing --- .../src/installation/installUltimate.js | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 278aab4..6cb3f46 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -41,9 +41,10 @@ async function installOnWindows() { await downloadFile(downloadUrl, msiPath); stopServiceIfRunning(); + uninstallIfInstalled(); - // Wait a moment for the service to fully stop before installing - await new Promise((resolve) => setTimeout(resolve, 10000)); + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); ui.writeInformation("Installing SafeChain Agent..."); ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn /norestart`); @@ -92,27 +93,25 @@ async function downloadFile(url, destPath) { await pipeline(response.body, createWriteStream(destPath)); } +function uninstallIfInstalled() { + try { + ui.writeInformation("Uninstalling existing SafeChain Agent..."); + ui.writeVerbose('Running: wmic product where "name=\'SafeChain Agent\'" call uninstall /nointeractive'); + execSync('wmic product where "name=\'SafeChain Agent\'" call uninstall /nointeractive', { stdio: "inherit" }); + } catch { + // Not installed or uninstall failed, which is fine for a fresh install + ui.writeVerbose("No existing SafeChain Agent installation found."); + } +} + /** * @param {string} msiPath */ function runMsiInstaller(msiPath) { - // Try to install/upgrade - // /i = install (will upgrade if product code matches) + // /i = install // /qn = quiet mode (no UI) // /norestart = suppress restarts - try { - execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); - } catch { - // If installation fails, it might be because it's already installed - // Try to force a reinstall - ui.writeVerbose( - "Initial installation failed, attempting to force reinstall...", - ); - execSync( - `msiexec /i "${msiPath}" /qn /norestart REINSTALL=ALL REINSTALLMODE=vomus`, - { stdio: "inherit" }, - ); - } + execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); } function stopServiceIfRunning() { From 3958fcfcefeaca7d9e4a215b54b433da02d4178d Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:06:43 +0100 Subject: [PATCH 24/97] Parse cli args in ultimate installation --- .../src/installation/installUltimate.js | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 6cb3f46..fc2b93f 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -5,10 +5,13 @@ import { execSync } from "child_process"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; import { ui } from "../environment/userInteraction.js"; +import { initializeCliArguments } from "../config/cliArguments.js"; const ULTIMATE_VERSION = "v0.2.0"; export function installUltimate() { + initializeCliArguments(process.argv); + const operatingSystem = platform(); if (operatingSystem === "win32") { @@ -96,8 +99,25 @@ async function downloadFile(url, destPath) { function uninstallIfInstalled() { try { ui.writeInformation("Uninstalling existing SafeChain Agent..."); - ui.writeVerbose('Running: wmic product where "name=\'SafeChain Agent\'" call uninstall /nointeractive'); - execSync('wmic product where "name=\'SafeChain Agent\'" call uninstall /nointeractive', { stdio: "inherit" }); + + // Use PowerShell to find the product code, then use msiexec to uninstall + // This is the modern alternative to wmic which is deprecated + const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; + ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); + + const productCode = execSync(findProductCodeCmd, { + encoding: "utf8", + }).trim(); + + if (productCode) { + ui.writeVerbose(`Found product code: ${productCode}`); + ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); + execSync(`msiexec /x ${productCode} /qn /norestart`, { + stdio: "inherit", + }); + } else { + ui.writeVerbose("No existing SafeChain Agent installation found."); + } } catch { // Not installed or uninstall failed, which is fine for a fresh install ui.writeVerbose("No existing SafeChain Agent installation found."); From 2784dfd34e89b87f92eb3ed683a821c299adbfb0 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:23:15 +0100 Subject: [PATCH 25/97] Check if the agents service is running before starting it --- .../safe-chain/src/installation/installUltimate.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index fc2b93f..dd8de84 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -146,6 +146,19 @@ function stopServiceIfRunning() { } function startService() { + try { + // Check if service is already running + ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); + const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); + + if (status.includes("RUNNING")) { + ui.writeVerbose("SafeChain Agent service is already running."); + return; + } + } catch { + // Service might not exist yet or query failed, proceed with start + } + ui.writeVerbose('Running: net start "SafeChainAgent"'); execSync('net start "SafeChainAgent"', { stdio: "inherit" }); } From 0e7cce750d7ddfaaef20f00ea3bbc595573163d3 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:30:09 +0100 Subject: [PATCH 26/97] Improve output --- .../src/installation/installUltimate.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index dd8de84..d1ccf28 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -36,29 +36,35 @@ async function installOnWindows() { const downloadUrl = buildDownloadUrl(architecture); const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + ui.emptyLine(); ui.writeInformation( - `Downloading SafeChain Agent ${ULTIMATE_VERSION} for ${architecture}...`, + `📥 Downloading SafeChain Agent ${ULTIMATE_VERSION} (${architecture})...`, ); ui.writeVerbose(`Download URL: ${downloadUrl}`); ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); + ui.emptyLine(); stopServiceIfRunning(); uninstallIfInstalled(); // Wait a moment for uninstall to complete await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("Installing SafeChain Agent..."); + ui.writeInformation("⚙️ Installing SafeChain Agent..."); ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn /norestart`); runMsiInstaller(msiPath); - ui.writeInformation("Starting SafeChain Agent service..."); + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); startService(); ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); cleanup(msiPath); - ui.writeInformation("SafeChain Agent installed and started successfully!"); + + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); + ui.emptyLine(); } function isRunningAsAdmin() { @@ -98,8 +104,6 @@ async function downloadFile(url, destPath) { function uninstallIfInstalled() { try { - ui.writeInformation("Uninstalling existing SafeChain Agent..."); - // Use PowerShell to find the product code, then use msiexec to uninstall // This is the modern alternative to wmic which is deprecated const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; @@ -110,17 +114,18 @@ function uninstallIfInstalled() { }).trim(); if (productCode) { + ui.writeInformation("🗑️ Removing previous installation..."); ui.writeVerbose(`Found product code: ${productCode}`); ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); execSync(`msiexec /x ${productCode} /qn /norestart`, { stdio: "inherit", }); } else { - ui.writeVerbose("No existing SafeChain Agent installation found."); + ui.writeVerbose("No existing installation found (fresh install)."); } } catch { // Not installed or uninstall failed, which is fine for a fresh install - ui.writeVerbose("No existing SafeChain Agent installation found."); + ui.writeVerbose("No existing installation found (fresh install)."); } } @@ -136,12 +141,12 @@ function runMsiInstaller(msiPath) { function stopServiceIfRunning() { try { - ui.writeInformation("Stopping existing SafeChain Agent service..."); + ui.writeInformation("⏹️ Stopping running service..."); ui.writeVerbose('Running: net stop "SafeChainAgent"'); execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); } catch { // Service is not running or doesn't exist, which is fine - ui.writeVerbose("SafeChain Agent service not running or not installed."); + ui.writeVerbose("Service not running (will start after installation)."); } } From fd559cfc63779037152dc892fdcb34d70d42f4a3 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:46:04 +0100 Subject: [PATCH 27/97] Restructure code into separate files --- .../src/installation/downloadAgent.js | 40 +++++ .../src/installation/installOnWindows.js | 146 +++++++++++++++ .../src/installation/installUltimate.js | 166 +----------------- packages/safe-chain/src/main.js | 10 +- 4 files changed, 193 insertions(+), 169 deletions(-) create mode 100644 packages/safe-chain/src/installation/downloadAgent.js create mode 100644 packages/safe-chain/src/installation/installOnWindows.js diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js new file mode 100644 index 0000000..2e45b79 --- /dev/null +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -0,0 +1,40 @@ +import { createWriteStream } from "fs"; +import { pipeline } from "stream/promises"; +import fetch from "make-fetch-happen"; + +const ULTIMATE_VERSION = "v0.2.0"; + +/** + * @typedef {"windows"} Platform + * @typedef {"amd64" | "arm64"} Architecture + */ + +/** + * Builds the download URL for the SafeChain Agent installer. + * @param {Platform} platform + * @param {Architecture} architecture + */ +export function getAgentDownloadUrl(platform, architecture) { + const extension = platform === "windows" ? "msi" : "pkg"; + return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-${platform}-${architecture}.${extension}`; +} + +/** + * Downloads a file from a URL to a local path. + * @param {string} url + * @param {string} destPath + */ +export async function downloadFile(url, destPath) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Download failed: ${response.statusText}`); + } + await pipeline(response.body, createWriteStream(destPath)); +} + +/** + * Returns the current agent version. + */ +export function getAgentVersion() { + return ULTIMATE_VERSION; +} diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js new file mode 100644 index 0000000..27db104 --- /dev/null +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -0,0 +1,146 @@ +import { arch, tmpdir } from "os"; +import { unlinkSync } from "fs"; +import { join } from "path"; +import { execSync } from "child_process"; +import { ui } from "../environment/userInteraction.js"; +import { + getAgentDownloadUrl, + getAgentVersion, + downloadFile, +} from "./downloadAgent.js"; + +export async function installOnWindows() { + if (!isRunningAsAdmin()) { + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator).", + ); + return; + } + + const architecture = getWindowsArchitecture(); + const downloadUrl = getAgentDownloadUrl("windows", architecture); + const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + + ui.emptyLine(); + ui.writeInformation( + `📥 Downloading SafeChain Agent ${getAgentVersion()} (${architecture})...`, + ); + ui.writeVerbose(`Download URL: ${downloadUrl}`); + ui.writeVerbose(`Destination: ${msiPath}`); + await downloadFile(downloadUrl, msiPath); + + ui.emptyLine(); + stopServiceIfRunning(); + uninstallIfInstalled(); + + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); + + ui.writeInformation("⚙️ Installing SafeChain Agent..."); + runMsiInstaller(msiPath); + + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); + startService(); + + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); + ui.emptyLine(); +} + +function isRunningAsAdmin() { + try { + execSync("net session", { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +function getWindowsArchitecture() { + const nodeArch = arch(); + if (nodeArch === "x64") return "amd64"; + if (nodeArch === "arm64") return "arm64"; + throw new Error(`Unsupported architecture: ${nodeArch}`); +} + +function uninstallIfInstalled() { + try { + // Use PowerShell to find the product code, then use msiexec to uninstall + // This is the modern alternative to wmic which is deprecated + const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; + ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); + + const productCode = execSync(findProductCodeCmd, { + encoding: "utf8", + }).trim(); + + if (productCode) { + ui.writeInformation("🗑️ Removing previous installation..."); + ui.writeVerbose(`Found product code: ${productCode}`); + ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); + execSync(`msiexec /x ${productCode} /qn /norestart`, { + stdio: "inherit", + }); + } else { + ui.writeVerbose("No existing installation found (fresh install)."); + } + } catch { + // Not installed or uninstall failed, which is fine for a fresh install + ui.writeVerbose("No existing installation found (fresh install)."); + } +} + +/** + * @param {string} msiPath + */ +function runMsiInstaller(msiPath) { + // /i = install + // /qn = quiet mode (no UI) + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); +} + +function stopServiceIfRunning() { + try { + ui.writeInformation("⏹️ Stopping running service..."); + ui.writeVerbose('Running: net stop "SafeChainAgent"'); + execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); + } catch { + // Service is not running or doesn't exist, which is fine + ui.writeVerbose("Service not running (will start after installation)."); + } +} + +function startService() { + try { + // Check if service is already running + ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); + const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); + + if (status.includes("RUNNING")) { + ui.writeVerbose("SafeChain Agent service is already running."); + return; + } + } catch { + // Service might not exist yet or query failed, proceed with start + } + + ui.writeVerbose('Running: net start "SafeChainAgent"'); + execSync('net start "SafeChainAgent"', { stdio: "inherit" }); +} + +/** + * @param {string} msiPath + */ +function cleanup(msiPath) { + try { + unlinkSync(msiPath); + } catch { + // Ignore cleanup errors + } +} diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index d1ccf28..7383d2c 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -1,13 +1,7 @@ -import { platform, arch, tmpdir } from "os"; -import { createWriteStream, unlinkSync } from "fs"; -import { join } from "path"; -import { execSync } from "child_process"; -import { pipeline } from "stream/promises"; -import fetch from "make-fetch-happen"; +import { platform } from "os"; import { ui } from "../environment/userInteraction.js"; import { initializeCliArguments } from "../config/cliArguments.js"; - -const ULTIMATE_VERSION = "v0.2.0"; +import { installOnWindows } from "./installOnWindows.js"; export function installUltimate() { initializeCliArguments(process.argv); @@ -22,159 +16,3 @@ export function installUltimate() { ); } } - -async function installOnWindows() { - if (!isRunningAsAdmin()) { - ui.writeError("Administrator privileges required."); - ui.writeInformation( - "Please run this command in an elevated terminal (Run as Administrator).", - ); - return; - } - - const architecture = getWindowsArchitecture(); - const downloadUrl = buildDownloadUrl(architecture); - const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); - - ui.emptyLine(); - ui.writeInformation( - `📥 Downloading SafeChain Agent ${ULTIMATE_VERSION} (${architecture})...`, - ); - ui.writeVerbose(`Download URL: ${downloadUrl}`); - ui.writeVerbose(`Destination: ${msiPath}`); - await downloadFile(downloadUrl, msiPath); - - ui.emptyLine(); - stopServiceIfRunning(); - uninstallIfInstalled(); - - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - - ui.writeInformation("⚙️ Installing SafeChain Agent..."); - ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn /norestart`); - runMsiInstaller(msiPath); - - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); - startService(); - - ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); - cleanup(msiPath); - - ui.emptyLine(); - ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); - ui.emptyLine(); -} - -function isRunningAsAdmin() { - try { - execSync("net session", { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function getWindowsArchitecture() { - const nodeArch = arch(); - if (nodeArch === "x64") return "amd64"; - if (nodeArch === "arm64") return "arm64"; - throw new Error(`Unsupported architecture: ${nodeArch}`); -} - -/** - * @param {string} architecture - */ -function buildDownloadUrl(architecture) { - return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-${architecture}.msi`; -} - -/** - * @param {string} url - * @param {string} destPath - */ -async function downloadFile(url, destPath) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Download failed: ${response.statusText}`); - } - await pipeline(response.body, createWriteStream(destPath)); -} - -function uninstallIfInstalled() { - try { - // Use PowerShell to find the product code, then use msiexec to uninstall - // This is the modern alternative to wmic which is deprecated - const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; - ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); - - const productCode = execSync(findProductCodeCmd, { - encoding: "utf8", - }).trim(); - - if (productCode) { - ui.writeInformation("🗑️ Removing previous installation..."); - ui.writeVerbose(`Found product code: ${productCode}`); - ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); - execSync(`msiexec /x ${productCode} /qn /norestart`, { - stdio: "inherit", - }); - } else { - ui.writeVerbose("No existing installation found (fresh install)."); - } - } catch { - // Not installed or uninstall failed, which is fine for a fresh install - ui.writeVerbose("No existing installation found (fresh install)."); - } -} - -/** - * @param {string} msiPath - */ -function runMsiInstaller(msiPath) { - // /i = install - // /qn = quiet mode (no UI) - // /norestart = suppress restarts - execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); -} - -function stopServiceIfRunning() { - try { - ui.writeInformation("⏹️ Stopping running service..."); - ui.writeVerbose('Running: net stop "SafeChainAgent"'); - execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); - } catch { - // Service is not running or doesn't exist, which is fine - ui.writeVerbose("Service not running (will start after installation)."); - } -} - -function startService() { - try { - // Check if service is already running - ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); - const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); - - if (status.includes("RUNNING")) { - ui.writeVerbose("SafeChain Agent service is already running."); - return; - } - } catch { - // Service might not exist yet or query failed, proceed with start - } - - ui.writeVerbose('Running: net start "SafeChainAgent"'); - execSync('net start "SafeChainAgent"', { stdio: "inherit" }); -} - -/** - * @param {string} msiPath - */ -function cleanup(msiPath) { - try { - unlinkSync(msiPath); - } catch { - // Ignore cleanup errors - } -} diff --git a/packages/safe-chain/src/main.js b/packages/safe-chain/src/main.js index 9b7ba53..0b37eba 100644 --- a/packages/safe-chain/src/main.js +++ b/packages/safe-chain/src/main.js @@ -73,20 +73,20 @@ export async function main(args) { ui.writeVerbose( `${chalk.green("✔")} Safe-chain: Scanned ${ auditStats.totalPackages - } packages, no malware found.` + } packages, no malware found.`, ); } if (proxy.hasSuppressedVersions()) { ui.writeInformation( `${chalk.yellow( - "ℹ" - )} Safe-chain: Some package versions were suppressed due to minimum age requirement.` + "ℹ", + )} Safe-chain: Some package versions were suppressed due to minimum age requirement.`, ); ui.writeInformation( ` To disable this check, use: ${chalk.cyan( - "--safe-chain-skip-minimum-package-age" - )}` + "--safe-chain-skip-minimum-package-age", + )}`, ); } From 079e4893b1e55d43f8c772d9246588bb5184f7a2 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:53:33 +0100 Subject: [PATCH 28/97] Move download name construction to os installer function --- .../safe-chain/src/installation/downloadAgent.js | 13 +++---------- .../safe-chain/src/installation/installOnWindows.js | 3 ++- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index 2e45b79..d74cbf7 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -4,19 +4,12 @@ import fetch from "make-fetch-happen"; const ULTIMATE_VERSION = "v0.2.0"; -/** - * @typedef {"windows"} Platform - * @typedef {"amd64" | "arm64"} Architecture - */ - /** * Builds the download URL for the SafeChain Agent installer. - * @param {Platform} platform - * @param {Architecture} architecture + * @param {string} fileName */ -export function getAgentDownloadUrl(platform, architecture) { - const extension = platform === "windows" ? "msi" : "pkg"; - return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-${platform}-${architecture}.${extension}`; +export function getAgentDownloadUrl(fileName) { + return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/${fileName}`; } /** diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 27db104..9f92893 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -19,7 +19,8 @@ export async function installOnWindows() { } const architecture = getWindowsArchitecture(); - const downloadUrl = getAgentDownloadUrl("windows", architecture); + const fileName = `SafeChainAgent-windows-${architecture}.msi`; + const downloadUrl = getAgentDownloadUrl(fileName); const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); ui.emptyLine(); From 471ef2821015309ecdd82c8400f27a33a7d52793 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:22:24 +0100 Subject: [PATCH 29/97] Handle code quality comments --- packages/safe-chain/bin/safe-chain.js | 4 +++- packages/safe-chain/src/installation/installOnWindows.js | 4 ++-- packages/safe-chain/src/installation/installUltimate.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index e33ad9f..d048ce1 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -64,7 +64,9 @@ if (tool) { } else if (command === "setup") { setup(); } else if (command === "--ultimate") { - installUltimate(); + (async () => { + await installUltimate(); + })(); } else if (command === "teardown") { teardownDirectories(); teardown(); diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 9f92893..094ec58 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -103,7 +103,7 @@ function runMsiInstaller(msiPath) { // /i = install // /qn = quiet mode (no UI) ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); - execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); + execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); // noopengrep this is ok, we control the msiPath } function stopServiceIfRunning() { @@ -128,7 +128,7 @@ function startService() { return; } } catch { - // Service might not exist yet or query failed, proceed with start + ui.writeVerbose("Service not found or query failed, attempting to start."); } ui.writeVerbose('Running: net start "SafeChainAgent"'); diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 7383d2c..3b6846a 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -3,13 +3,13 @@ import { ui } from "../environment/userInteraction.js"; import { initializeCliArguments } from "../config/cliArguments.js"; import { installOnWindows } from "./installOnWindows.js"; -export function installUltimate() { +export async function installUltimate() { initializeCliArguments(process.argv); const operatingSystem = platform(); if (operatingSystem === "win32") { - installOnWindows(); + await installOnWindows(); } else { ui.writeInformation( `${operatingSystem} is not supported yet by safe-chain's ultimate version.`, From 9b61a325fa1eccb98becd11088ba9763da4c2d1c Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:24:49 +0100 Subject: [PATCH 30/97] Log when installer file cleanup failed --- packages/safe-chain/src/installation/installOnWindows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 094ec58..f3c9ee8 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -142,6 +142,6 @@ function cleanup(msiPath) { try { unlinkSync(msiPath); } catch { - // Ignore cleanup errors + ui.writeVerbose("Failed to clean up temporary installer file."); } } From 8b189443b7e293ad7f4bc88944da577ce2ace311 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:31:41 +0100 Subject: [PATCH 31/97] Use safeSpawn instead of execSync --- .../src/installation/installOnWindows.js | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index f3c9ee8..54893bf 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -1,8 +1,8 @@ import { arch, tmpdir } from "os"; import { unlinkSync } from "fs"; import { join } from "path"; -import { execSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; +import { safeSpawn } from "../utils/safeSpawn.js"; import { getAgentDownloadUrl, getAgentVersion, @@ -10,7 +10,7 @@ import { } from "./downloadAgent.js"; export async function installOnWindows() { - if (!isRunningAsAdmin()) { + if (!(await isRunningAsAdmin())) { ui.writeError("Administrator privileges required."); ui.writeInformation( "Please run this command in an elevated terminal (Run as Administrator).", @@ -32,18 +32,18 @@ export async function installOnWindows() { await downloadFile(downloadUrl, msiPath); ui.emptyLine(); - stopServiceIfRunning(); - uninstallIfInstalled(); + await stopServiceIfRunning(); + await uninstallIfInstalled(); // Wait a moment for uninstall to complete await new Promise((resolve) => setTimeout(resolve, 2000)); ui.writeInformation("⚙️ Installing SafeChain Agent..."); - runMsiInstaller(msiPath); + await runMsiInstaller(msiPath); ui.emptyLine(); ui.writeInformation("🚀 Starting SafeChain Agent service..."); - startService(); + await startService(); ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); cleanup(msiPath); @@ -53,13 +53,9 @@ export async function installOnWindows() { ui.emptyLine(); } -function isRunningAsAdmin() { - try { - execSync("net session", { stdio: "ignore" }); - return true; - } catch { - return false; - } +async function isRunningAsAdmin() { + const result = await safeSpawn("net", ["session"], { stdio: "ignore" }); + return result.status === 0; } function getWindowsArchitecture() { @@ -69,29 +65,31 @@ function getWindowsArchitecture() { throw new Error(`Unsupported architecture: ${nodeArch}`); } -function uninstallIfInstalled() { - try { - // Use PowerShell to find the product code, then use msiexec to uninstall - // This is the modern alternative to wmic which is deprecated - const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; - ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); +async function uninstallIfInstalled() { + // Use PowerShell to find the product code, then use msiexec to uninstall + // This is the modern alternative to wmic which is deprecated + const powershellScript = `$app = Get-WmiObject -Class Win32_Product -Filter "Name='SafeChain Agent'"; if ($app) { Write-Output $app.IdentifyingNumber }`; + ui.writeVerbose(`Finding product code with PowerShell`); - const productCode = execSync(findProductCodeCmd, { - encoding: "utf8", - }).trim(); + const result = await safeSpawn("powershell", ["-Command", powershellScript], { + stdio: "pipe", + }); - if (productCode) { - ui.writeInformation("🗑️ Removing previous installation..."); - ui.writeVerbose(`Found product code: ${productCode}`); - ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); - execSync(`msiexec /x ${productCode} /qn /norestart`, { - stdio: "inherit", - }); - } else { - ui.writeVerbose("No existing installation found (fresh install)."); - } - } catch { - // Not installed or uninstall failed, which is fine for a fresh install + if (result.status !== 0) { + ui.writeVerbose("No existing installation found (fresh install)."); + return; + } + + const productCode = result.stdout.trim(); + + if (productCode) { + ui.writeInformation("🗑️ Removing previous installation..."); + ui.writeVerbose(`Found product code: ${productCode}`); + ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); + await safeSpawn("msiexec", ["/x", productCode, "/qn", "/norestart"], { + stdio: "inherit", + }); + } else { ui.writeVerbose("No existing installation found (fresh install)."); } } @@ -99,40 +97,43 @@ function uninstallIfInstalled() { /** * @param {string} msiPath */ -function runMsiInstaller(msiPath) { +async function runMsiInstaller(msiPath) { // /i = install // /qn = quiet mode (no UI) ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); - execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); // noopengrep this is ok, we control the msiPath + await safeSpawn("msiexec", ["/i", msiPath, "/qn"], { stdio: "inherit" }); } -function stopServiceIfRunning() { - try { - ui.writeInformation("⏹️ Stopping running service..."); - ui.writeVerbose('Running: net stop "SafeChainAgent"'); - execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); - } catch { - // Service is not running or doesn't exist, which is fine +async function stopServiceIfRunning() { + ui.writeInformation("⏹️ Stopping running service..."); + ui.writeVerbose('Running: net stop "SafeChainAgent"'); + const result = await safeSpawn("net", ["stop", "SafeChainAgent"], { + stdio: "inherit", + }); + + if (result.status !== 0) { ui.writeVerbose("Service not running (will start after installation)."); } } -function startService() { - try { - // Check if service is already running - ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); - const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); +async function startService() { + // Check if service is already running + ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); + const queryResult = await safeSpawn("sc", ["query", "SafeChainAgent"], { + stdio: "pipe", + }); - if (status.includes("RUNNING")) { - ui.writeVerbose("SafeChain Agent service is already running."); - return; - } - } catch { + if (queryResult.status === 0 && queryResult.stdout.includes("RUNNING")) { + ui.writeVerbose("SafeChain Agent service is already running."); + return; + } + + if (queryResult.status !== 0) { ui.writeVerbose("Service not found or query failed, attempting to start."); } ui.writeVerbose('Running: net start "SafeChainAgent"'); - execSync('net start "SafeChainAgent"', { stdio: "inherit" }); + await safeSpawn("net", ["start", "SafeChainAgent"], { stdio: "inherit" }); } /** From 4a90bd262109d307e0767eed654258a3e90801bd Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:34:16 +0100 Subject: [PATCH 32/97] Code quality: use early return --- .../src/installation/installOnWindows.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 54893bf..a6e0938 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -82,16 +82,17 @@ async function uninstallIfInstalled() { const productCode = result.stdout.trim(); - if (productCode) { - ui.writeInformation("🗑️ Removing previous installation..."); - ui.writeVerbose(`Found product code: ${productCode}`); - ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); - await safeSpawn("msiexec", ["/x", productCode, "/qn", "/norestart"], { - stdio: "inherit", - }); - } else { + if (!productCode) { ui.writeVerbose("No existing installation found (fresh install)."); + return; } + + ui.writeInformation("🗑️ Removing previous installation..."); + ui.writeVerbose(`Found product code: ${productCode}`); + ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); + await safeSpawn("msiexec", ["/x", productCode, "/qn", "/norestart"], { + stdio: "inherit", + }); } /** From 86e600773370e0b5da5f743ba1840bd008899201 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:45:32 +0100 Subject: [PATCH 33/97] Improve error handling --- .../src/installation/installOnWindows.js | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index a6e0938..6db1cb0 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -31,31 +31,43 @@ export async function installOnWindows() { ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); - ui.emptyLine(); - await stopServiceIfRunning(); - await uninstallIfInstalled(); + try { + ui.emptyLine(); + await stopServiceIfRunning(); + await uninstallIfInstalled(); - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("⚙️ Installing SafeChain Agent..."); - await runMsiInstaller(msiPath); + ui.writeInformation("⚙️ Installing SafeChain Agent..."); + await runMsiInstaller(msiPath); - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); - await startService(); + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); + await startService(); - ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); - cleanup(msiPath); - - ui.emptyLine(); - ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); - ui.emptyLine(); + ui.emptyLine(); + ui.writeInformation( + "✅ SafeChain Agent installed and started successfully!", + ); + ui.emptyLine(); + } finally { + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + } } async function isRunningAsAdmin() { - const result = await safeSpawn("net", ["session"], { stdio: "ignore" }); - return result.status === 0; + const result = await safeSpawn( + "powershell", + [ + "-Command", + "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)", + ], + { stdio: "pipe" }, + ); + + return result.status === 0 && result.stdout.trim() === "True"; } function getWindowsArchitecture() { @@ -66,8 +78,6 @@ function getWindowsArchitecture() { } async function uninstallIfInstalled() { - // Use PowerShell to find the product code, then use msiexec to uninstall - // This is the modern alternative to wmic which is deprecated const powershellScript = `$app = Get-WmiObject -Class Win32_Product -Filter "Name='SafeChain Agent'"; if ($app) { Write-Output $app.IdentifyingNumber }`; ui.writeVerbose(`Finding product code with PowerShell`); @@ -81,7 +91,6 @@ async function uninstallIfInstalled() { } const productCode = result.stdout.trim(); - if (!productCode) { ui.writeVerbose("No existing installation found (fresh install)."); return; @@ -89,27 +98,38 @@ async function uninstallIfInstalled() { ui.writeInformation("🗑️ Removing previous installation..."); ui.writeVerbose(`Found product code: ${productCode}`); - ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); - await safeSpawn("msiexec", ["/x", productCode, "/qn", "/norestart"], { - stdio: "inherit", - }); + + const uninstallResult = await safeSpawn( + "msiexec", + ["/x", productCode, "/qn", "/norestart"], + { stdio: "inherit" }, + ); + + if (uninstallResult.status !== 0) { + throw new Error(`Uninstall failed (exit code: ${uninstallResult.status})`); + } } /** * @param {string} msiPath */ async function runMsiInstaller(msiPath) { - // /i = install - // /qn = quiet mode (no UI) ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); - await safeSpawn("msiexec", ["/i", msiPath, "/qn"], { stdio: "inherit" }); + + const result = await safeSpawn("msiexec", ["/i", msiPath, "/qn"], { + stdio: "inherit", + }); + + if (result.status !== 0) { + throw new Error(`MSI installer failed (exit code: ${result.status})`); + } } async function stopServiceIfRunning() { ui.writeInformation("⏹️ Stopping running service..."); - ui.writeVerbose('Running: net stop "SafeChainAgent"'); + const result = await safeSpawn("net", ["stop", "SafeChainAgent"], { - stdio: "inherit", + stdio: "pipe", }); if (result.status !== 0) { @@ -118,7 +138,6 @@ async function stopServiceIfRunning() { } async function startService() { - // Check if service is already running ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); const queryResult = await safeSpawn("sc", ["query", "SafeChainAgent"], { stdio: "pipe", @@ -129,12 +148,14 @@ async function startService() { return; } - if (queryResult.status !== 0) { - ui.writeVerbose("Service not found or query failed, attempting to start."); - } - ui.writeVerbose('Running: net start "SafeChainAgent"'); - await safeSpawn("net", ["start", "SafeChainAgent"], { stdio: "inherit" }); + const startResult = await safeSpawn("net", ["start", "SafeChainAgent"], { + stdio: "pipe", + }); + + if (startResult.status !== 0) { + throw new Error(`Failed to start service (exit code: ${startResult.status})`); + } } /** From eb00fe6f3d8a17ec9e8ecf522db15f05072adc06 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:54:02 +0100 Subject: [PATCH 34/97] Write error output --- packages/safe-chain/src/installation/installOnWindows.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 6db1cb0..8610394 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -106,6 +106,8 @@ async function uninstallIfInstalled() { ); if (uninstallResult.status !== 0) { + ui.writeInformation(uninstallResult.stdout); + ui.writeInformation(uninstallResult.stderr); throw new Error(`Uninstall failed (exit code: ${uninstallResult.status})`); } } @@ -154,7 +156,9 @@ async function startService() { }); if (startResult.status !== 0) { - throw new Error(`Failed to start service (exit code: ${startResult.status})`); + throw new Error( + `Failed to start service (exit code: ${startResult.status})`, + ); } } From 4ebbbca4326295f1d144e0fd22c08cc03690ffbb Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 15:58:11 +0100 Subject: [PATCH 35/97] Temporarily disable cleanup --- .../src/installation/installOnWindows.js | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 8610394..0257fd8 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -31,30 +31,29 @@ export async function installOnWindows() { ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); - try { - ui.emptyLine(); - await stopServiceIfRunning(); - await uninstallIfInstalled(); + // try { + ui.emptyLine(); + await stopServiceIfRunning(); + await uninstallIfInstalled(); - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("⚙️ Installing SafeChain Agent..."); - await runMsiInstaller(msiPath); + ui.writeInformation("⚙️ Installing SafeChain Agent..."); + await runMsiInstaller(msiPath); - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); - await startService(); + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); + await startService(); - ui.emptyLine(); - ui.writeInformation( - "✅ SafeChain Agent installed and started successfully!", - ); - ui.emptyLine(); - } finally { - ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); - cleanup(msiPath); - } + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); + ui.emptyLine(); + // } + // finally { + // ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + // cleanup(msiPath); + // } } async function isRunningAsAdmin() { From 211f877384950e40a4c0e814c291b49517e7a066 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 16:03:51 +0100 Subject: [PATCH 36/97] Write stdout stderr --- .../src/installation/installOnWindows.js | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 0257fd8..7511eef 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -31,29 +31,30 @@ export async function installOnWindows() { ui.writeVerbose(`Destination: ${msiPath}`); await downloadFile(downloadUrl, msiPath); - // try { - ui.emptyLine(); - await stopServiceIfRunning(); - await uninstallIfInstalled(); + try { + ui.emptyLine(); + await stopServiceIfRunning(); + await uninstallIfInstalled(); - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("⚙️ Installing SafeChain Agent..."); - await runMsiInstaller(msiPath); + ui.writeInformation("⚙️ Installing SafeChain Agent..."); + await runMsiInstaller(msiPath); - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); - await startService(); + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); + await startService(); - ui.emptyLine(); - ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); - ui.emptyLine(); - // } - // finally { - // ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); - // cleanup(msiPath); - // } + ui.emptyLine(); + ui.writeInformation( + "✅ SafeChain Agent installed and started successfully!", + ); + ui.emptyLine(); + } finally { + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + } } async function isRunningAsAdmin() { @@ -85,7 +86,9 @@ async function uninstallIfInstalled() { }); if (result.status !== 0) { - ui.writeVerbose("No existing installation found (fresh install)."); + ui.writeVerbose( + `No existing installation found (fresh install). Output: ${result.stdout} ${result.stderr}`, + ); return; } From 4a7629a17487217500f2b8054e970eb7ca83e186 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 16:11:51 +0100 Subject: [PATCH 37/97] Use execSync to execute powershell command --- .../src/installation/installOnWindows.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 7511eef..84dcd9c 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -1,6 +1,7 @@ import { arch, tmpdir } from "os"; import { unlinkSync } from "fs"; import { join } from "path"; +import { execSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; import { safeSpawn } from "../utils/safeSpawn.js"; import { @@ -81,18 +82,15 @@ async function uninstallIfInstalled() { const powershellScript = `$app = Get-WmiObject -Class Win32_Product -Filter "Name='SafeChain Agent'"; if ($app) { Write-Output $app.IdentifyingNumber }`; ui.writeVerbose(`Finding product code with PowerShell`); - const result = await safeSpawn("powershell", ["-Command", powershellScript], { - stdio: "pipe", - }); - - if (result.status !== 0) { - ui.writeVerbose( - `No existing installation found (fresh install). Output: ${result.stdout} ${result.stderr}`, - ); + let productCode; + try { + productCode = execSync(`powershell -Command "${powershellScript}"`, { + encoding: "utf8", + }).trim(); + } catch { + ui.writeVerbose("No existing installation found (fresh install)."); return; } - - const productCode = result.stdout.trim(); if (!productCode) { ui.writeVerbose("No existing installation found (fresh install)."); return; From 20fb949a2351d6dc1940b0294f74036b9cb10a4f Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 16:17:34 +0100 Subject: [PATCH 38/97] Fix uninstall --- packages/safe-chain/src/installation/installOnWindows.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 84dcd9c..d6e7207 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -79,14 +79,14 @@ function getWindowsArchitecture() { } async function uninstallIfInstalled() { - const powershellScript = `$app = Get-WmiObject -Class Win32_Product -Filter "Name='SafeChain Agent'"; if ($app) { Write-Output $app.IdentifyingNumber }`; ui.writeVerbose(`Finding product code with PowerShell`); let productCode; try { - productCode = execSync(`powershell -Command "${powershellScript}"`, { - encoding: "utf8", - }).trim(); + productCode = execSync( + `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`, + { encoding: "utf8" }, + ).trim(); } catch { ui.writeVerbose("No existing installation found (fresh install)."); return; From c200ea56cf4aef90290654b2d1b40e1787515ac1 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 16:23:59 +0100 Subject: [PATCH 39/97] Cleanup debug logging --- packages/safe-chain/src/installation/installOnWindows.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index d6e7207..60a339b 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -106,8 +106,6 @@ async function uninstallIfInstalled() { ); if (uninstallResult.status !== 0) { - ui.writeInformation(uninstallResult.stdout); - ui.writeInformation(uninstallResult.stderr); throw new Error(`Uninstall failed (exit code: ${uninstallResult.status})`); } } From da6c022ef49c5fcb6faba43a1c6bb9d685a8cc37 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 16:25:50 +0100 Subject: [PATCH 40/97] Add explaining comments for powershell scritps --- packages/safe-chain/src/installation/installOnWindows.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 60a339b..41bb1ca 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -59,6 +59,8 @@ export async function installOnWindows() { } async function isRunningAsAdmin() { + // Uses Windows Security API to check if current process has admin privileges. + // Returns "True" or "False" as a string. const result = await safeSpawn( "powershell", [ @@ -79,6 +81,8 @@ function getWindowsArchitecture() { } async function uninstallIfInstalled() { + // Query Win32_Product via WMI to find the installed SafeChain Agent. + // If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall. ui.writeVerbose(`Finding product code with PowerShell`); let productCode; From 9651e05f4b04773dcd9029a93ce7aa97b2c49859 Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Mon, 19 Jan 2026 18:59:37 +0100 Subject: [PATCH 41/97] Fix naming of SafeChain Agent --- packages/safe-chain/src/installation/installOnWindows.js | 8 ++++---- packages/safe-chain/src/installation/installUltimate.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 41bb1ca..9837d18 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -40,16 +40,16 @@ export async function installOnWindows() { // Wait a moment for uninstall to complete await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("⚙️ Installing SafeChain Agent..."); + ui.writeInformation("⚙️ Installing SafeChain Ultimate..."); await runMsiInstaller(msiPath); ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); + ui.writeInformation("🚀 Starting SafeChain Ultimate service..."); await startService(); ui.emptyLine(); ui.writeInformation( - "✅ SafeChain Agent installed and started successfully!", + "✅ SafeChain Ultimate installed and started successfully!", ); ui.emptyLine(); } finally { @@ -148,7 +148,7 @@ async function startService() { }); if (queryResult.status === 0 && queryResult.stdout.includes("RUNNING")) { - ui.writeVerbose("SafeChain Agent service is already running."); + ui.writeVerbose("SafeChain Ultimate service is already running."); return; } diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 3b6846a..086b6a4 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -12,7 +12,7 @@ export async function installUltimate() { await installOnWindows(); } else { ui.writeInformation( - `${operatingSystem} is not supported yet by safe-chain's ultimate version.`, + `${operatingSystem} is not supported yet by SafeChain's ultimate version.`, ); } } From 3dad1c2516bdc26141a57eeb0eae24eb37f42ecc Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Mon, 19 Jan 2026 19:01:28 +0100 Subject: [PATCH 42/97] Update packages/safe-chain/src/installation/installOnWindows.js --- packages/safe-chain/src/installation/installOnWindows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 9837d18..3cd3428 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -26,7 +26,7 @@ export async function installOnWindows() { ui.emptyLine(); ui.writeInformation( - `📥 Downloading SafeChain Agent ${getAgentVersion()} (${architecture})...`, + `📥 Downloading SafeChain Ultimate ${getAgentVersion()} (${architecture})...`, ); ui.writeVerbose(`Download URL: ${downloadUrl}`); ui.writeVerbose(`Destination: ${msiPath}`); From 7d55c5453bc19ac8ab203d6f37922c369627659a Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 20 Jan 2026 09:12:00 +0100 Subject: [PATCH 43/97] Move os and arch detection to downloader, add checksum verification. --- .../src/installation/downloadAgent.js | 82 ++++++++++++++++++- .../src/installation/installOnWindows.js | 49 +++++------ 2 files changed, 102 insertions(+), 29 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index d74cbf7..2441f7d 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -1,9 +1,25 @@ -import { createWriteStream } from "fs"; +import { createWriteStream, createReadStream } from "fs"; +import { createHash } from "crypto"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; const ULTIMATE_VERSION = "v0.2.0"; +const DOWNLOAD_URLS = { + win32: { + x64: { + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-amd64.msi", + checksum: + "sha256:c699f74a3666d85b70b8ede076a2192a6a023f1b395e8e6c7556927ee698a020", + }, + arm64: { + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-arm64.msi", + checksum: + "sha256:5b08dd4749c8befe5379bc01f7a8a5ac1d6a35b6bee37c6c72a4ba8744c3b052", + }, + }, +}; + /** * Builds the download URL for the SafeChain Agent installer. * @param {string} fileName @@ -31,3 +47,67 @@ export async function downloadFile(url, destPath) { export function getAgentVersion() { return ULTIMATE_VERSION; } + +/** + * Returns download info (url, checksum) for the current OS and architecture. + * @returns {{ url: string, checksum: string } | null} + */ +export function getDownloadInfoForCurrentPlatform() { + const platform = process.platform; + const arch = process.arch; + + if (!Object.hasOwn(DOWNLOAD_URLS, platform)) { + return null; + } + const platformUrls = + DOWNLOAD_URLS[/** @type {keyof typeof DOWNLOAD_URLS} */ (platform)]; + + if (!Object.hasOwn(platformUrls, arch)) { + return null; + } + + return platformUrls[/** @type {keyof typeof platformUrls} */ (arch)]; +} + +/** + * Verifies the checksum of a file. + * @param {string} filePath + * @param {string} expectedChecksum - Format: "algorithm:hash" (e.g., "sha256:abc123...") + * @returns {Promise} + */ +async function verifyChecksum(filePath, expectedChecksum) { + const [algorithm, expected] = expectedChecksum.split(":"); + + const hash = createHash(algorithm); + + if (filePath.includes("..")) throw new Error("Invalid file path"); + const stream = createReadStream(filePath); + + for await (const chunk of stream) { + hash.update(chunk); + } + + const actual = hash.digest("hex"); + return actual === expected; +} + +/** + * Downloads the SafeChain agent for the current OS/arch and verifies its checksum. + * @param {string} fileName - Destination file path + * @returns {Promise} The file path if successful, null if no download URL for current platform + */ +export async function downloadAgentToFile(fileName) { + const info = getDownloadInfoForCurrentPlatform(); + if (!info) { + return null; + } + + await downloadFile(info.url, fileName); + + const isValid = await verifyChecksum(fileName, info.checksum); + if (!isValid) { + throw new Error("Checksum verification failed"); + } + + return fileName; +} diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 3cd3428..33ae293 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -1,14 +1,13 @@ -import { arch, tmpdir } from "os"; +import { tmpdir } from "os"; import { unlinkSync } from "fs"; import { join } from "path"; import { execSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; import { safeSpawn } from "../utils/safeSpawn.js"; -import { - getAgentDownloadUrl, - getAgentVersion, - downloadFile, -} from "./downloadAgent.js"; +import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; + +const WINDOWS_SERVICE_NAME = "SafeChainAgent"; +const WINDOWS_APP_NAME = "SafeChain Agent"; export async function installOnWindows() { if (!(await isRunningAsAdmin())) { @@ -19,18 +18,17 @@ export async function installOnWindows() { return; } - const architecture = getWindowsArchitecture(); - const fileName = `SafeChainAgent-windows-${architecture}.msi`; - const downloadUrl = getAgentDownloadUrl(fileName); - const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + const msiPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.msi`); ui.emptyLine(); - ui.writeInformation( - `📥 Downloading SafeChain Ultimate ${getAgentVersion()} (${architecture})...`, - ); - ui.writeVerbose(`Download URL: ${downloadUrl}`); + ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`); ui.writeVerbose(`Destination: ${msiPath}`); - await downloadFile(downloadUrl, msiPath); + + const result = await downloadAgentToFile(msiPath); + if (!result) { + ui.writeError("No download available for this platform/architecture."); + return; + } try { ui.emptyLine(); @@ -73,13 +71,6 @@ async function isRunningAsAdmin() { return result.status === 0 && result.stdout.trim() === "True"; } -function getWindowsArchitecture() { - const nodeArch = arch(); - if (nodeArch === "x64") return "amd64"; - if (nodeArch === "arm64") return "arm64"; - throw new Error(`Unsupported architecture: ${nodeArch}`); -} - async function uninstallIfInstalled() { // Query Win32_Product via WMI to find the installed SafeChain Agent. // If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall. @@ -88,7 +79,7 @@ async function uninstallIfInstalled() { let productCode; try { productCode = execSync( - `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`, + `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='${WINDOWS_APP_NAME}'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`, { encoding: "utf8" }, ).trim(); } catch { @@ -132,7 +123,7 @@ async function runMsiInstaller(msiPath) { async function stopServiceIfRunning() { ui.writeInformation("⏹️ Stopping running service..."); - const result = await safeSpawn("net", ["stop", "SafeChainAgent"], { + const result = await safeSpawn("net", ["stop", WINDOWS_SERVICE_NAME], { stdio: "pipe", }); @@ -142,8 +133,10 @@ async function stopServiceIfRunning() { } async function startService() { - ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); - const queryResult = await safeSpawn("sc", ["query", "SafeChainAgent"], { + ui.writeVerbose( + `Checking service status: sc query "${WINDOWS_SERVICE_NAME}"`, + ); + const queryResult = await safeSpawn("sc", ["query", WINDOWS_SERVICE_NAME], { stdio: "pipe", }); @@ -152,8 +145,8 @@ async function startService() { return; } - ui.writeVerbose('Running: net start "SafeChainAgent"'); - const startResult = await safeSpawn("net", ["start", "SafeChainAgent"], { + ui.writeVerbose(`Running: net start "${WINDOWS_SERVICE_NAME}"`); + const startResult = await safeSpawn("net", ["start", WINDOWS_SERVICE_NAME], { stdio: "pipe", }); From 626bb0d2b9c4262a92472beda086cbb25fe5f715 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 20 Jan 2026 12:21:45 +0100 Subject: [PATCH 44/97] Don't start the windows service - the msi already does this --- .../src/installation/installOnWindows.js | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 33ae293..2380a7f 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -35,16 +35,9 @@ export async function installOnWindows() { await stopServiceIfRunning(); await uninstallIfInstalled(); - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - ui.writeInformation("⚙️ Installing SafeChain Ultimate..."); await runMsiInstaller(msiPath); - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Ultimate service..."); - await startService(); - ui.emptyLine(); ui.writeInformation( "✅ SafeChain Ultimate installed and started successfully!", @@ -132,31 +125,6 @@ async function stopServiceIfRunning() { } } -async function startService() { - ui.writeVerbose( - `Checking service status: sc query "${WINDOWS_SERVICE_NAME}"`, - ); - const queryResult = await safeSpawn("sc", ["query", WINDOWS_SERVICE_NAME], { - stdio: "pipe", - }); - - if (queryResult.status === 0 && queryResult.stdout.includes("RUNNING")) { - ui.writeVerbose("SafeChain Ultimate service is already running."); - return; - } - - ui.writeVerbose(`Running: net start "${WINDOWS_SERVICE_NAME}"`); - const startResult = await safeSpawn("net", ["start", WINDOWS_SERVICE_NAME], { - stdio: "pipe", - }); - - if (startResult.status !== 0) { - throw new Error( - `Failed to start service (exit code: ${startResult.status})`, - ); - } -} - /** * @param {string} msiPath */ From 99cd41662846d7182369dbae82dbfa2b712b5644 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 20 Jan 2026 12:53:18 +0100 Subject: [PATCH 45/97] Support Windows in install-safe-shain.sh (git bash, cygwin, ...) --- install-scripts/install-safe-chain.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/install-scripts/install-safe-chain.sh b/install-scripts/install-safe-chain.sh index 7ee07c2..182cdad 100755 --- a/install-scripts/install-safe-chain.sh +++ b/install-scripts/install-safe-chain.sh @@ -42,8 +42,9 @@ detect_os() { echo "linuxstatic" fi ;; - Darwin*) echo "macos" ;; - *) error "Unsupported operating system: $(uname -s)" ;; + Darwin*) echo "macos" ;; + MINGW*|MSYS*|CYGWIN*) echo "win" ;; + *) error "Unsupported operating system: $(uname -s)" ;; esac } @@ -293,7 +294,11 @@ main() { # Detect platform OS=$(detect_os) ARCH=$(detect_arch) - BINARY_NAME="safe-chain-${OS}-${ARCH}" + if [ "$OS" = "win" ]; then + BINARY_NAME="safe-chain-${OS}-${ARCH}.exe" + else + BINARY_NAME="safe-chain-${OS}-${ARCH}" + fi info "Detected platform: ${OS}-${ARCH}" @@ -311,9 +316,15 @@ main() { download "$DOWNLOAD_URL" "$TEMP_FILE" # Rename and make executable - FINAL_FILE="${INSTALL_DIR}/safe-chain" + if [ "$OS" = "win" ]; then + FINAL_FILE="${INSTALL_DIR}/safe-chain.exe" + else + FINAL_FILE="${INSTALL_DIR}/safe-chain" + fi mv "$TEMP_FILE" "$FINAL_FILE" || error "Failed to move binary to $FINAL_FILE" - chmod +x "$FINAL_FILE" || error "Failed to make binary executable" + if [ "$OS" != "win" ]; then + chmod +x "$FINAL_FILE" || error "Failed to make binary executable" + fi info "Binary installed to: $FINAL_FILE" From a7e21bbfe272e31b11c7aae7c7b1e825090c2aa2 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 21 Jan 2026 07:58:06 +0100 Subject: [PATCH 46/97] Update download urls --- .../src/installation/downloadAgent.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index 2441f7d..0ee994e 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -8,14 +8,26 @@ const ULTIMATE_VERSION = "v0.2.0"; const DOWNLOAD_URLS = { win32: { x64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-amd64.msi", + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-windows-amd64.msi", checksum: - "sha256:c699f74a3666d85b70b8ede076a2192a6a023f1b395e8e6c7556927ee698a020", + "sha256:bba5deb250ebc6008f1cb33fa4209d2455a2f47fa99f0a40e3babef64939ac77", }, arm64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.2.0/SafeChainAgent-windows-arm64.msi", + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-windows-arm64.msi", checksum: - "sha256:5b08dd4749c8befe5379bc01f7a8a5ac1d6a35b6bee37c6c72a4ba8744c3b052", + "sha256:9553ed15d5efed4185b990a1b86af0b11c23f11d96f8ce04e16b6b98aaf0506e", + }, + }, + darwin: { + x64: { + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-darwin-amd64.pkg", + checksum: + "sha256:cbccf32e987a45bc8cc20b620f7b597ff7f9c2f966c2bc21132349612ddb619f", + }, + arm64: { + url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-darwin-arm64.pkg", + checksum: + "sha256:4d53a43a47bf7e8133eb61d306a1fb16348b9ec89c1c825e5f746f4fe847796e", }, }, }; From d4c496d60d625b1ba6886cd8fd312af79bd2afc7 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 21 Jan 2026 09:14:44 +0100 Subject: [PATCH 47/97] Add mac os installation --- .../src/installation/installOnMacOS.js | 78 +++++++++++++++++++ .../src/installation/installUltimate.js | 3 + 2 files changed, 81 insertions(+) create mode 100644 packages/safe-chain/src/installation/installOnMacOS.js diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js new file mode 100644 index 0000000..6963291 --- /dev/null +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -0,0 +1,78 @@ +import { tmpdir } from "os"; +import { unlinkSync } from "fs"; +import { join } from "path"; +import { ui } from "../environment/userInteraction.js"; +import { safeSpawn } from "../utils/safeSpawn.js"; +import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; + +const MACOS_SERVICE_LABEL = "com.aikido.SafeChainAgent"; + +export async function installOnMacOS() { + if (!isRunningAsRoot()) { + ui.writeError("Root privileges required."); + ui.writeInformation("Please run this command with sudo:"); + ui.writeInformation(" sudo safe-chain --ultimate"); + return; + } + + const pkgPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.pkg`); + + ui.emptyLine(); + ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`); + ui.writeVerbose(`Destination: ${pkgPath}`); + + const result = await downloadAgentToFile(pkgPath); + if (!result) { + ui.writeError("No download available for this platform/architecture."); + return; + } + + try { + ui.writeInformation("⚙️ Installing SafeChain Ultimate..."); + await runPkgInstaller(pkgPath); + + ui.emptyLine(); + ui.writeInformation( + "✅ SafeChain Ultimate installed and started successfully!", + ); + ui.emptyLine(); + } finally { + ui.writeVerbose(`Cleaning up temporary file: ${pkgPath}`); + cleanup(pkgPath); + } +} + +function isRunningAsRoot() { + const rootUserUid = 0; + return process.getuid?.() === rootUserUid; +} + +/** + * @param {string} pkgPath + */ +async function runPkgInstaller(pkgPath) { + ui.writeVerbose(`Running: installer -pkg "${pkgPath}" -target /`); + + const result = await safeSpawn( + "installer", + ["-pkg", pkgPath, "-target", "/"], + { + stdio: "inherit", + }, + ); + + if (result.status !== 0) { + throw new Error(`PKG installer failed (exit code: ${result.status})`); + } +} + +/** + * @param {string} pkgPath + */ +function cleanup(pkgPath) { + try { + unlinkSync(pkgPath); + } catch { + ui.writeVerbose("Failed to clean up temporary installer file."); + } +} diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index 086b6a4..a79a2b1 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -2,6 +2,7 @@ import { platform } from "os"; import { ui } from "../environment/userInteraction.js"; import { initializeCliArguments } from "../config/cliArguments.js"; import { installOnWindows } from "./installOnWindows.js"; +import { installOnMacOS } from "./installOnMacOS.js"; export async function installUltimate() { initializeCliArguments(process.argv); @@ -10,6 +11,8 @@ export async function installUltimate() { if (operatingSystem === "win32") { await installOnWindows(); + } else if (operatingSystem === "darwin") { + await installOnMacOS(); } else { ui.writeInformation( `${operatingSystem} is not supported yet by SafeChain's ultimate version.`, From b9aade2da40082c95c992f02427fc161be95307a Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 21 Jan 2026 09:18:26 +0100 Subject: [PATCH 48/97] Remove unused variable --- packages/safe-chain/src/installation/installOnMacOS.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 6963291..b2a953a 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -5,8 +5,6 @@ import { ui } from "../environment/userInteraction.js"; import { safeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; -const MACOS_SERVICE_LABEL = "com.aikido.SafeChainAgent"; - export async function installOnMacOS() { if (!isRunningAsRoot()) { ui.writeError("Root privileges required."); From 9cde77a408bcfc97f474ec1c213321ec03a4d612 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 08:20:45 +0100 Subject: [PATCH 49/97] PR comments --- .../src/installation/downloadAgent.js | 18 +++++----- .../src/installation/installOnMacOS.js | 10 ++++-- .../src/installation/installOnWindows.js | 34 ++++++++++++++----- packages/safe-chain/src/utils/safeSpawn.js | 16 +++++++++ 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index 0ee994e..2f2baac 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -3,31 +3,31 @@ import { createHash } from "crypto"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; -const ULTIMATE_VERSION = "v0.2.0"; +const ULTIMATE_VERSION = "v0.2.1"; const DOWNLOAD_URLS = { win32: { x64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-windows-amd64.msi", + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-amd64.msi`, checksum: - "sha256:bba5deb250ebc6008f1cb33fa4209d2455a2f47fa99f0a40e3babef64939ac77", + "sha256:8d86a44d314746099ba50cfae0cc1eae6232522deb348b226da92aae12754eec", }, arm64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-windows-arm64.msi", + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-arm64.msi`, checksum: - "sha256:9553ed15d5efed4185b990a1b86af0b11c23f11d96f8ce04e16b6b98aaf0506e", + "sha256:ab5b8335cc257d53424f73d6681920875083cd9b3f53e52d944bf867a415e027", }, }, darwin: { x64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-darwin-amd64.pkg", + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-darwin-amd64.pkg`, checksum: - "sha256:cbccf32e987a45bc8cc20b620f7b597ff7f9c2f966c2bc21132349612ddb619f", + "sha256:73f83d9352c4fd25f7693d9e53bbbb2b7ac70d16217d745495c9efb50dc4a3a6", }, arm64: { - url: "https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.2-macos-release-artifact/SafeChainAgent-darwin-arm64.pkg", + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-darwin-arm64.pkg`, checksum: - "sha256:4d53a43a47bf7e8133eb61d306a1fb16348b9ec89c1c825e5f746f4fe847796e", + "sha256:bd419e9c82488539b629b04c97aa1d2dc90e54ff045bd7277a6b40d26f8ebc73", }, }, }; diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index b2a953a..0d7081c 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -2,7 +2,7 @@ import { tmpdir } from "os"; import { unlinkSync } from "fs"; import { join } from "path"; import { ui } from "../environment/userInteraction.js"; -import { safeSpawn } from "../utils/safeSpawn.js"; +import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; export async function installOnMacOS() { @@ -49,9 +49,13 @@ function isRunningAsRoot() { * @param {string} pkgPath */ async function runPkgInstaller(pkgPath) { - ui.writeVerbose(`Running: installer -pkg "${pkgPath}" -target /`); + // Uses installer to install the package (https://ss64.com/mac/installer.html) + // Options: + // -pkg (required): The package to be installed. + // -target (required): The target volume is specified with the -target parameter. + // --> "-target /" installs to the current boot volume. - const result = await safeSpawn( + const result = await printVerboseAndSafeSpawn( "installer", ["-pkg", pkgPath, "-target", "/"], { diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 2380a7f..c6bc744 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -3,7 +3,7 @@ import { unlinkSync } from "fs"; import { join } from "path"; import { execSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; -import { safeSpawn } from "../utils/safeSpawn.js"; +import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; const WINDOWS_SERVICE_NAME = "SafeChainAgent"; @@ -87,7 +87,12 @@ async function uninstallIfInstalled() { ui.writeInformation("🗑️ Removing previous installation..."); ui.writeVerbose(`Found product code: ${productCode}`); - const uninstallResult = await safeSpawn( + // Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec) + // Options: + // - /x: Uninstalls the package. + // - /qn: Specifies there's no UI during the installation process. + // - /norestart: Stops the device from restarting after the installation completes. + const uninstallResult = await printVerboseAndSafeSpawn( "msiexec", ["/x", productCode, "/qn", "/norestart"], { stdio: "inherit" }, @@ -102,11 +107,18 @@ async function uninstallIfInstalled() { * @param {string} msiPath */ async function runMsiInstaller(msiPath) { - ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + // Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec) + // Options: + // - /i: Specifies normal installation + // - /qn: Specifies there's no UI during the installation process. - const result = await safeSpawn("msiexec", ["/i", msiPath, "/qn"], { - stdio: "inherit", - }); + const result = await printVerboseAndSafeSpawn( + "msiexec", + ["/i", msiPath, "/qn"], + { + stdio: "inherit", + }, + ); if (result.status !== 0) { throw new Error(`MSI installer failed (exit code: ${result.status})`); @@ -116,9 +128,13 @@ async function runMsiInstaller(msiPath) { async function stopServiceIfRunning() { ui.writeInformation("⏹️ Stopping running service..."); - const result = await safeSpawn("net", ["stop", WINDOWS_SERVICE_NAME], { - stdio: "pipe", - }); + const result = await printVerboseAndSafeSpawn( + "net", + ["stop", WINDOWS_SERVICE_NAME], + { + stdio: "pipe", + }, + ); if (result.status !== 0) { ui.writeVerbose("Service not running (will start after installation)."); diff --git a/packages/safe-chain/src/utils/safeSpawn.js b/packages/safe-chain/src/utils/safeSpawn.js index e17bdb5..69c827a 100644 --- a/packages/safe-chain/src/utils/safeSpawn.js +++ b/packages/safe-chain/src/utils/safeSpawn.js @@ -1,5 +1,6 @@ import { spawn, execSync } from "child_process"; import os from "os"; +import { ui } from "../environment/userInteraction.js"; /** * @param {string} arg @@ -135,3 +136,18 @@ export async function safeSpawn(command, args, options = {}) { }); }); } + +/** + * @param {string} command + * @param {string[]} args + * @param {import("child_process").SpawnOptions} options + * + * @returns {Promise<{status: number, stdout: string, stderr: string}>} + */ +export async function printVerboseAndSafeSpawn(command, args, options = {}) { + ui.writeVerbose(`Running: ${command} ${args.join(" ")}`); + + const result = await safeSpawn(command, args, options); + + return result; +} From b7a5adf67017b455582eba8688b3fc6aaa8154c7 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 09:13:43 +0100 Subject: [PATCH 50/97] Fix linting --- packages/safe-chain/src/installation/installOnMacOS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 0d7081c..074bbe6 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -2,7 +2,7 @@ import { tmpdir } from "os"; import { unlinkSync } from "fs"; import { join } from "path"; import { ui } from "../environment/userInteraction.js"; -import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js"; +import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; export async function installOnMacOS() { From b2d94aaa167b720c78759221e5ebae7a7d3beb10 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 09:18:23 +0100 Subject: [PATCH 51/97] Fix download links --- packages/safe-chain/src/installation/downloadAgent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index 2f2baac..df4a933 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -8,24 +8,24 @@ const ULTIMATE_VERSION = "v0.2.1"; const DOWNLOAD_URLS = { win32: { x64: { - url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-amd64.msi`, + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`, checksum: "sha256:8d86a44d314746099ba50cfae0cc1eae6232522deb348b226da92aae12754eec", }, arm64: { - url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-arm64.msi`, + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-arm64.msi`, checksum: "sha256:ab5b8335cc257d53424f73d6681920875083cd9b3f53e52d944bf867a415e027", }, }, darwin: { x64: { - url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-darwin-amd64.pkg`, + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-amd64.pkg`, checksum: "sha256:73f83d9352c4fd25f7693d9e53bbbb2b7ac70d16217d745495c9efb50dc4a3a6", }, arm64: { - url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-darwin-arm64.pkg`, + url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-arm64.pkg`, checksum: "sha256:bd419e9c82488539b629b04c97aa1d2dc90e54ff045bd7277a6b40d26f8ebc73", }, From 09730a07752173ec5d10296b59ee0641f417bb56 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 09:23:17 +0100 Subject: [PATCH 52/97] Update application names on Windows --- packages/safe-chain/src/installation/installOnWindows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index c6bc744..0741fb7 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -6,8 +6,8 @@ import { ui } from "../environment/userInteraction.js"; import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; -const WINDOWS_SERVICE_NAME = "SafeChainAgent"; -const WINDOWS_APP_NAME = "SafeChain Agent"; +const WINDOWS_SERVICE_NAME = "SafeChainUltimate"; +const WINDOWS_APP_NAME = "SafeChain Ultimate"; export async function installOnWindows() { if (!(await isRunningAsAdmin())) { From c02d0785fac707816d10f81efb9dbd5fe06a9cf9 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 11:58:52 +0100 Subject: [PATCH 53/97] Fix tests for mitm registryproxy --- .../registryProxy/registryProxy.mitm.spec.js | 132 ++++++++++++++---- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js b/packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js index df4332e..407aa3c 100644 --- a/packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js +++ b/packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js @@ -2,12 +2,17 @@ import { before, after, describe, it } from "node:test"; import assert from "node:assert"; import net from "net"; import tls from "tls"; +import { gunzipSync } from "zlib"; import { createSafeChainProxy, mergeSafeChainProxyEnvironmentVariables, } from "./registryProxy.js"; import { getCaCertPath } from "./certUtils.js"; -import { setEcoSystem, ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js"; +import { + setEcoSystem, + ECOSYSTEM_JS, + ECOSYSTEM_PY, +} from "../config/settings.js"; import fs from "fs"; describe("registryProxy.mitm", () => { @@ -33,7 +38,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash" + "/lodash", ); assert.strictEqual(response.statusCode, 200); @@ -45,7 +50,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash/-/lodash-4.17.21.tgz" + "/lodash/-/lodash-4.17.21.tgz", ); // Should get a response (200 or redirect, but not 403 blocked) @@ -57,7 +62,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/this-package-definitely-does-not-exist-12345" + "/this-package-definitely-does-not-exist-12345", ); assert.strictEqual(response.statusCode, 404); @@ -68,7 +73,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash?write=true" + "/lodash?write=true", ); assert.strictEqual(response.statusCode, 200); @@ -79,7 +84,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.yarnpkg.com", - "/lodash" + "/lodash", ); assert.strictEqual(response.statusCode, 200); @@ -90,7 +95,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash" + "/lodash", ); // Check certificate common name matches the target hostname @@ -109,14 +114,14 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash" + "/lodash", ); const { cert: cert2 } = await makeRegistryRequestAndGetCert( proxyHost, proxyPort, "registry.yarnpkg.com", - "/lodash" + "/lodash", ); // Different hostnames should have different certificates @@ -130,14 +135,14 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "registry.npmjs.org", - "/lodash" + "/lodash", ); const { cert: cert2 } = await makeRegistryRequestAndGetCert( proxyHost, proxyPort, "registry.npmjs.org", - "/package/lodash" + "/package/lodash", ); // Same hostname should get the same certificate (fingerprint) @@ -159,7 +164,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "pypi.org", - "/packages/source/f/foo_bar/foo_bar-2.0.0.tar.gz" + "/packages/source/f/foo_bar/foo_bar-2.0.0.tar.gz", ); assert.notStrictEqual(response.statusCode, 403); assert.ok(typeof response.body === "string"); @@ -172,7 +177,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "files.pythonhosted.org", - "/packages/xx/yy/foo_bar-2.0.0-py3-none-any.whl" + "/packages/xx/yy/foo_bar-2.0.0-py3-none-any.whl", ); assert.notStrictEqual(response.statusCode, 403); assert.ok(typeof response.body === "string"); @@ -185,7 +190,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "pypi.org", - "/packages/source/f/foo_bar/foo_bar-2.0.0a1.tar.gz" + "/packages/source/f/foo_bar/foo_bar-2.0.0a1.tar.gz", ); assert.notStrictEqual(response.statusCode, 403); assert.ok(typeof response.body === "string"); @@ -198,7 +203,7 @@ describe("registryProxy.mitm", () => { proxyHost, proxyPort, "pypi.org", - "/packages/source/f/foo_bar/foo_bar-latest.tar.gz" + "/packages/source/f/foo_bar/foo_bar-latest.tar.gz", ); assert.notStrictEqual(response.statusCode, 403); assert.ok(typeof response.body === "string"); @@ -234,34 +239,73 @@ async function makeRegistryRequest(proxyHost, proxyPort, targetHost, path) { }); // Step 4: Send HTTP request over TLS - const httpRequest = `GET ${path} HTTP/1.1\r\nHost: ${targetHost}\r\nConnection: close\r\n\r\n`; + const httpRequest = `GET ${path} HTTP/1.1\r\nHost: ${targetHost}\r\nConnection: close\r\nAccept-encoding: gzip\r\n\r\n`; tlsSocket.write(httpRequest); - // Step 5: Read response + // Step 5: Read response as binary chunks return new Promise((resolve, reject) => { - let data = ""; + const chunks = []; tlsSocket.on("data", (chunk) => { - data += chunk.toString(); + chunks.push(chunk); }); tlsSocket.on("end", () => { - const lines = data.split("\r\n"); - const statusLine = lines[0]; + const buffer = Buffer.concat(chunks); + + // Find the header/body separator (\r\n\r\n) in binary + const separator = Buffer.from("\r\n\r\n"); + let separatorIndex = buffer.indexOf(separator); + if (separatorIndex === -1) { + return reject( + new Error("Invalid HTTP response: no header/body separator"), + ); + } + + // Extract headers as text + const headersText = buffer.subarray(0, separatorIndex).toString("utf8"); + const headerLines = headersText.split("\r\n"); + const statusLine = headerLines[0]; const statusCode = parseInt(statusLine.split(" ")[1]); - // Find body after empty line - const emptyLineIndex = lines.findIndex(line => line === ""); - const body = lines.slice(emptyLineIndex + 1).join("\r\n"); + // Parse headers into object + const headers = {}; + for (let i = 1; i < headerLines.length; i++) { + const colonIndex = headerLines[i].indexOf(":"); + if (colonIndex > 0) { + const key = headerLines[i].substring(0, colonIndex).toLowerCase(); + const value = headerLines[i].substring(colonIndex + 1).trim(); + headers[key] = value; + } + } - resolve({ statusCode, body }); + // Extract body as binary + let bodyBuffer = buffer.subarray(separatorIndex + separator.length); + + // Decode chunked transfer encoding if present + if (headers["transfer-encoding"] === "chunked") { + bodyBuffer = decodeChunked(bodyBuffer); + } + + // Decompress if gzip encoded + if (headers["content-encoding"] === "gzip" && bodyBuffer.length > 0) { + bodyBuffer = gunzipSync(bodyBuffer); + } + + const body = bodyBuffer.toString("utf8"); + resolve({ statusCode, body, headers }); }); tlsSocket.on("error", reject); }); } -async function makeRegistryRequestAndGetCert(proxyHost, proxyPort, targetHost, path) { +async function makeRegistryRequestAndGetCert( + proxyHost, + proxyPort, + targetHost, + path, +) { // Step 1: Connect to proxy const socket = await new Promise((resolve, reject) => { const sock = net.connect({ host: proxyHost, port: proxyPort }, () => { @@ -311,7 +355,7 @@ async function makeRegistryRequestAndGetCert(proxyHost, proxyPort, targetHost, p const statusCode = parseInt(statusLine.split(" ")[1]); // Find body after empty line - const emptyLineIndex = lines.findIndex(line => line === ""); + const emptyLineIndex = lines.findIndex((line) => line === ""); const body = lines.slice(emptyLineIndex + 1).join("\r\n"); resolve({ statusCode, body }); @@ -322,3 +366,37 @@ async function makeRegistryRequestAndGetCert(proxyHost, proxyPort, targetHost, p return { cert: peerCert, response }; } + +/** + * Decode HTTP chunked transfer encoding + * Format: \r\n\r\n ... 0\r\n\r\n + * @param {Buffer} buffer + * @returns {Buffer} + */ +function decodeChunked(buffer) { + const chunks = []; + let offset = 0; + + while (offset < buffer.length) { + // Find the end of the chunk size line + const lineEnd = buffer.indexOf(Buffer.from("\r\n"), offset); + if (lineEnd === -1) break; + + // Parse chunk size (hex) + const sizeHex = buffer.subarray(offset, lineEnd).toString("utf8"); + const chunkSize = parseInt(sizeHex, 16); + + // End of chunks + if (chunkSize === 0) break; + + // Extract chunk data + const dataStart = lineEnd + 2; + const dataEnd = dataStart + chunkSize; + chunks.push(buffer.subarray(dataStart, dataEnd)); + + // Move past chunk data and trailing \r\n + offset = dataEnd + 2; + } + + return Buffer.concat(chunks); +} From f825f84faa88f6e578663674368fc06fe0ccac6e Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 22 Jan 2026 12:51:25 +0100 Subject: [PATCH 54/97] Add message about the certificate popup --- .../safe-chain/src/installation/installOnMacOS.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 074bbe6..0d475b1 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -4,6 +4,7 @@ import { join } from "path"; import { ui } from "../environment/userInteraction.js"; import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; +import chalk from "chalk"; export async function installOnMacOS() { if (!isRunningAsRoot()) { @@ -34,6 +35,17 @@ export async function installOnMacOS() { "✅ SafeChain Ultimate installed and started successfully!", ); ui.emptyLine(); + ui.writeInformation( + chalk.cyan("🔐 ") + + chalk.bold("ACTION REQUIRED: ") + + "macOS will show a popup to install our certificate.", + ); + ui.writeInformation( + " " + + chalk.bold("Please accept the certificate") + + " to complete the installation.", + ); + ui.emptyLine(); } finally { ui.writeVerbose(`Cleaning up temporary file: ${pkgPath}`); cleanup(pkgPath); From 309d7df050c636da03ef04757e1f056635710c12 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 07:42:36 +0100 Subject: [PATCH 55/97] Don't insert empty line in rc file when it already ends with an empty line --- packages/safe-chain/src/shell-integration/helpers.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 064aca1..3e71d71 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -99,7 +99,7 @@ export const knownAikidoTools = [ aikidoCommand: "aikido-pipx", ecoSystem: ECOSYSTEM_PY, internalPackageManagerName: "pipx", - } + }, // When adding a new tool here, also update the documentation for the new tool in the README.md ]; @@ -216,7 +216,13 @@ export function addLineToFile(filePath, line, eol) { eol = eol || os.EOL; const fileContent = fs.readFileSync(filePath, "utf-8"); - const updatedContent = fileContent + eol + line + eol; + let updatedContent = fileContent; + + if (!fileContent.endsWith(eol)) { + updatedContent += eol; + } + + updatedContent += line + eol; fs.writeFileSync(filePath, updatedContent, "utf-8"); } From 1058630dd1773b2e2281d5e1e2370d6061d7b0cd Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 11:29:19 +0100 Subject: [PATCH 56/97] Add uninstallation process for ultimate --- packages/safe-chain/bin/safe-chain.js | 14 ++- .../src/installation/installOnMacOS.js | 86 ++++++++++++++++++- .../src/installation/installOnWindows.js | 59 +++++++++++-- .../src/installation/installUltimate.js | 20 ++++- 4 files changed, 167 insertions(+), 12 deletions(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index d048ce1..06add7e 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -16,7 +16,10 @@ import path from "path"; import { fileURLToPath } from "url"; import fs from "fs"; import { knownAikidoTools } from "../src/shell-integration/helpers.js"; -import { installUltimate } from "../src/installation/installUltimate.js"; +import { + installUltimate, + uninstallUltimate, +} from "../src/installation/installUltimate.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -67,6 +70,10 @@ if (tool) { (async () => { await installUltimate(); })(); +} else if (command === "--uninstall-ultimate") { + (async () => { + await uninstallUltimate(); + })(); } else if (command === "teardown") { teardownDirectories(); teardown(); @@ -108,6 +115,11 @@ function writeHelp() { "safe-chain --ultimate", )}: This installs the ultimate version of safe-chain, enabling protection for more eco-systems (vscode).`, ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain --uninstall-ultimate", + )}: This uninstalls the ultimate version of safe-chain.`, + ); ui.writeInformation( `- ${chalk.cyan( "safe-chain teardown", diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 0d475b1..b2d39ce 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -1,11 +1,14 @@ import { tmpdir } from "os"; -import { unlinkSync } from "fs"; +import { unlinkSync, rmSync } from "fs"; import { join } from "path"; +import { execSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; import chalk from "chalk"; +const MACOS_PKG_IDENTIFIER = "com.aikidosecurity.safechainultimate"; + export async function installOnMacOS() { if (!isRunningAsRoot()) { ui.writeError("Root privileges required."); @@ -52,6 +55,87 @@ export async function installOnMacOS() { } } +export async function uninstallOnMacOS() { + if (!isRunningAsRoot()) { + ui.writeError("Root privileges required."); + ui.writeInformation("Please run this command with sudo:"); + ui.writeInformation(" sudo safe-chain --uninstall-ultimate"); + return; + } + + ui.emptyLine(); + + if (!isPackageInstalled()) { + ui.writeInformation("SafeChain Ultimate is not installed."); + return; + } + + ui.writeInformation("⏹️ Stopping service..."); + await stopService(); + + ui.writeInformation("🗑️ Removing installed files..."); + removeKnownFiles(); + + ui.writeInformation("🧹 Forgetting package receipt..."); + forgetPackage(); + + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Ultimate has been uninstalled."); + ui.emptyLine(); +} + +function isPackageInstalled() { + try { + const output = execSync(`pkgutil --pkg-info ${MACOS_PKG_IDENTIFIER}`, { + encoding: "utf8", + stdio: "pipe", + }); + return output.includes(MACOS_PKG_IDENTIFIER); + } catch { + return false; + } +} + +async function stopService() { + const result = await printVerboseAndSafeSpawn( + "launchctl", + ["bootout", `system/${MACOS_PKG_IDENTIFIER}`], + { stdio: "pipe" }, + ); + + if (result.status !== 0) { + ui.writeVerbose("Service not running (will continue with uninstall)."); + } +} + +const MACOS_KNOWN_PATHS = [ + "/Library/Application Support/AikidoSecurity/SafeChainUltimate", + "/Library/Logs/AikidoSecurity/SafeChainUltimate", + `/Library/LaunchDaemons/${MACOS_PKG_IDENTIFIER}.plist`, +]; + +function removeKnownFiles() { + for (const filePath of MACOS_KNOWN_PATHS) { + try { + rmSync(filePath, { recursive: true, force: true }); + ui.writeVerbose(`Removed: ${filePath}`); + } catch { + ui.writeVerbose(`Failed to remove: ${filePath}`); + } + } +} + +function forgetPackage() { + try { + execSync(`pkgutil --forget ${MACOS_PKG_IDENTIFIER}`, { + encoding: "utf8", + stdio: "pipe", + }); + } catch { + ui.writeVerbose("Failed to forget package receipt."); + } +} + function isRunningAsRoot() { const rootUserUid = 0; return process.getuid?.() === rootUserUid; diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 0741fb7..16bf2b7 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -9,6 +9,34 @@ import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; const WINDOWS_SERVICE_NAME = "SafeChainUltimate"; const WINDOWS_APP_NAME = "SafeChain Ultimate"; +export async function uninstallOnWindows() { + if (!(await isRunningAsAdmin())) { + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator).", + ); + return; + } + + ui.emptyLine(); + + const productCode = getInstalledProductCode(); + if (!productCode) { + ui.writeInformation("SafeChain Ultimate is not installed."); + return; + } + + ui.writeInformation("⏹️ Stopping running service..."); + await stopServiceIfRunning(); + + ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate..."); + await uninstallByProductCode(productCode); + + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Ultimate has been uninstalled."); + ui.emptyLine(); +} + export async function installOnWindows() { if (!(await isRunningAsAdmin())) { ui.writeError("Administrator privileges required."); @@ -64,7 +92,11 @@ async function isRunningAsAdmin() { return result.status === 0 && result.stdout.trim() === "True"; } -async function uninstallIfInstalled() { +/** + * Returns the MSI product code for SafeChain Ultimate, or null if not installed. + * @returns {string | null} + */ +function getInstalledProductCode() { // Query Win32_Product via WMI to find the installed SafeChain Agent. // If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall. ui.writeVerbose(`Finding product code with PowerShell`); @@ -76,15 +108,15 @@ async function uninstallIfInstalled() { { encoding: "utf8" }, ).trim(); } catch { - ui.writeVerbose("No existing installation found (fresh install)."); - return; - } - if (!productCode) { - ui.writeVerbose("No existing installation found (fresh install)."); - return; + return null; } + return productCode || null; +} - ui.writeInformation("🗑️ Removing previous installation..."); +/** + * @param {string} productCode + */ +async function uninstallByProductCode(productCode) { ui.writeVerbose(`Found product code: ${productCode}`); // Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec) @@ -103,6 +135,17 @@ async function uninstallIfInstalled() { } } +async function uninstallIfInstalled() { + const productCode = getInstalledProductCode(); + if (!productCode) { + ui.writeVerbose("No existing installation found (fresh install)."); + return; + } + + ui.writeInformation("🗑️ Removing previous installation..."); + await uninstallByProductCode(productCode); +} + /** * @param {string} msiPath */ diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index a79a2b1..cfcdcca 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -1,8 +1,24 @@ import { platform } from "os"; import { ui } from "../environment/userInteraction.js"; import { initializeCliArguments } from "../config/cliArguments.js"; -import { installOnWindows } from "./installOnWindows.js"; -import { installOnMacOS } from "./installOnMacOS.js"; +import { installOnWindows, uninstallOnWindows } from "./installOnWindows.js"; +import { installOnMacOS, uninstallOnMacOS } from "./installOnMacOS.js"; + +export async function uninstallUltimate() { + initializeCliArguments(process.argv); + + const operatingSystem = platform(); + + if (operatingSystem === "win32") { + await uninstallOnWindows(); + } else if (operatingSystem === "darwin") { + await uninstallOnMacOS(); + } else { + ui.writeInformation( + `Uninstall is not yet supported on ${operatingSystem}.`, + ); + } +} export async function installUltimate() { initializeCliArguments(process.argv); From af4bbb10fcd0b24d168a90680bb8522a88235235 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 12:41:11 +0100 Subject: [PATCH 57/97] Bump safe-chain-internals version --- packages/safe-chain/src/installation/downloadAgent.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index df4a933..4d076ee 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -3,31 +3,31 @@ import { createHash } from "crypto"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; -const ULTIMATE_VERSION = "v0.2.1"; +const ULTIMATE_VERSION = "v0.2.2"; const DOWNLOAD_URLS = { win32: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`, checksum: - "sha256:8d86a44d314746099ba50cfae0cc1eae6232522deb348b226da92aae12754eec", + "sha256:82d6939579c23c357d0f6d368001a5ac8dc66ce13d32ee1700467555ee97e10a", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-arm64.msi`, checksum: - "sha256:ab5b8335cc257d53424f73d6681920875083cd9b3f53e52d944bf867a415e027", + "sha256:d626da40e3d0c4e02a36e6c7e309f18f0ffde64e97a4f2fefd4b25722842ac19", }, }, darwin: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-amd64.pkg`, checksum: - "sha256:73f83d9352c4fd25f7693d9e53bbbb2b7ac70d16217d745495c9efb50dc4a3a6", + "sha256:d7c31914deff8b332bf3d0e18ed00660e47ace87f06f22606c7866f7e0809507", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-arm64.pkg`, checksum: - "sha256:bd419e9c82488539b629b04c97aa1d2dc90e54ff045bd7277a6b40d26f8ebc73", + "sha256:73b092689e00c98e3c376afa50fc3477cedfd01445a113d42b36c5fcd956a6f4", }, }, }; From 12caa6d1d43c9d684bee1bcb467300861e12da85 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 12:44:47 +0100 Subject: [PATCH 58/97] Verify download links in a test --- .../src/installation/downloadAgent.js | 4 +- .../src/installation/downloadAgent.spec.js | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 packages/safe-chain/src/installation/downloadAgent.spec.js diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index 4d076ee..cb2f84b 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -5,7 +5,7 @@ import fetch from "make-fetch-happen"; const ULTIMATE_VERSION = "v0.2.2"; -const DOWNLOAD_URLS = { +export const DOWNLOAD_URLS = { win32: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`, @@ -87,7 +87,7 @@ export function getDownloadInfoForCurrentPlatform() { * @param {string} expectedChecksum - Format: "algorithm:hash" (e.g., "sha256:abc123...") * @returns {Promise} */ -async function verifyChecksum(filePath, expectedChecksum) { +export async function verifyChecksum(filePath, expectedChecksum) { const [algorithm, expected] = expectedChecksum.split(":"); const hash = createHash(algorithm); diff --git a/packages/safe-chain/src/installation/downloadAgent.spec.js b/packages/safe-chain/src/installation/downloadAgent.spec.js new file mode 100644 index 0000000..17aecb9 --- /dev/null +++ b/packages/safe-chain/src/installation/downloadAgent.spec.js @@ -0,0 +1,45 @@ +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 { + DOWNLOAD_URLS, + downloadFile, + verifyChecksum, +} from "./downloadAgent.js"; + +describe("downloadAgent checksums", { timeout: 120_000 }, () => { + const downloadedFiles = []; + + after(() => { + for (const file of downloadedFiles) { + try { + unlinkSync(file); + } catch { + // ignore cleanup errors + } + } + }); + + 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})` + ); + }); + } + } +}); From a016483057a4605e094c94ace3f0f9c1d1c1ec2d Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 12:57:40 +0100 Subject: [PATCH 59/97] Remove duplicate "Stopping running service" log --- packages/safe-chain/src/installation/installOnWindows.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index 16bf2b7..f20bd9b 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -26,7 +26,6 @@ export async function uninstallOnWindows() { return; } - ui.writeInformation("⏹️ Stopping running service..."); await stopServiceIfRunning(); ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate..."); From 7218d778cfabb98ad2698c6f50fce4c3541d1da5 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Tue, 27 Jan 2026 13:06:17 +0100 Subject: [PATCH 60/97] Update commands for ultimate --- packages/safe-chain/bin/safe-chain.js | 44 +++++++++++-------- .../src/installation/installOnMacOS.js | 4 +- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 06add7e..9a07657 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -66,14 +66,17 @@ if (tool) { process.exit(0); } else if (command === "setup") { setup(); -} else if (command === "--ultimate") { - (async () => { - await installUltimate(); - })(); -} else if (command === "--uninstall-ultimate") { - (async () => { - await uninstallUltimate(); - })(); +} else if (command === "ultimate") { + const subCommand = process.argv[3]; + if (subCommand === "uninstall") { + (async () => { + await uninstallUltimate(); + })(); + } else { + (async () => { + await installUltimate(); + })(); + } } else if (command === "teardown") { teardownDirectories(); teardown(); @@ -100,7 +103,7 @@ function writeHelp() { ui.writeInformation( `Available commands: ${chalk.cyan("setup")}, ${chalk.cyan( "teardown", - )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan( + )}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("ultimate")}, ${chalk.cyan("help")}, ${chalk.cyan( "--version", )}`, ); @@ -110,16 +113,6 @@ function writeHelp() { "safe-chain setup", )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`, ); - ui.writeInformation( - `- ${chalk.cyan( - "safe-chain --ultimate", - )}: This installs the ultimate version of safe-chain, enabling protection for more eco-systems (vscode).`, - ); - ui.writeInformation( - `- ${chalk.cyan( - "safe-chain --uninstall-ultimate", - )}: This uninstalls the ultimate version of safe-chain.`, - ); ui.writeInformation( `- ${chalk.cyan( "safe-chain teardown", @@ -136,6 +129,19 @@ function writeHelp() { )}): Display the current version of safe-chain.`, ); ui.emptyLine(); + ui.writeInformation(chalk.bold("Ultimate commands:")); + ui.emptyLine(); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain ultimate", + )}: Install the ultimate version of safe-chain, enabling protection for more eco-systems.`, + ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain ultimate uninstall", + )}: Uninstall the ultimate version of safe-chain.`, + ); + ui.emptyLine(); } async function getVersion() { diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index b2d39ce..018b911 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -13,7 +13,7 @@ export async function installOnMacOS() { if (!isRunningAsRoot()) { ui.writeError("Root privileges required."); ui.writeInformation("Please run this command with sudo:"); - ui.writeInformation(" sudo safe-chain --ultimate"); + ui.writeInformation(" sudo safe-chain ultimate"); return; } @@ -59,7 +59,7 @@ export async function uninstallOnMacOS() { if (!isRunningAsRoot()) { ui.writeError("Root privileges required."); ui.writeInformation("Please run this command with sudo:"); - ui.writeInformation(" sudo safe-chain --uninstall-ultimate"); + ui.writeInformation(" sudo safe-chain ultimate uninstall"); return; } From a3ab80b8b44cd232bbbeb36c849561a38ce56d59 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 28 Jan 2026 07:53:39 +0100 Subject: [PATCH 61/97] PR comment: extract requireRootPrivileges / requireAdminPrivileges into separate function --- .../src/installation/installOnMacOS.js | 36 ++++++++++++------- .../src/installation/installOnWindows.js | 28 +++++++++------ 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 018b911..21f8f1d 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -9,11 +9,29 @@ import chalk from "chalk"; const MACOS_PKG_IDENTIFIER = "com.aikidosecurity.safechainultimate"; +/** + * Checks if root privileges are available and displays error message if not. + * @param {string} command - The sudo command to show in the error message + * @returns {boolean} True if running as root, false otherwise. + */ +function requireRootPrivileges(command) { + if (isRunningAsRoot()) { + return true; + } + + ui.writeError("Root privileges required."); + ui.writeInformation("Please run this command with sudo:"); + ui.writeInformation(` ${command}`); + return false; +} + +function isRunningAsRoot() { + const rootUserUid = 0; + return process.getuid?.() === rootUserUid; +} + export async function installOnMacOS() { - if (!isRunningAsRoot()) { - ui.writeError("Root privileges required."); - ui.writeInformation("Please run this command with sudo:"); - ui.writeInformation(" sudo safe-chain ultimate"); + if (!requireRootPrivileges("sudo safe-chain ultimate")) { return; } @@ -56,10 +74,7 @@ export async function installOnMacOS() { } export async function uninstallOnMacOS() { - if (!isRunningAsRoot()) { - ui.writeError("Root privileges required."); - ui.writeInformation("Please run this command with sudo:"); - ui.writeInformation(" sudo safe-chain ultimate uninstall"); + if (!requireRootPrivileges("sudo safe-chain ultimate uninstall")) { return; } @@ -136,11 +151,6 @@ function forgetPackage() { } } -function isRunningAsRoot() { - const rootUserUid = 0; - return process.getuid?.() === rootUserUid; -} - /** * @param {string} pkgPath */ diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js index f20bd9b..4cee911 100644 --- a/packages/safe-chain/src/installation/installOnWindows.js +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -10,11 +10,7 @@ const WINDOWS_SERVICE_NAME = "SafeChainUltimate"; const WINDOWS_APP_NAME = "SafeChain Ultimate"; export async function uninstallOnWindows() { - if (!(await isRunningAsAdmin())) { - ui.writeError("Administrator privileges required."); - ui.writeInformation( - "Please run this command in an elevated terminal (Run as Administrator).", - ); + if (!(await requireAdminPrivileges())) { return; } @@ -37,11 +33,7 @@ export async function uninstallOnWindows() { } export async function installOnWindows() { - if (!(await isRunningAsAdmin())) { - ui.writeError("Administrator privileges required."); - ui.writeInformation( - "Please run this command in an elevated terminal (Run as Administrator).", - ); + if (!(await requireAdminPrivileges())) { return; } @@ -76,6 +68,22 @@ export async function installOnWindows() { } } +/** + * Checks if admin privileges are available and displays error message if not. + * @returns {Promise} True if running as admin, false otherwise. + */ +async function requireAdminPrivileges() { + if (await isRunningAsAdmin()) { + return true; + } + + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator).", + ); + return false; +} + async function isRunningAsAdmin() { // Uses Windows Security API to check if current process has admin privileges. // Returns "True" or "False" as a string. From 57c090c3a773857480d997b4995116e2b3324981 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 28 Jan 2026 07:54:35 +0100 Subject: [PATCH 62/97] Rename output --- packages/safe-chain/src/installation/installOnMacOS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index 21f8f1d..ab20a8a 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -88,7 +88,7 @@ export async function uninstallOnMacOS() { ui.writeInformation("⏹️ Stopping service..."); await stopService(); - ui.writeInformation("🗑️ Removing installed files..."); + ui.writeInformation("🗑️ Removing files..."); removeKnownFiles(); ui.writeInformation("🧹 Forgetting package receipt..."); From aa6553716d056fe58a1a2d55fbbb33c98bc1f39f Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 28 Jan 2026 15:33:45 +0100 Subject: [PATCH 63/97] Mac: use uninstaller script --- .../src/installation/downloadAgent.js | 10 +-- .../src/installation/installOnMacOS.js | 65 +++++-------------- 2 files changed, 22 insertions(+), 53 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index cb2f84b..a5dcb0d 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -3,31 +3,31 @@ import { createHash } from "crypto"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; -const ULTIMATE_VERSION = "v0.2.2"; +const ULTIMATE_VERSION = "v0.2.3"; export const DOWNLOAD_URLS = { win32: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`, checksum: - "sha256:82d6939579c23c357d0f6d368001a5ac8dc66ce13d32ee1700467555ee97e10a", + "sha256:bd196ae05b876588f828a57c4d19b3e7ad96ba40007cf2b36693dc6e792d28cc", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-arm64.msi`, checksum: - "sha256:d626da40e3d0c4e02a36e6c7e309f18f0ffde64e97a4f2fefd4b25722842ac19", + "sha256:79e046f24405e869494291e77c6d8640c8dc58d2ac1db87d3038e9eb8afbdc8b", }, }, darwin: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-amd64.pkg`, checksum: - "sha256:d7c31914deff8b332bf3d0e18ed00660e47ace87f06f22606c7866f7e0809507", + "sha256:99868cb663eef44d063d995d2dcc063f55b10eb719ee945d05fe8cf5fef5e2a5", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-arm64.pkg`, checksum: - "sha256:73b092689e00c98e3c376afa50fc3477cedfd01445a113d42b36c5fcd956a6f4", + "sha256:000b334c2eb85d8692be5d23af73f8b9fb686c9db726992223187b341ea79306", }, }, }; diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index ab20a8a..ae4fea3 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -1,7 +1,7 @@ import { tmpdir } from "os"; -import { unlinkSync, rmSync } from "fs"; +import { unlinkSync } from "fs"; import { join } from "path"; -import { execSync } from "child_process"; +import { execSync, spawnSync } from "child_process"; import { ui } from "../environment/userInteraction.js"; import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js"; import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js"; @@ -73,6 +73,9 @@ export async function installOnMacOS() { } } +const MACOS_UNINSTALL_SCRIPT = + "/Library/Application Support/AikidoSecurity/SafeChainUltimate/scripts/uninstall"; + export async function uninstallOnMacOS() { if (!requireRootPrivileges("sudo safe-chain ultimate uninstall")) { return; @@ -85,14 +88,20 @@ export async function uninstallOnMacOS() { return; } - ui.writeInformation("⏹️ Stopping service..."); - await stopService(); + ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate..."); + ui.writeVerbose(`Running: ${MACOS_UNINSTALL_SCRIPT}`); - ui.writeInformation("🗑️ Removing files..."); - removeKnownFiles(); + const result = spawnSync(MACOS_UNINSTALL_SCRIPT, { + stdio: "inherit", + shell: true, + }); - ui.writeInformation("🧹 Forgetting package receipt..."); - forgetPackage(); + if (result.status !== 0) { + ui.writeError( + `Uninstall script failed (exit code: ${result.status}). Please try again or remove manually.`, + ); + return; + } ui.emptyLine(); ui.writeInformation("✅ SafeChain Ultimate has been uninstalled."); @@ -111,46 +120,6 @@ function isPackageInstalled() { } } -async function stopService() { - const result = await printVerboseAndSafeSpawn( - "launchctl", - ["bootout", `system/${MACOS_PKG_IDENTIFIER}`], - { stdio: "pipe" }, - ); - - if (result.status !== 0) { - ui.writeVerbose("Service not running (will continue with uninstall)."); - } -} - -const MACOS_KNOWN_PATHS = [ - "/Library/Application Support/AikidoSecurity/SafeChainUltimate", - "/Library/Logs/AikidoSecurity/SafeChainUltimate", - `/Library/LaunchDaemons/${MACOS_PKG_IDENTIFIER}.plist`, -]; - -function removeKnownFiles() { - for (const filePath of MACOS_KNOWN_PATHS) { - try { - rmSync(filePath, { recursive: true, force: true }); - ui.writeVerbose(`Removed: ${filePath}`); - } catch { - ui.writeVerbose(`Failed to remove: ${filePath}`); - } - } -} - -function forgetPackage() { - try { - execSync(`pkgutil --forget ${MACOS_PKG_IDENTIFIER}`, { - encoding: "utf8", - stdio: "pipe", - }); - } catch { - ui.writeVerbose("Failed to forget package receipt."); - } -} - /** * @param {string} pkgPath */ From e36b7e80b427b55d2444cb704894c7fd9dfe577c Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 28 Jan 2026 15:42:15 +0100 Subject: [PATCH 64/97] Fix uninstall script --- packages/safe-chain/src/installation/installOnMacOS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/installation/installOnMacOS.js b/packages/safe-chain/src/installation/installOnMacOS.js index ae4fea3..22ce1a8 100644 --- a/packages/safe-chain/src/installation/installOnMacOS.js +++ b/packages/safe-chain/src/installation/installOnMacOS.js @@ -74,7 +74,7 @@ export async function installOnMacOS() { } const MACOS_UNINSTALL_SCRIPT = - "/Library/Application Support/AikidoSecurity/SafeChainUltimate/scripts/uninstall"; + "/Library/Application\\ Support/AikidoSecurity/SafeChainUltimate/scripts/uninstall"; export async function uninstallOnMacOS() { if (!requireRootPrivileges("sudo safe-chain ultimate uninstall")) { From 4ccdd9fef6ba2df1dff75d81404fbf2a3e9ed014 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 29 Jan 2026 17:06:39 +0100 Subject: [PATCH 65/97] Bump agent version to v1.0.0 --- packages/safe-chain/src/installation/downloadAgent.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js index a5dcb0d..297908a 100644 --- a/packages/safe-chain/src/installation/downloadAgent.js +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -3,31 +3,31 @@ import { createHash } from "crypto"; import { pipeline } from "stream/promises"; import fetch from "make-fetch-happen"; -const ULTIMATE_VERSION = "v0.2.3"; +const ULTIMATE_VERSION = "v1.0.0"; export const DOWNLOAD_URLS = { win32: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`, checksum: - "sha256:bd196ae05b876588f828a57c4d19b3e7ad96ba40007cf2b36693dc6e792d28cc", + "sha256:c6a36f9b8e55ab6b7e8742cbabc4469d85809237c0f5e6c21af20b36c416ee1d", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-arm64.msi`, checksum: - "sha256:79e046f24405e869494291e77c6d8640c8dc58d2ac1db87d3038e9eb8afbdc8b", + "sha256:46acd1af6a9938ea194c8ee8b34ca9b47c8de22e088a0791f3c0751dd6239c90", }, }, darwin: { x64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-amd64.pkg`, checksum: - "sha256:99868cb663eef44d063d995d2dcc063f55b10eb719ee945d05fe8cf5fef5e2a5", + "sha256:bb1829e8ca422e885baf37bef08dcbe7df7a30f248e2e89c4071564f7d4f3396", }, arm64: { url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-arm64.pkg`, checksum: - "sha256:000b334c2eb85d8692be5d23af73f8b9fb686c9db726992223187b341ea79306", + "sha256:7fe4a785709911cc366d8224b4c290677573b8c4833bd9054768299e55c5f0ed", }, }, }; From 632b3948e3214938c3b9186cc78a6cadd86d7e14 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Fri, 30 Jan 2026 13:57:39 +0100 Subject: [PATCH 66/97] Add troubleshooting steps for powershell when executionpolicy doens't allow to run code --- docs/troubleshooting.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0cd6098..0b2845b 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -149,6 +149,37 @@ Should include `~/.safe-chain/bin` **If persists:** Re-run the installation script +### PowerShell Execution Policy Blocks Scripts (Windows) + +**Symptom:** When opening PowerShell, you see an error like: + +``` +. : File C:\Users\\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 cannot be loaded because +running scripts is disabled on this system. +CategoryInfo : SecurityError: (:) [], PSSecurityException +FullyQualifiedErrorId : UnauthorizedAccess +``` + +**Cause:** Windows PowerShell's default execution policy (`Restricted`) blocks all script execution, including safe-chain's initialization script that's sourced from your PowerShell profile. + +**Resolution:** + +1. **Set the execution policy to allow local scripts:** + + Open PowerShell as Administrator and run: + + ```powershell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned + ``` + + This allows: + - Local scripts (like safe-chain's) to run without signing + - Downloaded scripts to run only if signed by a trusted publisher + +2. **Restart PowerShell** and verify the error is resolved. + +> **Note:** `RemoteSigned` is Microsoft's recommended execution policy for client computers. It provides a good balance between security and usability. + ### Shell Aliases Persist After Uninstallation **Symptom:** safe-chain commands still active after running uninstall script From dfac510c15d103f73f98669a656cbb0d1c0d3dad Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 14:15:00 +0100 Subject: [PATCH 67/97] add safe-chain ultimate logs --- packages/safe-chain/bin/safe-chain.js | 5 ++ .../src/ultimate/printUltimateLogs.js | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 packages/safe-chain/src/ultimate/printUltimateLogs.js diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 9a07657..6ecdabd 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -20,6 +20,7 @@ import { installUltimate, uninstallUltimate, } from "../src/installation/installUltimate.js"; +import { printUltimateLogs } from "../src/ultimate/printUltimateLogs.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -72,6 +73,10 @@ if (tool) { (async () => { await uninstallUltimate(); })(); + } else if (subCommand === "logs") { + (async () => { + await printUltimateLogs(); + })(); } else { (async () => { await installUltimate(); diff --git a/packages/safe-chain/src/ultimate/printUltimateLogs.js b/packages/safe-chain/src/ultimate/printUltimateLogs.js new file mode 100644 index 0000000..65a978e --- /dev/null +++ b/packages/safe-chain/src/ultimate/printUltimateLogs.js @@ -0,0 +1,69 @@ +// @ts-nocheck +import { platform } from 'os'; +import { ui } from "../environment/userInteraction.js"; +import { readFileSync, existsSync } from "node:fs"; + +export async function printUltimateLogs() { + const { proxyLogPath, ultimateLogPath, proxyErrLogPath, ultimateErrLogPath } = getPathsPerPlatform(); + + await printLogs( + "SafeChain Proxy", + proxyLogPath, + proxyErrLogPath + ); + + await printLogs( + "SafeChain Ultimate", + ultimateLogPath, + ultimateErrLogPath + ); +} + +function getPathsPerPlatform() { + const os = platform(); + if (os === 'win32') { + const logDir = `C:\\ProgramData\\AikidoSecurity\\SafeChainUltimate\\logs`; + return { + proxyLogPath: `${logDir}\\SafeChainProxy.log`, + ultimateLogPath: `${logDir}\\SafeChainUltimate.log`, + proxyErrLogPath: `${logDir}\\SafeChainProxy.err`, + ultimateErrLogPath: `${logDir}\\SafeChainUltimate.err`, + }; + } else if (os === 'darwin') { + const logDir = `/Library/Logs/AikidoSecurity/SafeChainUltimate`; + return { + proxyLogPath: `${logDir}/safechain-proxy.log`, + ultimateLogPath: `${logDir}/safechain-ultimate.log`, + proxyErrLogPath: `${logDir}/safechain-proxy.error.log`, + ultimateErrLogPath: `${logDir}/safechain-ultimate.error.log`, + }; + } else { + throw new Error('Unsupported platform for log printing.'); + } +} + +async function printLogs(appName, logPath, errLogPath) { + ui.writeInformation(`=== ${appName} Logs ===`); + try { + if (existsSync(logPath)) { + const logs = readFileSync(logPath, "utf-8"); + ui.writeInformation(logs); + } else { + ui.writeWarning(`${appName} log file not found: ${logPath}`); + } + } catch (error) { + ui.writeError(`Failed to read ${appName} logs: ${error.message}`); + } + + ui.writeInformation(`=== ${appName} Error Logs ===`); + try { + if (existsSync(errLogPath)) { + const errLogs = readFileSync(errLogPath, "utf-8"); + ui.writeInformation(errLogs); + } else { + ui.writeInformation(`No error log file found for ${appName}.`); + } + } catch (error) { + ui.writeError(`Failed to read ${appName} error logs: ${error.message}`); + } +} From 4c29eb3549905ce066c4a5c1eeadaa9b027a5fd1 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 14:22:21 +0100 Subject: [PATCH 68/97] install archiver --- package-lock.json | 923 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 7 +- 2 files changed, 902 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index c852d4f..9ca91f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "packages/*", "test/e2e" ], + "dependencies": { + "archiver": "^7.0.1" + }, "devDependencies": { "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", @@ -555,6 +558,102 @@ "node": "20 || >=22" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -748,6 +847,16 @@ "win32" ] }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -919,6 +1028,18 @@ "node": ">= 6" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -932,7 +1053,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -942,7 +1062,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -954,6 +1073,243 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/archiver/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -964,7 +1320,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -975,11 +1330,16 @@ } } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, "node_modules/bare-events": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "dev": true, "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -1076,7 +1436,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1127,6 +1486,15 @@ "dev": true, "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1152,6 +1520,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/cacache": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", @@ -1233,7 +1610,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1246,7 +1622,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1261,13 +1636,205 @@ "node": ">= 0.8" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/compress-commons/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/compress-commons/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/crc32-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1354,11 +1921,16 @@ "readable-stream": "^2.0.2" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encoding": { @@ -1484,11 +2056,28 @@ "node": ">=6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "bare-events": "^2.7.0" @@ -1508,7 +2097,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, "license": "MIT" }, "node_modules/fdir": { @@ -1529,6 +2117,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -1686,7 +2290,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-symbols": { @@ -1789,7 +2392,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -1819,7 +2421,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -1877,19 +2478,50 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1925,6 +2557,24 @@ ], "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -2281,6 +2931,15 @@ "nan": "^2.17.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-package-arg": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", @@ -2381,6 +3040,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2512,11 +3186,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/progress": { @@ -2580,7 +3262,6 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -2592,6 +3273,27 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2636,7 +3338,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/safer-buffer": { @@ -2658,6 +3359,39 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -2769,7 +3503,6 @@ "version": "2.23.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -2781,7 +3514,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -2791,7 +3523,21 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -2806,7 +3552,19 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -2874,7 +3632,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -2896,7 +3653,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" @@ -3010,7 +3766,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/validate-npm-package-name": { @@ -3040,6 +3795,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3058,6 +3828,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3110,6 +3898,89 @@ "node": ">=10" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/zip-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/zip-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "packages/safe-chain": { "name": "@aikidosec/safe-chain", "version": "1.0.0", diff --git a/package.json b/package.json index 2793f9c..864ac73 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,11 @@ "author": "Aikido Security", "license": "AGPL-3.0-or-later", "devDependencies": { - "oxlint": "^1.22.0", + "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", - "@yao-pkg/pkg": "6.10.1" + "oxlint": "^1.22.0" + }, + "dependencies": { + "archiver": "^7.0.1" } } From 460be68cd3646a348002e937089d8d40a9ea2c28 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 14:22:47 +0100 Subject: [PATCH 69/97] create an export async collectLogs --- .../src/ultimate/printUltimateLogs.js | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/safe-chain/src/ultimate/printUltimateLogs.js b/packages/safe-chain/src/ultimate/printUltimateLogs.js index 65a978e..c8e5403 100644 --- a/packages/safe-chain/src/ultimate/printUltimateLogs.js +++ b/packages/safe-chain/src/ultimate/printUltimateLogs.js @@ -1,7 +1,9 @@ -// @ts-nocheck import { platform } from 'os'; import { ui } from "../environment/userInteraction.js"; import { readFileSync, existsSync } from "node:fs"; +import {randomUUID} from "node:crypto"; +import {createWriteStream} from "fs"; +import archiver from 'archiver'; export async function printUltimateLogs() { const { proxyLogPath, ultimateLogPath, proxyErrLogPath, ultimateErrLogPath } = getPathsPerPlatform(); @@ -19,11 +21,44 @@ export async function printUltimateLogs() { ); } +export async function collectLogs() { + const { logDir } = getPathsPerPlatform(); + return new Promise((resolve, reject) => { + if (!existsSync(logDir)) { + ui.writeError(`Log directory not found: ${logDir}`); + reject(new Error(`Log directory not found: ${logDir}`)); + return; + } + + const date = new Date().toISOString().split('T')[0]; + const uuid = randomUUID(); + const zipFileName = `safechain-ultimate-${date}-${uuid}.zip`; + const output = createWriteStream(zipFileName); + const archive = archiver('zip', { zlib: { level: 9 } }); + + output.on('close', () => { + ui.writeInformation(`Logs collected and zipped as: ${zipFileName}`); + resolve(zipFileName); + }); + + archive.on('error', (err) => { + ui.writeError(`Failed to zip logs: ${err.message}`); + reject(err); + }); + + archive.pipe(output); + archive.directory(logDir, false); + archive.finalize(); + }); +} + + function getPathsPerPlatform() { const os = platform(); if (os === 'win32') { const logDir = `C:\\ProgramData\\AikidoSecurity\\SafeChainUltimate\\logs`; return { + logDir, proxyLogPath: `${logDir}\\SafeChainProxy.log`, ultimateLogPath: `${logDir}\\SafeChainUltimate.log`, proxyErrLogPath: `${logDir}\\SafeChainProxy.err`, @@ -32,6 +67,7 @@ function getPathsPerPlatform() { } else if (os === 'darwin') { const logDir = `/Library/Logs/AikidoSecurity/SafeChainUltimate`; return { + logDir, proxyLogPath: `${logDir}/safechain-proxy.log`, ultimateLogPath: `${logDir}/safechain-ultimate.log`, proxyErrLogPath: `${logDir}/safechain-proxy.error.log`, From 5ab5fee130240580ec113e07e85f89c8b73151cf Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 14:25:20 +0100 Subject: [PATCH 70/97] add docs & collect-logs to safe-chain bin --- packages/safe-chain/bin/safe-chain.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 6ecdabd..770362b 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -20,7 +20,10 @@ import { installUltimate, uninstallUltimate, } from "../src/installation/installUltimate.js"; -import { printUltimateLogs } from "../src/ultimate/printUltimateLogs.js"; +import { + collectLogs, + printUltimateLogs +} from "../src/ultimate/printUltimateLogs.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -77,6 +80,10 @@ if (tool) { (async () => { await printUltimateLogs(); })(); + } else if (subCommand === "collect-logs") { + (async () => { + await collectLogs(); + })(); } else { (async () => { await installUltimate(); @@ -141,6 +148,16 @@ function writeHelp() { "safe-chain ultimate", )}: Install the ultimate version of safe-chain, enabling protection for more eco-systems.`, ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain ultimate logs", + )}: Prints standard and error logs for safe-chain ultimate and it's proxy.`, + ); + ui.writeInformation( + `- ${chalk.cyan( + "safe-chain ultimate collect-logs", + )}: Creates a zip archive of safe-chain ultimate logs that can be shared with support.`, + ); ui.writeInformation( `- ${chalk.cyan( "safe-chain ultimate uninstall", From adc384dd7842bb06f3be55bc7b7a39977c8c3b5e Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 14:26:26 +0100 Subject: [PATCH 71/97] use path.resolve to print full file --- packages/safe-chain/src/ultimate/printUltimateLogs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/safe-chain/src/ultimate/printUltimateLogs.js b/packages/safe-chain/src/ultimate/printUltimateLogs.js index c8e5403..a11c9f7 100644 --- a/packages/safe-chain/src/ultimate/printUltimateLogs.js +++ b/packages/safe-chain/src/ultimate/printUltimateLogs.js @@ -4,6 +4,7 @@ import { readFileSync, existsSync } from "node:fs"; import {randomUUID} from "node:crypto"; import {createWriteStream} from "fs"; import archiver from 'archiver'; +import path from "node:path"; export async function printUltimateLogs() { const { proxyLogPath, ultimateLogPath, proxyErrLogPath, ultimateErrLogPath } = getPathsPerPlatform(); @@ -37,7 +38,7 @@ export async function collectLogs() { const archive = archiver('zip', { zlib: { level: 9 } }); output.on('close', () => { - ui.writeInformation(`Logs collected and zipped as: ${zipFileName}`); + ui.writeInformation(`Logs collected and zipped as: ${path.resolve(zipFileName)}`); resolve(zipFileName); }); From ef057626359dd19140978c6582111b3f5c456c57 Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Fri, 30 Jan 2026 14:42:43 +0100 Subject: [PATCH 72/97] add 'archiver' types --- package-lock.json | 21 +++++++++++++++++++++ package.json | 1 + 2 files changed, 22 insertions(+) diff --git a/package-lock.json b/package-lock.json index 9ca91f2..4b20556 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "archiver": "^7.0.1" }, "devDependencies": { + "@types/archiver": "^7.0.0", "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", "oxlint": "^1.22.0" @@ -857,6 +858,16 @@ "node": ">=14" } }, + "node_modules/@types/archiver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -931,6 +942,16 @@ "@types/node": "*" } }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", diff --git a/package.json b/package.json index 864ac73..818539e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "Aikido Security", "license": "AGPL-3.0-or-later", "devDependencies": { + "@types/archiver": "^7.0.0", "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", "oxlint": "^1.22.0" From 38b7c51985ff3166ceac6423d7c752aa16540ba1 Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Fri, 30 Jan 2026 14:44:26 +0100 Subject: [PATCH 73/97] Cleanup linting errors --- packages/safe-chain/src/ultimate/printUltimateLogs.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/ultimate/printUltimateLogs.js b/packages/safe-chain/src/ultimate/printUltimateLogs.js index a11c9f7..2fe432b 100644 --- a/packages/safe-chain/src/ultimate/printUltimateLogs.js +++ b/packages/safe-chain/src/ultimate/printUltimateLogs.js @@ -79,6 +79,11 @@ function getPathsPerPlatform() { } } +/** + * @param {string} appName + * @param {string} logPath + * @param {string} errLogPath + */ async function printLogs(appName, logPath, errLogPath) { ui.writeInformation(`=== ${appName} Logs ===`); try { @@ -89,7 +94,7 @@ async function printLogs(appName, logPath, errLogPath) { ui.writeWarning(`${appName} log file not found: ${logPath}`); } } catch (error) { - ui.writeError(`Failed to read ${appName} logs: ${error.message}`); + ui.writeError(`Failed to read ${appName} logs: ${error}`); } ui.writeInformation(`=== ${appName} Error Logs ===`); @@ -101,6 +106,6 @@ async function printLogs(appName, logPath, errLogPath) { ui.writeInformation(`No error log file found for ${appName}.`); } } catch (error) { - ui.writeError(`Failed to read ${appName} error logs: ${error.message}`); + ui.writeError(`Failed to read ${appName} error logs: ${error}`); } } From adcf609066c3408fedd20588d1da2db3574e00aa Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Fri, 30 Jan 2026 15:16:39 +0100 Subject: [PATCH 74/97] rename to troubleshooting-* --- packages/safe-chain/bin/safe-chain.js | 15 ++++++--------- ...UltimateLogs.js => ultimateTroubleshooting.js} | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) rename packages/safe-chain/src/ultimate/{printUltimateLogs.js => ultimateTroubleshooting.js} (98%) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 770362b..b1d66b1 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -20,10 +20,7 @@ import { installUltimate, uninstallUltimate, } from "../src/installation/installUltimate.js"; -import { - collectLogs, - printUltimateLogs -} from "../src/ultimate/printUltimateLogs.js"; +import {printUltimateLogs, troubleshootingExport } from "../src/ultimate/ultimateTroubleshooting.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -76,13 +73,13 @@ if (tool) { (async () => { await uninstallUltimate(); })(); - } else if (subCommand === "logs") { + } else if (subCommand === "troubleshooting-logs") { (async () => { await printUltimateLogs(); })(); - } else if (subCommand === "collect-logs") { + } else if (subCommand === "troubleshooting-export") { (async () => { - await collectLogs(); + await troubleshootingExport(); })(); } else { (async () => { @@ -150,12 +147,12 @@ function writeHelp() { ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain ultimate logs", + "safe-chain ultimate troubleshooting-logs", )}: Prints standard and error logs for safe-chain ultimate and it's proxy.`, ); ui.writeInformation( `- ${chalk.cyan( - "safe-chain ultimate collect-logs", + "safe-chain ultimate troubleshooting-export", )}: Creates a zip archive of safe-chain ultimate logs that can be shared with support.`, ); ui.writeInformation( diff --git a/packages/safe-chain/src/ultimate/printUltimateLogs.js b/packages/safe-chain/src/ultimate/ultimateTroubleshooting.js similarity index 98% rename from packages/safe-chain/src/ultimate/printUltimateLogs.js rename to packages/safe-chain/src/ultimate/ultimateTroubleshooting.js index 2fe432b..e333615 100644 --- a/packages/safe-chain/src/ultimate/printUltimateLogs.js +++ b/packages/safe-chain/src/ultimate/ultimateTroubleshooting.js @@ -22,7 +22,7 @@ export async function printUltimateLogs() { ); } -export async function collectLogs() { +export async function troubleshootingExport() { const { logDir } = getPathsPerPlatform(); return new Promise((resolve, reject) => { if (!existsSync(logDir)) { From 7e35d8df5690c8df48ffd8da47da2eeab8e92d5e Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Fri, 30 Jan 2026 15:19:56 +0100 Subject: [PATCH 75/97] troubleshooting-export: update description --- packages/safe-chain/bin/safe-chain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index b1d66b1..e438e12 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -153,7 +153,7 @@ function writeHelp() { ui.writeInformation( `- ${chalk.cyan( "safe-chain ultimate troubleshooting-export", - )}: Creates a zip archive of safe-chain ultimate logs that can be shared with support.`, + )}: Creates a zip archive of useful data for troubleshooting safe-chain ultimate, that can be shared with our support team.`, ); ui.writeInformation( `- ${chalk.cyan( From ceaf69c27d51da2f36d203cafe6218474ee2ccd9 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 15:47:41 +0100 Subject: [PATCH 76/97] Revert "add 'archiver' types" This reverts commit ef057626359dd19140978c6582111b3f5c456c57. --- package-lock.json | 21 --------------------- package.json | 1 - 2 files changed, 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b20556..9ca91f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "archiver": "^7.0.1" }, "devDependencies": { - "@types/archiver": "^7.0.0", "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", "oxlint": "^1.22.0" @@ -858,16 +857,6 @@ "node": ">=14" } }, - "node_modules/@types/archiver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", - "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/readdir-glob": "*" - } - }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -942,16 +931,6 @@ "@types/node": "*" } }, - "node_modules/@types/readdir-glob": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", - "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/retry": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", diff --git a/package.json b/package.json index 818539e..864ac73 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "author": "Aikido Security", "license": "AGPL-3.0-or-later", "devDependencies": { - "@types/archiver": "^7.0.0", "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", "oxlint": "^1.22.0" From 90a44d999a0757dacd2d70e9eed0487579b51026 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 15:47:49 +0100 Subject: [PATCH 77/97] Revert "install archiver" This reverts commit 4c29eb3549905ce066c4a5c1eeadaa9b027a5fd1. --- package-lock.json | 923 ++-------------------------------------------- package.json | 7 +- 2 files changed, 28 insertions(+), 902 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ca91f2..c852d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,6 @@ "packages/*", "test/e2e" ], - "dependencies": { - "archiver": "^7.0.1" - }, "devDependencies": { "@yao-pkg/pkg": "6.10.1", "esbuild": "^0.27.0", @@ -558,102 +555,6 @@ "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -847,16 +748,6 @@ "win32" ] }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -1028,18 +919,6 @@ "node": ">= 6" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -1053,6 +932,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1062,6 +942,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1073,243 +954,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "license": "MIT", - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "license": "MIT", - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver-utils/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/archiver-utils/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/archiver-utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/archiver-utils/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/archiver/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/archiver/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1320,6 +964,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -1330,16 +975,11 @@ } } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, "node_modules/bare-events": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -1436,6 +1076,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -1486,15 +1127,6 @@ "dev": true, "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1520,15 +1152,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/cacache": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", @@ -1610,6 +1233,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1622,6 +1246,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1636,205 +1261,13 @@ "node": ">= 0.8" } }, - "node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/compress-commons/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/compress-commons/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/compress-commons/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, "license": "MIT" }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/crc32-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/crc32-stream/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/crc32-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1921,16 +1354,11 @@ "readable-stream": "^2.0.2" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encoding": { @@ -2056,28 +1484,11 @@ "node": ">=6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "bare-events": "^2.7.0" @@ -2097,6 +1508,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, "license": "MIT" }, "node_modules/fdir": { @@ -2117,22 +1529,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -2290,6 +1686,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-symbols": { @@ -2392,6 +1789,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -2421,6 +1819,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -2478,50 +1877,19 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -2557,24 +1925,6 @@ ], "license": "MIT" }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -2931,15 +2281,6 @@ "nan": "^2.17.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-package-arg": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", @@ -3040,21 +2381,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3186,19 +2512,11 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, "license": "MIT" }, "node_modules/progress": { @@ -3262,6 +2580,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -3273,27 +2592,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3338,6 +2636,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/safer-buffer": { @@ -3359,39 +2658,6 @@ "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -3503,6 +2769,7 @@ "version": "2.23.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -3514,6 +2781,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -3523,21 +2791,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3552,19 +2806,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3632,6 +2874,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -3653,6 +2896,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" @@ -3766,6 +3010,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/validate-npm-package-name": { @@ -3795,21 +3040,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3828,24 +3058,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3898,89 +3110,6 @@ "node": ">=10" } }, - "node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "license": "MIT", - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/zip-stream/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/zip-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "packages/safe-chain": { "name": "@aikidosec/safe-chain", "version": "1.0.0", diff --git a/package.json b/package.json index 864ac73..2793f9c 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,8 @@ "author": "Aikido Security", "license": "AGPL-3.0-or-later", "devDependencies": { - "@yao-pkg/pkg": "6.10.1", + "oxlint": "^1.22.0", "esbuild": "^0.27.0", - "oxlint": "^1.22.0" - }, - "dependencies": { - "archiver": "^7.0.1" + "@yao-pkg/pkg": "6.10.1" } } From 768de61401937c008f08708d80e5caa853a03354 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 30 Jan 2026 15:48:39 +0100 Subject: [PATCH 78/97] install deps in safe-chain/package.json --- package-lock.json | 942 ++++++++++++++++++++++++++++++- packages/safe-chain/package.json | 2 + 2 files changed, 918 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index c852d4f..ea8c410 100644 --- a/package-lock.json +++ b/package-lock.json @@ -555,6 +555,102 @@ "node": "20 || >=22" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -748,6 +844,26 @@ "win32" ] }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/archiver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, "node_modules/@types/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", @@ -822,6 +938,16 @@ "@types/node": "*" } }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", @@ -919,6 +1045,18 @@ "node": ">= 6" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -932,7 +1070,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -942,7 +1079,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -954,6 +1090,243 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/archiver/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -964,7 +1337,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -975,11 +1347,16 @@ } } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, "node_modules/bare-events": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "dev": true, "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -1076,7 +1453,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1127,6 +1503,15 @@ "dev": true, "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1152,6 +1537,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/cacache": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", @@ -1233,7 +1627,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1246,7 +1639,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1261,13 +1653,205 @@ "node": ">= 0.8" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/compress-commons/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/compress-commons/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/crc32-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1354,11 +1938,16 @@ "readable-stream": "^2.0.2" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encoding": { @@ -1484,11 +2073,28 @@ "node": ">=6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "bare-events": "^2.7.0" @@ -1508,7 +2114,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, "license": "MIT" }, "node_modules/fdir": { @@ -1529,6 +2134,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -1686,7 +2307,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-symbols": { @@ -1789,7 +2409,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -1819,7 +2438,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -1877,19 +2495,50 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1925,6 +2574,24 @@ ], "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -2281,6 +2948,15 @@ "nan": "^2.17.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-package-arg": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", @@ -2381,6 +3057,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2512,11 +3203,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/progress": { @@ -2580,7 +3279,6 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -2592,6 +3290,27 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2636,7 +3355,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/safer-buffer": { @@ -2658,6 +3376,39 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -2769,7 +3520,6 @@ "version": "2.23.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -2781,7 +3531,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -2791,7 +3540,21 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -2806,7 +3569,19 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -2874,7 +3649,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -2896,7 +3670,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" @@ -3010,7 +3783,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/validate-npm-package-name": { @@ -3040,6 +3812,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3058,6 +3845,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3110,11 +3915,95 @@ "node": ">=10" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/zip-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/zip-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "packages/safe-chain": { "name": "@aikidosec/safe-chain", "version": "1.0.0", "license": "AGPL-3.0-or-later", "dependencies": { + "archiver": "^7.0.1", "certifi": "14.5.15", "chalk": "5.4.1", "https-proxy-agent": "7.0.6", @@ -3142,6 +4031,7 @@ "safe-chain": "bin/safe-chain.js" }, "devDependencies": { + "@types/archiver": "^7.0.0", "@types/ini": "^4.1.1", "@types/make-fetch-happen": "^10.0.4", "@types/node": "^18.19.130", diff --git a/packages/safe-chain/package.json b/packages/safe-chain/package.json index 3d527cb..d4f3501 100644 --- a/packages/safe-chain/package.json +++ b/packages/safe-chain/package.json @@ -38,6 +38,7 @@ "license": "AGPL-3.0-or-later", "description": "The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), [bunx](https://bun.sh/docs/cli/bunx), [uv](https://docs.astral.sh/uv/) (Python), and [pip](https://pip.pypa.io/) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, bunx, uv, or pip/pip3 from downloading or running the malware.", "dependencies": { + "archiver": "^7.0.1", "certifi": "14.5.15", "chalk": "5.4.1", "https-proxy-agent": "7.0.6", @@ -48,6 +49,7 @@ "semver": "7.7.2" }, "devDependencies": { + "@types/archiver": "^7.0.0", "@types/ini": "^4.1.1", "@types/make-fetch-happen": "^10.0.4", "@types/node": "^18.19.130", From e9ed6063c3c6a0f49cf435caa095bf380cc188d7 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 2 Feb 2026 15:28:44 +0100 Subject: [PATCH 79/97] Verify the number of arguments for ultimate commands --- packages/safe-chain/bin/safe-chain.js | 37 ++++++++++++++++++- .../src/installation/installUltimate.js | 2 - 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index e438e12..dbefa10 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -20,7 +20,10 @@ import { installUltimate, uninstallUltimate, } from "../src/installation/installUltimate.js"; -import {printUltimateLogs, troubleshootingExport } from "../src/ultimate/ultimateTroubleshooting.js"; +import { + printUltimateLogs, + troubleshootingExport, +} from "../src/ultimate/ultimateTroubleshooting.js"; /** @type {string} */ // This checks the current file's dirname in a way that's compatible with: @@ -68,20 +71,34 @@ if (tool) { } else if (command === "setup") { setup(); } else if (command === "ultimate") { - const subCommand = process.argv[3]; + const cliArgs = initializeCliArguments(process.argv.slice(2)); + const subCommand = cliArgs[1]; if (subCommand === "uninstall") { + guardCliArgsMaxLenght(2, cliArgs, "safe-chain ultimate uninstall"); (async () => { await uninstallUltimate(); })(); } else if (subCommand === "troubleshooting-logs") { + guardCliArgsMaxLenght( + 2, + cliArgs, + "safe-chain ultimate troubleshooting-logs", + ); (async () => { await printUltimateLogs(); })(); } else if (subCommand === "troubleshooting-export") { + guardCliArgsMaxLenght( + 2, + cliArgs, + "safe-chain ultimate troubleshooting-export", + ); (async () => { await troubleshootingExport(); })(); } else { + guardCliArgsMaxLenght(1, cliArgs, "safe-chain ultimate"); + // Install command = when no subcommand is provided (safe-chain ultimate) (async () => { await installUltimate(); })(); @@ -104,6 +121,22 @@ if (tool) { process.exit(1); } +/** + * @param {Number} maxLength + * @param {String[]} args + * @param {String} command + */ +function guardCliArgsMaxLenght(maxLength, args, command) { + if (args.length > maxLength) { + ui.writeError(`Unexpected number of arguments for command ${command}.`); + ui.emptyLine(); + + writeHelp(); + + process.exit(1); + } +} + function writeHelp() { ui.writeInformation( chalk.bold("Usage: ") + chalk.cyan("safe-chain "), diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index cfcdcca..257c953 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -21,8 +21,6 @@ export async function uninstallUltimate() { } export async function installUltimate() { - initializeCliArguments(process.argv); - const operatingSystem = platform(); if (operatingSystem === "win32") { From 90eba0a0b66aa6fe031fbd2c87b4762656df7c9a Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 4 Feb 2026 14:04:46 +0100 Subject: [PATCH 80/97] Document CI/CD for GitLab --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 128d662..4973573 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ You can find all available versions on the [releases page](https://github.com/Ai ### Verify the installation 1. **❗Restart your terminal** to start using the Aikido Safe Chain. - - This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm, pnpx, bun, bunx, pip, pip3, poetry, uv and pipx are loaded correctly. If you do not restart your terminal, the aliases will not be available. 2. **Verify the installation** by running the verification command: @@ -159,7 +158,6 @@ You can control the output from Aikido Safe Chain using the `--safe-chain-loggin You can set the logging level through multiple sources (in order of priority): 1. **CLI Argument** (highest priority): - - `--safe-chain-logging=silent` - Suppresses all Aikido Safe Chain output except when malware is blocked. The package manager output is written to stdout as normal, and Safe Chain only writes a short message if it has blocked malware and causes the process to exit. ```shell @@ -288,6 +286,7 @@ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download - ✅ **CircleCI** - ✅ **Jenkins** - ✅ **Bitbucket Pipelines** +- ✅ **GitLab Pipelines** ## GitHub Actions Example @@ -386,14 +385,76 @@ steps: - step: name: Install script: - - npm install -g @aikidosec/safe-chain - - safe-chain setup-ci + - curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - export PATH=~/.safe-chain/shims:$PATH - npm ci ``` After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection. +## GitLab Pipelines Example + +To add safe-chain in GitLab pipelines, you need to install it in the image running the pipeline. This can be done by: + +1. Define a dockerfile to run your build + + ```dockerfile + FROM node:lts + + # Install safe-chain + RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci + + # Add safe-chain to PATH + ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}" + ``` + +2. Build the Docker image in your CI pipeline + + ```yaml + build-image: + stage: build-image + image: docker:latest + services: + - docker:dind + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:latest . + - docker push $CI_REGISTRY_IMAGE:latest + ``` + +3. Use the image in your pipeline: + ```yaml + npm-ci: + stage: install + image: $CI_REGISTRY_IMAGE:latest + script: + - npm ci + ``` + +The full pipeline for this example looks like this: + +```yaml +stages: + - build-image + - install + +build-image: + stage: build-image + image: docker:latest + services: + - docker:dind + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:latest . + - docker push $CI_REGISTRY_IMAGE:latest + +npm-ci: + stage: install + image: $CI_REGISTRY_IMAGE:latest + script: + - npm ci +``` + # Troubleshooting Having issues? See the [Troubleshooting Guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md) for help with common problems. From c765438e63e7d175208a779d70cc1836eca128bc Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 4 Feb 2026 16:30:29 +0100 Subject: [PATCH 81/97] Powershell: check if the executionpolicy allow to run safe-chain --- install-scripts/install-safe-chain.ps1 | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/install-scripts/install-safe-chain.ps1 b/install-scripts/install-safe-chain.ps1 index ffe2505..25ef8b7 100644 --- a/install-scripts/install-safe-chain.ps1 +++ b/install-scripts/install-safe-chain.ps1 @@ -31,6 +31,28 @@ function Write-Error-Custom { exit 1 } +# Check if the PowerShell execution policy allows script execution +function Test-ExecutionPolicy { + $policy = Get-ExecutionPolicy + $acceptablePolicies = @('RemoteSigned', 'Unrestricted', 'Bypass') + return $acceptablePolicies -contains $policy +} + + +if (-not (Test-ExecutionPolicy)) { + $currentPolicy = Get-ExecutionPolicy + Write-Error-Custom @" +PowerShell execution policy is set to '$currentPolicy', which prevents safe-chain from running. + +The execution policy must be at least 'RemoteSigned' to allow safe-chain's initialization script to run. + +To fix this, open PowerShell as Administrator and run: + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned + +Then restart this installation. +"@ +} + # Get currently installed version of safe-chain function Get-InstalledVersion { # Check if safe-chain command exists @@ -157,7 +179,8 @@ function Install-SafeChain { Write-Warn "" if ($ci) { Write-Warn " iex `"& { `$(iwr 'https://github.com/AikidoSec/safe-chain/releases/download/$env:SAFE_CHAIN_VERSION/install-safe-chain.ps1' -UseBasicParsing) } -ci`"" - } else { + } + else { Write-Warn " iex (iwr `"https://github.com/AikidoSec/safe-chain/releases/download/$env:SAFE_CHAIN_VERSION/install-safe-chain.ps1`" -UseBasicParsing)" } Write-Warn "" From e9799e283fc74137bf5b246bec38959812af29b9 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 09:49:36 +0100 Subject: [PATCH 82/97] Check powershell execution policy in setup function --- .../src/shell-integration/helpers.js | 33 ++++++++++++++++- .../supported-shells/powershell.js | 11 ++++++ .../supported-shells/powershell.spec.js | 36 +++++++++++++++---- .../supported-shells/windowsPowershell.js | 11 ++++++ .../windowsPowershell.spec.js | 36 +++++++++++++++---- 5 files changed, 114 insertions(+), 13 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 3e71d71..17b527c 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -1,4 +1,4 @@ -import { spawnSync } from "child_process"; +import { spawnSync, execSync } from "child_process"; import * as os from "os"; import fs from "fs"; import path from "path"; @@ -243,3 +243,34 @@ function createFileIfNotExists(filePath) { fs.writeFileSync(filePath, "", "utf-8"); } + +/** + * Checks if PowerShell execution policy allows script execution + * @param {string} shellExecutableName - The name of the PowerShell executable ("pwsh" or "powershell") + * @returns {{isValid: boolean, policy: string}} validation result + */ +export function validatePowerShellExecutionPolicy(shellExecutableName) { + // Security: Only allow known shell executables + const validShells = ["pwsh", "powershell"]; + if (!validShells.includes(shellExecutableName)) { + return { isValid: false, policy: "Unknown" }; + } + + try { + // Security: Use literal command string, no interpolation + const policy = execSync("Get-ExecutionPolicy", { + encoding: "utf8", + shell: shellExecutableName, + timeout: 5000, // 5 second timeout + }).trim(); + + const acceptablePolicies = ["RemoteSigned", "Unrestricted", "Bypass"]; + return { + isValid: acceptablePolicies.includes(policy), + policy: policy, + }; + } catch (/** @type {any} */ error) { + // If we can't check the policy, return false to be safe + return { isValid: false, policy: "Unknown" }; + } +} 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 8cec258..b26a3ff 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -2,6 +2,7 @@ import { addLineToFile, doesExecutableExistOnSystem, removeLinesMatchingPattern, + validatePowerShellExecutionPolicy, } from "../helpers.js"; import { execSync } from "child_process"; @@ -39,6 +40,16 @@ function teardown(tools) { } function setup() { + // Check execution policy + const { isValid, policy } = validatePowerShellExecutionPolicy(executableName); + if (!isValid) { + throw new Error( + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + + `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + + `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows` + ); + } + const startupFile = getStartupFile(); addLineToFile( diff --git a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js index 3a15376..5c93f45 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js @@ -8,14 +8,20 @@ import { knownAikidoTools } from "../helpers.js"; describe("PowerShell Core shell integration", () => { let mockStartupFile; let powershell; + let executionPolicyResult; beforeEach(async () => { // Create temporary startup file for testing mockStartupFile = path.join( tmpdir(), - `test-powershell-profile-${Date.now()}.ps1` + `test-powershell-profile-${Date.now()}.ps1`, ); + executionPolicyResult = { + isValid: true, + policy: "RemoteSigned", + }; + // Mock the helpers module mock.module("../helpers.js", { namedExports: { @@ -33,6 +39,7 @@ describe("PowerShell Core shell integration", () => { const filteredLines = lines.filter((line) => !pattern.test(line)); fs.writeFileSync(filePath, filteredLines.join("\n"), "utf-8"); }, + validatePowerShellExecutionPolicy: () => executionPolicyResult, }, }); @@ -76,8 +83,8 @@ describe("PowerShell Core shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( content.includes( - '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script' - ) + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script', + ), ); }); }); @@ -98,7 +105,7 @@ describe("PowerShell Core shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); assert.ok(content.includes("Set-Alias ls ")); assert.ok(content.includes("Set-Alias grep ")); @@ -173,14 +180,14 @@ describe("PowerShell Core shell integration", () => { powershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); // Teardown powershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); }); @@ -197,4 +204,21 @@ describe("PowerShell Core shell integration", () => { assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); + + describe("execution policy", () => { + it(`should throw for restricted policies`, () => { + executionPolicyResult = { + isValid: false, + policy: "Restricted", + }; + + assert.throws( + () => powershell.setup(), + (err) => + err.message.startsWith( + "PowerShell execution policy is set to 'Restricted'", + ), + ); + }); + }); }); 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 e554a32..cb07e0f 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -2,6 +2,7 @@ import { addLineToFile, doesExecutableExistOnSystem, removeLinesMatchingPattern, + validatePowerShellExecutionPolicy, } from "../helpers.js"; import { execSync } from "child_process"; @@ -39,6 +40,16 @@ function teardown(tools) { } function setup() { + // Check execution policy + const { isValid, policy } = validatePowerShellExecutionPolicy(executableName); + if (!isValid) { + throw new Error( + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + + `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + + `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows` + ); + } + const startupFile = getStartupFile(); addLineToFile( diff --git a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js index c201c60..9a3a696 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -8,14 +8,20 @@ import { knownAikidoTools } from "../helpers.js"; describe("Windows PowerShell shell integration", () => { let mockStartupFile; let windowsPowershell; + let executionPolicyResult; beforeEach(async () => { // Create temporary startup file for testing mockStartupFile = path.join( tmpdir(), - `test-windows-powershell-profile-${Date.now()}.ps1` + `test-windows-powershell-profile-${Date.now()}.ps1`, ); + executionPolicyResult = { + isValid: true, + policy: "RemoteSigned", + }; + // Mock the helpers module mock.module("../helpers.js", { namedExports: { @@ -33,6 +39,7 @@ describe("Windows PowerShell shell integration", () => { const filteredLines = lines.filter((line) => !pattern.test(line)); fs.writeFileSync(filePath, filteredLines.join("\n"), "utf-8"); }, + validatePowerShellExecutionPolicy: () => executionPolicyResult, }, }); @@ -76,8 +83,8 @@ describe("Windows PowerShell shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( content.includes( - '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script' - ) + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script', + ), ); }); }); @@ -98,7 +105,7 @@ describe("Windows PowerShell shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); assert.ok(content.includes("Set-Alias ls ")); assert.ok(content.includes("Set-Alias grep ")); @@ -173,14 +180,14 @@ describe("Windows PowerShell shell integration", () => { windowsPowershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); // Teardown windowsPowershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), ); }); @@ -197,4 +204,21 @@ describe("Windows PowerShell shell integration", () => { assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); + + describe("execution policy", () => { + it(`should throw for restricted policies`, () => { + executionPolicyResult = { + isValid: false, + policy: "Restricted", + }; + + assert.throws( + () => windowsPowershell.setup(), + (err) => + err.message.startsWith( + "PowerShell execution policy is set to 'Restricted'", + ), + ); + }); + }); }); From ff16530314f0951491cc7a656b707ce00eebe80d Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 09:52:18 +0100 Subject: [PATCH 83/97] Fix linting --- packages/safe-chain/src/shell-integration/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 17b527c..044cc07 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -269,7 +269,7 @@ export function validatePowerShellExecutionPolicy(shellExecutableName) { isValid: acceptablePolicies.includes(policy), policy: policy, }; - } catch (/** @type {any} */ error) { + } catch { // If we can't check the policy, return false to be safe return { isValid: false, policy: "Unknown" }; } From ad32a8d9be67ce3b412ad7ea5e033fa9ee6b6607 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 10:05:26 +0100 Subject: [PATCH 84/97] Run command for execution policy with -Command --- packages/safe-chain/src/shell-integration/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 044cc07..3c60ac1 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -258,7 +258,7 @@ export function validatePowerShellExecutionPolicy(shellExecutableName) { try { // Security: Use literal command string, no interpolation - const policy = execSync("Get-ExecutionPolicy", { + const policy = execSync('-Command "Get-ExecutionPolicy"', { encoding: "utf8", shell: shellExecutableName, timeout: 5000, // 5 second timeout From 3e90c0abd115c0584ef6dd6cb971bfe666d89368 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 10:12:43 +0100 Subject: [PATCH 85/97] Import module for execution policy --- .../safe-chain/src/shell-integration/helpers.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 3c60ac1..d243123 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -258,11 +258,15 @@ export function validatePowerShellExecutionPolicy(shellExecutableName) { try { // Security: Use literal command string, no interpolation - const policy = execSync('-Command "Get-ExecutionPolicy"', { - encoding: "utf8", - shell: shellExecutableName, - timeout: 5000, // 5 second timeout - }).trim(); + // Import the Security module first - works for both powershell.exe and pwsh.exe + const policy = execSync( + "Import-Module Microsoft.PowerShell.Security; Get-ExecutionPolicy", + { + encoding: "utf8", + shell: shellExecutableName, + timeout: 5000, // 5 second timeout + } + ).trim(); const acceptablePolicies = ["RemoteSigned", "Unrestricted", "Bypass"]; return { From aa461b27c36b1bbcb13e177abafde2740b6bbd0e Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 10:24:28 +0100 Subject: [PATCH 86/97] Use safeSpawn --- .../src/shell-integration/helpers.js | 23 ++++++------- .../safe-chain/src/shell-integration/setup.js | 32 +++++++++---------- .../src/shell-integration/shellDetection.js | 4 +-- .../supported-shells/powershell.js | 15 +++++---- .../supported-shells/powershell.spec.js | 18 +++++------ .../supported-shells/windowsPowershell.js | 15 +++++---- .../windowsPowershell.spec.js | 18 +++++------ 7 files changed, 62 insertions(+), 63 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index d243123..a3d2f5e 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -1,8 +1,9 @@ -import { spawnSync, execSync } from "child_process"; +import { spawnSync } from "child_process"; import * as os from "os"; import fs from "fs"; import path from "path"; import { ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js"; +import { safeSpawn } from "../utils/safeSpawn.js"; /** * @typedef {Object} AikidoTool @@ -247,9 +248,9 @@ function createFileIfNotExists(filePath) { /** * Checks if PowerShell execution policy allows script execution * @param {string} shellExecutableName - The name of the PowerShell executable ("pwsh" or "powershell") - * @returns {{isValid: boolean, policy: string}} validation result + * @returns {Promise<{isValid: boolean, policy: string}>} validation result */ -export function validatePowerShellExecutionPolicy(shellExecutableName) { +export async function validatePowerShellExecutionPolicy(shellExecutableName) { // Security: Only allow known shell executables const validShells = ["pwsh", "powershell"]; if (!validShells.includes(shellExecutableName)) { @@ -257,16 +258,12 @@ export function validatePowerShellExecutionPolicy(shellExecutableName) { } try { - // Security: Use literal command string, no interpolation - // Import the Security module first - works for both powershell.exe and pwsh.exe - const policy = execSync( - "Import-Module Microsoft.PowerShell.Security; Get-ExecutionPolicy", - { - encoding: "utf8", - shell: shellExecutableName, - timeout: 5000, // 5 second timeout - } - ).trim(); + const commandResult = await safeSpawn(shellExecutableName, [ + "-Command", + "Get-ExecutionPolicy", + ]); + + const policy = commandResult.stdout.trim(); const acceptablePolicies = ["RemoteSigned", "Unrestricted", "Bypass"]; return { diff --git a/packages/safe-chain/src/shell-integration/setup.js b/packages/safe-chain/src/shell-integration/setup.js index 7e64c0b..4138db6 100644 --- a/packages/safe-chain/src/shell-integration/setup.js +++ b/packages/safe-chain/src/shell-integration/setup.js @@ -1,7 +1,11 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { detectShells } from "./shellDetection.js"; -import { knownAikidoTools, getPackageManagerList, getScriptsDir } from "./helpers.js"; +import { + knownAikidoTools, + getPackageManagerList, + getScriptsDir, +} from "./helpers.js"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; @@ -26,7 +30,7 @@ if (import.meta.url) { export async function setup() { ui.writeInformation( chalk.bold("Setting up shell aliases.") + - ` This will wrap safe-chain around ${getPackageManagerList()}.` + ` This will wrap safe-chain around ${getPackageManagerList()}.`, ); ui.emptyLine(); @@ -42,12 +46,12 @@ export async function setup() { ui.writeInformation( `Detected ${shells.length} supported shell(s): ${shells .map((shell) => chalk.bold(shell.name)) - .join(", ")}.` + .join(", ")}.`, ); let updatedCount = 0; for (const shell of shells) { - if (setupShell(shell)) { + if (await setupShell(shell)) { updatedCount++; } } @@ -58,7 +62,7 @@ export async function setup() { } } catch (/** @type {any} */ error) { ui.writeError( - `Failed to set up shell aliases: ${error.message}. Please check your shell configuration.` + `Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`, ); return; } @@ -68,12 +72,12 @@ export async function setup() { * Calls the setup function for the given shell and reports the result. * @param {import("./shellDetection.js").Shell} shell */ -function setupShell(shell) { +async function setupShell(shell) { let success = false; let error; try { shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases - success = shell.setup(knownAikidoTools); + success = await shell.setup(knownAikidoTools); } catch (/** @type {any} */ err) { success = false; error = err; @@ -82,14 +86,14 @@ function setupShell(shell) { if (success) { ui.writeInformation( `${chalk.bold("- " + shell.name + ":")} ${chalk.green( - "Setup successful" - )}` + "Setup successful", + )}`, ); } else { ui.writeError( `${chalk.bold("- " + shell.name + ":")} ${chalk.red( - "Setup failed" - )}. Please check your ${shell.name} configuration.` + "Setup failed", + )}. Please check your ${shell.name} configuration.`, ); if (error) { let message = ` Error: ${error.message}`; @@ -115,11 +119,7 @@ function copyStartupFiles() { } // Use absolute path for source - const sourcePath = path.join( - dirname, - "startup-scripts", - file - ); + const sourcePath = path.join(dirname, "startup-scripts", file); fs.copyFileSync(sourcePath, targetPath); } } diff --git a/packages/safe-chain/src/shell-integration/shellDetection.js b/packages/safe-chain/src/shell-integration/shellDetection.js index 9e0f110..996125c 100644 --- a/packages/safe-chain/src/shell-integration/shellDetection.js +++ b/packages/safe-chain/src/shell-integration/shellDetection.js @@ -9,7 +9,7 @@ import { ui } from "../environment/userInteraction.js"; * @typedef {Object} Shell * @property {string} name * @property {() => boolean} isInstalled - * @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} setup + * @property {(tools: import("./helpers.js").AikidoTool[]) => boolean|Promise} setup * @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} teardown */ @@ -28,7 +28,7 @@ export function detectShells() { } } catch (/** @type {any} */ error) { ui.writeError( - `We were not able to detect which shells are installed on your system. Please check your shell configuration. Error: ${error.message}` + `We were not able to detect which shells are installed on your system. Please check your shell configuration. Error: ${error.message}`, ); return []; } 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 b26a3ff..a169915 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -26,27 +26,28 @@ function teardown(tools) { // Remove any existing alias for the tool removeLinesMatchingPattern( startupFile, - new RegExp(`^Set-Alias\\s+${tool}\\s+`) + new RegExp(`^Set-Alias\\s+${tool}\\s+`), ); } // Remove the line that sources the safe-chain PowerShell initialization script removeLinesMatchingPattern( startupFile, - /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/ + /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/, ); return true; } -function setup() { +async function setup() { // Check execution policy - const { isValid, policy } = validatePowerShellExecutionPolicy(executableName); + const { isValid, policy } = + await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + - `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows` + `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows`, ); } @@ -54,7 +55,7 @@ function setup() { addLineToFile( startupFile, - `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script` + `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script`, ); return true; @@ -68,7 +69,7 @@ function getStartupFile() { }).trim(); } catch (/** @type {any} */ error) { throw new Error( - `Command failed: ${startupFileCommand}. Error: ${error.message}` + `Command failed: ${startupFileCommand}. Error: ${error.message}`, ); } } diff --git a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js index 5c93f45..de2c14b 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js @@ -76,8 +76,8 @@ describe("PowerShell Core shell integration", () => { }); describe("setup", () => { - it("should add init-pwsh.ps1 source line", () => { - const result = powershell.setup(); + it("should add init-pwsh.ps1 source line", async () => { + const result = await powershell.setup(); assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); @@ -175,9 +175,9 @@ describe("PowerShell Core shell integration", () => { }); describe("integration tests", () => { - it("should handle complete setup and teardown cycle", () => { + it("should handle complete setup and teardown cycle", async () => { // Setup - powershell.setup(); + await powershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), @@ -191,10 +191,10 @@ describe("PowerShell Core shell integration", () => { ); }); - it("should handle multiple setup calls", () => { - powershell.setup(); + it("should handle multiple setup calls", async () => { + await powershell.setup(); powershell.teardown(knownAikidoTools); - powershell.setup(); + await powershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); const sourceMatches = ( @@ -206,13 +206,13 @@ describe("PowerShell Core shell integration", () => { }); describe("execution policy", () => { - it(`should throw for restricted policies`, () => { + it(`should throw for restricted policies`, async () => { executionPolicyResult = { isValid: false, policy: "Restricted", }; - assert.throws( + await assert.rejects( () => powershell.setup(), (err) => err.message.startsWith( 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 cb07e0f..acf0830 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -26,27 +26,28 @@ function teardown(tools) { // Remove any existing alias for the tool removeLinesMatchingPattern( startupFile, - new RegExp(`^Set-Alias\\s+${tool}\\s+`) + new RegExp(`^Set-Alias\\s+${tool}\\s+`), ); } // Remove the line that sources the safe-chain PowerShell initialization script removeLinesMatchingPattern( startupFile, - /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/ + /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/, ); return true; } -function setup() { +async function setup() { // Check execution policy - const { isValid, policy } = validatePowerShellExecutionPolicy(executableName); + const { isValid, policy } = + await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + - `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows` + `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows`, ); } @@ -54,7 +55,7 @@ function setup() { addLineToFile( startupFile, - `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script` + `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script`, ); return true; @@ -68,7 +69,7 @@ function getStartupFile() { }).trim(); } catch (/** @type {any} */ error) { throw new Error( - `Command failed: ${startupFileCommand}. Error: ${error.message}` + `Command failed: ${startupFileCommand}. Error: ${error.message}`, ); } } diff --git a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js index 9a3a696..561d0d4 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -76,8 +76,8 @@ describe("Windows PowerShell shell integration", () => { }); describe("setup", () => { - it("should add init-pwsh.ps1 source line", () => { - const result = windowsPowershell.setup(); + it("should add init-pwsh.ps1 source line", async () => { + const result = await windowsPowershell.setup(); assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); @@ -175,9 +175,9 @@ describe("Windows PowerShell shell integration", () => { }); describe("integration tests", () => { - it("should handle complete setup and teardown cycle", () => { + it("should handle complete setup and teardown cycle", async () => { // Setup - windowsPowershell.setup(); + await windowsPowershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'), @@ -191,10 +191,10 @@ describe("Windows PowerShell shell integration", () => { ); }); - it("should handle multiple setup calls", () => { - windowsPowershell.setup(); + it("should handle multiple setup calls", async () => { + await windowsPowershell.setup(); windowsPowershell.teardown(knownAikidoTools); - windowsPowershell.setup(); + await windowsPowershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); const sourceMatches = ( @@ -206,13 +206,13 @@ describe("Windows PowerShell shell integration", () => { }); describe("execution policy", () => { - it(`should throw for restricted policies`, () => { + it(`should throw for restricted policies`, async () => { executionPolicyResult = { isValid: false, policy: "Restricted", }; - assert.throws( + await assert.rejects( () => windowsPowershell.setup(), (err) => err.message.startsWith( From 13f2ae6e2228866dcf69419c4e8f29d0c2153169 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 10:45:13 +0100 Subject: [PATCH 87/97] Fix PSModulePath --- .../src/shell-integration/helpers.js | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index a3d2f5e..23380db 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -258,10 +258,30 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { } try { - const commandResult = await safeSpawn(shellExecutableName, [ - "-Command", - "Get-ExecutionPolicy", - ]); + const spawnOptions = {}; + + // For Windows PowerShell (5.1), clean PSModulePath to avoid conflicts with PowerShell 7 modules + // When PowerShell 7 is installed, it adds its module paths to PSModulePath, causing + // Windows PowerShell to try loading incompatible PowerShell 7 modules (TypeData conflicts) + if (shellExecutableName === "powershell") { + const userProfile = process.env.USERPROFILE || ""; + const cleanPSModulePath = [ + path.join(userProfile, "Documents", "WindowsPowerShell", "Modules"), + "C:\\Program Files\\WindowsPowerShell\\Modules", + "C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules", + ].join(";"); + + spawnOptions.env = { + ...process.env, + PSModulePath: cleanPSModulePath, + }; + } + + const commandResult = await safeSpawn( + shellExecutableName, + ["-Command", "Get-ExecutionPolicy"], + spawnOptions + ); const policy = commandResult.stdout.trim(); From 0dfa151b024da1a32cf88bcc2e11f5399132a967 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 10:45:45 +0100 Subject: [PATCH 88/97] Fix linting --- .../safe-chain/src/shell-integration/helpers.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 23380db..8f1450d 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -258,11 +258,10 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { } try { - const spawnOptions = {}; - // For Windows PowerShell (5.1), clean PSModulePath to avoid conflicts with PowerShell 7 modules // When PowerShell 7 is installed, it adds its module paths to PSModulePath, causing // Windows PowerShell to try loading incompatible PowerShell 7 modules (TypeData conflicts) + let spawnOptions; if (shellExecutableName === "powershell") { const userProfile = process.env.USERPROFILE || ""; const cleanPSModulePath = [ @@ -271,10 +270,14 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { "C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules", ].join(";"); - spawnOptions.env = { - ...process.env, - PSModulePath: cleanPSModulePath, + spawnOptions = { + env: { + ...process.env, + PSModulePath: cleanPSModulePath, + }, }; + } else { + spawnOptions = {}; } const commandResult = await safeSpawn( From f1e5e7bab29c71e862d258a06c3e03a1dbad2d0b Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:01:56 +0100 Subject: [PATCH 89/97] Improve error message --- .../src/shell-integration/supported-shells/powershell.js | 4 +--- .../shell-integration/supported-shells/windowsPowershell.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) 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 a169915..fd2e3dd 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -45,9 +45,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + - `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + - `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows`, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n\nTo fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, ); } 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 acf0830..0a4d282 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -45,9 +45,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running. ` + - `To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. ` + - `For more information, see: https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md#powershell-execution-policy-blocks-scripts-windows`, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n\nTo fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, ); } From bab128ab2663acb9a754d3440f38c09be1b91def Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:03:49 +0100 Subject: [PATCH 90/97] Undo install script changes --- install-scripts/install-safe-chain.ps1 | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/install-scripts/install-safe-chain.ps1 b/install-scripts/install-safe-chain.ps1 index 25ef8b7..ffe2505 100644 --- a/install-scripts/install-safe-chain.ps1 +++ b/install-scripts/install-safe-chain.ps1 @@ -31,28 +31,6 @@ function Write-Error-Custom { exit 1 } -# Check if the PowerShell execution policy allows script execution -function Test-ExecutionPolicy { - $policy = Get-ExecutionPolicy - $acceptablePolicies = @('RemoteSigned', 'Unrestricted', 'Bypass') - return $acceptablePolicies -contains $policy -} - - -if (-not (Test-ExecutionPolicy)) { - $currentPolicy = Get-ExecutionPolicy - Write-Error-Custom @" -PowerShell execution policy is set to '$currentPolicy', which prevents safe-chain from running. - -The execution policy must be at least 'RemoteSigned' to allow safe-chain's initialization script to run. - -To fix this, open PowerShell as Administrator and run: - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned - -Then restart this installation. -"@ -} - # Get currently installed version of safe-chain function Get-InstalledVersion { # Check if safe-chain command exists @@ -179,8 +157,7 @@ function Install-SafeChain { Write-Warn "" if ($ci) { Write-Warn " iex `"& { `$(iwr 'https://github.com/AikidoSec/safe-chain/releases/download/$env:SAFE_CHAIN_VERSION/install-safe-chain.ps1' -UseBasicParsing) } -ci`"" - } - else { + } else { Write-Warn " iex (iwr `"https://github.com/AikidoSec/safe-chain/releases/download/$env:SAFE_CHAIN_VERSION/install-safe-chain.ps1`" -UseBasicParsing)" } Write-Warn "" From 369167e005808d40f499bb05abe873a34ee94201 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:08:04 +0100 Subject: [PATCH 91/97] Error message indentation fix --- .../src/shell-integration/supported-shells/powershell.js | 2 +- .../src/shell-integration/supported-shells/windowsPowershell.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 fd2e3dd..b05b57b 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -45,7 +45,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n\nTo fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, ); } 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 0a4d282..17820e0 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -45,7 +45,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n\nTo fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, ); } From 03d67d92be865bb576e9c35ae24d6045a8e91489 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:09:15 +0100 Subject: [PATCH 92/97] Change teardown order --- packages/safe-chain/bin/safe-chain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index dbefa10..2913d28 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -104,8 +104,8 @@ if (tool) { })(); } } else if (command === "teardown") { - teardownDirectories(); teardown(); + teardownDirectories(); } else if (command === "setup-ci") { setupCi(); } else if (command === "--version" || command === "-v" || command === "-v") { From 149a28e0dc0ac99c92a9e85c2b605478dd86439d Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:20:14 +0100 Subject: [PATCH 93/97] Improve comments --- packages/safe-chain/src/shell-integration/helpers.js | 7 ++++--- .../src/shell-integration/supported-shells/powershell.js | 1 - .../supported-shells/windowsPowershell.js | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 8f1450d..36fa908 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -259,8 +259,9 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { try { // For Windows PowerShell (5.1), clean PSModulePath to avoid conflicts with PowerShell 7 modules - // When PowerShell 7 is installed, it adds its module paths to PSModulePath, causing - // Windows PowerShell to try loading incompatible PowerShell 7 modules (TypeData conflicts) + // When safe-chain is invoked from PowerShell 7, it sets its module paths to PSModulePath, causing + // Windows PowerShell to try loading incompatible PowerShell 7 modules. + // Setting the environment to Windows PowerShell's modules fixes this. let spawnOptions; if (shellExecutableName === "powershell") { const userProfile = process.env.USERPROFILE || ""; @@ -283,7 +284,7 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { const commandResult = await safeSpawn( shellExecutableName, ["-Command", "Get-ExecutionPolicy"], - spawnOptions + spawnOptions, ); const policy = commandResult.stdout.trim(); 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 b05b57b..657548a 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -40,7 +40,6 @@ function teardown(tools) { } async function setup() { - // Check execution policy const { isValid, policy } = await validatePowerShellExecutionPolicy(executableName); if (!isValid) { 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 17820e0..f6f67aa 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -40,7 +40,6 @@ function teardown(tools) { } async function setup() { - // Check execution policy const { isValid, policy } = await validatePowerShellExecutionPolicy(executableName); if (!isValid) { From cab1e11e95b3aea93ffcce17dd53bbafb312e117 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 5 Feb 2026 11:33:37 +0100 Subject: [PATCH 94/97] Remove duplicate verbose logging information from troubleshooting Removed section on enabling verbose logging for diagnostics. --- docs/troubleshooting.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0b2845b..456fe58 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -308,22 +308,6 @@ Look for and remove: rm -rf ~/.safe-chain ``` -## Getting More Information - -### Enable Verbose Logging - -Get detailed diagnostic output using a CLI flag or environment variable: - -```bash -# Using CLI flag -npm install express --safe-chain-logging=verbose -pip install requests --safe-chain-logging=verbose - -# Using environment variable (applies to all commands) -export SAFE_CHAIN_LOGGING=verbose -npm install express -``` - ### Report Issues If you encounter problems: From 446f45cc283c51b8315b6e910269f87166cfa66a Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:35:30 +0100 Subject: [PATCH 95/97] Add link to help --- .../src/shell-integration/supported-shells/powershell.js | 2 +- .../src/shell-integration/supported-shells/windowsPowershell.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 657548a..96eb219 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -44,7 +44,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned.\n For more information, see: https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting#powershell-execution-policy-blocks-scripts-windows`, ); } 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 f6f67aa..2740456 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -44,7 +44,7 @@ async function setup() { await validatePowerShellExecutionPolicy(executableName); if (!isValid) { throw new Error( - `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned. `, + `PowerShell execution policy is set to '${policy}', which prevents safe-chain from running.\n -> To fix this, open PowerShell as Administrator and run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned.\n For more information, see: https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting#powershell-execution-policy-blocks-scripts-windows`, ); } From 8ea4463ac5bf64bb823df36673be6312b572d375 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:38:28 +0100 Subject: [PATCH 96/97] Update troubleshooting link --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 128d662..003921c 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ You can find all available versions on the [releases page](https://github.com/Ai ### Verify the installation 1. **❗Restart your terminal** to start using the Aikido Safe Chain. - - This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm, pnpx, bun, bunx, pip, pip3, poetry, uv and pipx are loaded correctly. If you do not restart your terminal, the aliases will not be available. 2. **Verify the installation** by running the verification command: @@ -159,7 +158,6 @@ You can control the output from Aikido Safe Chain using the `--safe-chain-loggin You can set the logging level through multiple sources (in order of priority): 1. **CLI Argument** (highest priority): - - `--safe-chain-logging=silent` - Suppresses all Aikido Safe Chain output except when malware is blocked. The package manager output is written to stdout as normal, and Safe Chain only writes a short message if it has blocked malware and causes the process to exit. ```shell @@ -396,4 +394,4 @@ After setup, all subsequent package manager commands in your CI pipeline will au # Troubleshooting -Having issues? See the [Troubleshooting Guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md) for help with common problems. +Having issues? See the [Troubleshooting Guide](https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting) for help with common problems. From 87c5eddc9e934834406eef1f8e37e66808643e4f Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:52:06 +0100 Subject: [PATCH 97/97] Write warning when getting executionpolicy fails --- packages/safe-chain/src/shell-integration/helpers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 36fa908..18ba52e 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -4,6 +4,7 @@ import fs from "fs"; import path from "path"; import { ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js"; import { safeSpawn } from "../utils/safeSpawn.js"; +import { ui } from "../environment/userInteraction.js"; /** * @typedef {Object} AikidoTool @@ -294,8 +295,10 @@ export async function validatePowerShellExecutionPolicy(shellExecutableName) { isValid: acceptablePolicies.includes(policy), policy: policy, }; - } catch { - // If we can't check the policy, return false to be safe + } catch (err) { + ui.writeWarning( + `An error happened while trying to find the current executionpolicy in powershell: ${err}`, + ); return { isValid: false, policy: "Unknown" }; } }