@aikidosec/safe-chain 0.0.1-custom-install-dir

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.
Files changed (116) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +537 -0
  3. package/bin/aikido-bun.js +14 -0
  4. package/bin/aikido-bunx.js +14 -0
  5. package/bin/aikido-npm.js +14 -0
  6. package/bin/aikido-npx.js +14 -0
  7. package/bin/aikido-pip.js +17 -0
  8. package/bin/aikido-pip3.js +17 -0
  9. package/bin/aikido-pipx.js +16 -0
  10. package/bin/aikido-pnpm.js +14 -0
  11. package/bin/aikido-pnpx.js +14 -0
  12. package/bin/aikido-poetry.js +13 -0
  13. package/bin/aikido-python.js +19 -0
  14. package/bin/aikido-python3.js +19 -0
  15. package/bin/aikido-uv.js +16 -0
  16. package/bin/aikido-uvx.js +16 -0
  17. package/bin/aikido-yarn.js +14 -0
  18. package/bin/safe-chain.js +147 -0
  19. package/docs/Release.md +25 -0
  20. package/docs/banner.svg +151 -0
  21. package/docs/safe-package-manager-demo.gif +0 -0
  22. package/docs/safe-package-manager-demo.png +0 -0
  23. package/docs/shell-integration.md +149 -0
  24. package/docs/troubleshooting.md +321 -0
  25. package/npm-shrinkwrap.json +3180 -0
  26. package/package.json +71 -0
  27. package/src/api/aikido.js +187 -0
  28. package/src/api/npmApi.js +71 -0
  29. package/src/config/cliArguments.js +161 -0
  30. package/src/config/configFile.js +327 -0
  31. package/src/config/environmentVariables.js +57 -0
  32. package/src/config/safeChainDir.js +71 -0
  33. package/src/config/settings.js +247 -0
  34. package/src/environment/environment.js +14 -0
  35. package/src/environment/userInteraction.js +122 -0
  36. package/src/installLocation.js +42 -0
  37. package/src/main.js +123 -0
  38. package/src/packagemanager/_shared/commandErrors.js +17 -0
  39. package/src/packagemanager/_shared/matchesCommand.js +18 -0
  40. package/src/packagemanager/bun/createBunPackageManager.js +48 -0
  41. package/src/packagemanager/currentPackageManager.js +82 -0
  42. package/src/packagemanager/npm/createPackageManager.js +72 -0
  43. package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +74 -0
  44. package/src/packagemanager/npm/dependencyScanner/nullScanner.js +9 -0
  45. package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +144 -0
  46. package/src/packagemanager/npm/runNpmCommand.js +20 -0
  47. package/src/packagemanager/npm/utils/abbrevs-generated.js +359 -0
  48. package/src/packagemanager/npm/utils/cmd-list.js +174 -0
  49. package/src/packagemanager/npm/utils/npmCommands.js +34 -0
  50. package/src/packagemanager/npx/createPackageManager.js +15 -0
  51. package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +43 -0
  52. package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +130 -0
  53. package/src/packagemanager/npx/runNpxCommand.js +20 -0
  54. package/src/packagemanager/pip/createPackageManager.js +25 -0
  55. package/src/packagemanager/pip/pipSettings.js +6 -0
  56. package/src/packagemanager/pip/runPipCommand.js +209 -0
  57. package/src/packagemanager/pipx/createPipXPackageManager.js +18 -0
  58. package/src/packagemanager/pipx/runPipXCommand.js +60 -0
  59. package/src/packagemanager/pnpm/createPackageManager.js +57 -0
  60. package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +35 -0
  61. package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +109 -0
  62. package/src/packagemanager/pnpm/runPnpmCommand.js +32 -0
  63. package/src/packagemanager/poetry/createPoetryPackageManager.js +72 -0
  64. package/src/packagemanager/uv/createUvPackageManager.js +18 -0
  65. package/src/packagemanager/uv/runUvCommand.js +66 -0
  66. package/src/packagemanager/uvx/createUvxPackageManager.js +18 -0
  67. package/src/packagemanager/yarn/createPackageManager.js +41 -0
  68. package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +35 -0
  69. package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +128 -0
  70. package/src/packagemanager/yarn/runYarnCommand.js +36 -0
  71. package/src/registryProxy/certBundle.js +203 -0
  72. package/src/registryProxy/certUtils.js +178 -0
  73. package/src/registryProxy/getConnectTimeout.js +13 -0
  74. package/src/registryProxy/http-utils.js +80 -0
  75. package/src/registryProxy/interceptors/createInterceptorForEcoSystem.js +25 -0
  76. package/src/registryProxy/interceptors/interceptorBuilder.js +179 -0
  77. package/src/registryProxy/interceptors/minimumPackageAgeExclusions.js +33 -0
  78. package/src/registryProxy/interceptors/npm/modifyNpmInfo.js +180 -0
  79. package/src/registryProxy/interceptors/npm/npmInterceptor.js +101 -0
  80. package/src/registryProxy/interceptors/npm/parseNpmPackageUrl.js +60 -0
  81. package/src/registryProxy/interceptors/pip/modifyPipInfo.js +167 -0
  82. package/src/registryProxy/interceptors/pip/modifyPipJsonResponse.js +176 -0
  83. package/src/registryProxy/interceptors/pip/parsePipPackageUrl.js +162 -0
  84. package/src/registryProxy/interceptors/pip/pipInterceptor.js +122 -0
  85. package/src/registryProxy/interceptors/pip/pipMetadataResponseUtils.js +27 -0
  86. package/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js +131 -0
  87. package/src/registryProxy/interceptors/suppressedVersionsState.js +21 -0
  88. package/src/registryProxy/isImdsEndpoint.js +13 -0
  89. package/src/registryProxy/mitmRequestHandler.js +240 -0
  90. package/src/registryProxy/plainHttpProxy.js +95 -0
  91. package/src/registryProxy/registryProxy.js +255 -0
  92. package/src/registryProxy/tunnelRequestHandler.js +213 -0
  93. package/src/scanning/audit/index.js +129 -0
  94. package/src/scanning/index.js +82 -0
  95. package/src/scanning/malwareDatabase.js +131 -0
  96. package/src/scanning/newPackagesDatabaseBuilder.js +71 -0
  97. package/src/scanning/newPackagesDatabaseWarnings.js +17 -0
  98. package/src/scanning/newPackagesListCache.js +126 -0
  99. package/src/scanning/packageNameVariants.js +29 -0
  100. package/src/shell-integration/helpers.js +296 -0
  101. package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +37 -0
  102. package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +25 -0
  103. package/src/shell-integration/setup-ci.js +152 -0
  104. package/src/shell-integration/setup.js +110 -0
  105. package/src/shell-integration/shellDetection.js +39 -0
  106. package/src/shell-integration/startup-scripts/init-fish.fish +122 -0
  107. package/src/shell-integration/startup-scripts/init-posix.sh +112 -0
  108. package/src/shell-integration/startup-scripts/init-pwsh.ps1 +176 -0
  109. package/src/shell-integration/supported-shells/bash.js +222 -0
  110. package/src/shell-integration/supported-shells/fish.js +97 -0
  111. package/src/shell-integration/supported-shells/powershell.js +102 -0
  112. package/src/shell-integration/supported-shells/windowsPowershell.js +102 -0
  113. package/src/shell-integration/supported-shells/zsh.js +94 -0
  114. package/src/shell-integration/teardown.js +114 -0
  115. package/src/utils/safeSpawn.js +153 -0
  116. package/tsconfig.json +21 -0
package/README.md ADDED
@@ -0,0 +1,537 @@
1
+ ![Aikido Safe Chain](https://raw.githubusercontent.com/AikidoSec/safe-chain/main/docs/banner.svg)
2
+
3
+ # Aikido Safe Chain
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/%40aikidosec%2Fsafe-chain?style=flat-square)](https://www.npmjs.com/package/@aikidosec/safe-chain)
6
+ [![NPM Downloads](https://img.shields.io/npm/dw/%40aikidosec%2Fsafe-chain?style=flat-square)](https://www.npmjs.com/package/@aikidosec/safe-chain)
7
+
8
+ - ✅ **Block malware on developer laptops and CI/CD**
9
+ - ✅ **Supports npm and PyPI** more package managers coming
10
+ - ✅ **Blocks packages newer than 48 hours** without breaking your build
11
+ - ✅ **Tokenless, free, no build data shared**
12
+
13
+ Aikido Safe Chain supports the following package managers:
14
+
15
+ - 📦 **npm**
16
+ - 📦 **npx**
17
+ - 📦 **yarn**
18
+ - 📦 **pnpm**
19
+ - 📦 **pnpx**
20
+ - 📦 **bun**
21
+ - 📦 **bunx**
22
+ - 📦 **pip**
23
+ - 📦 **pip3**
24
+ - 📦 **uv**
25
+ - 📦 **poetry**
26
+ - 📦 **uvx**
27
+ - 📦 **pipx**
28
+
29
+ # Usage
30
+
31
+ ![Aikido Safe Chain demo](https://raw.githubusercontent.com/AikidoSec/safe-chain/main/docs/safe-package-manager-demo.gif)
32
+
33
+ ## Installation
34
+
35
+ Installing the Aikido Safe Chain is easy with our one-line installer.
36
+
37
+ ### Unix/Linux/macOS
38
+
39
+ ```shell
40
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh
41
+ ```
42
+
43
+ ### Windows (PowerShell)
44
+
45
+ ```powershell
46
+ iex (iwr "https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.ps1" -UseBasicParsing)
47
+ ```
48
+
49
+ ### Pinning to a specific version
50
+
51
+ To install a specific version instead of the latest, replace `latest` with the version number in the URL (available from version 1.3.2 onwards):
52
+
53
+ **Unix/Linux/macOS:**
54
+
55
+ ```shell
56
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/x.x.x/install-safe-chain.sh | sh
57
+ ```
58
+
59
+ **Windows (PowerShell):**
60
+
61
+ ```powershell
62
+ iex (iwr "https://github.com/AikidoSec/safe-chain/releases/download/x.x.x/install-safe-chain.ps1" -UseBasicParsing)
63
+ ```
64
+
65
+ You can find all available versions on the [releases page](https://github.com/AikidoSec/safe-chain/releases).
66
+
67
+ ### Verify the installation
68
+
69
+ 1. **❗Restart your terminal** to start using the Aikido Safe Chain.
70
+ - 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.
71
+
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:
86
+
87
+ For JavaScript/Node.js:
88
+
89
+ ```shell
90
+ npm install safe-chain-test
91
+ ```
92
+
93
+ For Python:
94
+
95
+ ```shell
96
+ pip3 install safe-chain-pi-test
97
+ ```
98
+
99
+ - The output should show that Aikido Safe Chain is blocking the installation of these test packages as they are flagged as malware.
100
+
101
+ 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.
102
+
103
+ You can check the installed version by running:
104
+
105
+ ```shell
106
+ safe-chain --version
107
+ ```
108
+
109
+ ## How it works
110
+
111
+ ### Malware Blocking
112
+
113
+ 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.
114
+
115
+ ### Minimum package age
116
+
117
+ Safe Chain applies minimum package age checks to supported ecosystems.
118
+
119
+ Current enforcement differs by ecosystem:
120
+
121
+ - npm-based package managers:
122
+ - during normal package resolution, Safe Chain suppresses versions that are newer than the configured minimum age from the package metadata returned by the registry
123
+ - for direct package download requests that bypass that metadata flow, Safe Chain can block the request itself using a cached list of newly released packages
124
+ - Python package managers:
125
+ - during package resolution, Safe Chain suppresses too-young files and releases from PyPI metadata responses
126
+ - for direct package download requests that bypass that metadata flow, Safe Chain can block the request itself using a cached list of newly released packages
127
+
128
+ By default, the minimum package age is 48 hours. This provides an additional security layer during the critical period when newly published packages are most vulnerable to containing undetected threats. You can configure this threshold or bypass this protection entirely - see the [Minimum Package Age Configuration](#minimum-package-age) section below.
129
+
130
+ ### Shell Integration
131
+
132
+ 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:
133
+
134
+ - ✅ **Bash**
135
+ - ✅ **Zsh**
136
+ - ✅ **Fish**
137
+ - ✅ **PowerShell**
138
+ - ✅ **PowerShell Core**
139
+
140
+ More information about the shell integration can be found in the [shell integration documentation](https://github.com/AikidoSec/safe-chain/blob/main/docs/shell-integration.md).
141
+
142
+ ## Uninstallation
143
+
144
+ To uninstall the Aikido Safe Chain, use our one-line uninstaller:
145
+
146
+ ### Unix/Linux/macOS
147
+
148
+ ```shell
149
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/uninstall-safe-chain.sh | sh
150
+ ```
151
+
152
+ ### Windows (PowerShell)
153
+
154
+ ```powershell
155
+ iex (iwr "https://github.com/AikidoSec/safe-chain/releases/latest/download/uninstall-safe-chain.ps1" -UseBasicParsing)
156
+ ```
157
+
158
+ **❗Restart your terminal** after uninstalling to ensure all aliases are removed.
159
+
160
+ # Configuration
161
+
162
+ ## Logging
163
+
164
+ You can control the output from Aikido Safe Chain using the `--safe-chain-logging` flag or the `SAFE_CHAIN_LOGGING` environment variable.
165
+
166
+ ### Configuration Options
167
+
168
+ You can set the logging level through multiple sources (in order of priority):
169
+
170
+ 1. **CLI Argument** (highest priority):
171
+ - `--safe-chain-logging=silent` - Suppresses all Aikido Safe Chain output except when malware is blocked. The package manager output is written to stdout as normal, and Safe Chain only writes a short message if it has blocked malware and causes the process to exit.
172
+
173
+ ```shell
174
+ npm install express --safe-chain-logging=silent
175
+ ```
176
+
177
+ - `--safe-chain-logging=verbose` - Enables detailed diagnostic output from Aikido Safe Chain. Useful for troubleshooting issues or understanding what Safe Chain is doing behind the scenes.
178
+
179
+ ```shell
180
+ npm install express --safe-chain-logging=verbose
181
+ ```
182
+
183
+ 2. **Environment Variable**:
184
+
185
+ ```shell
186
+ export SAFE_CHAIN_LOGGING=verbose
187
+ npm install express
188
+ ```
189
+
190
+ Valid values: `silent`, `normal`, `verbose`
191
+
192
+ This is useful for setting a default logging level for all package manager commands in your terminal session or CI/CD environment.
193
+
194
+ ## Minimum Package Age
195
+
196
+ You can configure how long packages must exist before Safe Chain allows their installation. By default, packages must be at least 48 hours old before they can be installed.
197
+
198
+ For npm-based package managers, this check currently has two enforcement modes:
199
+
200
+ - Safe Chain suppresses too-young versions from package metadata during normal dependency resolution.
201
+ - Safe Chain blocks direct package download requests when they are matched against the cached newly released packages list.
202
+
203
+ For Python package managers, this check currently has two enforcement modes:
204
+
205
+ - Safe Chain suppresses too-young files and releases from PyPI metadata during dependency resolution.
206
+ - Safe Chain blocks direct package download requests when they are matched against the cached newly released packages list.
207
+
208
+ ### Configuration Options
209
+
210
+ You can set the minimum package age through multiple sources (in order of priority):
211
+
212
+ 1. **CLI Argument** (highest priority):
213
+
214
+ ```shell
215
+ npm install express --safe-chain-minimum-package-age-hours=48
216
+ ```
217
+
218
+ 2. **Environment Variable**:
219
+
220
+ ```shell
221
+ export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS=48
222
+ npm install express
223
+ ```
224
+
225
+ 3. **Config File** (`~/.safe-chain/config.json`):
226
+
227
+ ```json
228
+ {
229
+ "minimumPackageAgeHours": 48
230
+ }
231
+ ```
232
+
233
+ ### Excluding Packages
234
+
235
+ Exclude trusted packages from minimum age filtering via environment variable or config file (both are merged). Use `@scope/*` to trust all packages from an organization:
236
+
237
+ ```shell
238
+ export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*"
239
+ ```
240
+
241
+ ```json
242
+ {
243
+ "npm": {
244
+ "minimumPackageAgeExclusions": ["@aikidosec/*"]
245
+ },
246
+ "pip": {
247
+ "minimumPackageAgeExclusions": ["requests"]
248
+ }
249
+ }
250
+ ```
251
+
252
+ ## Custom Registries
253
+
254
+ Configure Safe Chain to scan packages from custom or private registries.
255
+
256
+ Supported ecosystems:
257
+
258
+ - Node.js
259
+ - Python
260
+
261
+ ### Configuration Options
262
+
263
+ You can set custom registries through environment variable or config file. Both sources are merged together.
264
+
265
+ 1. **Environment Variable** (comma-separated):
266
+
267
+ ```shell
268
+ export SAFE_CHAIN_NPM_CUSTOM_REGISTRIES="npm.company.com,registry.internal.net"
269
+ export SAFE_CHAIN_PIP_CUSTOM_REGISTRIES="pip.company.com,registry.internal.net"
270
+ ```
271
+
272
+ 2. **Config File** (`~/.safe-chain/config.json`):
273
+
274
+ ```json
275
+ {
276
+ "npm": {
277
+ "customRegistries": ["npm.company.com", "registry.internal.net"]
278
+ },
279
+ "pip": {
280
+ "customRegistries": ["pip.company.com", "registry.internal.net"]
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Malware List Base URL
286
+
287
+ Configure Safe Chain to fetch malware databases and new packages lists from a custom mirror URL. This allows you to host your own copy of the Aikido malware database.
288
+
289
+ ### Configuration Options
290
+
291
+ You can set the malware list base URL through multiple sources (in order of priority):
292
+
293
+ 1. **CLI Argument** (highest priority):
294
+
295
+ ```shell
296
+ npm install express --safe-chain-malware-list-base-url=https://your-mirror.com
297
+ ```
298
+
299
+ 2. **Environment Variable**:
300
+
301
+ ```shell
302
+ export SAFE_CHAIN_MALWARE_LIST_BASE_URL=https://your-mirror.com
303
+ npm install express
304
+ ```
305
+
306
+ 3. **Config File** (`~/.safe-chain/config.json`):
307
+
308
+ ```json
309
+ {
310
+ "malwareListBaseUrl": "https://your-mirror.com"
311
+ }
312
+ ```
313
+
314
+ The base URL should point to a server that mirrors the structure of `https://malware-list.aikido.dev/`, including the following paths:
315
+ - `/malware_predictions.json` (JavaScript ecosystem malware database)
316
+ - `/malware_pypi.json` (Python ecosystem malware database)
317
+ - `/releases/npm.json` (JavaScript new packages list)
318
+ - `/releases/pypi.json` (Python new packages list)
319
+
320
+ ## Custom Install Directory
321
+
322
+ By default, Safe Chain installs itself into `~/.safe-chain`. You can change this by passing an explicit install directory to the installer. This is useful for system-wide installations (e.g. inside a Docker image) or when you need to avoid conflicts with other tools.
323
+
324
+ When set, all Safe Chain data (binary, shims, scripts, config) is placed under the custom directory instead of `~/.safe-chain`.
325
+
326
+ ### Unix/Linux/macOS
327
+
328
+ ```shell
329
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --install-dir /usr/local/.safe-chain
330
+ ```
331
+
332
+ ### Windows
333
+
334
+ ```powershell
335
+ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.ps1' -UseBasicParsing) } -InstallDir 'C:\ProgramData\safe-chain'"
336
+ ```
337
+
338
+ # Usage in CI/CD
339
+
340
+ 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.
341
+
342
+ ## Installation for CI/CD
343
+
344
+ Use the `--ci` flag to automatically configure Aikido Safe Chain for CI/CD environments. This sets up executable shims in the PATH instead of shell aliases.
345
+
346
+ ### Unix/Linux/macOS (GitHub Actions, Azure Pipelines, etc.)
347
+
348
+ ```shell
349
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
350
+ ```
351
+
352
+ ### Windows (Azure Pipelines, etc.)
353
+
354
+ ```powershell
355
+ iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.ps1' -UseBasicParsing) } -ci"
356
+ ```
357
+
358
+ ## Supported Platforms
359
+
360
+ - ✅ **GitHub Actions**
361
+ - ✅ **Azure Pipelines**
362
+ - ✅ **CircleCI**
363
+ - ✅ **Jenkins**
364
+ - ✅ **Bitbucket Pipelines**
365
+ - ✅ **GitLab Pipelines**
366
+
367
+ ## GitHub Actions Example
368
+
369
+ ```yaml
370
+ - name: Setup Node.js
371
+ uses: actions/setup-node@v4
372
+ with:
373
+ node-version: "22"
374
+ cache: "npm"
375
+
376
+ - name: Install safe-chain
377
+ run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
378
+
379
+ - name: Install dependencies
380
+ run: npm ci
381
+ ```
382
+
383
+ ## Azure DevOps Example
384
+
385
+ ```yaml
386
+ - task: NodeTool@0
387
+ inputs:
388
+ versionSpec: "22.x"
389
+ displayName: "Install Node.js"
390
+
391
+ - script: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
392
+ displayName: "Install safe-chain"
393
+
394
+ - script: npm ci
395
+ displayName: "Install dependencies"
396
+ ```
397
+
398
+ ## CircleCI Example
399
+
400
+ ```yaml
401
+ version: 2.1
402
+ jobs:
403
+ build:
404
+ docker:
405
+ - image: cimg/node:lts
406
+ steps:
407
+ - checkout
408
+ - run: |
409
+ curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci
410
+ - run: npm ci
411
+ workflows:
412
+ build_and_test:
413
+ jobs:
414
+ - build
415
+ ```
416
+
417
+ ## Jenkins Example
418
+
419
+ Note: This assumes Node.js and npm are installed on the Jenkins agent.
420
+
421
+ ```groovy
422
+ pipeline {
423
+ agent any
424
+
425
+ environment {
426
+ // Jenkins does not automatically persist PATH updates from setup-ci,
427
+ // so add the shims + binary directory explicitly for all stages.
428
+ // If you installed into a custom directory, replace ~/.safe-chain with that path here.
429
+ PATH = "${env.HOME}/.safe-chain/shims:${env.HOME}/.safe-chain/bin:${env.PATH}"
430
+ }
431
+
432
+ stages {
433
+ stage('Install safe-chain') {
434
+ steps {
435
+ sh '''
436
+ set -euo pipefail
437
+
438
+ # Install Safe Chain for CI
439
+ curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
440
+ '''
441
+ }
442
+ }
443
+
444
+ stage('Install project dependencies etc...') {
445
+ steps {
446
+ sh '''
447
+ set -euo pipefail
448
+ npm ci
449
+ '''
450
+ }
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ ## Bitbucket Pipelines Example
457
+
458
+ ```yaml
459
+ image: node:22
460
+
461
+ steps:
462
+ - step:
463
+ name: Install
464
+ script:
465
+ - curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
466
+ - export PATH=~/.safe-chain/shims:$PATH
467
+ - npm ci
468
+ ```
469
+
470
+ After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
471
+
472
+ ## GitLab Pipelines Example
473
+
474
+ To add safe-chain in GitLab pipelines, you need to install it in the image running the pipeline. This can be done by:
475
+
476
+ 1. Define a dockerfile to run your build
477
+
478
+ ```dockerfile
479
+ FROM node:lts
480
+
481
+ # Install safe-chain
482
+ RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
483
+
484
+ # Add safe-chain to PATH (update paths if you used a custom install dir)
485
+ ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}"
486
+ ```
487
+
488
+ 2. Build the Docker image in your CI pipeline
489
+
490
+ ```yaml
491
+ build-image:
492
+ stage: build-image
493
+ image: docker:latest
494
+ services:
495
+ - docker:dind
496
+ script:
497
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
498
+ - docker build -t $CI_REGISTRY_IMAGE:latest .
499
+ - docker push $CI_REGISTRY_IMAGE:latest
500
+ ```
501
+
502
+ 3. Use the image in your pipeline:
503
+ ```yaml
504
+ npm-ci:
505
+ stage: install
506
+ image: $CI_REGISTRY_IMAGE:latest
507
+ script:
508
+ - npm ci
509
+ ```
510
+
511
+ The full pipeline for this example looks like this:
512
+
513
+ ```yaml
514
+ stages:
515
+ - build-image
516
+ - install
517
+
518
+ build-image:
519
+ stage: build-image
520
+ image: docker:latest
521
+ services:
522
+ - docker:dind
523
+ script:
524
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
525
+ - docker build -t $CI_REGISTRY_IMAGE:latest .
526
+ - docker push $CI_REGISTRY_IMAGE:latest
527
+
528
+ npm-ci:
529
+ stage: install
530
+ image: $CI_REGISTRY_IMAGE:latest
531
+ script:
532
+ - npm ci
533
+ ```
534
+
535
+ # Troubleshooting
536
+
537
+ Having issues? See the [Troubleshooting Guide](https://help.aikido.dev/code-scanning/aikido-malware-scanning/safe-chain-troubleshooting) for help with common problems.
@@ -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 = "bun";
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 = "bunx";
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 = "npm";
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 = "npx";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,17 @@
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_PY } from "../src/config/settings.js";
6
+ import { PIP_PACKAGE_MANAGER, PIP_COMMAND } from "../src/packagemanager/pip/pipSettings.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ initializePackageManager(PIP_PACKAGE_MANAGER, { tool: PIP_COMMAND, args: process.argv.slice(2) });
12
+
13
+ (async () => {
14
+ // Pass through only user-supplied pip args
15
+ var exitCode = await main(process.argv.slice(2));
16
+ process.exit(exitCode);
17
+ })();
@@ -0,0 +1,17 @@
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_PY } from "../src/config/settings.js";
6
+ import { PIP_PACKAGE_MANAGER, PIP3_COMMAND } from "../src/packagemanager/pip/pipSettings.js";
7
+
8
+ // Set eco system
9
+ setEcoSystem(ECOSYSTEM_PY);
10
+
11
+ initializePackageManager(PIP_PACKAGE_MANAGER, { tool: PIP3_COMMAND, args: process.argv.slice(2) });
12
+
13
+ (async () => {
14
+ // Pass through only user-supplied pip args
15
+ var exitCode = await main(process.argv.slice(2));
16
+ process.exit(exitCode);
17
+ })();
@@ -0,0 +1,16 @@
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_PY } from "../src/config/settings.js";
6
+
7
+ // Set eco system
8
+ setEcoSystem(ECOSYSTEM_PY);
9
+
10
+ initializePackageManager("pipx");
11
+
12
+ (async () => {
13
+ // Pass through only user-supplied pipx args
14
+ var exitCode = await main(process.argv.slice(2));
15
+ process.exit(exitCode);
16
+ })();
@@ -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 = "pnpm";
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 = "pnpx";
9
+ initializePackageManager(packageManagerName);
10
+
11
+ (async () => {
12
+ var exitCode = await main(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ })();
@@ -0,0 +1,13 @@
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_PY } from "../src/config/settings.js";
6
+
7
+ setEcoSystem(ECOSYSTEM_PY);
8
+ initializePackageManager("poetry");
9
+
10
+ (async () => {
11
+ var exitCode = await main(process.argv.slice(2));
12
+ process.exit(exitCode);
13
+ })();