@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 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 one of the following commands:
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 NPM Registries
202
+ ## Custom Registries
192
203
 
193
- Configure Safe Chain to scan packages from custom or private npm registries.
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 { teardown, teardownDirectories } from "../src/shell-integration/teardown.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikidosec/safe-chain",
3
- "version": "1.3.3",
3
+ "version": "1.4.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'",
@@ -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
+ }
@@ -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 registry = knownPipRegistries.find((reg) => url.includes(reg));
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 },
@@ -83,6 +83,6 @@ function wrapSafeChainCommand() {
83
83
  # If the aikido command is not available, print a warning and run the original command
84
84
  printSafeChainWarning "$original_cmd"
85
85
 
86
- command "$original_cmd" "$@"
86
+ command "$@"
87
87
  fi
88
88
  }
@@ -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.