@aikidosec/safe-chain 1.0.23 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -2
- package/bin/aikido-bun.js +10 -0
- package/bin/aikido-bunx.js +10 -0
- package/bin/aikido-npm.js +3 -1
- package/bin/aikido-npx.js +3 -1
- package/bin/aikido-pnpm.js +3 -1
- package/bin/aikido-pnpx.js +3 -1
- package/bin/aikido-yarn.js +3 -1
- package/bin/safe-chain.js +8 -0
- package/docs/safe-package-manager-demo.png +0 -0
- package/docs/shell-integration.md +42 -27
- package/package.json +5 -1
- package/src/main.js +35 -4
- package/src/packagemanager/bun/createBunPackageManager.js +42 -0
- package/src/packagemanager/currentPackageManager.js +8 -0
- package/src/packagemanager/npm/dependencyScanner/dryRunScanner.js +23 -6
- package/src/packagemanager/npm/runNpmCommand.js +26 -10
- package/src/packagemanager/npx/runNpxCommand.js +8 -5
- package/src/packagemanager/pnpm/runPnpmCommand.js +18 -11
- package/src/packagemanager/yarn/runYarnCommand.js +41 -5
- package/src/registryProxy/certUtils.js +114 -0
- package/src/registryProxy/mitmRequestHandler.js +90 -0
- package/src/registryProxy/parsePackageFromUrl.js +48 -0
- package/src/registryProxy/registryProxy.js +158 -0
- package/src/registryProxy/tunnelRequestHandler.js +98 -0
- package/src/scanning/index.js +14 -9
- package/src/scanning/malwareDatabase.js +10 -1
- package/src/shell-integration/helpers.js +20 -3
- package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +22 -0
- package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +24 -0
- package/src/shell-integration/setup-ci.js +123 -0
- package/src/shell-integration/setup.js +2 -2
- package/src/shell-integration/startup-scripts/init-fish.fish +17 -5
- package/src/shell-integration/startup-scripts/init-posix.sh +8 -0
- package/src/shell-integration/startup-scripts/init-pwsh.ps1 +8 -0
- package/src/shell-integration/teardown.js +2 -2
- package/src/utils/safeSpawn.js +50 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The Aikido Safe Chain **prevents developers from installing malware** on their w
|
|
|
4
4
|
|
|
5
5
|
The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), and [pnpx](https://pnpm.io/cli/dlx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm or pnpx from downloading or running the malware.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
Aikido Safe Chain works on Node.js version 18 and above and supports the following package managers:
|
|
10
10
|
|
|
@@ -88,4 +88,60 @@ npm install suspicious-package --safe-chain-malware-action=prompt
|
|
|
88
88
|
|
|
89
89
|
# Usage in CI/CD
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
You can protect your CI/CD pipelines from malicious packages by integrating Aikido Safe Chain into your build process. This ensures that any packages installed during your automated builds are checked for malware before installation.
|
|
92
|
+
|
|
93
|
+
For optimal protection in CI/CD environments, we recommend using **npm >= 10.4.0** as it provides full dependency tree scanning. Other package managers currently offer limited scanning of install command arguments only.
|
|
94
|
+
|
|
95
|
+
## Setup
|
|
96
|
+
|
|
97
|
+
To use Aikido Safe Chain in CI/CD environments, run the following command after installing the package:
|
|
98
|
+
|
|
99
|
+
```shell
|
|
100
|
+
safe-chain setup-ci
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This automatically configures your CI environment to use Aikido Safe Chain for all package manager commands.
|
|
104
|
+
|
|
105
|
+
## Supported Platforms
|
|
106
|
+
|
|
107
|
+
- ✅ **GitHub Actions**
|
|
108
|
+
- ✅ **Azure Pipelines**
|
|
109
|
+
|
|
110
|
+
## GitHub Actions Example
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- name: Setup Node.js
|
|
114
|
+
uses: actions/setup-node@v4
|
|
115
|
+
with:
|
|
116
|
+
node-version: "22"
|
|
117
|
+
cache: "npm"
|
|
118
|
+
|
|
119
|
+
- name: Setup safe-chain
|
|
120
|
+
run: |
|
|
121
|
+
npm i -g @aikidosec/safe-chain
|
|
122
|
+
safe-chain setup-ci
|
|
123
|
+
|
|
124
|
+
- name: Install dependencies
|
|
125
|
+
run: |
|
|
126
|
+
npm ci
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Azure DevOps Example
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
- task: NodeTool@0
|
|
133
|
+
inputs:
|
|
134
|
+
versionSpec: "22.x"
|
|
135
|
+
displayName: "Install Node.js"
|
|
136
|
+
|
|
137
|
+
- script: |
|
|
138
|
+
npm i -g @aikidosec/safe-chain
|
|
139
|
+
safe-chain setup-ci
|
|
140
|
+
displayName: "Install safe chain"
|
|
141
|
+
|
|
142
|
+
- script: |
|
|
143
|
+
npm ci
|
|
144
|
+
displayName: "npm install and build"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
|
|
6
|
+
const packageManagerName = "bun";
|
|
7
|
+
initializePackageManager(packageManagerName);
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
|
|
6
|
+
const packageManagerName = "bunx";
|
|
7
|
+
initializePackageManager(packageManagerName);
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
package/bin/aikido-npm.js
CHANGED
|
@@ -6,7 +6,9 @@ import { initializePackageManager } from "../src/packagemanager/currentPackageMa
|
|
|
6
6
|
|
|
7
7
|
const packageManagerName = "npm";
|
|
8
8
|
initializePackageManager(packageManagerName, getNpmVersion());
|
|
9
|
-
await main(process.argv.slice(2));
|
|
9
|
+
var exitCode = await main(process.argv.slice(2));
|
|
10
|
+
|
|
11
|
+
process.exit(exitCode);
|
|
10
12
|
|
|
11
13
|
function getNpmVersion() {
|
|
12
14
|
try {
|
package/bin/aikido-npx.js
CHANGED
|
@@ -5,4 +5,6 @@ import { initializePackageManager } from "../src/packagemanager/currentPackageMa
|
|
|
5
5
|
|
|
6
6
|
const packageManagerName = "npx";
|
|
7
7
|
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
-
await main(process.argv.slice(2));
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
package/bin/aikido-pnpm.js
CHANGED
|
@@ -5,4 +5,6 @@ import { initializePackageManager } from "../src/packagemanager/currentPackageMa
|
|
|
5
5
|
|
|
6
6
|
const packageManagerName = "pnpm";
|
|
7
7
|
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
-
await main(process.argv.slice(2));
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
package/bin/aikido-pnpx.js
CHANGED
|
@@ -5,4 +5,6 @@ import { initializePackageManager } from "../src/packagemanager/currentPackageMa
|
|
|
5
5
|
|
|
6
6
|
const packageManagerName = "pnpx";
|
|
7
7
|
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
-
await main(process.argv.slice(2));
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
package/bin/aikido-yarn.js
CHANGED
|
@@ -5,4 +5,6 @@ import { initializePackageManager } from "../src/packagemanager/currentPackageMa
|
|
|
5
5
|
|
|
6
6
|
const packageManagerName = "yarn";
|
|
7
7
|
initializePackageManager(packageManagerName, process.versions.node);
|
|
8
|
-
await main(process.argv.slice(2));
|
|
8
|
+
var exitCode = await main(process.argv.slice(2));
|
|
9
|
+
|
|
10
|
+
process.exit(exitCode);
|
package/bin/safe-chain.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from "chalk";
|
|
|
4
4
|
import { ui } from "../src/environment/userInteraction.js";
|
|
5
5
|
import { setup } from "../src/shell-integration/setup.js";
|
|
6
6
|
import { teardown } from "../src/shell-integration/teardown.js";
|
|
7
|
+
import { setupCi } from "../src/shell-integration/setup-ci.js";
|
|
7
8
|
|
|
8
9
|
if (process.argv.length < 3) {
|
|
9
10
|
ui.writeError("No command provided. Please provide a command to execute.");
|
|
@@ -23,6 +24,8 @@ if (command === "setup") {
|
|
|
23
24
|
setup();
|
|
24
25
|
} else if (command === "teardown") {
|
|
25
26
|
teardown();
|
|
27
|
+
} else if (command === "setup-ci") {
|
|
28
|
+
setupCi();
|
|
26
29
|
} else {
|
|
27
30
|
ui.writeError(`Unknown command: ${command}.`);
|
|
28
31
|
ui.emptyLine();
|
|
@@ -53,5 +56,10 @@ function writeHelp() {
|
|
|
53
56
|
"safe-chain teardown"
|
|
54
57
|
)}: This will remove safe-chain aliases from your shell configuration.`
|
|
55
58
|
);
|
|
59
|
+
ui.writeInformation(
|
|
60
|
+
`- ${chalk.cyan(
|
|
61
|
+
"safe-chain setup-ci"
|
|
62
|
+
)}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.`
|
|
63
|
+
);
|
|
56
64
|
ui.emptyLine();
|
|
57
65
|
}
|
|
Binary file
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`) with Aikido's security scanning functionality. This is achieved by
|
|
5
|
+
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`) with Aikido's security scanning functionality. This is achieved by sourcing startup scripts that define shell functions to wrap these commands with their Aikido-protected equivalents.
|
|
6
6
|
|
|
7
7
|
## Supported Shells
|
|
8
8
|
|
|
9
9
|
Aikido Safe Chain supports integration with the following shells.
|
|
10
10
|
|
|
11
|
-
| Shell | Startup File |
|
|
12
|
-
| ---------------------- | ---------------------------- |
|
|
13
|
-
| **Bash** | `~/.bashrc` |
|
|
14
|
-
| **Zsh** | `~/.zshrc` |
|
|
15
|
-
| **Fish** | `~/.config/fish/config.fish` |
|
|
16
|
-
| **PowerShell Core** | `$PROFILE` |
|
|
17
|
-
| **Windows PowerShell** | `$PROFILE` |
|
|
11
|
+
| Shell | Startup File |
|
|
12
|
+
| ---------------------- | ---------------------------- |
|
|
13
|
+
| **Bash** | `~/.bashrc` |
|
|
14
|
+
| **Zsh** | `~/.zshrc` |
|
|
15
|
+
| **Fish** | `~/.config/fish/config.fish` |
|
|
16
|
+
| **PowerShell Core** | `$PROFILE` |
|
|
17
|
+
| **Windows PowerShell** | `$PROFILE` |
|
|
18
18
|
|
|
19
|
-
## Commands
|
|
19
|
+
## Setup Commands
|
|
20
20
|
|
|
21
21
|
### Setup Shell Integration
|
|
22
22
|
|
|
@@ -26,10 +26,11 @@ safe-chain setup
|
|
|
26
26
|
|
|
27
27
|
This command:
|
|
28
28
|
|
|
29
|
+
- Copies necessary startup scripts to Safe Chain's installation directory (`~/.safe-chain/scripts`)
|
|
29
30
|
- Detects all supported shells on your system
|
|
30
|
-
-
|
|
31
|
+
- Sources each shell's startup file to add Safe Chain functions for `npm`, `npx`, `yarn`, `pnpm`, and `pnpx`
|
|
31
32
|
|
|
32
|
-
❗ After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the
|
|
33
|
+
❗ After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the startup scripts are sourced correctly.
|
|
33
34
|
|
|
34
35
|
### Remove Shell Integration
|
|
35
36
|
|
|
@@ -40,13 +41,13 @@ safe-chain teardown
|
|
|
40
41
|
This command:
|
|
41
42
|
|
|
42
43
|
- Detects all supported shells on your system
|
|
43
|
-
- Removes
|
|
44
|
+
- Removes the Safe Chain scripts from each shell's startup file, restoring the original commands
|
|
44
45
|
|
|
45
46
|
❗ After running this command, **you must restart your terminal** to restore the original commands.
|
|
46
47
|
|
|
47
48
|
## File Locations
|
|
48
49
|
|
|
49
|
-
The system modifies the following files
|
|
50
|
+
The system modifies the following files to source Safe Chain startup scripts:
|
|
50
51
|
|
|
51
52
|
### Unix/Linux/macOS
|
|
52
53
|
|
|
@@ -64,15 +65,16 @@ The system modifies the following files based on your shell configuration:
|
|
|
64
65
|
|
|
65
66
|
### Common Issues
|
|
66
67
|
|
|
67
|
-
**
|
|
68
|
+
**Shell functions not working after setup:**
|
|
68
69
|
|
|
69
70
|
- Make sure to restart your terminal
|
|
70
|
-
- Check that the startup file was
|
|
71
|
+
- Check that the startup file was modified to source Safe Chain scripts
|
|
72
|
+
- Check the sourced file exists at `~/.safe-chain/scripts/`
|
|
71
73
|
- Verify your shell is reading the correct startup file
|
|
72
74
|
|
|
73
75
|
**Getting 'command not found: aikido-npm' error:**
|
|
74
76
|
|
|
75
|
-
This means the
|
|
77
|
+
This means the shell functions are working but the Aikido commands aren't installed or available in your PATH:
|
|
76
78
|
|
|
77
79
|
- Make sure Aikido Safe Chain is properly installed on your system
|
|
78
80
|
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm` and `aikido-pnpx` commands exist
|
|
@@ -82,27 +84,40 @@ This means the aliases are working but the Aikido commands aren't installed or a
|
|
|
82
84
|
|
|
83
85
|
To verify the integration is working, follow these steps:
|
|
84
86
|
|
|
85
|
-
1. **Check if
|
|
87
|
+
1. **Check if startup scripts were sourced in your shell startup file:**
|
|
86
88
|
|
|
87
89
|
- **For Bash**: Open `~/.bashrc` in your text editor
|
|
88
90
|
- **For Zsh**: Open `~/.zshrc` in your text editor
|
|
89
91
|
- **For Fish**: Open `~/.config/fish/config.fish` in your text editor
|
|
90
92
|
- **For PowerShell**: Open your PowerShell profile file (run `$PROFILE` in PowerShell to see the path)
|
|
91
93
|
|
|
92
|
-
Look for lines
|
|
94
|
+
Look for lines that source the Safe Chain startup scripts from `~/.safe-chain/scripts/`
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
- `alias npm "aikido-npm"` (Fish)
|
|
96
|
-
- `Set-Alias npm aikido-npm` (PowerShell)
|
|
97
|
-
|
|
98
|
-
2. **Test that aliases are active in your terminal:**
|
|
96
|
+
2. **Test that shell functions are active in your terminal:**
|
|
99
97
|
|
|
100
98
|
After restarting your terminal, run these commands:
|
|
101
99
|
|
|
102
|
-
- `which npm` - Should show the path to `aikido-npm` instead of the original npm
|
|
103
100
|
- `npm --version` - Should show output from the Aikido-wrapped version
|
|
104
|
-
- `type npm` -
|
|
101
|
+
- `type npm` - Should show that `npm` is a function
|
|
102
|
+
|
|
103
|
+
3. **If you need to remove the integration manually:**
|
|
104
|
+
|
|
105
|
+
Edit the same startup file from step 1 and delete any lines that source Safe Chain scripts from `~/.safe-chain/scripts/`.
|
|
106
|
+
|
|
107
|
+
## Manual Setup
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
For advanced users who prefer manual configuration, you can create wrapper functions directly in your shell's startup file. Shell functions take precedence over commands in PATH, so defining an `npm` function will intercept all `npm` calls:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Example for Bash/Zsh
|
|
113
|
+
npm() {
|
|
114
|
+
if command -v aikido-npm > /dev/null 2>&1; then
|
|
115
|
+
aikido-npm "$@"
|
|
116
|
+
else
|
|
117
|
+
echo "Warning: safe-chain is not installed. npm will run without protection."
|
|
118
|
+
command npm "$@"
|
|
119
|
+
fi
|
|
120
|
+
}
|
|
121
|
+
```
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
Repeat this pattern for `npx`, `yarn`, `pnpm`, and `pnpx` using their respective `aikido-*` commands. After adding these functions, restart your terminal to apply the changes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/safe-chain",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "node --test --experimental-test-module-mocks 'src/**/*.spec.js'",
|
|
6
6
|
"test:watch": "node --test --watch --experimental-test-module-mocks 'src/**/*.spec.js'",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"aikido-yarn": "bin/aikido-yarn.js",
|
|
13
13
|
"aikido-pnpm": "bin/aikido-pnpm.js",
|
|
14
14
|
"aikido-pnpx": "bin/aikido-pnpx.js",
|
|
15
|
+
"aikido-bun": "bin/aikido-bun.js",
|
|
16
|
+
"aikido-bunx": "bin/aikido-bunx.js",
|
|
15
17
|
"safe-chain": "bin/safe-chain.js"
|
|
16
18
|
},
|
|
17
19
|
"type": "module",
|
|
@@ -30,7 +32,9 @@
|
|
|
30
32
|
"dependencies": {
|
|
31
33
|
"abbrev": "3.0.1",
|
|
32
34
|
"chalk": "5.4.1",
|
|
35
|
+
"https-proxy-agent": "7.0.6",
|
|
33
36
|
"make-fetch-happen": "14.0.3",
|
|
37
|
+
"node-forge": "1.3.1",
|
|
34
38
|
"npm-registry-fetch": "18.0.2",
|
|
35
39
|
"ora": "8.2.0",
|
|
36
40
|
"semver": "7.7.2"
|
package/src/main.js
CHANGED
|
@@ -4,19 +4,50 @@ import { scanCommand, shouldScanCommand } from "./scanning/index.js";
|
|
|
4
4
|
import { ui } from "./environment/userInteraction.js";
|
|
5
5
|
import { getPackageManager } from "./packagemanager/currentPackageManager.js";
|
|
6
6
|
import { initializeCliArguments } from "./config/cliArguments.js";
|
|
7
|
+
import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
|
|
8
|
+
import chalk from "chalk";
|
|
7
9
|
|
|
8
10
|
export async function main(args) {
|
|
11
|
+
const proxy = createSafeChainProxy();
|
|
12
|
+
await proxy.startServer();
|
|
13
|
+
|
|
9
14
|
try {
|
|
10
15
|
// This parses all the --safe-chain arguments and removes them from the args array
|
|
11
16
|
args = initializeCliArguments(args);
|
|
12
17
|
|
|
13
18
|
if (shouldScanCommand(args)) {
|
|
14
|
-
await scanCommand(args);
|
|
19
|
+
const commandScanResult = await scanCommand(args);
|
|
20
|
+
|
|
21
|
+
// Returning the exit code back to the caller allows the promise
|
|
22
|
+
// to be awaited in the bin files and return the correct exit code
|
|
23
|
+
if (commandScanResult !== 0) {
|
|
24
|
+
return commandScanResult;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const packageManagerResult = await getPackageManager().runCommand(args);
|
|
29
|
+
|
|
30
|
+
if (!proxy.verifyNoMaliciousPackages()) {
|
|
31
|
+
return 1;
|
|
15
32
|
}
|
|
33
|
+
|
|
34
|
+
ui.emptyLine();
|
|
35
|
+
ui.writeInformation(
|
|
36
|
+
`${chalk.green(
|
|
37
|
+
"✔"
|
|
38
|
+
)} Safe-chain: Command completed, no malicious packages found.`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Returning the exit code back to the caller allows the promise
|
|
42
|
+
// to be awaited in the bin files and return the correct exit code
|
|
43
|
+
return packageManagerResult.status;
|
|
16
44
|
} catch (error) {
|
|
17
45
|
ui.writeError("Failed to check for malicious packages:", error.message);
|
|
18
|
-
}
|
|
19
46
|
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
// Returning the exit code back to the caller allows the promise
|
|
48
|
+
// to be awaited in the bin files and return the correct exit code
|
|
49
|
+
return 1;
|
|
50
|
+
} finally {
|
|
51
|
+
await proxy.stopServer();
|
|
52
|
+
}
|
|
22
53
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ui } from "../../environment/userInteraction.js";
|
|
2
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
3
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
4
|
+
|
|
5
|
+
export function createBunPackageManager() {
|
|
6
|
+
return {
|
|
7
|
+
runCommand: (args) => runBunCommand("bun", args),
|
|
8
|
+
|
|
9
|
+
// For bun, we use the proxy-only approach to block package downloads,
|
|
10
|
+
// so we don't need to analyze commands.
|
|
11
|
+
isSupportedCommand: () => false,
|
|
12
|
+
getDependencyUpdatesForCommand: () => [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createBunxPackageManager() {
|
|
17
|
+
return {
|
|
18
|
+
runCommand: (args) => runBunCommand("bunx", args),
|
|
19
|
+
|
|
20
|
+
// For bunx, we use the proxy-only approach to block package downloads,
|
|
21
|
+
// so we don't need to analyze commands.
|
|
22
|
+
isSupportedCommand: () => false,
|
|
23
|
+
getDependencyUpdatesForCommand: () => [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runBunCommand(command, args) {
|
|
28
|
+
try {
|
|
29
|
+
const result = await safeSpawn(command, args, {
|
|
30
|
+
stdio: "inherit",
|
|
31
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
32
|
+
});
|
|
33
|
+
return { status: result.status };
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.status) {
|
|
36
|
+
return { status: error.status };
|
|
37
|
+
} else {
|
|
38
|
+
ui.writeError("Error executing command:", error.message);
|
|
39
|
+
return { status: 1 };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBunPackageManager,
|
|
3
|
+
createBunxPackageManager,
|
|
4
|
+
} from "./bun/createBunPackageManager.js";
|
|
1
5
|
import { createNpmPackageManager } from "./npm/createPackageManager.js";
|
|
2
6
|
import { createNpxPackageManager } from "./npx/createPackageManager.js";
|
|
3
7
|
import {
|
|
@@ -21,6 +25,10 @@ export function initializePackageManager(packageManagerName, version) {
|
|
|
21
25
|
state.packageManagerName = createPnpmPackageManager();
|
|
22
26
|
} else if (packageManagerName === "pnpx") {
|
|
23
27
|
state.packageManagerName = createPnpxPackageManager();
|
|
28
|
+
} else if (packageManagerName === "bun") {
|
|
29
|
+
state.packageManagerName = createBunPackageManager();
|
|
30
|
+
} else if (packageManagerName === "bunx") {
|
|
31
|
+
state.packageManagerName = createBunxPackageManager();
|
|
24
32
|
} else {
|
|
25
33
|
throw new Error("Unsupported package manager: " + packageManagerName);
|
|
26
34
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ui } from "../../../environment/userInteraction.js";
|
|
2
1
|
import { parseDryRunOutput } from "../parsing/parseNpmInstallDryRunOutput.js";
|
|
3
2
|
import { dryRunNpmCommandAndOutput } from "../runNpmCommand.js";
|
|
4
3
|
import { hasDryRunArg } from "../utils/npmCommands.js";
|
|
@@ -9,6 +8,7 @@ export function dryRunScanner(scannerOptions) {
|
|
|
9
8
|
shouldScan: (args) => shouldScanDependencies(scannerOptions, args),
|
|
10
9
|
};
|
|
11
10
|
}
|
|
11
|
+
|
|
12
12
|
function scanDependencies(scannerOptions, args) {
|
|
13
13
|
let dryRunArgs = args;
|
|
14
14
|
|
|
@@ -32,15 +32,22 @@ function shouldScanDependencies(scannerOptions, args) {
|
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function checkChangesWithDryRun(args) {
|
|
36
|
-
const dryRunOutput = dryRunNpmCommandAndOutput(args);
|
|
35
|
+
async function checkChangesWithDryRun(args) {
|
|
36
|
+
const dryRunOutput = await dryRunNpmCommandAndOutput(args);
|
|
37
37
|
|
|
38
38
|
// Dry-run can return a non-zero status code in some cases
|
|
39
39
|
// e.g., when running "npm audit fix --dry-run", it returns exit code 1
|
|
40
|
-
// when there are
|
|
40
|
+
// when there are vulnerabilities that can be fixed.
|
|
41
|
+
if (dryRunOutput.status !== 0 && !canCommandReturnNonZeroOnSuccess(args)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Dry-run command failed with exit code ${dryRunOutput.status} and output:\n${dryRunOutput.output}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
if (dryRunOutput.status !== 0 && !dryRunOutput.output) {
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Dry-run command failed with exit code ${dryRunOutput.status} and produced no output.`
|
|
50
|
+
);
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
const parsedOutput = parseDryRunOutput(dryRunOutput.output);
|
|
@@ -48,3 +55,13 @@ function checkChangesWithDryRun(args) {
|
|
|
48
55
|
// reverse the array to have the top-level packages first
|
|
49
56
|
return parsedOutput.reverse();
|
|
50
57
|
}
|
|
58
|
+
|
|
59
|
+
function canCommandReturnNonZeroOnSuccess(args) {
|
|
60
|
+
if (args.length < 2) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `npm audit fix --dry-run` can return exit code 1 when it succesfully ran and
|
|
65
|
+
// there were vulnerabilities that could be fixed
|
|
66
|
+
return args[0] === "audit" && args[1] === "fix";
|
|
67
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
1
|
import { ui } from "../../environment/userInteraction.js";
|
|
2
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
3
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
3
4
|
|
|
4
|
-
export function runNpm(args) {
|
|
5
|
+
export async function runNpm(args) {
|
|
5
6
|
try {
|
|
6
|
-
const
|
|
7
|
-
|
|
7
|
+
const result = await safeSpawn("npm", args, {
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
10
|
+
});
|
|
11
|
+
return { status: result.status };
|
|
8
12
|
} catch (error) {
|
|
9
13
|
if (error.status) {
|
|
10
14
|
return { status: error.status };
|
|
@@ -13,17 +17,29 @@ export function runNpm(args) {
|
|
|
13
17
|
return { status: 1 };
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
|
-
return { status: 0 };
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
export function dryRunNpmCommandAndOutput(args) {
|
|
22
|
+
export async function dryRunNpmCommandAndOutput(args) {
|
|
20
23
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const result = await safeSpawn(
|
|
25
|
+
"npm",
|
|
26
|
+
[...args, "--ignore-scripts", "--dry-run"],
|
|
27
|
+
{
|
|
28
|
+
stdio: "pipe",
|
|
29
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
status: result.status,
|
|
34
|
+
output: result.status === 0 ? result.stdout : result.stderr,
|
|
35
|
+
};
|
|
24
36
|
} catch (error) {
|
|
25
37
|
if (error.status) {
|
|
26
|
-
const output =
|
|
38
|
+
const output =
|
|
39
|
+
error.stdout?.toString() ??
|
|
40
|
+
error.stderr?.toString() ??
|
|
41
|
+
error.message ??
|
|
42
|
+
"";
|
|
27
43
|
return { status: error.status, output };
|
|
28
44
|
} else {
|
|
29
45
|
ui.writeError("Error executing command:", error.message);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
1
|
import { ui } from "../../environment/userInteraction.js";
|
|
2
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
3
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
3
4
|
|
|
4
|
-
export function runNpx(args) {
|
|
5
|
+
export async function runNpx(args) {
|
|
5
6
|
try {
|
|
6
|
-
const
|
|
7
|
-
|
|
7
|
+
const result = await safeSpawn("npx", args, {
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
10
|
+
});
|
|
11
|
+
return { status: result.status };
|
|
8
12
|
} catch (error) {
|
|
9
13
|
if (error.status) {
|
|
10
14
|
return { status: error.status };
|
|
@@ -13,5 +17,4 @@ export function runNpx(args) {
|
|
|
13
17
|
return { status: 1 };
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
|
-
return { status: 0 };
|
|
17
20
|
}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import { spawnSync } from "child_process";
|
|
2
1
|
import { ui } from "../../environment/userInteraction.js";
|
|
2
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
3
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
3
4
|
|
|
4
|
-
export function runPnpmCommand(args, toolName = "pnpm") {
|
|
5
|
+
export async function runPnpmCommand(args, toolName = "pnpm") {
|
|
5
6
|
try {
|
|
6
7
|
let result;
|
|
7
|
-
|
|
8
8
|
if (toolName === "pnpm") {
|
|
9
|
-
result =
|
|
9
|
+
result = await safeSpawn("pnpm", args, {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
12
|
+
});
|
|
10
13
|
} else if (toolName === "pnpx") {
|
|
11
|
-
result =
|
|
14
|
+
result = await safeSpawn("pnpx", args, {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
17
|
+
});
|
|
12
18
|
} else {
|
|
13
19
|
throw new Error(`Unsupported tool name for aikido-pnpm: ${toolName}`);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
return { status: result.status };
|
|
18
|
-
}
|
|
22
|
+
return { status: result.status };
|
|
19
23
|
} catch (error) {
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
if (error.status) {
|
|
25
|
+
return { status: error.status };
|
|
26
|
+
} else {
|
|
27
|
+
ui.writeError("Error executing command:", error.message);
|
|
28
|
+
return { status: 1 };
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
|
-
return { status: 0 };
|
|
24
31
|
}
|