From 031c9683b1ed71325e9119283206fe324934be63 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Mon, 13 Apr 2026 11:10:16 -0700 Subject: [PATCH] Some more cleanup --- .../supported-shells/bash.js | 27 ++- .../supported-shells/bash.spec.js | 34 ---- .../supported-shells/fish.js | 29 ++- .../supported-shells/fish.spec.js | 33 ---- .../supported-shells/powershell.js | 26 ++- .../supported-shells/powershell.spec.js | 34 ---- .../supported-shells/windowsPowershell.js | 26 ++- .../windowsPowershell.spec.js | 34 ---- .../shell-integration/supported-shells/zsh.js | 27 ++- .../supported-shells/zsh.spec.js | 34 ---- test/e2e/bun.e2e.spec.js | 34 ---- test/e2e/npm-ci.e2e.spec.js | 42 ----- test/e2e/npm.e2e.spec.js | 34 ---- test/e2e/pip-ci.e2e.spec.js | 39 ---- test/e2e/pip.e2e.spec.js | 35 ---- test/e2e/pipx.e2e.spec.js | 34 ---- test/e2e/pnpm-ci.e2e.spec.js | 40 ----- test/e2e/pnpm.e2e.spec.js | 34 ---- test/e2e/poetry.e2e.spec.js | 41 ----- test/e2e/safe-chain-dir.e2e.spec.js | 166 ------------------ test/e2e/uv.e2e.spec.js | 38 ---- test/e2e/yarn-ci.e2e.spec.js | 40 ----- test/e2e/yarn.e2e.spec.js | 38 ---- 23 files changed, 55 insertions(+), 864 deletions(-) delete mode 100644 test/e2e/safe-chain-dir.e2e.spec.js diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.js index fc56025..5e113bd 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.js @@ -34,19 +34,13 @@ function teardown(tools) { ); } - // Marker comment ensures only safe-chain-added lines are removed, not user's own source statements + // Removes the line that sources the safe-chain bash initialization script. removeLinesMatchingPattern( startupFile, /^source\s+.*init-posix\.sh.*#\s*Safe-chain/, eol ); - removeLinesMatchingPattern( - startupFile, - /^export\s+SAFE_CHAIN_DIR=.*#\s*Safe-chain/, - eol - ); - return true; } @@ -131,19 +125,20 @@ function cygpathw(path) { } } -/** @param {string} preamble */ -function buildManualInstructions(preamble) { - const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-posix.sh")}`]; - instructions.push(`Then restart your terminal or run: source ~/.bashrc`); - return instructions; -} - function getManualTeardownInstructions() { - return buildManualInstructions(`Remove the following line from your ~/.bashrc file:`); + return [ + `Remove the following line from your ~/.bashrc file:`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + `Then restart your terminal or run: source ~/.bashrc`, + ]; } function getManualSetupInstructions() { - return buildManualInstructions(`Add the following line to your ~/.bashrc file:`); + return [ + `Add the following line to your ~/.bashrc file:`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + `Then restart your terminal or run: source ~/.bashrc`, + ]; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js index 4eaaa6f..f0a56d2 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js @@ -200,40 +200,6 @@ describe("Bash shell integration", () => { }); }); - describe("custom install dir", () => { - it("writes only the source line to the rc file", () => { - bash.setup(); - - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok( - content.includes("source /test-home/.safe-chain/scripts/init-posix.sh") - ); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("removes legacy export lines on teardown", () => { - const initialContent = [ - '#!/bin/bash', - 'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory', - 'source /test-home/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script', - ].join("\n"); - - fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); - - bash.teardown(knownAikidoTools); - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("shows source-only manual setup instructions", () => { - assert.deepStrictEqual(bash.getManualSetupInstructions(), [ - "Add the following line to your ~/.bashrc file:", - " source /test-home/.safe-chain/scripts/init-posix.sh", - "Then restart your terminal or run: source ~/.bashrc", - ]); - }); - }); - describe("integration tests", () => { it("should handle complete setup and teardown cycle", () => { const tools = [ diff --git a/packages/safe-chain/src/shell-integration/supported-shells/fish.js b/packages/safe-chain/src/shell-integration/supported-shells/fish.js index d5ea308..28323bf 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/fish.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/fish.js @@ -33,19 +33,13 @@ function teardown(tools) { ); } - // Removes the line that sources the safe-chain fish initialization script (any path, requires safe-chain comment) + // Removes the line that sources the safe-chain fish initialization script. removeLinesMatchingPattern( startupFile, /^source\s+.*init-fish\.fish.*#\s*Safe-chain/, eol ); - removeLinesMatchingPattern( - startupFile, - /^set\s+-gx\s+SAFE_CHAIN_DIR\s+.*#\s*Safe-chain/, - eol - ); - return true; } @@ -74,21 +68,20 @@ function getStartupFile() { } } -/** @param {string} preamble */ -function buildManualInstructions(preamble) { - const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-fish.fish")}`]; - instructions.push( - `Then restart your terminal or run: source ~/.config/fish/config.fish`, - ); - return instructions; -} - function getManualTeardownInstructions() { - return buildManualInstructions(`Remove the following line from your ~/.config/fish/config.fish file:`); + return [ + `Remove the following line from your ~/.config/fish/config.fish file:`, + ` source ${path.join(getScriptsDir(), "init-fish.fish")}`, + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ]; } function getManualSetupInstructions() { - return buildManualInstructions(`Add the following line to your ~/.config/fish/config.fish file:`); + return [ + `Add the following line to your ~/.config/fish/config.fish file:`, + ` source ${path.join(getScriptsDir(), "init-fish.fish")}`, + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ]; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js index 9a30f11..0933b6e 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js @@ -153,39 +153,6 @@ describe("Fish shell integration", () => { }); }); - describe("custom install dir", () => { - it("writes only the source line to the config file", () => { - fish.setup(); - - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok( - content.includes("source /test-home/.safe-chain/scripts/init-fish.fish") - ); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("removes legacy set lines on teardown", () => { - const initialContent = [ - 'set -gx SAFE_CHAIN_DIR "/custom/safe-chain" # Safe-chain installation directory', - "source /test-home/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script", - ].join("\n"); - - fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); - - fish.teardown(knownAikidoTools); - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("shows source-only manual setup instructions", () => { - assert.deepStrictEqual(fish.getManualSetupInstructions(), [ - "Add the following line to your ~/.config/fish/config.fish file:", - " source /test-home/.safe-chain/scripts/init-fish.fish", - "Then restart your terminal or run: source ~/.config/fish/config.fish", - ]); - }); - }); - describe("integration tests", () => { it("should handle complete setup and teardown cycle", () => { const tools = [ 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 becc3db..d0f5eed 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -32,17 +32,12 @@ function teardown(tools) { ); } - // Remove the line that sources the safe-chain PowerShell initialization script (any path, requires safe-chain comment) + // Removes the line that sources the safe-chain PowerShell initialization script. removeLinesMatchingPattern( startupFile, /^\.\s+["']?.*init-pwsh\.ps1["']?.*#\s*Safe-chain/, ); - removeLinesMatchingPattern( - startupFile, - /^\$env:SAFE_CHAIN_DIR\s*=.*#\s*Safe-chain/, - ); - return true; } @@ -78,19 +73,20 @@ function getStartupFile() { } } -/** @param {string} preamble */ -function buildManualInstructions(preamble) { - const instructions = [preamble, ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`]; - instructions.push(`Then restart your terminal or run: . $PROFILE`); - return instructions; -} - function getManualTeardownInstructions() { - return buildManualInstructions(`Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`); + return [ + `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + `Then restart your terminal or run: . $PROFILE`, + ]; } function getManualSetupInstructions() { - return buildManualInstructions(`Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`); + return [ + `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + `Then restart your terminal or run: . $PROFILE`, + ]; } /** 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 16023b5..1d9f65c 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 @@ -206,40 +206,6 @@ describe("PowerShell Core shell integration", () => { }); }); - describe("custom install dir", () => { - it("writes only the source line to the profile", async () => { - await powershell.setup(); - - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok( - content.includes('. "/test-home/.safe-chain/scripts/init-pwsh.ps1"') - ); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("removes legacy env lines on teardown", () => { - const initialContent = [ - "# PowerShell profile", - "$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory", - '. "/test-home/.safe-chain/scripts/init-pwsh.ps1" # Safe-chain PowerShell initialization script', - ].join("\n"); - - fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); - - powershell.teardown(knownAikidoTools); - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("shows source-only manual setup instructions", () => { - assert.deepStrictEqual(powershell.getManualSetupInstructions(), [ - 'Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):', - ' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"', - "Then restart your terminal or run: . $PROFILE", - ]); - }); - }); - describe("execution policy", () => { it(`should throw for restricted policies`, async () => { executionPolicyResult = { 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 4a27fe9..87c2fae 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -32,17 +32,12 @@ function teardown(tools) { ); } - // Match any installation path but require the Safe-chain marker to avoid removing unrelated user scripts + // Removes the line that sources the safe-chain PowerShell initialization script. removeLinesMatchingPattern( startupFile, /^\.\s+["']?.*init-pwsh\.ps1["']?.*#\s*Safe-chain/, ); - removeLinesMatchingPattern( - startupFile, - /^\$env:SAFE_CHAIN_DIR\s*=.*#\s*Safe-chain/, - ); - return true; } @@ -78,19 +73,20 @@ function getStartupFile() { } } -/** @param {string} preamble */ -function buildManualInstructions(preamble) { - const instructions = [preamble, ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`]; - instructions.push(`Then restart your terminal or run: . $PROFILE`); - return instructions; -} - function getManualTeardownInstructions() { - return buildManualInstructions(`Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`); + return [ + `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + `Then restart your terminal or run: . $PROFILE`, + ]; } function getManualSetupInstructions() { - return buildManualInstructions(`Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`); + return [ + `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + `Then restart your terminal or run: . $PROFILE`, + ]; } /** 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 ac26ca7..621b380 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 @@ -206,40 +206,6 @@ describe("Windows PowerShell shell integration", () => { }); }); - describe("custom install dir", () => { - it("writes only the source line to the profile", async () => { - await windowsPowershell.setup(); - - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok( - content.includes('. "/test-home/.safe-chain/scripts/init-pwsh.ps1"') - ); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("removes legacy env lines on teardown", () => { - const initialContent = [ - "# Windows PowerShell profile", - "$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory", - '. "/test-home/.safe-chain/scripts/init-pwsh.ps1" # Safe-chain PowerShell initialization script', - ].join("\n"); - - fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); - - windowsPowershell.teardown(knownAikidoTools); - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("shows source-only manual teardown instructions", () => { - assert.deepStrictEqual(windowsPowershell.getManualTeardownInstructions(), [ - 'Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):', - ' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"', - "Then restart your terminal or run: . $PROFILE", - ]); - }); - }); - describe("execution policy", () => { it(`should throw for restricted policies`, async () => { executionPolicyResult = { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js index 3fa775c..c1c1232 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js @@ -33,19 +33,13 @@ function teardown(tools) { ); } - // Remove init script source line to uninstall shell integration; marker ensures only safe-chain-added lines are removed + // Removes the line that sources the safe-chain zsh initialization script. removeLinesMatchingPattern( startupFile, /^source\s+.*init-posix\.sh.*#\s*Safe-chain/, eol ); - removeLinesMatchingPattern( - startupFile, - /^export\s+SAFE_CHAIN_DIR=.*#\s*Safe-chain/, - eol - ); - return true; } @@ -74,19 +68,20 @@ function getStartupFile() { } } -/** @param {string} preamble */ -function buildManualInstructions(preamble) { - const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-posix.sh")}`]; - instructions.push(`Then restart your terminal or run: source ~/.zshrc`); - return instructions; -} - function getManualTeardownInstructions() { - return buildManualInstructions(`Remove the following line from your ~/.zshrc file:`); + return [ + `Remove the following line from your ~/.zshrc file:`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + `Then restart your terminal or run: source ~/.zshrc`, + ]; } function getManualSetupInstructions() { - return buildManualInstructions(`Add the following line to your ~/.zshrc file:`); + return [ + `Add the following line to your ~/.zshrc file:`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + `Then restart your terminal or run: source ~/.zshrc`, + ]; } export default { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js index caa85f4..41e1bd1 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js @@ -171,40 +171,6 @@ describe("Zsh shell integration", () => { }); }); - describe("custom install dir", () => { - it("writes only the source line to the rc file", () => { - zsh.setup(); - - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok( - content.includes("source /test-home/.safe-chain/scripts/init-posix.sh") - ); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("removes legacy export lines on teardown", () => { - const initialContent = [ - "#!/bin/zsh", - 'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory', - "source /test-home/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script", - ].join("\n"); - - fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); - - zsh.teardown(knownAikidoTools); - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("SAFE_CHAIN_DIR")); - }); - - it("shows source-only manual teardown instructions", () => { - assert.deepStrictEqual(zsh.getManualTeardownInstructions(), [ - "Remove the following line from your ~/.zshrc file:", - " source /test-home/.safe-chain/scripts/init-posix.sh", - "Then restart your terminal or run: source ~/.zshrc", - ]); - }); - }); - describe("integration tests", () => { it("should handle complete setup and teardown cycle", () => { const tools = [ diff --git a/test/e2e/bun.e2e.spec.js b/test/e2e/bun.e2e.spec.js index 1de6100..27a8923 100644 --- a/test/e2e/bun.e2e.spec.js +++ b/test/e2e/bun.e2e.spec.js @@ -79,38 +79,4 @@ describe("E2E: bun coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("bash"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious bun packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("bash"); - const result = await shell.runCommand("bunx safe-chain-test"); - - assert.ok( - result.output.includes("blocked 1 malicious package downloads"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/npm-ci.e2e.spec.js b/test/e2e/npm-ci.e2e.spec.js index cc3349b..9cb0886 100644 --- a/test/e2e/npm-ci.e2e.spec.js +++ b/test/e2e/npm-ci.e2e.spec.js @@ -103,46 +103,4 @@ describe("E2E: npm coverage using PATH", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup-ci"); - // Persist SAFE_CHAIN_DIR and the custom shims dir in .zshrc so new shells - // inherit both (shims need SAFE_CHAIN_DIR to strip themselves from PATH) - await setupShell.runCommand( - `echo 'export SAFE_CHAIN_DIR=${CUSTOM_DIR}' >> ~/.zshrc` - ); - await setupShell.runCommand( - `echo 'export PATH="${CUSTOM_DIR}/shims:$PATH"' >> ~/.zshrc` - ); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious npm packages when shims are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("npm i safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/npm.e2e.spec.js b/test/e2e/npm.e2e.spec.js index d86af3c..c07b648 100644 --- a/test/e2e/npm.e2e.spec.js +++ b/test/e2e/npm.e2e.spec.js @@ -120,38 +120,4 @@ describe("E2E: npm coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious npm packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("npm i safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/pip-ci.e2e.spec.js b/test/e2e/pip-ci.e2e.spec.js index e1a7aed..7857ef2 100644 --- a/test/e2e/pip-ci.e2e.spec.js +++ b/test/e2e/pip-ci.e2e.spec.js @@ -205,43 +205,4 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => { }); } - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand("pip3 cache purge"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup-ci"); - await setupShell.runCommand( - `echo 'export SAFE_CHAIN_DIR=${CUSTOM_DIR}' >> ~/.zshrc` - ); - await setupShell.runCommand( - `echo 'export PATH="${CUSTOM_DIR}/shims:$PATH"' >> ~/.zshrc` - ); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("intercepts pip3 install when shims are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand( - "pip3 install --break-system-packages certifi --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes("no malware found."), - `Expected pip3 to be protected with SAFE_CHAIN_DIR. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/pip.e2e.spec.js b/test/e2e/pip.e2e.spec.js index 684ee4f..c86e1cd 100644 --- a/test/e2e/pip.e2e.spec.js +++ b/test/e2e/pip.e2e.spec.js @@ -845,39 +845,4 @@ describe("E2E: pip coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - await setupShell.runCommand("pip3 cache purge"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("intercepts pip3 install when scripts are in a custom directory", async () => { - // New shell sources ~/.zshrc → sources init-posix.sh from custom dir - // → defines pip3() shell function that routes through safe-chain - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand( - "pip3 install --break-system-packages requests --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes("no malware found."), - `Expected pip3 to be protected with SAFE_CHAIN_DIR. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/pipx.e2e.spec.js b/test/e2e/pipx.e2e.spec.js index 489d8c6..8278bb4 100644 --- a/test/e2e/pipx.e2e.spec.js +++ b/test/e2e/pipx.e2e.spec.js @@ -198,38 +198,4 @@ describe("E2E: pipx coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious pipx packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("pipx install safe-chain-pi-test"); - - assert.ok( - result.output.includes("blocked by safe-chain"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/pnpm-ci.e2e.spec.js b/test/e2e/pnpm-ci.e2e.spec.js index 391001e..edba881 100644 --- a/test/e2e/pnpm-ci.e2e.spec.js +++ b/test/e2e/pnpm-ci.e2e.spec.js @@ -123,44 +123,4 @@ describe("E2E: pnpm coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup-ci"); - await setupShell.runCommand( - `echo 'export SAFE_CHAIN_DIR=${CUSTOM_DIR}' >> ~/.zshrc` - ); - await setupShell.runCommand( - `echo 'export PATH="${CUSTOM_DIR}/shims:$PATH"' >> ~/.zshrc` - ); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious pnpm packages when shims are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("pnpm add safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/pnpm.e2e.spec.js b/test/e2e/pnpm.e2e.spec.js index 90ef57c..1c8d5ab 100644 --- a/test/e2e/pnpm.e2e.spec.js +++ b/test/e2e/pnpm.e2e.spec.js @@ -140,38 +140,4 @@ describe("E2E: pnpm coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious pnpm packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("pnpm add safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/poetry.e2e.spec.js b/test/e2e/poetry.e2e.spec.js index 072d1b6..96761bc 100644 --- a/test/e2e/poetry.e2e.spec.js +++ b/test/e2e/poetry.e2e.spec.js @@ -423,45 +423,4 @@ describe("E2E: poetry coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - await setupShell.runCommand("command poetry cache clear pypi --all -n"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious poetry packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - await shell.runCommand("mkdir /tmp/test-poetry-custom-dir"); - await shell.runCommand( - "cd /tmp/test-poetry-custom-dir && poetry init --no-interaction" - ); - const result = await shell.runCommand( - "cd /tmp/test-poetry-custom-dir && poetry add safe-chain-pi-test" - ); - - assert.ok( - result.output.includes("blocked by safe-chain"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/safe-chain-dir.e2e.spec.js b/test/e2e/safe-chain-dir.e2e.spec.js deleted file mode 100644 index e738949..0000000 --- a/test/e2e/safe-chain-dir.e2e.spec.js +++ /dev/null @@ -1,166 +0,0 @@ -import { describe, it, before, beforeEach, afterEach } from "node:test"; -import { DockerTestContainer } from "./DockerTestContainer.js"; -import assert from "node:assert"; - -const CUSTOM_DIR = "/usr/local/.safe-chain"; - -describe("E2E: SAFE_CHAIN_DIR support", () => { - let container; - - before(async () => { - DockerTestContainer.buildImage(); - }); - - beforeEach(async () => { - container = new DockerTestContainer(); - await container.start(); - }); - - afterEach(async () => { - if (container) { - await container.stop(); - container = null; - } - }); - - it("setup-ci installs shims in the custom directory when SAFE_CHAIN_DIR is set", async () => { - const shell = await container.openShell("bash"); - await shell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await shell.runCommand("safe-chain setup-ci"); - - // Shims should be in the custom dir - const customShimResult = await shell.runCommand( - `test -f ${CUSTOM_DIR}/shims/npm && echo "EXISTS"` - ); - assert.ok( - customShimResult.output.includes("EXISTS"), - `Expected npm shim at ${CUSTOM_DIR}/shims/npm. Output:\n${customShimResult.output}` - ); - - // Default location should NOT have been created - const defaultShimResult = await shell.runCommand( - `test -d $HOME/.safe-chain/shims && echo "EXISTS" || echo "ABSENT"` - ); - assert.ok( - defaultShimResult.output.includes("ABSENT"), - `Expected default shims dir to be absent. Output:\n${defaultShimResult.output}` - ); - }); - - it("setup-ci writes the custom directory path to GITHUB_PATH when SAFE_CHAIN_DIR is set", async () => { - const shell = await container.openShell("bash"); - await shell.runCommand("export GITHUB_PATH=/tmp/github_path"); - await shell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await shell.runCommand("safe-chain setup-ci"); - - const result = await shell.runCommand("cat /tmp/github_path"); - assert.ok( - result.output.includes(`${CUSTOM_DIR}/shims`), - `Expected GITHUB_PATH to contain custom shims dir. Output:\n${result.output}` - ); - assert.ok( - result.output.includes(`${CUSTOM_DIR}/bin`), - `Expected GITHUB_PATH to contain custom bin dir. Output:\n${result.output}` - ); - }); - - it("setup writes the custom path to ~/.bashrc when SAFE_CHAIN_DIR is set", async () => { - const shell = await container.openShell("bash"); - await shell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await shell.runCommand("safe-chain setup"); - - const result = await shell.runCommand("cat ~/.bashrc"); - - assert.ok( - result.output.includes(`source ${CUSTOM_DIR}/scripts/init-posix.sh`), - `Expected ~/.bashrc to contain custom scripts path. Output:\n${result.output}` - ); - assert.ok( - !result.output.includes("source ~/.safe-chain/scripts/init-posix.sh"), - `Expected ~/.bashrc to NOT contain default path. Output:\n${result.output}` - ); - }); - - it("setup with SAFE_CHAIN_DIR still protects npm in a new shell session", async () => { - // Run setup with the custom dir - const setupShell = await container.openShell("bash"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - - // Open a fresh shell — it will source ~/.bashrc which sources init-posix.sh - // from the custom dir, defining the npm wrapper function - const projectShell = await container.openShell("bash"); - await projectShell.runCommand("cd /testapp"); - const result = await projectShell.runCommand( - "npm i axios@1.13.0 --safe-chain-logging=verbose" - ); - - // "Safe-chain: Package" appears before npm downloads — confirms interception happened - assert.ok( - result.output.includes("Safe-chain: Package"), - `Expected npm to be protected after setup with SAFE_CHAIN_DIR. Output:\n${result.output}` - ); - }); - - it("teardown removes the custom SAFE_CHAIN_DIR source line from ~/.bashrc", async () => { - const shell = await container.openShell("bash"); - await shell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await shell.runCommand("safe-chain setup"); - await shell.runCommand("safe-chain teardown"); - - const result = await shell.runCommand("cat ~/.bashrc"); - assert.ok( - !result.output.includes(`source ${CUSTOM_DIR}/scripts/init-posix.sh`), - `Expected custom source line to be removed from ~/.bashrc. Output:\n${result.output}` - ); - }); - - it("safe-chain protects a non-root user when installed to a shared dir with SAFE_CHAIN_DIR", async () => { - // Step 1: create a non-root user inside the container - container.dockerExec("useradd -m safeuser"); - - // Step 2: as root, run setup-ci with the shared SAFE_CHAIN_DIR - const rootShell = await container.openShell("bash"); - await rootShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await rootShell.runCommand("safe-chain setup-ci"); - - // Step 3: simulate what install-safe-chain.sh does — place the safe-chain binary - // in SAFE_CHAIN_DIR/bin. In Docker tests safe-chain is installed via npm/Volta, - // so we symlink it there. - container.dockerExec(`mkdir -p ${CUSTOM_DIR}/bin`); - container.dockerExec( - `ln -sf \\$(which safe-chain) ${CUSTOM_DIR}/bin/safe-chain` - ); - - // Step 4: make npm accessible to all users (in real Dockerfiles npm is installed - // before the user switch; here Volta manages it for root, so we symlink it). - container.dockerExec("ln -sf \\$(which npm) /usr/local/bin/npm"); - - // Step 5: make the shared safe-chain dir readable + executable by all users - container.dockerExec(`chmod -R a+rx ${CUSTOM_DIR}`); - - // Step 6: Volta installs under /root/.volta which is only accessible to root by - // default. /root/ itself is mode 700, so safeuser can't traverse into it even - // if .volta/ is world-readable. Fix both levels. Safe in a throw-away container. - container.dockerExec("chmod a+x /root && chmod -R a+rX /root/.volta"); - - // Step 7: as the non-root user, set SAFE_CHAIN_DIR and PATH, then run npm. - // SAFE_CHAIN_DIR must be set so the shim knows which dir to strip from PATH - // when invoking the real npm (prevents infinite loop). - const userShell = await container.openShell("bash", { user: "safeuser" }); - await userShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - // Reuse root's Volta dir so safeuser doesn't trigger a slow first-run setup - await userShell.runCommand("export VOLTA_HOME=/root/.volta"); - await userShell.runCommand( - `export PATH="${CUSTOM_DIR}/shims:${CUSTOM_DIR}/bin:$PATH"` - ); - const result = await userShell.runCommand( - "npm i axios@1.13.0 --safe-chain-logging=verbose" - ); - - assert.ok( - result.output.includes("Safe-chain: Scanned"), - `Expected safe-chain to protect non-root user. Output:\n${result.output}` - ); - }); -}); diff --git a/test/e2e/uv.e2e.spec.js b/test/e2e/uv.e2e.spec.js index ad24f6e..d7254c2 100644 --- a/test/e2e/uv.e2e.spec.js +++ b/test/e2e/uv.e2e.spec.js @@ -570,42 +570,4 @@ describe("E2E: uv coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - await setupShell.runCommand("uv cache clean"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious uv packages when scripts are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - await shell.runCommand("uv init test-project-custom-dir"); - const result = await shell.runCommand( - "cd test-project-custom-dir && uv add safe-chain-pi-test" - ); - - assert.ok( - result.output.includes("blocked 1 malicious package downloads:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/yarn-ci.e2e.spec.js b/test/e2e/yarn-ci.e2e.spec.js index 35047c1..3740207 100644 --- a/test/e2e/yarn-ci.e2e.spec.js +++ b/test/e2e/yarn-ci.e2e.spec.js @@ -85,44 +85,4 @@ describe("E2E: yarn coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup-ci"); - await setupShell.runCommand( - `echo 'export SAFE_CHAIN_DIR=${CUSTOM_DIR}' >> ~/.zshrc` - ); - await setupShell.runCommand( - `echo 'export PATH="${CUSTOM_DIR}/shims:$PATH"' >> ~/.zshrc` - ); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious yarn packages when shims are in a custom directory", async () => { - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("yarn add safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); }); diff --git a/test/e2e/yarn.e2e.spec.js b/test/e2e/yarn.e2e.spec.js index 5b677d6..7fe2533 100644 --- a/test/e2e/yarn.e2e.spec.js +++ b/test/e2e/yarn.e2e.spec.js @@ -126,42 +126,4 @@ describe("E2E: yarn coverage", () => { ); }); - describe("with SAFE_CHAIN_DIR (custom install directory)", () => { - const CUSTOM_DIR = "/usr/local/.safe-chain"; - let customContainer; - - beforeEach(async () => { - customContainer = new DockerTestContainer(); - await customContainer.start(); - - // Run setup with the custom dir — init-posix.sh is copied to the custom - // scripts dir, and ~/.zshrc gets a source line pointing there - const setupShell = await customContainer.openShell("zsh"); - await setupShell.runCommand(`export SAFE_CHAIN_DIR=${CUSTOM_DIR}`); - await setupShell.runCommand("safe-chain setup"); - }); - - afterEach(async () => { - if (customContainer) { - await customContainer.stop(); - customContainer = null; - } - }); - - it("blocks malicious yarn packages when scripts are in a custom directory", async () => { - // New shell sources ~/.zshrc → sources init-posix.sh from custom dir - // → defines yarn() shell function that routes through safe-chain - const shell = await customContainer.openShell("zsh"); - const result = await shell.runCommand("yarn add safe-chain-test"); - - assert.ok( - result.output.includes("Malicious changes detected:"), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - assert.ok( - result.output.includes("Exiting without installing malicious packages."), - `Expected malicious package to be blocked. Output:\n${result.output}` - ); - }); - }); });