Check powershell execution policy in setup function

This commit is contained in:
Sander Declerck 2026-02-05 09:49:36 +01:00
parent c765438e63
commit e9799e283f
No known key found for this signature in database
5 changed files with 114 additions and 13 deletions

View file

@ -1,4 +1,4 @@
import { spawnSync } from "child_process"; import { spawnSync, execSync } from "child_process";
import * as os from "os"; import * as os from "os";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@ -243,3 +243,34 @@ function createFileIfNotExists(filePath) {
fs.writeFileSync(filePath, "", "utf-8"); 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" };
}
}

View file

@ -2,6 +2,7 @@ import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
removeLinesMatchingPattern, removeLinesMatchingPattern,
validatePowerShellExecutionPolicy,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process"; import { execSync } from "child_process";
@ -39,6 +40,16 @@ function teardown(tools) {
} }
function setup() { 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(); const startupFile = getStartupFile();
addLineToFile( addLineToFile(

View file

@ -8,14 +8,20 @@ import { knownAikidoTools } from "../helpers.js";
describe("PowerShell Core shell integration", () => { describe("PowerShell Core shell integration", () => {
let mockStartupFile; let mockStartupFile;
let powershell; let powershell;
let executionPolicyResult;
beforeEach(async () => { beforeEach(async () => {
// Create temporary startup file for testing // Create temporary startup file for testing
mockStartupFile = path.join( mockStartupFile = path.join(
tmpdir(), tmpdir(),
`test-powershell-profile-${Date.now()}.ps1` `test-powershell-profile-${Date.now()}.ps1`,
); );
executionPolicyResult = {
isValid: true,
policy: "RemoteSigned",
};
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
@ -33,6 +39,7 @@ describe("PowerShell Core shell integration", () => {
const filteredLines = lines.filter((line) => !pattern.test(line)); const filteredLines = lines.filter((line) => !pattern.test(line));
fs.writeFileSync(filePath, filteredLines.join("\n"), "utf-8"); 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"); const content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( assert.ok(
content.includes( 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"); const content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( 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 ls "));
assert.ok(content.includes("Set-Alias grep ")); assert.ok(content.includes("Set-Alias grep "));
@ -173,14 +180,14 @@ describe("PowerShell Core shell integration", () => {
powershell.setup(); powershell.setup();
let content = fs.readFileSync(mockStartupFile, "utf-8"); let content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( assert.ok(
content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'),
); );
// Teardown // Teardown
powershell.teardown(knownAikidoTools); powershell.teardown(knownAikidoTools);
content = fs.readFileSync(mockStartupFile, "utf-8"); content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( 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"); 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'",
),
);
});
});
}); });

View file

@ -2,6 +2,7 @@ import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
removeLinesMatchingPattern, removeLinesMatchingPattern,
validatePowerShellExecutionPolicy,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process"; import { execSync } from "child_process";
@ -39,6 +40,16 @@ function teardown(tools) {
} }
function setup() { 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(); const startupFile = getStartupFile();
addLineToFile( addLineToFile(

View file

@ -8,14 +8,20 @@ import { knownAikidoTools } from "../helpers.js";
describe("Windows PowerShell shell integration", () => { describe("Windows PowerShell shell integration", () => {
let mockStartupFile; let mockStartupFile;
let windowsPowershell; let windowsPowershell;
let executionPolicyResult;
beforeEach(async () => { beforeEach(async () => {
// Create temporary startup file for testing // Create temporary startup file for testing
mockStartupFile = path.join( mockStartupFile = path.join(
tmpdir(), 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 the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
@ -33,6 +39,7 @@ describe("Windows PowerShell shell integration", () => {
const filteredLines = lines.filter((line) => !pattern.test(line)); const filteredLines = lines.filter((line) => !pattern.test(line));
fs.writeFileSync(filePath, filteredLines.join("\n"), "utf-8"); 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"); const content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( assert.ok(
content.includes( 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"); const content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( 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 ls "));
assert.ok(content.includes("Set-Alias grep ")); assert.ok(content.includes("Set-Alias grep "));
@ -173,14 +180,14 @@ describe("Windows PowerShell shell integration", () => {
windowsPowershell.setup(); windowsPowershell.setup();
let content = fs.readFileSync(mockStartupFile, "utf-8"); let content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( assert.ok(
content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"'),
); );
// Teardown // Teardown
windowsPowershell.teardown(knownAikidoTools); windowsPowershell.teardown(knownAikidoTools);
content = fs.readFileSync(mockStartupFile, "utf-8"); content = fs.readFileSync(mockStartupFile, "utf-8");
assert.ok( 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"); 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'",
),
);
});
});
}); });