From c765438e63e7d175208a779d70cc1836eca128bc Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 4 Feb 2026 16:30:29 +0100 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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 07/16] 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 08/16] 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 09/16] 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 10/16] 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 11/16] 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 12/16] 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 13/16] 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 446f45cc283c51b8315b6e910269f87166cfa66a Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Thu, 5 Feb 2026 11:35:30 +0100 Subject: [PATCH 14/16] 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 15/16] 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 16/16] 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" }; } }