mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge branch 'main' into new-proxy-beta
This commit is contained in:
commit
03ecd0dfb9
10 changed files with 261 additions and 74 deletions
84
README.md
84
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
|
||||
|
|
@ -228,6 +226,22 @@ export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*"
|
|||
}
|
||||
```
|
||||
|
||||
### Excluding Packages
|
||||
|
||||
Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged). Use `@scope/*` to trust all packages from an organization:
|
||||
|
||||
```shell
|
||||
export SAFE_CHAIN_NPM_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"npm": {
|
||||
"minimumPackageAgeExclusions": ["@aikidosec/*"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Registries
|
||||
|
||||
Configure Safe Chain to scan packages from custom or private registries.
|
||||
|
|
@ -288,6 +302,7 @@ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download
|
|||
- ✅ **CircleCI**
|
||||
- ✅ **Jenkins**
|
||||
- ✅ **Bitbucket Pipelines**
|
||||
- ✅ **GitLab Pipelines**
|
||||
|
||||
## GitHub Actions Example
|
||||
|
||||
|
|
@ -394,6 +409,69 @@ steps:
|
|||
|
||||
After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
|
||||
|
||||
## GitLab Pipelines Example
|
||||
|
||||
To add safe-chain in GitLab pipelines, you need to install it in the image running the pipeline. This can be done by:
|
||||
|
||||
1. Define a dockerfile to run your build
|
||||
|
||||
```dockerfile
|
||||
FROM node:lts
|
||||
|
||||
# Install safe-chain
|
||||
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
|
||||
|
||||
# Add safe-chain to PATH
|
||||
ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}"
|
||||
```
|
||||
|
||||
2. Build the Docker image in your CI pipeline
|
||||
|
||||
```yaml
|
||||
build-image:
|
||||
stage: build-image
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker build -t $CI_REGISTRY_IMAGE:latest .
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
```
|
||||
|
||||
3. Use the image in your pipeline:
|
||||
```yaml
|
||||
npm-ci:
|
||||
stage: install
|
||||
image: $CI_REGISTRY_IMAGE:latest
|
||||
script:
|
||||
- npm ci
|
||||
```
|
||||
|
||||
The full pipeline for this example looks like this:
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build-image
|
||||
- install
|
||||
|
||||
build-image:
|
||||
stage: build-image
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker build -t $CI_REGISTRY_IMAGE:latest .
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
|
||||
npm-ci:
|
||||
stage: install
|
||||
image: $CI_REGISTRY_IMAGE:latest
|
||||
script:
|
||||
- npm ci
|
||||
```
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Having issues? See the [Troubleshooting Guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md) for help with common problems.
|
||||
Having issues? See the [Troubleshooting Guide](https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting) for help with common problems.
|
||||
|
|
|
|||
|
|
@ -308,22 +308,6 @@ Look for and remove:
|
|||
rm -rf ~/.safe-chain
|
||||
```
|
||||
|
||||
## Getting More Information
|
||||
|
||||
### Enable Verbose Logging
|
||||
|
||||
Get detailed diagnostic output using a CLI flag or environment variable:
|
||||
|
||||
```bash
|
||||
# Using CLI flag
|
||||
npm install express --safe-chain-logging=verbose
|
||||
pip install requests --safe-chain-logging=verbose
|
||||
|
||||
# Using environment variable (applies to all commands)
|
||||
export SAFE_CHAIN_LOGGING=verbose
|
||||
npm install express
|
||||
```
|
||||
|
||||
### Report Issues
|
||||
|
||||
If you encounter problems:
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ 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";
|
||||
import { ui } from "../environment/userInteraction.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} AikidoTool
|
||||
|
|
@ -243,3 +245,60 @@ 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 {Promise<{isValid: boolean, policy: string}>} validation result
|
||||
*/
|
||||
export async function validatePowerShellExecutionPolicy(shellExecutableName) {
|
||||
// Security: Only allow known shell executables
|
||||
const validShells = ["pwsh", "powershell"];
|
||||
if (!validShells.includes(shellExecutableName)) {
|
||||
return { isValid: false, policy: "Unknown" };
|
||||
}
|
||||
|
||||
try {
|
||||
// For Windows PowerShell (5.1), clean PSModulePath to avoid conflicts with PowerShell 7 modules
|
||||
// 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 || "";
|
||||
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,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
spawnOptions = {};
|
||||
}
|
||||
|
||||
const commandResult = await safeSpawn(
|
||||
shellExecutableName,
|
||||
["-Command", "Get-ExecutionPolicy"],
|
||||
spawnOptions,
|
||||
);
|
||||
|
||||
const policy = commandResult.stdout.trim();
|
||||
|
||||
const acceptablePolicies = ["RemoteSigned", "Unrestricted", "Bypass"];
|
||||
return {
|
||||
isValid: acceptablePolicies.includes(policy),
|
||||
policy: policy,
|
||||
};
|
||||
} catch (err) {
|
||||
ui.writeWarning(
|
||||
`An error happened while trying to find the current executionpolicy in powershell: ${err}`,
|
||||
);
|
||||
return { isValid: false, policy: "Unknown" };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<boolean>} 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 [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
addLineToFile,
|
||||
doesExecutableExistOnSystem,
|
||||
removeLinesMatchingPattern,
|
||||
validatePowerShellExecutionPolicy,
|
||||
} from "../helpers.js";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
|
|
@ -25,25 +26,33 @@ 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() {
|
||||
const { isValid, policy } =
|
||||
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.\n For more information, see: https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting#powershell-execution-policy-blocks-scripts-windows`,
|
||||
);
|
||||
}
|
||||
|
||||
const startupFile = getStartupFile();
|
||||
|
||||
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;
|
||||
|
|
@ -57,7 +66,7 @@ function getStartupFile() {
|
|||
}).trim();
|
||||
} catch (/** @type {any} */ error) {
|
||||
throw new Error(
|
||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -69,15 +76,15 @@ 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");
|
||||
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 "));
|
||||
|
|
@ -168,26 +175,26 @@ 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"')
|
||||
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"'),
|
||||
);
|
||||
});
|
||||
|
||||
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 = (
|
||||
|
|
@ -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`, async () => {
|
||||
executionPolicyResult = {
|
||||
isValid: false,
|
||||
policy: "Restricted",
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => powershell.setup(),
|
||||
(err) =>
|
||||
err.message.startsWith(
|
||||
"PowerShell execution policy is set to 'Restricted'",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
addLineToFile,
|
||||
doesExecutableExistOnSystem,
|
||||
removeLinesMatchingPattern,
|
||||
validatePowerShellExecutionPolicy,
|
||||
} from "../helpers.js";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
|
|
@ -25,25 +26,33 @@ 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() {
|
||||
const { isValid, policy } =
|
||||
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.\n For more information, see: https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting#powershell-execution-policy-blocks-scripts-windows`,
|
||||
);
|
||||
}
|
||||
|
||||
const startupFile = getStartupFile();
|
||||
|
||||
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;
|
||||
|
|
@ -57,7 +66,7 @@ function getStartupFile() {
|
|||
}).trim();
|
||||
} catch (/** @type {any} */ error) {
|
||||
throw new Error(
|
||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
||||
`Command failed: ${startupFileCommand}. Error: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -69,15 +76,15 @@ 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");
|
||||
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 "));
|
||||
|
|
@ -168,26 +175,26 @@ 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"')
|
||||
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"'),
|
||||
);
|
||||
});
|
||||
|
||||
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 = (
|
||||
|
|
@ -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`, async () => {
|
||||
executionPolicyResult = {
|
||||
isValid: false,
|
||||
policy: "Restricted",
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => windowsPowershell.setup(),
|
||||
(err) =>
|
||||
err.message.startsWith(
|
||||
"PowerShell execution policy is set to 'Restricted'",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue