@aikidosec/safe-chain 1.3.3 → 1.4.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 +68 -5
- package/bin/safe-chain.js +5 -2
- package/docs/troubleshooting.md +249 -0
- package/package.json +1 -1
- package/src/config/configFile.js +26 -0
- package/src/config/environmentVariables.js +10 -0
- package/src/config/settings.js +18 -0
- package/src/main.js +13 -0
- package/src/registryProxy/interceptors/pipInterceptor.js +6 -3
- package/src/registryProxy/mitmRequestHandler.js +11 -8
- package/src/shell-integration/startup-scripts/init-posix.sh +1 -1
- package/docs/npm-to-binary-migration.md +0 -89
package/README.md
CHANGED
|
@@ -33,8 +33,6 @@ Aikido Safe Chain supports the following package managers:
|
|
|
33
33
|
|
|
34
34
|
Installing the Aikido Safe Chain is easy with our one-line installer.
|
|
35
35
|
|
|
36
|
-
> ⚠️ **Already installed via npm?** See the [migration guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/npm-to-binary-migration.md) to switch to the binary version.
|
|
37
|
-
|
|
38
36
|
### Unix/Linux/macOS
|
|
39
37
|
|
|
40
38
|
```shell
|
|
@@ -71,7 +69,20 @@ You can find all available versions on the [releases page](https://github.com/Ai
|
|
|
71
69
|
|
|
72
70
|
- 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.
|
|
73
71
|
|
|
74
|
-
2. **Verify the installation** by running
|
|
72
|
+
2. **Verify the installation** by running the verification command:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
npm safe-chain-verify
|
|
76
|
+
pnpm safe-chain-verify
|
|
77
|
+
pip safe-chain-verify
|
|
78
|
+
uv safe-chain-verify
|
|
79
|
+
|
|
80
|
+
# Any other supported package manager: {packagemanager} safe-chain-verify
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- The output should display "OK: Safe-chain works!" confirming that Aikido Safe Chain is properly installed and running.
|
|
84
|
+
|
|
85
|
+
3. **(Optional) Test malware blocking** by attempting to install a test package:
|
|
75
86
|
|
|
76
87
|
For JavaScript/Node.js:
|
|
77
88
|
|
|
@@ -188,9 +199,14 @@ You can set the minimum package age through multiple sources (in order of priori
|
|
|
188
199
|
}
|
|
189
200
|
```
|
|
190
201
|
|
|
191
|
-
## Custom
|
|
202
|
+
## Custom Registries
|
|
192
203
|
|
|
193
|
-
Configure Safe Chain to scan packages from custom or private
|
|
204
|
+
Configure Safe Chain to scan packages from custom or private registries.
|
|
205
|
+
|
|
206
|
+
Supported ecosystems:
|
|
207
|
+
|
|
208
|
+
- Node.js
|
|
209
|
+
- Python
|
|
194
210
|
|
|
195
211
|
### Configuration Options
|
|
196
212
|
|
|
@@ -200,6 +216,7 @@ You can set custom registries through environment variable or config file. Both
|
|
|
200
216
|
|
|
201
217
|
```shell
|
|
202
218
|
export SAFE_CHAIN_NPM_CUSTOM_REGISTRIES="npm.company.com,registry.internal.net"
|
|
219
|
+
export SAFE_CHAIN_PIP_CUSTOM_REGISTRIES="pip.company.com,registry.internal.net"
|
|
203
220
|
```
|
|
204
221
|
|
|
205
222
|
2. **Config File** (`~/.aikido/config.json`):
|
|
@@ -208,6 +225,9 @@ You can set custom registries through environment variable or config file. Both
|
|
|
208
225
|
{
|
|
209
226
|
"npm": {
|
|
210
227
|
"customRegistries": ["npm.company.com", "registry.internal.net"]
|
|
228
|
+
},
|
|
229
|
+
"pip": {
|
|
230
|
+
"customRegistries": ["pip.company.com", "registry.internal.net"]
|
|
211
231
|
}
|
|
212
232
|
}
|
|
213
233
|
```
|
|
@@ -237,6 +257,7 @@ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download
|
|
|
237
257
|
- ✅ **GitHub Actions**
|
|
238
258
|
- ✅ **Azure Pipelines**
|
|
239
259
|
- ✅ **CircleCI**
|
|
260
|
+
- ✅ **Jenkins**
|
|
240
261
|
|
|
241
262
|
## GitHub Actions Example
|
|
242
263
|
|
|
@@ -288,4 +309,46 @@ workflows:
|
|
|
288
309
|
- build
|
|
289
310
|
```
|
|
290
311
|
|
|
312
|
+
## Jenkins Example
|
|
313
|
+
|
|
314
|
+
Note: This assumes Node.js and npm are installed on the Jenkins agent.
|
|
315
|
+
|
|
316
|
+
```groovy
|
|
317
|
+
pipeline {
|
|
318
|
+
agent any
|
|
319
|
+
|
|
320
|
+
environment {
|
|
321
|
+
// Jenkins does not automatically persist PATH updates from setup-ci,
|
|
322
|
+
// so add the shims + binary directory explicitly for all stages.
|
|
323
|
+
PATH = "${env.HOME}/.safe-chain/shims:${env.HOME}/.safe-chain/bin:${env.PATH}"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
stages {
|
|
327
|
+
stage('Install safe-chain') {
|
|
328
|
+
steps {
|
|
329
|
+
sh '''
|
|
330
|
+
set -euo pipefail
|
|
331
|
+
|
|
332
|
+
# Install Safe Chain for CI
|
|
333
|
+
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
|
|
334
|
+
'''
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
stage('Install project dependencies etc...') {
|
|
339
|
+
steps {
|
|
340
|
+
sh '''
|
|
341
|
+
set -euo pipefail
|
|
342
|
+
npm ci
|
|
343
|
+
'''
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
291
350
|
After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
|
|
351
|
+
|
|
352
|
+
# Troubleshooting
|
|
353
|
+
|
|
354
|
+
Having issues? See the [Troubleshooting Guide](https://github.com/AikidoSec/safe-chain/blob/main/docs/troubleshooting.md) for help with common problems.
|
package/bin/safe-chain.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { ui } from "../src/environment/userInteraction.js";
|
|
5
5
|
import { setup } from "../src/shell-integration/setup.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
teardown,
|
|
8
|
+
teardownDirectories,
|
|
9
|
+
} from "../src/shell-integration/teardown.js";
|
|
7
10
|
import { setupCi } from "../src/shell-integration/setup-ci.js";
|
|
8
11
|
import { initializeCliArguments } from "../src/config/cliArguments.js";
|
|
9
12
|
import { setEcoSystem } from "../src/config/settings.js";
|
|
@@ -45,7 +48,7 @@ if (tool) {
|
|
|
45
48
|
const args = process.argv.slice(3);
|
|
46
49
|
|
|
47
50
|
setEcoSystem(tool.ecoSystem);
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
// Provide tool context to PM (pip uses this; others ignore)
|
|
50
53
|
const toolContext = { tool: tool.tool, args };
|
|
51
54
|
initializePackageManager(tool.internalPackageManagerName, toolContext);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
This guide helps you diagnose and resolve common issues with Aikido Safe Chain.
|
|
4
|
+
|
|
5
|
+
## Verification & Diagnostics
|
|
6
|
+
|
|
7
|
+
### Check Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Check version
|
|
11
|
+
safe-chain --version
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Verify Shell Integration
|
|
15
|
+
|
|
16
|
+
Run the verification command for your package manager:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm safe-chain-verify
|
|
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
|
+
```
|
|
26
|
+
|
|
27
|
+
Expected output: `OK: Safe-chain works!`
|
|
28
|
+
|
|
29
|
+
### Test Malware Blocking
|
|
30
|
+
|
|
31
|
+
Verify that malware detection is working:
|
|
32
|
+
|
|
33
|
+
**For JavaScript/Node.js:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install safe-chain-test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**For Python:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip3 install safe-chain-pi-test
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
These test packages are flagged as malware and should be blocked by Safe Chain.
|
|
46
|
+
|
|
47
|
+
### Logging Options
|
|
48
|
+
|
|
49
|
+
Use logging flags to get more information:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Verbose mode - detailed diagnostic output for troubleshooting
|
|
53
|
+
npm install express --safe-chain-logging=verbose
|
|
54
|
+
|
|
55
|
+
# Silent mode - suppress all output except malware blocking
|
|
56
|
+
npm install express --safe-chain-logging=silent
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Common Issues
|
|
60
|
+
|
|
61
|
+
### Shell Aliases Not Working After Installation
|
|
62
|
+
|
|
63
|
+
**Symptom:** Running `npm` shows regular npm instead of safe-chain wrapped version
|
|
64
|
+
|
|
65
|
+
**First step:** Restart your terminal (most common fix)
|
|
66
|
+
|
|
67
|
+
**Verify it's working:**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
type npm
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Should show: `npm is a function`
|
|
74
|
+
|
|
75
|
+
**If still not working:**
|
|
76
|
+
|
|
77
|
+
Check that your startup file sources safe-chain scripts from `~/.safe-chain/scripts/`:
|
|
78
|
+
|
|
79
|
+
- Bash: `~/.bashrc`
|
|
80
|
+
- Zsh: `~/.zshrc`
|
|
81
|
+
- Fish: `~/.config/fish/config.fish`
|
|
82
|
+
- PowerShell: `$PROFILE`
|
|
83
|
+
|
|
84
|
+
### "Command Not Found: safe-chain"
|
|
85
|
+
|
|
86
|
+
**Symptom:** Binary not found in PATH
|
|
87
|
+
|
|
88
|
+
**First step:** Restart your terminal
|
|
89
|
+
|
|
90
|
+
**Check PATH:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
echo $PATH
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Should include `~/.safe-chain/bin`
|
|
97
|
+
|
|
98
|
+
**If persists:** Re-run the installation script
|
|
99
|
+
|
|
100
|
+
### Shell Aliases Persist After Uninstallation
|
|
101
|
+
|
|
102
|
+
**Symptom:** safe-chain commands still active after running uninstall script
|
|
103
|
+
|
|
104
|
+
**Steps:**
|
|
105
|
+
|
|
106
|
+
1. Run `safe-chain teardown` (if binary still exists)
|
|
107
|
+
2. Restart your terminal
|
|
108
|
+
3. If still present, manually edit shell config files:
|
|
109
|
+
- Bash: `~/.bashrc`
|
|
110
|
+
- Zsh: `~/.zshrc`
|
|
111
|
+
- Fish: `~/.config/fish/config.fish`
|
|
112
|
+
- PowerShell: `$PROFILE`
|
|
113
|
+
4. Remove lines that source scripts from `~/.safe-chain/scripts/`
|
|
114
|
+
5. Restart terminal again
|
|
115
|
+
|
|
116
|
+
## Manual Verification Steps
|
|
117
|
+
|
|
118
|
+
### Check Installation Status
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Check installation location (helps identify if installed via npm or as standalone binary)
|
|
122
|
+
which safe-chain
|
|
123
|
+
|
|
124
|
+
# Verify binary exists
|
|
125
|
+
ls ~/.safe-chain/bin/safe-chain
|
|
126
|
+
|
|
127
|
+
# Check version
|
|
128
|
+
safe-chain --version
|
|
129
|
+
|
|
130
|
+
# Test shell integration
|
|
131
|
+
type npm
|
|
132
|
+
type pip
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Expected `which` output:**
|
|
136
|
+
|
|
137
|
+
- Standalone binary (correct): `~/.safe-chain/bin/safe-chain` or `/Users/<username>/.safe-chain/bin/safe-chain`
|
|
138
|
+
- npm global (outdated): path containing `node_modules` or nvm version paths
|
|
139
|
+
|
|
140
|
+
If `which` shows an npm installation, see [Check for Conflicting Installations](#check-for-conflicting-installations).
|
|
141
|
+
|
|
142
|
+
### Check Shell Integration
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Which shell you're using
|
|
146
|
+
echo $SHELL
|
|
147
|
+
|
|
148
|
+
# Check if startup file sources safe-chain
|
|
149
|
+
# For Bash:
|
|
150
|
+
grep safe-chain ~/.bashrc
|
|
151
|
+
|
|
152
|
+
# For Zsh:
|
|
153
|
+
grep safe-chain ~/.zshrc
|
|
154
|
+
|
|
155
|
+
# For Fish:
|
|
156
|
+
grep safe-chain ~/.config/fish/config.fish
|
|
157
|
+
|
|
158
|
+
# Verify scripts exist
|
|
159
|
+
ls ~/.safe-chain/scripts/
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Check for Conflicting Installations
|
|
163
|
+
|
|
164
|
+
> **Note:** The install/uninstall scripts automatically detect and remove conflicting installations, but you can manually check:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Check npm global
|
|
168
|
+
npm list -g @aikidosec/safe-chain
|
|
169
|
+
|
|
170
|
+
# Check Volta
|
|
171
|
+
volta list safe-chain
|
|
172
|
+
|
|
173
|
+
# Check nvm (all versions)
|
|
174
|
+
for version in $(nvm list | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+'); do
|
|
175
|
+
nvm exec "$version" npm list -g @aikidosec/safe-chain 2>/dev/null && echo "Found in $version"
|
|
176
|
+
done
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Manual Cleanup
|
|
180
|
+
|
|
181
|
+
> **Note:** The install and uninstall scripts automatically handle these cleanup steps. Use these manual commands only if automatic cleanup fails.
|
|
182
|
+
|
|
183
|
+
### Remove npm Global Installation
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm uninstall -g @aikidosec/safe-chain
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Remove Volta Installation
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
volta uninstall @aikidosec/safe-chain
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Remove nvm Installations (All Versions)
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Automated approach
|
|
199
|
+
for version in $(nvm list | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+'); do
|
|
200
|
+
nvm exec "$version" npm uninstall -g @aikidosec/safe-chain
|
|
201
|
+
done
|
|
202
|
+
|
|
203
|
+
# Or manual per version
|
|
204
|
+
nvm use <version>
|
|
205
|
+
npm uninstall -g @aikidosec/safe-chain
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Clean Shell Configuration Files
|
|
209
|
+
|
|
210
|
+
Manually remove safe-chain entries from:
|
|
211
|
+
|
|
212
|
+
- Bash: `~/.bashrc`
|
|
213
|
+
- Zsh: `~/.zshrc`
|
|
214
|
+
- Fish: `~/.config/fish/config.fish`
|
|
215
|
+
- PowerShell: `$PROFILE`
|
|
216
|
+
|
|
217
|
+
Look for and remove:
|
|
218
|
+
|
|
219
|
+
- Lines sourcing from `~/.safe-chain/scripts/`
|
|
220
|
+
- Any safe-chain related function definitions
|
|
221
|
+
|
|
222
|
+
### Remove Installation Directory
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
rm -rf ~/.safe-chain
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Getting More Information
|
|
229
|
+
|
|
230
|
+
### Enable Verbose Logging
|
|
231
|
+
|
|
232
|
+
Get detailed diagnostic output:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm install express --safe-chain-logging=verbose
|
|
236
|
+
pip install requests --safe-chain-logging=verbose
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Report Issues
|
|
240
|
+
|
|
241
|
+
If you encounter problems:
|
|
242
|
+
|
|
243
|
+
1. Visit [GitHub Issues](https://github.com/AikidoSec/safe-chain/issues)
|
|
244
|
+
2. Include:
|
|
245
|
+
- Operating system and version
|
|
246
|
+
- Shell type and version
|
|
247
|
+
- `safe-chain --version` output
|
|
248
|
+
- Output from verification commands
|
|
249
|
+
- Verbose logs of the failing command
|
package/package.json
CHANGED
package/src/config/configFile.js
CHANGED
|
@@ -11,6 +11,7 @@ import { getEcoSystem } from "./settings.js";
|
|
|
11
11
|
* @property {unknown | Number} scanTimeout
|
|
12
12
|
* @property {unknown | Number} minimumPackageAgeHours
|
|
13
13
|
* @property {unknown | SafeChainRegistryConfiguration} npm
|
|
14
|
+
* @property {unknown | SafeChainRegistryConfiguration} pip
|
|
14
15
|
*
|
|
15
16
|
* @typedef {Object} SafeChainRegistryConfiguration
|
|
16
17
|
* We cannot trust the input and should add the necessary validations.
|
|
@@ -104,6 +105,28 @@ export function getNpmCustomRegistries() {
|
|
|
104
105
|
return customRegistries.filter((item) => typeof item === "string");
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Gets the custom npm registries from the config file (format parsing only, no validation)
|
|
110
|
+
* @returns {string[]}
|
|
111
|
+
*/
|
|
112
|
+
export function getPipCustomRegistries() {
|
|
113
|
+
const config = readConfigFile();
|
|
114
|
+
|
|
115
|
+
if (!config || !config.pip) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// TypeScript needs help understanding that config.pip exists and has customRegistries
|
|
120
|
+
const pipConfig = /** @type {SafeChainRegistryConfiguration} */ (config.pip);
|
|
121
|
+
const customRegistries = pipConfig.customRegistries;
|
|
122
|
+
|
|
123
|
+
if (!Array.isArray(customRegistries)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return customRegistries.filter((item) => typeof item === "string");
|
|
128
|
+
}
|
|
129
|
+
|
|
107
130
|
/**
|
|
108
131
|
* @param {import("../api/aikido.js").MalwarePackage[]} data
|
|
109
132
|
* @param {string | number} version
|
|
@@ -169,6 +192,9 @@ function readConfigFile() {
|
|
|
169
192
|
npm: {
|
|
170
193
|
customRegistries: undefined,
|
|
171
194
|
},
|
|
195
|
+
pip: {
|
|
196
|
+
customRegistries: undefined,
|
|
197
|
+
},
|
|
172
198
|
};
|
|
173
199
|
|
|
174
200
|
const configFilePath = getConfigFilePath();
|
|
@@ -15,3 +15,13 @@ export function getMinimumPackageAgeHours() {
|
|
|
15
15
|
export function getNpmCustomRegistries() {
|
|
16
16
|
return process.env.SAFE_CHAIN_NPM_CUSTOM_REGISTRIES;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets the custom pip registries from environment variable
|
|
21
|
+
* Expected format: comma-separated list of registry domains
|
|
22
|
+
* Example: "pip.company.com,registry.internal.net"
|
|
23
|
+
* @returns {string | undefined}
|
|
24
|
+
*/
|
|
25
|
+
export function getPipCustomRegistries() {
|
|
26
|
+
return process.env.SAFE_CHAIN_PIP_CUSTOM_REGISTRIES;
|
|
27
|
+
}
|
package/src/config/settings.js
CHANGED
|
@@ -143,3 +143,21 @@ export function getNpmCustomRegistries() {
|
|
|
143
143
|
// Normalize each registry (remove protocol if any)
|
|
144
144
|
return uniqueRegistries.map(normalizeRegistry);
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets the custom npm registries from both environment variable and config file (merged)
|
|
149
|
+
* @returns {string[]}
|
|
150
|
+
*/
|
|
151
|
+
export function getPipCustomRegistries() {
|
|
152
|
+
const envRegistries = parseRegistriesFromEnv(
|
|
153
|
+
environmentVariables.getPipCustomRegistries()
|
|
154
|
+
);
|
|
155
|
+
const configRegistries = configFile.getPipCustomRegistries();
|
|
156
|
+
|
|
157
|
+
// Merge both sources and remove duplicates
|
|
158
|
+
const allRegistries = [...envRegistries, ...configRegistries];
|
|
159
|
+
const uniqueRegistries = [...new Set(allRegistries)];
|
|
160
|
+
|
|
161
|
+
// Normalize each registry (remove protocol if any)
|
|
162
|
+
return uniqueRegistries.map(normalizeRegistry);
|
|
163
|
+
}
|
package/src/main.js
CHANGED
|
@@ -13,6 +13,10 @@ import { getAuditStats } from "./scanning/audit/index.js";
|
|
|
13
13
|
* @returns {Promise<number>}
|
|
14
14
|
*/
|
|
15
15
|
export async function main(args) {
|
|
16
|
+
if (isSafeChainVerify(args)) {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
process.on("SIGINT", handleProcessTermination);
|
|
17
21
|
process.on("SIGTERM", handleProcessTermination);
|
|
18
22
|
|
|
@@ -104,3 +108,12 @@ export async function main(args) {
|
|
|
104
108
|
function handleProcessTermination() {
|
|
105
109
|
ui.writeBufferedLogsAndStopBuffering();
|
|
106
110
|
}
|
|
111
|
+
|
|
112
|
+
/** @param {string[]} args */
|
|
113
|
+
function isSafeChainVerify(args) {
|
|
114
|
+
const safeChainCheckCommand = "safe-chain-verify";
|
|
115
|
+
if (args.length > 0 && args[0] === safeChainCheckCommand) {
|
|
116
|
+
ui.writeInformation("OK: Safe-chain works!");
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPipCustomRegistries } from "../../config/settings.js";
|
|
1
2
|
import { isMalwarePackage } from "../../scanning/audit/index.js";
|
|
2
3
|
import { interceptRequests } from "./interceptorBuilder.js";
|
|
3
4
|
|
|
@@ -13,7 +14,9 @@ const knownPipRegistries = [
|
|
|
13
14
|
* @returns {import("./interceptorBuilder.js").Interceptor | undefined}
|
|
14
15
|
*/
|
|
15
16
|
export function pipInterceptorForUrl(url) {
|
|
16
|
-
const
|
|
17
|
+
const customRegistries = getPipCustomRegistries();
|
|
18
|
+
const registries = [...knownPipRegistries, ...customRegistries];
|
|
19
|
+
const registry = registries.find((reg) => url.includes(reg));
|
|
17
20
|
|
|
18
21
|
if (registry) {
|
|
19
22
|
return buildPipInterceptor(registry);
|
|
@@ -37,8 +40,8 @@ function buildPipInterceptor(registry) {
|
|
|
37
40
|
// Per python, packages that differ only by hyphen vs underscore are considered the same.
|
|
38
41
|
const hyphenName = packageName?.includes("_") ? packageName.replace(/_/g, "-") : packageName;
|
|
39
42
|
|
|
40
|
-
const isMalicious =
|
|
41
|
-
await isMalwarePackage(packageName, version)
|
|
43
|
+
const isMalicious =
|
|
44
|
+
await isMalwarePackage(packageName, version)
|
|
42
45
|
|| await isMalwarePackage(hyphenName, version);
|
|
43
46
|
|
|
44
47
|
if (isMalicious) {
|
|
@@ -15,7 +15,7 @@ import { gunzipSync, gzipSync } from "zlib";
|
|
|
15
15
|
*/
|
|
16
16
|
export function mitmConnect(req, clientSocket, interceptor) {
|
|
17
17
|
ui.writeVerbose(`Safe-chain: Set up MITM tunnel for ${req.url}`);
|
|
18
|
-
const { hostname } = new URL(`http://${req.url}`);
|
|
18
|
+
const { hostname, port } = new URL(`http://${req.url}`);
|
|
19
19
|
|
|
20
20
|
clientSocket.on("error", (err) => {
|
|
21
21
|
ui.writeVerbose(
|
|
@@ -26,7 +26,7 @@ export function mitmConnect(req, clientSocket, interceptor) {
|
|
|
26
26
|
// Not subscribing to 'close' event will cause node to throw and crash.
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
const server = createHttpsServer(hostname, interceptor);
|
|
29
|
+
const server = createHttpsServer(hostname, port, interceptor);
|
|
30
30
|
|
|
31
31
|
server.on("error", (err) => {
|
|
32
32
|
ui.writeError(`Safe-chain: HTTPS server error: ${err.message}`);
|
|
@@ -46,10 +46,11 @@ export function mitmConnect(req, clientSocket, interceptor) {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* @param {string} hostname
|
|
49
|
+
* @param {string} port
|
|
49
50
|
* @param {Interceptor} interceptor
|
|
50
51
|
* @returns {import("https").Server}
|
|
51
52
|
*/
|
|
52
|
-
function createHttpsServer(hostname, interceptor) {
|
|
53
|
+
function createHttpsServer(hostname, port, interceptor) {
|
|
53
54
|
const cert = generateCertForHost(hostname);
|
|
54
55
|
|
|
55
56
|
/**
|
|
@@ -80,7 +81,7 @@ function createHttpsServer(hostname, interceptor) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
// Collect request body
|
|
83
|
-
forwardRequest(req, hostname, res, requestInterceptor);
|
|
84
|
+
forwardRequest(req, hostname, port, res, requestInterceptor);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const server = https.createServer(
|
|
@@ -109,11 +110,12 @@ function getRequestPathAndQuery(url) {
|
|
|
109
110
|
/**
|
|
110
111
|
* @param {import("http").IncomingMessage} req
|
|
111
112
|
* @param {string} hostname
|
|
113
|
+
* @param {string} port
|
|
112
114
|
* @param {import("http").ServerResponse} res
|
|
113
115
|
* @param {import("./interceptors/interceptorBuilder.js").RequestInterceptionHandler} requestHandler
|
|
114
116
|
*/
|
|
115
|
-
function forwardRequest(req, hostname, res, requestHandler) {
|
|
116
|
-
const proxyReq = createProxyRequest(hostname, req, res, requestHandler);
|
|
117
|
+
function forwardRequest(req, hostname, port, res, requestHandler) {
|
|
118
|
+
const proxyReq = createProxyRequest(hostname, port, req, res, requestHandler);
|
|
117
119
|
|
|
118
120
|
proxyReq.on("error", (err) => {
|
|
119
121
|
ui.writeVerbose(
|
|
@@ -144,13 +146,14 @@ function forwardRequest(req, hostname, res, requestHandler) {
|
|
|
144
146
|
|
|
145
147
|
/**
|
|
146
148
|
* @param {string} hostname
|
|
149
|
+
* @param {string} port
|
|
147
150
|
* @param {import("http").IncomingMessage} req
|
|
148
151
|
* @param {import("http").ServerResponse} res
|
|
149
152
|
* @param {import("./interceptors/interceptorBuilder.js").RequestInterceptionHandler} requestHandler
|
|
150
153
|
*
|
|
151
154
|
* @returns {import("http").ClientRequest}
|
|
152
155
|
*/
|
|
153
|
-
function createProxyRequest(hostname, req, res, requestHandler) {
|
|
156
|
+
function createProxyRequest(hostname, port, req, res, requestHandler) {
|
|
154
157
|
/** @type {NodeJS.Dict<string | string[]> | undefined} */
|
|
155
158
|
let headers = { ...req.headers };
|
|
156
159
|
// Remove the host header from the incoming request before forwarding.
|
|
@@ -163,7 +166,7 @@ function createProxyRequest(hostname, req, res, requestHandler) {
|
|
|
163
166
|
/** @type {import("http").RequestOptions} */
|
|
164
167
|
const options = {
|
|
165
168
|
hostname: hostname,
|
|
166
|
-
port: 443,
|
|
169
|
+
port: port || 443,
|
|
167
170
|
path: req.url,
|
|
168
171
|
method: req.method,
|
|
169
172
|
headers: { ...headers },
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# Migrating from npm global tool to binary installation
|
|
2
|
-
|
|
3
|
-
If you previously installed safe-chain as an npm global package, you need to migrate to the binary installation.
|
|
4
|
-
|
|
5
|
-
Depending on the version manager you're using, the uninstall process differs:
|
|
6
|
-
|
|
7
|
-
### Standard npm (no version manager)
|
|
8
|
-
|
|
9
|
-
1. **Clean up shell aliases:**
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
safe-chain teardown
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
2. **Restart your terminal**
|
|
16
|
-
|
|
17
|
-
3. **Uninstall the npm package:**
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm uninstall -g @aikidosec/safe-chain
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
4. **Install the binary version** (see [Installation](https://github.com/AikidoSec/safe-chain/blob/main/README.md#installation))
|
|
24
|
-
|
|
25
|
-
### nvm (Node Version Manager)
|
|
26
|
-
|
|
27
|
-
**Important:** nvm installs global packages separately for each Node version, so safe-chain must be uninstalled from each version where it was installed.
|
|
28
|
-
|
|
29
|
-
1. **Clean up shell aliases:**
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
safe-chain teardown
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
2. **Restart your terminal**
|
|
36
|
-
|
|
37
|
-
3. **Uninstall from all Node versions:**
|
|
38
|
-
|
|
39
|
-
**Option A** - Automated script (recommended):
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
for version in $(nvm list | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+'); do nvm use $version && npm uninstall -g @aikidosec/safe-chain; done
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Option B** - Manual per version:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
nvm use <version>
|
|
49
|
-
npm uninstall -g @aikidosec/safe-chain
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Repeat for each Node version where safe-chain was installed.
|
|
53
|
-
|
|
54
|
-
4. **Install the binary version** (see [Installation](https://github.com/AikidoSec/safe-chain/blob/main/README.md#installation))
|
|
55
|
-
|
|
56
|
-
### Volta
|
|
57
|
-
|
|
58
|
-
1. **Clean up shell aliases:**
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
safe-chain teardown
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
2. **Restart your terminal**
|
|
65
|
-
|
|
66
|
-
3. **Uninstall the Volta package:**
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
volta uninstall @aikidosec/safe-chain
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
4. **Install the binary version** (see [Installation](https://github.com/AikidoSec/safe-chain/blob/main/README.md#installation))
|
|
73
|
-
|
|
74
|
-
## Troubleshooting
|
|
75
|
-
|
|
76
|
-
### Shell aliases still present after migration
|
|
77
|
-
|
|
78
|
-
1. Run `safe-chain teardown` (if the binary is installed)
|
|
79
|
-
2. Manually remove any safe-chain entries from your shell config files:
|
|
80
|
-
- Bash: `~/.bashrc`
|
|
81
|
-
- Zsh: `~/.zshrc`
|
|
82
|
-
- Fish: `~/.config/fish/config.fish`
|
|
83
|
-
- PowerShell: `$PROFILE`
|
|
84
|
-
3. Restart your terminal
|
|
85
|
-
4. Re-run the install script
|
|
86
|
-
|
|
87
|
-
### "command not found: safe-chain" after migration
|
|
88
|
-
|
|
89
|
-
The binary installation directory (`~/.safe-chain/bin`) may not be in your PATH. Restart your terminal. If the problem persists: re-run the installation of safe-chain.
|