
Protect against malicious code installed via npm, yarn, pnpm, npx, pnpx, pip, uv and poetry with Aikido Safe Chain. Free to use, no tokens required.
Aikido Device Protection builds on Safe Chain, extending package and extension security across more ecosystems: npm, PyPI, VS Code, Open VSX - (Cursor, Windsurf, Kiro, Vs Codium, ...), Maven, NuGet, Chrome extensions, Go, Skills.sh AI skills, Ruby, Rust, and more.
Get centralized policy management, request-and-approval workflows, and visibility across every developer workstation in your org. Powered by the same Aikido Intel feed. Deploy it manually or manage it through your MDM tool (Jamf, Fleet, or Iru).
Aikido Safe Chain supports the following package managers:

Installing the Aikido Safe Chain is easy with the installation script.
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh \
&& echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c - \
&& sh /tmp/install-safe-chain.sh \
&& rm /tmp/install-safe-chain.sh
$installer = Join-Path $env:TEMP "install-safe-chain.ps1"
Invoke-WebRequest "https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.ps1" -OutFile $installer -UseBasicParsing
$expectedHash = "5FC22EEF74814BEF6828AA07C0EAEC79598F343F98B94A4ACAB823769AD56DA1"
if ((Get-FileHash $installer -Algorithm SHA256).Hash -ne $expectedHash) {
Remove-Item $installer -ErrorAction SilentlyContinue
throw "Checksum verification failed for install-safe-chain.ps1"
}
& $installer
Remove-Item $installer
The install commands above always reference a specific release. To install a different version, replace the version with your desired version number. All available versions are on the releases page.
The install scripts are served from a versioned release URL (releases/download/1.5.15/...). GitHub releases are immutable — once an artifact is published at a versioned URL it cannot be modified or replaced, so the file you download is guaranteed to be exactly what was released.
❗Restart your terminal to start using the Aikido Safe Chain.
Verify the installation by running the verification command:
npm safe-chain-verify
pnpm safe-chain-verify
pip safe-chain-verify
uv safe-chain-verify
# Any other supported package manager: {packagemanager} safe-chain-verify
(Optional) Test malware blocking by attempting to install a test package:
For JavaScript/Node.js:
npm install safe-chain-test
For Python:
pip3 install safe-chain-pi-test
When running npm, npx, yarn, pnpm, pnpx, rush, rushx, bun, bunx, pip, pip3, uv, uvx, poetry, pipx and pdm 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., , ). If any malware is detected, it will prompt you to exit the command.
You can check the installed version by running:
safe-chain --version
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, pipx or pdm commands, all package downloads are routed through this local proxy, which verifies packages in real-time against Aikido Intel - Open Sources Threat Intelligence. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.
Safe Chain applies minimum package age checks to supported ecosystems.
Current enforcement differs by ecosystem:
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 section below.
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, pdm). 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:
More information about the shell integration can be found in the shell integration documentation.
To uninstall the Aikido Safe Chain, use our one-line uninstaller:
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/uninstall-safe-chain.sh | sh
iex (iwr "https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/uninstall-safe-chain.ps1" -UseBasicParsing)
❗Restart your terminal after uninstalling to ensure all aliases are removed.
You can control the output from Aikido Safe Chain using the --safe-chain-logging flag or the SAFE_CHAIN_LOGGING environment variable.
You can set the logging level through multiple sources (in order of priority):
CLI Argument (highest priority):
--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.
npm install express --safe-chain-logging=silent
--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.
npm install express --safe-chain-logging=verbose
Environment Variable:
export SAFE_CHAIN_LOGGING=verbose
npm install express
Valid values: silent, normal, verbose
This is useful for setting a default logging level for all package manager commands in your terminal session or CI/CD environment.
You can mirror Aikido Safe Chain output to a log file using the --safe-chain-log-file flag or the SAFE_CHAIN_LOG_FILE environment variable. File logging is disabled by default and enabled when a path is set. The file format (--safe-chain-log-file-format) and verbosity (--safe-chain-log-file-verbosity) are controlled independently from the terminal output.
Set through any of these (in order of priority):
CLI Argument (highest priority):
npm install express \
--safe-chain-log-file=~/safe-chain.log \
--safe-chain-log-file-format=plain \
--safe-chain-log-file-verbosity=normal
Environment Variable:
export SAFE_CHAIN_LOG_FILE=~/safe-chain.log
export SAFE_CHAIN_LOG_FILE_FORMAT=plain
export SAFE_CHAIN_LOG_FILE_VERBOSITY=normal
Config File (~/.safe-chain/config.json):
{
"logFile": "~/safe-chain.log",
"logFileFormat": "plain",
"logFileVerbosity": "normal"
}
logFileFormat — json (default) or plain.
logFileVerbosity — silent, normal, or verbose (default). Independent from --safe-chain-logging.
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.
For npm-based package managers, this check currently has two enforcement modes:
For Python package managers, this check currently has two enforcement modes:
You can set the minimum package age through multiple sources (in order of priority):
CLI Argument (highest priority):
npm install express --safe-chain-minimum-package-age-hours=48
Environment Variable:
export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS=48
npm install express
Config File (~/.safe-chain/config.json):
{
"minimumPackageAgeHours": 48
}
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:
export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="@aikidosec/*"
{
"npm": {
"minimumPackageAgeExclusions": ["@aikidosec/*"]
},
"pip": {
"minimumPackageAgeExclusions": ["requests"]
}
}
Configure Safe Chain to scan packages from custom or private registries.
Supported ecosystems:
You can set custom registries through environment variable or config file. Both sources are merged together.
Environment Variable (comma-separated):
export SAFE_CHAIN_NPM_CUSTOM_REGISTRIES="npm.company.com,registry.internal.net"
export SAFE_CHAIN_PIP_CUSTOM_REGISTRIES="pip.company.com,registry.internal.net"
Config File (~/.safe-chain/config.json):
{
"npm": {
"customRegistries": ["npm.company.com", "registry.internal.net"]
},
"pip": {
"customRegistries": ["pip.company.com", "registry.internal.net"]
}
}
If you rely on a pip.conf file for pip configuration you must point pip at it explicitly via the PIP_CONFIG_FILE environment variable so Safe Chain can merge it.
Safe Chain runs pip behind its MITM proxy and writes a temporary pip configuration file to inject its certificate and proxy settings. When PIP_CONFIG_FILE is set, Safe Chain merges its settings into a copy of your file (your original file is never modified) so your index-url, credentials, and other options are preserved. When PIP_CONFIG_FILE is not set, pip's user-level config (e.g. ~/.config/pip/pip.conf) might be overridden by Safe Chain's temporary file and your settings will not be picked up.
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.
You can set the malware list base URL through multiple sources (in order of priority):
CLI Argument (highest priority):
npm install express --safe-chain-malware-list-base-url=https://your-mirror.com
Environment Variable:
export SAFE_CHAIN_MALWARE_LIST_BASE_URL=https://your-mirror.com
npm install express
Config File (~/.safe-chain/config.json):
{
"malwareListBaseUrl": "https://your-mirror.com"
}
The base URL should point to a server that mirrors the structure of https://malware-list.aikido.dev/, including the following paths:
/malware_predictions.json (JavaScript ecosystem malware database)/malware_pypi.json (Python ecosystem malware database)/releases/npm.json (JavaScript new packages list)/releases/pypi.json (Python new packages list)In addition to the home-directory config file (~/.safe-chain/config.json), Safe Chain supports a project config file so that settings can be checked into a repository and shared across a team, instead of being configured per-machine.
Add a safe-chain: section to the .aikido file at the root of your repository (the same file used by other Aikido tools, Safe Chain only reads its own safe-chain: section and ignores the rest). When found, its settings are merged on top of your home-directory config file: values set in the project config take priority, and arrays (such as customRegistries) are combined from both files rather than one replacing the other.
Only the following settings can be set from a project config file:
safe-chain:
minimumPackageAgeHours: 48
npm:
customRegistries:
- npm.company.com
minimumPackageAgeExclusions:
- "@aikidosec/*"
pip:
customRegistries:
- pip.company.com
minimumPackageAgeExclusions:
- requests
Settings like scanTimeout, malwareListBaseUrl, and the logFile* options cannot be set from a project config file - they can only come from your home-directory config, CLI arguments, or environment variables.
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.
When set, all Safe Chain data (binary, shims, scripts, config) is placed under the custom directory instead of ~/.safe-chain.
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh \
&& echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c - \
&& sh /tmp/install-safe-chain.sh --install-dir /usr/local/.safe-chain \
&& rm /tmp/install-safe-chain.sh
$installer = Join-Path $env:TEMP "install-safe-chain.ps1"
Invoke-WebRequest "https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.ps1" -OutFile $installer -UseBasicParsing
$expectedHash = "5FC22EEF74814BEF6828AA07C0EAEC79598F343F98B94A4ACAB823769AD56DA1"
if ((Get-FileHash $installer -Algorithm SHA256).Hash -ne $expectedHash) {
Remove-Item $installer -ErrorAction SilentlyContinue
throw "Checksum verification failed for install-safe-chain.ps1"
}
& $installer -InstallDir 'C:\ProgramData\safe-chain'
Remove-Item $installer
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.
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.
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh \
&& echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c - \
&& sh /tmp/install-safe-chain.sh --ci \
&& rm /tmp/install-safe-chain.sh
$installer = Join-Path $env:TEMP "install-safe-chain.ps1"
Invoke-WebRequest "https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.ps1" -OutFile $installer -UseBasicParsing
$expectedHash = "5FC22EEF74814BEF6828AA07C0EAEC79598F343F98B94A4ACAB823769AD56DA1"
if ((Get-FileHash $installer -Algorithm SHA256).Hash -ne $expectedHash) {
Remove-Item $installer -ErrorAction SilentlyContinue
throw "Checksum verification failed for install-safe-chain.ps1"
}
& $installer -ci
Remove-Item $installer
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Install safe-chain
run: |
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh
echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c -
sh /tmp/install-safe-chain.sh --ci
rm /tmp/install-safe-chain.sh
- name: Install dependencies
run: npm ci
- task: NodeTool@0
inputs:
versionSpec: "22.x"
displayName: "Install Node.js"
- script: |
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh
echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c -
sh /tmp/install-safe-chain.sh --ci
rm /tmp/install-safe-chain.sh
displayName: "Install safe-chain"
- script: npm ci
displayName: "Install dependencies"
version: 2.1
jobs:
build:
docker:
- image: cimg/node:lts
steps:
- checkout
- run: |
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh
echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c -
sh /tmp/install-safe-chain.sh --ci
rm /tmp/install-safe-chain.sh
- run: npm ci
workflows:
build_and_test:
jobs:
- build
Note: This assumes Node.js and npm are installed on the Jenkins agent.
pipeline {
agent any
environment {
// Jenkins does not automatically persist PATH updates from setup-ci,
// so add the shims + binary directory explicitly for all stages.
// If you installed into a custom directory, replace ~/.safe-chain with that path here.
PATH = "${env.HOME}/.safe-chain/shims:${env.HOME}/.safe-chain/bin:${env.PATH}"
}
stages {
stage('Install safe-chain') {
steps {
sh '''
set -euo pipefail
# Install Safe Chain for CI
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh
echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c -
sh /tmp/install-safe-chain.sh --ci
rm /tmp/install-safe-chain.sh
'''
}
}
stage('Install project dependencies etc...') {
steps {
sh '''
set -euo pipefail
npm ci
'''
}
}
}
}
image: node:22
steps:
- step:
name: Install
script:
- |
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh
echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c -
sh /tmp/install-safe-chain.sh --ci
rm /tmp/install-safe-chain.sh
- export PATH=~/.safe-chain/shims:~/.safe-chain/bin:$PATH
- npm ci
After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.
To add safe-chain in GitLab pipelines, you need to install it in the image running the pipeline. This can be done by:
Define a dockerfile to run your build
FROM node:lts
# Install safe-chain
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.5.15/install-safe-chain.sh -o /tmp/install-safe-chain.sh \
&& echo "de0565e3d6346407a604e84e639e95fea8758748063da2216bbfdca5feda5dd2 /tmp/install-safe-chain.sh" | sha256sum -c - \
&& sh /tmp/install-safe-chain.sh --ci \
&& rm /tmp/install-safe-chain.sh
# Add safe-chain to PATH (update paths if you used a custom install dir)
ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}"
Build the Docker image in your CI pipeline
build-image:
stage: build-image
image: docker:latest
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
Use the image in your pipeline:
npm-ci:
stage: install
image: $CI_REGISTRY_IMAGE:latest
script:
- npm ci
The full pipeline for this example looks like this:
stages:
- build-image
- install
build-image:
stage: build-image
image: docker:latest
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
npm-ci:
stage: install
image: $CI_REGISTRY_IMAGE:latest
script:
- npm ci
Having issues? See the Troubleshooting Guide for help with common problems.
If you encounter problems:
safe-chain --version output--safe-chain-logging=verbose argument)python -m pip install ...python3 -m pip download ...