@aikidosec/safe-chain 1.5.2 β 1.5.3
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 +19 -5
- package/bin/aikido-rush.js +14 -0
- package/bin/aikido-rushx.js +14 -0
- package/bin/safe-chain.js +1 -1
- package/docs/shell-integration.md +4 -4
- package/docs/troubleshooting.md +68 -91
- package/npm-shrinkwrap.json +3 -1
- package/package.json +4 -2
- package/src/packagemanager/currentPackageManager.js +6 -0
- package/src/packagemanager/rush/createRushPackageManager.js +64 -0
- package/src/packagemanager/rush/parsing/parsePackagesFromRushAddArgs.js +71 -0
- package/src/packagemanager/rush/runRushCommand.js +21 -0
- package/src/packagemanager/rushx/createRushxPackageManager.js +18 -0
- package/src/shell-integration/helpers.js +12 -0
- package/src/shell-integration/startup-scripts/init-fish.fish +8 -0
- package/src/shell-integration/startup-scripts/init-posix.sh +8 -0
- package/src/shell-integration/startup-scripts/init-pwsh.ps1 +8 -0
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Aikido Safe Chain supports the following package managers:
|
|
|
25
25
|
- π¦ **yarn**
|
|
26
26
|
- π¦ **pnpm**
|
|
27
27
|
- π¦ **pnpx**
|
|
28
|
+
- π¦ **rush**
|
|
29
|
+
- π¦ **rushx**
|
|
28
30
|
- π¦ **bun**
|
|
29
31
|
- π¦ **bunx**
|
|
30
32
|
- π¦ **pip**
|
|
@@ -75,7 +77,7 @@ You can find all available versions on the [releases page](https://github.com/Ai
|
|
|
75
77
|
### Verify the installation
|
|
76
78
|
|
|
77
79
|
1. **βRestart your terminal** to start using the Aikido Safe Chain.
|
|
78
|
-
- This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm, pnpx, bun, bunx, pip, pip3, poetry, uv, uvx and pipx are loaded correctly. If you do not restart your terminal, the aliases will not be available.
|
|
80
|
+
- This step is crucial as it ensures that the shell aliases for npm, npx, yarn, pnpm, pnpx, rush, rushx, bun, bunx, pip, pip3, poetry, uv, uvx and pipx are loaded correctly. If you do not restart your terminal, the aliases will not be available.
|
|
79
81
|
|
|
80
82
|
2. **Verify the installation** by running the verification command:
|
|
81
83
|
|
|
@@ -106,7 +108,7 @@ You can find all available versions on the [releases page](https://github.com/Ai
|
|
|
106
108
|
|
|
107
109
|
- The output should show that Aikido Safe Chain is blocking the installation of these test packages as they are flagged as malware.
|
|
108
110
|
|
|
109
|
-
When running `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx` commands, the Aikido Safe Chain will automatically check for malware in the packages you are trying to install. It also intercepts Python module invocations for pip when available (e.g., `python -m pip install ...`, `python3 -m pip download ...`). If any malware is detected, it will prompt you to exit the command.
|
|
111
|
+
When running `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `rush`, `rushx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx` commands, the Aikido Safe Chain will automatically check for malware in the packages you are trying to install. It also intercepts Python module invocations for pip when available (e.g., `python -m pip install ...`, `python3 -m pip download ...`). If any malware is detected, it will prompt you to exit the command.
|
|
110
112
|
|
|
111
113
|
You can check the installed version by running:
|
|
112
114
|
|
|
@@ -118,7 +120,7 @@ safe-chain --version
|
|
|
118
120
|
|
|
119
121
|
### Malware Blocking
|
|
120
122
|
|
|
121
|
-
The Aikido Safe Chain works by running a lightweight proxy server that intercepts package downloads from the npm registry and PyPI. When you run npm, npx, yarn, pnpm, pnpx, bun, bunx, pip, pip3, uv, uvx, poetry or pipx commands, all package downloads are routed through this local proxy, which verifies packages in real-time against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.
|
|
123
|
+
The Aikido Safe Chain works by running a lightweight proxy server that intercepts package downloads from the npm registry and PyPI. When you run npm, npx, yarn, pnpm, pnpx, rush, rushx, bun, bunx, pip, pip3, uv, uvx, poetry or pipx commands, all package downloads are routed through this local proxy, which verifies packages in real-time against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.
|
|
122
124
|
|
|
123
125
|
### Minimum package age
|
|
124
126
|
|
|
@@ -137,7 +139,7 @@ By default, the minimum package age is 48 hours. This provides an additional sec
|
|
|
137
139
|
|
|
138
140
|
### Shell Integration
|
|
139
141
|
|
|
140
|
-
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm, pnpx, bun, bunx, and Python package managers (pip, uv, uvx, poetry, pipx). It sets up aliases for these commands so that they are wrapped by the Aikido Safe Chain commands, which manage the proxy server before executing the original commands. We currently support:
|
|
142
|
+
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm, pnpx, rush, rushx, bun, bunx, and Python package managers (pip, uv, uvx, poetry, pipx). It sets up aliases for these commands so that they are wrapped by the Aikido Safe Chain commands, which manage the proxy server before executing the original commands. We currently support:
|
|
141
143
|
|
|
142
144
|
- β
**Bash**
|
|
143
145
|
- β
**Zsh**
|
|
@@ -548,4 +550,16 @@ npm-ci:
|
|
|
548
550
|
|
|
549
551
|
# Troubleshooting
|
|
550
552
|
|
|
551
|
-
Having issues? See the [Troubleshooting Guide](
|
|
553
|
+
Having issues? See the [Troubleshooting Guide](./docs/troubleshooting) for help with common problems.
|
|
554
|
+
|
|
555
|
+
# Report Issues
|
|
556
|
+
|
|
557
|
+
If you encounter problems:
|
|
558
|
+
|
|
559
|
+
1. Visit [GitHub Issues](https://github.com/AikidoSec/safe-chain/issues)
|
|
560
|
+
2. Include:
|
|
561
|
+
* Operating system and version
|
|
562
|
+
* Shell type and version
|
|
563
|
+
* `safe-chain --version` output
|
|
564
|
+
* Output from verification commands
|
|
565
|
+
* Verbose logs of the failing command (add the `--safe-chain-logging=verbose` argument)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
|
|
6
|
+
|
|
7
|
+
setEcoSystem(ECOSYSTEM_JS);
|
|
8
|
+
const packageManagerName = "rush";
|
|
9
|
+
initializePackageManager(packageManagerName);
|
|
10
|
+
|
|
11
|
+
(async () => {
|
|
12
|
+
var exitCode = await main(process.argv.slice(2));
|
|
13
|
+
process.exit(exitCode);
|
|
14
|
+
})();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/main.js";
|
|
4
|
+
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
|
5
|
+
import { setEcoSystem, ECOSYSTEM_JS } from "../src/config/settings.js";
|
|
6
|
+
|
|
7
|
+
setEcoSystem(ECOSYSTEM_JS);
|
|
8
|
+
const packageManagerName = "rushx";
|
|
9
|
+
initializePackageManager(packageManagerName);
|
|
10
|
+
|
|
11
|
+
(async () => {
|
|
12
|
+
var exitCode = await main(process.argv.slice(2));
|
|
13
|
+
process.exit(exitCode);
|
|
14
|
+
})();
|
package/bin/safe-chain.js
CHANGED
|
@@ -108,7 +108,7 @@ function writeHelp() {
|
|
|
108
108
|
ui.writeInformation(
|
|
109
109
|
`- ${chalk.cyan(
|
|
110
110
|
"safe-chain setup",
|
|
111
|
-
)}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`,
|
|
111
|
+
)}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, rush, rushx, bun, bunx, pip and pip3.`,
|
|
112
112
|
);
|
|
113
113
|
ui.writeInformation(
|
|
114
114
|
`- ${chalk.cyan(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry`, `pipx`) with Aikido's security scanning functionality. It also intercepts Python module invocations for pip when available: `python -m pip`, `python -m pip3`, `python3 -m pip`, `python3 -m pip3`. This is achieved by sourcing startup scripts that define shell functions to wrap these commands with their Aikido-protected equivalents.
|
|
5
|
+
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `rush`, `rushx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry`, `pipx`) with Aikido's security scanning functionality. It also intercepts Python module invocations for pip when available: `python -m pip`, `python -m pip3`, `python3 -m pip`, `python3 -m pip3`. 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
|
|
|
@@ -28,7 +28,7 @@ This command:
|
|
|
28
28
|
|
|
29
29
|
- Copies necessary startup scripts to Safe Chain's installation directory (`~/.safe-chain/scripts`)
|
|
30
30
|
- Detects all supported shells on your system
|
|
31
|
-
- Sources each shell's startup file to add Safe Chain functions for `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx`
|
|
31
|
+
- Sources each shell's startup file to add Safe Chain functions for `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `rush`, `rushx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx`
|
|
32
32
|
- Adds lightweight interceptors so `python -m pip[...]` and `python3 -m pip[...]` route through Safe Chain when invoked by name
|
|
33
33
|
|
|
34
34
|
β After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the startup scripts are sourced correctly.
|
|
@@ -78,7 +78,7 @@ The system modifies the following files to source Safe Chain startup scripts:
|
|
|
78
78
|
This means the shell functions are working but the Aikido commands aren't installed or available in your PATH:
|
|
79
79
|
|
|
80
80
|
- Make sure Aikido Safe Chain is properly installed on your system
|
|
81
|
-
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm`, `aikido-pnpx`, `aikido-bun`, `aikido-bunx`, `aikido-pip`, `aikido-pip3`, `aikido-uv`, `aikido-uvx`, `aikido-poetry` and `aikido-pipx` commands exist
|
|
81
|
+
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm`, `aikido-pnpx`, `aikido-rush`, `aikido-rushx`, `aikido-bun`, `aikido-bunx`, `aikido-pip`, `aikido-pip3`, `aikido-uv`, `aikido-uvx`, `aikido-poetry` and `aikido-pipx` commands exist
|
|
82
82
|
- Check that these commands are in your system's PATH
|
|
83
83
|
|
|
84
84
|
### Manual Verification
|
|
@@ -121,7 +121,7 @@ npm() {
|
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
Repeat this pattern for `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx` using their respective `aikido-*` commands. After adding these functions, restart your terminal to apply the changes.
|
|
124
|
+
Repeat this pattern for `npx`, `yarn`, `pnpm`, `pnpx`, `rush`, `rushx`, `bun`, `bunx`, `pip`, `pip3`, `uv`, `uvx`, `poetry` and `pipx` using their respective `aikido-*` commands. After adding these functions, restart your terminal to apply the changes.
|
|
125
125
|
|
|
126
126
|
To intercept Python module invocations for pip without altering Python itself, you can add small forwarding functions:
|
|
127
127
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -4,49 +4,38 @@ This guide helps you diagnose and resolve common issues with Aikido Safe Chain.
|
|
|
4
4
|
|
|
5
5
|
## Verification & Diagnostics
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Check Installation**
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
# Check version
|
|
11
11
|
safe-chain --version
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**Verify Shell Integration**
|
|
15
15
|
|
|
16
16
|
Run the verification command for your package manager:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm safe-chain-verify
|
|
20
20
|
pnpm safe-chain-verify
|
|
21
|
-
pip safe-chain-verify
|
|
22
|
-
uv safe-chain-verify
|
|
23
|
-
|
|
24
|
-
# Any other supported package manager: {packagemanager} safe-chain-verify
|
|
25
21
|
```
|
|
26
22
|
|
|
23
|
+
```
|
|
27
24
|
Expected output: `OK: Safe-chain works!`
|
|
25
|
+
```
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
**Test Malware Blocking**
|
|
30
28
|
|
|
31
29
|
Verify that malware detection is working:
|
|
32
|
-
|
|
33
|
-
**For JavaScript/Node.js:**
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm install safe-chain-test
|
|
37
30
|
```
|
|
38
|
-
|
|
39
|
-
**For Python:**
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pip3 install safe-chain-pi-test
|
|
31
|
+
npm install safe-chain-test
|
|
43
32
|
```
|
|
44
33
|
|
|
45
34
|
These test packages are flagged as malware and should be blocked by Safe Chain.
|
|
46
35
|
|
|
47
|
-
**If the test package installs successfully instead of being blocked**, see
|
|
36
|
+
**If the test package installs successfully instead of being blocked**, see Malware Not Being Blocked below.
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
## Logging Options
|
|
50
39
|
|
|
51
40
|
Use logging flags or environment variables to get more information:
|
|
52
41
|
|
|
@@ -74,41 +63,39 @@ Safe-chain blocks malicious packages by intercepting network requests to package
|
|
|
74
63
|
|
|
75
64
|
When a package is already cached locally, the package manager skips downloading it from the registry, which bypasses the proxy.
|
|
76
65
|
|
|
77
|
-
**Resolution Steps
|
|
78
|
-
|
|
79
|
-
1. **Clear your package manager's cache:**
|
|
66
|
+
**Resolution Steps**
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
# For npm
|
|
83
|
-
npm cache clean --force
|
|
68
|
+
1) Clear your package manager's cache
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
```bash
|
|
71
|
+
# For npm
|
|
72
|
+
npm cache clean --force
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
# For pnpm
|
|
75
|
+
pnpm store prune
|
|
90
76
|
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
# For yarn (classic)
|
|
78
|
+
yarn cache clean
|
|
93
79
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```
|
|
80
|
+
# For yarn (berry/v2+)
|
|
81
|
+
yarn cache clean --all
|
|
97
82
|
|
|
98
|
-
|
|
83
|
+
# For bun
|
|
84
|
+
bun pm cache rm
|
|
85
|
+
```
|
|
99
86
|
|
|
100
|
-
2
|
|
87
|
+
2) Clean local installation artifacts:
|
|
101
88
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
```bash
|
|
90
|
+
# Remove node_modules if you want a completely fresh install
|
|
91
|
+
rm -rf node_modules
|
|
92
|
+
```
|
|
106
93
|
|
|
107
|
-
3
|
|
94
|
+
3) Re-test malware blocking:
|
|
108
95
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
96
|
+
```bash
|
|
97
|
+
npm install safe-chain-test # Should be blocked
|
|
98
|
+
```
|
|
112
99
|
|
|
113
100
|
### Shell Aliases Not Working After Installation
|
|
114
101
|
|
|
@@ -128,10 +115,10 @@ Should show: `npm is a function`
|
|
|
128
115
|
|
|
129
116
|
Check that your startup file sources safe-chain scripts from `~/.safe-chain/scripts/`:
|
|
130
117
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
118
|
+
* Bash: `~/.bashrc`
|
|
119
|
+
* Zsh: `~/.zshrc`
|
|
120
|
+
* Fish: `~/.config/fish/config.fish`
|
|
121
|
+
* PowerShell: `$PROFILE`
|
|
135
122
|
|
|
136
123
|
### "Command Not Found: safe-chain"
|
|
137
124
|
|
|
@@ -162,37 +149,39 @@ FullyQualifiedErrorId : UnauthorizedAccess
|
|
|
162
149
|
|
|
163
150
|
**Cause:** Windows PowerShell's default execution policy (`Restricted`) blocks all script execution, including safe-chain's initialization script that's sourced from your PowerShell profile.
|
|
164
151
|
|
|
165
|
-
**Resolution
|
|
152
|
+
**Resolution**
|
|
166
153
|
|
|
167
|
-
1
|
|
154
|
+
1) Set the execution policy to allow local scripts
|
|
168
155
|
|
|
169
|
-
|
|
156
|
+
Open PowerShell as Administrator and run:
|
|
170
157
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
158
|
+
```powershell
|
|
159
|
+
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This allows:
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- Downloaded scripts to run only if signed by a trusted publisher
|
|
164
|
+
* Local scripts (like safe-chain's) to run without signing
|
|
165
|
+
* Downloaded scripts to run only if signed by a trusted publisher
|
|
178
166
|
|
|
179
|
-
2
|
|
167
|
+
2) Restart PowerShell and verify the error is resolved.
|
|
180
168
|
|
|
181
|
-
>
|
|
169
|
+
> [!IMPORTANT]
|
|
170
|
+
> `RemoteSigned` is Microsoft's recommended execution policy for client computers. It provides a good balance between security and usability.
|
|
182
171
|
|
|
183
172
|
### Shell Aliases Persist After Uninstallation
|
|
184
173
|
|
|
185
174
|
**Symptom:** safe-chain commands still active after running uninstall script
|
|
186
175
|
|
|
187
|
-
**Steps
|
|
176
|
+
**Steps**
|
|
188
177
|
|
|
189
178
|
1. Run `safe-chain teardown` (if binary still exists)
|
|
190
179
|
2. Restart your terminal
|
|
191
180
|
3. If still present, manually edit shell config files:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
* Bash: `~/.bashrc`
|
|
182
|
+
* Zsh: `~/.zshrc`
|
|
183
|
+
* Fish: `~/.config/fish/config.fish`
|
|
184
|
+
* PowerShell: `$PROFILE`
|
|
196
185
|
4. Remove lines that source scripts from `~/.safe-chain/scripts/`
|
|
197
186
|
5. Restart terminal again
|
|
198
187
|
|
|
@@ -217,10 +206,10 @@ type pip
|
|
|
217
206
|
|
|
218
207
|
**Expected `which` output:**
|
|
219
208
|
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
* Standalone binary (correct): `~/.safe-chain/bin/safe-chain` or `/Users/<username>/.safe-chain/bin/safe-chain`
|
|
210
|
+
* npm global (outdated): path containing `node_modules` or nvm version paths
|
|
222
211
|
|
|
223
|
-
If `which` shows an npm installation, see
|
|
212
|
+
If `which` shows an npm installation, see Check for Conflicting Installations.
|
|
224
213
|
|
|
225
214
|
### Check Shell Integration
|
|
226
215
|
|
|
@@ -259,23 +248,23 @@ for version in $(nvm list | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+'); do
|
|
|
259
248
|
done
|
|
260
249
|
```
|
|
261
250
|
|
|
262
|
-
|
|
251
|
+
### Manual Cleanup
|
|
263
252
|
|
|
264
253
|
> **Note:** The install and uninstall scripts automatically handle these cleanup steps. Use these manual commands only if automatic cleanup fails.
|
|
265
254
|
|
|
266
|
-
|
|
255
|
+
#### Remove npm Global Installation
|
|
267
256
|
|
|
268
257
|
```bash
|
|
269
258
|
npm uninstall -g @aikidosec/safe-chain
|
|
270
259
|
```
|
|
271
260
|
|
|
272
|
-
|
|
261
|
+
#### Remove Volta Installation
|
|
273
262
|
|
|
274
263
|
```bash
|
|
275
264
|
volta uninstall @aikidosec/safe-chain
|
|
276
265
|
```
|
|
277
266
|
|
|
278
|
-
|
|
267
|
+
#### Remove nvm Installations (All Versions)
|
|
279
268
|
|
|
280
269
|
```bash
|
|
281
270
|
# Automated approach
|
|
@@ -288,34 +277,22 @@ nvm use <version>
|
|
|
288
277
|
npm uninstall -g @aikidosec/safe-chain
|
|
289
278
|
```
|
|
290
279
|
|
|
291
|
-
|
|
280
|
+
#### Clean Shell Configuration Files
|
|
292
281
|
|
|
293
282
|
Manually remove safe-chain entries from:
|
|
294
283
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
284
|
+
* Bash: `~/.bashrc`
|
|
285
|
+
* Zsh: `~/.zshrc`
|
|
286
|
+
* Fish: `~/.config/fish/config.fish`
|
|
287
|
+
* PowerShell: `$PROFILE`
|
|
299
288
|
|
|
300
289
|
Look for and remove:
|
|
301
290
|
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
* Lines sourcing from `~/.safe-chain/scripts/`
|
|
292
|
+
* Any safe-chain related function definitions
|
|
304
293
|
|
|
305
|
-
|
|
294
|
+
#### Remove Installation Directory
|
|
306
295
|
|
|
307
296
|
```bash
|
|
308
297
|
rm -rf ~/.safe-chain
|
|
309
298
|
```
|
|
310
|
-
|
|
311
|
-
### Report Issues
|
|
312
|
-
|
|
313
|
-
If you encounter problems:
|
|
314
|
-
|
|
315
|
-
1. Visit [GitHub Issues](https://github.com/AikidoSec/safe-chain/issues)
|
|
316
|
-
2. Include:
|
|
317
|
-
- Operating system and version
|
|
318
|
-
- Shell type and version
|
|
319
|
-
- `safe-chain --version` output
|
|
320
|
-
- Output from verification commands
|
|
321
|
-
- Verbose logs of the failing command (add the `--safe-chain-logging=verbose` argument)
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -3112,7 +3112,7 @@
|
|
|
3112
3112
|
},
|
|
3113
3113
|
"packages/safe-chain": {
|
|
3114
3114
|
"name": "@aikidosec/safe-chain",
|
|
3115
|
-
"version": "1.5.
|
|
3115
|
+
"version": "1.5.3",
|
|
3116
3116
|
"license": "AGPL-3.0-or-later",
|
|
3117
3117
|
"dependencies": {
|
|
3118
3118
|
"certifi": "14.5.15",
|
|
@@ -3137,6 +3137,8 @@
|
|
|
3137
3137
|
"aikido-poetry": "bin/aikido-poetry.js",
|
|
3138
3138
|
"aikido-python": "bin/aikido-python.js",
|
|
3139
3139
|
"aikido-python3": "bin/aikido-python3.js",
|
|
3140
|
+
"aikido-rush": "bin/aikido-rush.js",
|
|
3141
|
+
"aikido-rushx": "bin/aikido-rushx.js",
|
|
3140
3142
|
"aikido-uv": "bin/aikido-uv.js",
|
|
3141
3143
|
"aikido-uvx": "bin/aikido-uvx.js",
|
|
3142
3144
|
"aikido-yarn": "bin/aikido-yarn.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/safe-chain",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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'",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"aikido-yarn": "bin/aikido-yarn.js",
|
|
14
14
|
"aikido-pnpm": "bin/aikido-pnpm.js",
|
|
15
15
|
"aikido-pnpx": "bin/aikido-pnpx.js",
|
|
16
|
+
"aikido-rush": "bin/aikido-rush.js",
|
|
17
|
+
"aikido-rushx": "bin/aikido-rushx.js",
|
|
16
18
|
"aikido-bun": "bin/aikido-bun.js",
|
|
17
19
|
"aikido-bunx": "bin/aikido-bunx.js",
|
|
18
20
|
"aikido-uv": "bin/aikido-uv.js",
|
|
@@ -37,7 +39,7 @@
|
|
|
37
39
|
"keywords": [],
|
|
38
40
|
"author": "Aikido Security",
|
|
39
41
|
"license": "AGPL-3.0-or-later",
|
|
40
|
-
"description": "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/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), [bunx](https://bun.sh/docs/cli/bunx), [uv](https://docs.astral.sh/uv/) (Python), and [pip](https://pip.pypa.io/) 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, pnpx, bun, bunx, uv, uvx, or pip/pip3 from downloading or running the malware.",
|
|
42
|
+
"description": "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/), [pnpx](https://pnpm.io/cli/dlx), [rush](https://rushjs.io/), [rushx](https://rushjs.io/pages/commands/rushx/), [bun](https://bun.sh/), [bunx](https://bun.sh/docs/cli/bunx), [uv](https://docs.astral.sh/uv/) (Python), and [pip](https://pip.pypa.io/) 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, pnpx, rush, rushx, bun, bunx, uv, uvx, or pip/pip3 from downloading or running the malware.",
|
|
41
43
|
"dependencies": {
|
|
42
44
|
"certifi": "14.5.15",
|
|
43
45
|
"chalk": "5.4.1",
|
|
@@ -13,6 +13,8 @@ import { createPipPackageManager } from "./pip/createPackageManager.js";
|
|
|
13
13
|
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
|
|
14
14
|
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
|
|
15
15
|
import { createPipXPackageManager } from "./pipx/createPipXPackageManager.js";
|
|
16
|
+
import { createRushPackageManager } from "./rush/createRushPackageManager.js";
|
|
17
|
+
import { createRushxPackageManager } from "./rushx/createRushxPackageManager.js";
|
|
16
18
|
import { createUvxPackageManager } from "./uvx/createUvxPackageManager.js";
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -67,6 +69,10 @@ export function initializePackageManager(packageManagerName, context) {
|
|
|
67
69
|
state.packageManagerName = createPoetryPackageManager();
|
|
68
70
|
} else if (packageManagerName === "pipx") {
|
|
69
71
|
state.packageManagerName = createPipXPackageManager();
|
|
72
|
+
} else if (packageManagerName === "rush") {
|
|
73
|
+
state.packageManagerName = createRushPackageManager();
|
|
74
|
+
} else if (packageManagerName === "rushx") {
|
|
75
|
+
state.packageManagerName = createRushxPackageManager();
|
|
70
76
|
} else {
|
|
71
77
|
throw new Error("Unsupported package manager: " + packageManagerName);
|
|
72
78
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { runRushCommand } from "./runRushCommand.js";
|
|
2
|
+
import { resolvePackageVersion } from "../../api/npmApi.js";
|
|
3
|
+
import { parsePackagesFromRushAddArgs } from "./parsing/parsePackagesFromRushAddArgs.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @returns {import("../currentPackageManager.js").PackageManager}
|
|
7
|
+
*/
|
|
8
|
+
export function createRushPackageManager() {
|
|
9
|
+
return {
|
|
10
|
+
runCommand: (args) => runRushCommand("rush", args),
|
|
11
|
+
// We pre-scan rush add commands and rely on MITM for install/update flows.
|
|
12
|
+
isSupportedCommand: (args) => getRushCommand(args) === "add",
|
|
13
|
+
getDependencyUpdatesForCommand: scanRushAddCommand,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string[]} args
|
|
19
|
+
* @returns {Promise<import("../currentPackageManager.js").GetDependencyUpdatesResult[]>}
|
|
20
|
+
*/
|
|
21
|
+
async function scanRushAddCommand(args) {
|
|
22
|
+
if (getRushCommand(args) !== "add") {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const parsedSpecs = parsePackagesFromRushAddArgs(args.slice(1));
|
|
27
|
+
|
|
28
|
+
const resolvedVersions = await Promise.all(
|
|
29
|
+
parsedSpecs.map(async (parsed) => {
|
|
30
|
+
const exactVersion = await resolvePackageVersion(parsed.name, parsed.version);
|
|
31
|
+
return {
|
|
32
|
+
parsed,
|
|
33
|
+
exactVersion,
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const changes = [];
|
|
39
|
+
for (const resolved of resolvedVersions) {
|
|
40
|
+
if (!resolved.exactVersion) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
changes.push({
|
|
45
|
+
name: resolved.parsed.name,
|
|
46
|
+
version: resolved.exactVersion,
|
|
47
|
+
type: "add",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return changes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string[]} args
|
|
56
|
+
* @returns {string | undefined}
|
|
57
|
+
*/
|
|
58
|
+
function getRushCommand(args) {
|
|
59
|
+
if (!args || args.length === 0) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return args[0]?.toLowerCase();
|
|
64
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string[]} args
|
|
3
|
+
* @returns {{name: string, version: string | null}[]}
|
|
4
|
+
*/
|
|
5
|
+
export function parsePackagesFromRushAddArgs(args) {
|
|
6
|
+
const packageSpecs = [];
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < args.length; i++) {
|
|
9
|
+
const arg = args[i];
|
|
10
|
+
if (!arg) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (arg === "--package" || arg === "-p") {
|
|
15
|
+
const next = args[i + 1];
|
|
16
|
+
if (next && !next.startsWith("-")) {
|
|
17
|
+
packageSpecs.push(next);
|
|
18
|
+
i += 1;
|
|
19
|
+
}
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (arg.startsWith("--package=")) {
|
|
24
|
+
const value = arg.slice("--package=".length);
|
|
25
|
+
if (value) {
|
|
26
|
+
packageSpecs.push(value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return packageSpecs
|
|
32
|
+
.map((spec) => parsePackageSpec(spec))
|
|
33
|
+
.filter((spec) => spec !== null);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} spec
|
|
38
|
+
* @returns {{name: string, version: string | null} | null}
|
|
39
|
+
*/
|
|
40
|
+
function parsePackageSpec(spec) {
|
|
41
|
+
const value = removeAlias(spec.trim());
|
|
42
|
+
if (!value) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lastAtIndex = value.lastIndexOf("@");
|
|
47
|
+
if (lastAtIndex > 0) {
|
|
48
|
+
return {
|
|
49
|
+
name: value.slice(0, lastAtIndex),
|
|
50
|
+
version: value.slice(lastAtIndex + 1),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: value,
|
|
56
|
+
version: null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} spec
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function removeAlias(spec) {
|
|
65
|
+
const aliasIndex = spec.indexOf("@npm:");
|
|
66
|
+
if (aliasIndex !== -1) {
|
|
67
|
+
return spec.slice(aliasIndex + 5);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return spec;
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
|
2
|
+
import { safeSpawn } from "../../utils/safeSpawn.js";
|
|
3
|
+
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {"rush" | "rushx"} executableName
|
|
7
|
+
* @param {string[]} args
|
|
8
|
+
* @returns {Promise<{status: number}>}
|
|
9
|
+
*/
|
|
10
|
+
export async function runRushCommand(executableName, args) {
|
|
11
|
+
try {
|
|
12
|
+
const result = await safeSpawn(executableName, args, {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return { status: result.status };
|
|
18
|
+
} catch (/** @type any */ error) {
|
|
19
|
+
return reportCommandExecutionFailure(error, executableName);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runRushCommand } from "../rush/runRushCommand.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @returns {import("../currentPackageManager.js").PackageManager}
|
|
5
|
+
*/
|
|
6
|
+
export function createRushxPackageManager() {
|
|
7
|
+
return {
|
|
8
|
+
/**
|
|
9
|
+
* @param {string[]} args
|
|
10
|
+
*/
|
|
11
|
+
runCommand: (args) => {
|
|
12
|
+
return runRushCommand("rushx", args);
|
|
13
|
+
},
|
|
14
|
+
// For rushx, rely solely on MITM.
|
|
15
|
+
isSupportedCommand: () => false,
|
|
16
|
+
getDependencyUpdatesForCommand: () => [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -48,6 +48,18 @@ export const knownAikidoTools = [
|
|
|
48
48
|
ecoSystem: ECOSYSTEM_JS,
|
|
49
49
|
internalPackageManagerName: "pnpx",
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
tool: "rush",
|
|
53
|
+
aikidoCommand: "aikido-rush",
|
|
54
|
+
ecoSystem: ECOSYSTEM_JS,
|
|
55
|
+
internalPackageManagerName: "rush",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tool: "rushx",
|
|
59
|
+
aikidoCommand: "aikido-rushx",
|
|
60
|
+
ecoSystem: ECOSYSTEM_JS,
|
|
61
|
+
internalPackageManagerName: "rushx",
|
|
62
|
+
},
|
|
51
63
|
{
|
|
52
64
|
tool: "bun",
|
|
53
65
|
aikidoCommand: "aikido-bun",
|
|
@@ -19,6 +19,14 @@ function pnpx
|
|
|
19
19
|
wrapSafeChainCommand "pnpx" $argv
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
function rush
|
|
23
|
+
wrapSafeChainCommand "rush" $argv
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function rushx
|
|
27
|
+
wrapSafeChainCommand "rushx" $argv
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
function bun
|
|
23
31
|
wrapSafeChainCommand "bun" $argv
|
|
24
32
|
end
|
|
@@ -28,6 +28,14 @@ function pnpx() {
|
|
|
28
28
|
wrapSafeChainCommand "pnpx" "$@"
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function rush() {
|
|
32
|
+
wrapSafeChainCommand "rush" "$@"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function rushx() {
|
|
36
|
+
wrapSafeChainCommand "rushx" "$@"
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
function bun() {
|
|
32
40
|
wrapSafeChainCommand "bun" "$@"
|
|
33
41
|
}
|
|
@@ -22,6 +22,14 @@ function pnpx {
|
|
|
22
22
|
Invoke-WrappedCommand "pnpx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function rush {
|
|
26
|
+
Invoke-WrappedCommand "rush" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rushx {
|
|
30
|
+
Invoke-WrappedCommand "rushx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
function bun {
|
|
26
34
|
Invoke-WrappedCommand "bun" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
27
35
|
}
|