mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Add redirecting for explicit python(3) commands
This commit is contained in:
parent
f6381f5e91
commit
57bbb06f39
6 changed files with 143 additions and 5 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Aikido Safe Chain
|
# Aikido Safe Chain
|
||||||
|
|
||||||
The Aikido Safe Chain **prevents developers from installing malware** on their workstations while developing in the Python ecosystem (through pip or pip3) or in the Javascript ecosystem (through npm, npx, yarn, pnpm, pnpx, bun and bunx). It's **free** to use and does not require any token.
|
The Aikido Safe Chain **prevents developers from installing malware** on their workstations while developing in the Python ecosystem (through pip or pip3, including `python -m pip[...]` and `python3 -m pip[...]` where available) or in the Javascript ecosystem (through npm, npx, yarn, pnpm, pnpx, bun and bunx). It's **free** to use and does not require any token.
|
||||||
|
|
||||||
The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), [bunx](https://bun.sh/docs/cli/bunx), and [pip](https://pip.pypa.io/) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, bunx, or pip/pip3 from downloading or running the malware.
|
The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), [bunx](https://bun.sh/docs/cli/bunx), and [pip](https://pip.pypa.io/) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, bunx, or pip/pip3 from downloading or running the malware.
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ Installing the Aikido Safe Chain is easy. You just need 3 simple steps:
|
||||||
```
|
```
|
||||||
- The output should show that Aikido Safe Chain is blocking the installation of this package as it is flagged as malware.
|
- The output should show that Aikido Safe Chain is blocking the installation of this package as it is flagged as malware.
|
||||||
|
|
||||||
When running `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, or `pip3` commands, the Aikido Safe Chain will automatically check for malware in the packages you are trying to install. If any malware is detected, it will prompt you to exit the command.
|
When running `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, or `pip3` 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 ...`, and on Windows `py -m pip ...`). If any malware is detected, it will prompt you to exit the command.
|
||||||
|
|
||||||
You can check the installed version by running:
|
You can check the installed version by running:
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ safe-chain --version
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
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`, or `pip3` 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.
|
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`, or `pip3` 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)**. Python `-m pip[...]` invocations are also routed when invoked by command name. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.
|
||||||
|
|
||||||
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm, pnpx, bun, bunx, and pip commands. 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:
|
The Aikido Safe Chain integrates with your shell to provide a seamless experience when using npm, npx, yarn, pnpm, pnpx, bun, bunx, and pip commands. 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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`) with Aikido's security scanning functionality. This is achieved by sourcing startup scripts that define shell functions to wrap these commands with their Aikido-protected equivalents.
|
The shell integration automatically wraps common package manager commands (`npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, `pip3`) with Aikido's security scanning functionality. It also intercepts Python module invocations for pip when available: `python -m pip`, `python -m pip3`, `python3 -m pip`, `python3 -m pip3`, and on Windows PowerShell `py -m pip`/`py -m pip3`. This is achieved by sourcing startup scripts that define shell functions to wrap these commands with their Aikido-protected equivalents.
|
||||||
|
|
||||||
## Supported Shells
|
## Supported Shells
|
||||||
|
|
||||||
|
|
@ -29,6 +29,7 @@ This command:
|
||||||
- Copies necessary startup scripts to Safe Chain's installation directory (`~/.safe-chain/scripts`)
|
- Copies necessary startup scripts to Safe Chain's installation directory (`~/.safe-chain/scripts`)
|
||||||
- Detects all supported shells on your system
|
- Detects all supported shells on your system
|
||||||
- Sources each shell's startup file to add Safe Chain functions for `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, and `pip3`
|
- Sources each shell's startup file to add Safe Chain functions for `npm`, `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, and `pip3`
|
||||||
|
- Adds lightweight interceptors so `python -m pip[...]` and `python3 -m pip[...]` (and `py -m pip[...]` on Windows PowerShell) route through Safe Chain when invoked by name
|
||||||
|
|
||||||
❗ After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the startup scripts are sourced correctly.
|
❗ After running this command, **you must restart your terminal** for the changes to take effect. This ensures that the startup scripts are sourced correctly.
|
||||||
|
|
||||||
|
|
@ -80,6 +81,12 @@ This means the shell functions are working but the Aikido commands aren't instal
|
||||||
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm`, `aikido-pnpx`, `aikido-bun`, `aikido-bunx`, `aikido-pip`, and `aikido-pip3` commands exist
|
- Verify the `aikido-npm`, `aikido-npx`, `aikido-yarn`, `aikido-pnpm`, `aikido-pnpx`, `aikido-bun`, `aikido-bunx`, `aikido-pip`, and `aikido-pip3` commands exist
|
||||||
- Check that these commands are in your system's PATH
|
- Check that these commands are in your system's PATH
|
||||||
|
|
||||||
|
**`python -m pip` is not being intercepted:**
|
||||||
|
|
||||||
|
- Ensure you are invoking `python`/`python3`/`py` by name (not via an absolute path). Shell function interception only occurs for command names resolved through PATH and won’t catch absolute paths like `/usr/bin/python -m pip`.
|
||||||
|
- Restart your terminal so the updated startup scripts are sourced.
|
||||||
|
- On Windows PowerShell, verify `python`, `python3` or `py` resolves by running `Get-Command python` / `Get-Command py`.
|
||||||
|
|
||||||
### Manual Verification
|
### Manual Verification
|
||||||
|
|
||||||
To verify the integration is working, follow these steps:
|
To verify the integration is working, follow these steps:
|
||||||
|
|
@ -98,7 +105,8 @@ To verify the integration is working, follow these steps:
|
||||||
After restarting your terminal, run these commands:
|
After restarting your terminal, run these commands:
|
||||||
|
|
||||||
- `npm --version` - Should show output from the Aikido-wrapped version
|
- `npm --version` - Should show output from the Aikido-wrapped version
|
||||||
- `type npm` - Should show that `npm` is a function
|
- `type npm` - Should show that `npm` is a function
|
||||||
|
- Optionally: `python -m pip --version` (or `python3 -m pip --version`) should show Safe Chain output at the end
|
||||||
|
|
||||||
3. **If you need to remove the integration manually:**
|
3. **If you need to remove the integration manually:**
|
||||||
|
|
||||||
|
|
@ -121,3 +129,28 @@ npm() {
|
||||||
```
|
```
|
||||||
|
|
||||||
Repeat this pattern for `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, and `pip3` using their respective `aikido-*` commands. After adding these functions, restart your terminal to apply the changes.
|
Repeat this pattern for `npx`, `yarn`, `pnpm`, `pnpx`, `bun`, `bunx`, `pip`, and `pip3` using their respective `aikido-*` commands. After adding these functions, restart your terminal to apply the changes.
|
||||||
|
|
||||||
|
To intercept Python module invocations for pip without altering Python itself, you can add small forwarding functions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example for Bash/Zsh
|
||||||
|
python() {
|
||||||
|
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||||
|
local mod="$2"; shift 2
|
||||||
|
if [[ "$mod" == "pip3" ]]; then aikido-pip3 "$@"; else aikido-pip "$@"; fi
|
||||||
|
else
|
||||||
|
command python "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
python3() {
|
||||||
|
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||||
|
local mod="$2"; shift 2
|
||||||
|
if [[ "$mod" == "pip3" ]]; then aikido-pip3 "$@"; else aikido-pip "$@"; fi
|
||||||
|
else
|
||||||
|
command python3 "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Limitations: these only apply when invoking `python`/`python3` by name. Absolute paths (e.g., `/usr/bin/python -m pip`) bypass shell functions.
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,33 @@ end
|
||||||
function pip3
|
function pip3
|
||||||
wrapSafeChainCommand "pip3" "aikido-pip3" $argv
|
wrapSafeChainCommand "pip3" "aikido-pip3" $argv
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `python -m pip`, `python -m pip3`.
|
||||||
|
function python
|
||||||
|
if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2]
|
||||||
|
set mod $argv[2]
|
||||||
|
set args $argv[3..-1]
|
||||||
|
if test $mod = "pip3"
|
||||||
|
wrapSafeChainCommand "pip3" "aikido-pip3" $args
|
||||||
|
else
|
||||||
|
wrapSafeChainCommand "pip" "aikido-pip" $args
|
||||||
|
end
|
||||||
|
else
|
||||||
|
command python $argv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# `python3 -m pip`, `python3 -m pip3'.
|
||||||
|
function python3
|
||||||
|
if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2]
|
||||||
|
set mod $argv[2]
|
||||||
|
set args $argv[3..-1]
|
||||||
|
if test $mod = "pip3"
|
||||||
|
wrapSafeChainCommand "pip3" "aikido-pip3" $args
|
||||||
|
else
|
||||||
|
wrapSafeChainCommand "pip" "aikido-pip" $args
|
||||||
|
end
|
||||||
|
else
|
||||||
|
command python3 $argv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -68,3 +68,33 @@ function pip() {
|
||||||
function pip3() {
|
function pip3() {
|
||||||
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Intercept `python -m pip[...]` so it routes through safe-chain without changing python itself.
|
||||||
|
# Supports: `python -m pip`, `python -m pip3`, `python3 -m pip`, `python3 -m pip3`.
|
||||||
|
function python() {
|
||||||
|
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||||
|
local mod="$2"
|
||||||
|
shift 2
|
||||||
|
if [[ "$mod" == "pip3" ]]; then
|
||||||
|
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
||||||
|
else
|
||||||
|
wrapSafeChainCommand "pip" "aikido-pip" "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
command python "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function python3() {
|
||||||
|
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||||
|
local mod="$2"
|
||||||
|
shift 2
|
||||||
|
if [[ "$mod" == "pip3" ]]; then
|
||||||
|
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
||||||
|
else
|
||||||
|
wrapSafeChainCommand "pip" "aikido-pip" "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
command python3 "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,3 +94,28 @@ function pip {
|
||||||
function pip3 {
|
function pip3 {
|
||||||
Invoke-WrappedCommand "pip3" "aikido-pip3" $args
|
Invoke-WrappedCommand "pip3" "aikido-pip3" $args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# `python -m pip`, `python -m pip3`.
|
||||||
|
function python {
|
||||||
|
param([Parameter(ValueFromRemainingArguments=$true)]$Args)
|
||||||
|
if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') {
|
||||||
|
if ($Args[1] -eq 'pip3') { Invoke-WrappedCommand 'pip3' 'aikido-pip3' $Args[2..($Args.Length-1)] }
|
||||||
|
else { Invoke-WrappedCommand 'pip' 'aikido-pip' $Args[2..($Args.Length-1)] }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Invoke-RealCommand 'python' $Args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# `python3 -m pip`, `python3 -m pip3'.
|
||||||
|
function python3 {
|
||||||
|
param([Parameter(ValueFromRemainingArguments=$true)]$Args)
|
||||||
|
if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') {
|
||||||
|
if ($Args[1] -eq 'pip3') { Invoke-WrappedCommand 'pip3' 'aikido-pip3' $Args[2..($Args.Length-1)] }
|
||||||
|
else { Invoke-WrappedCommand 'pip' 'aikido-pip' $Args[2..($Args.Length-1)] }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Invoke-RealCommand 'python3' $Args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,4 +86,24 @@ describe("E2E: pip coverage", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`python3 -m pip install routes through safe-chain`, async () => {
|
||||||
|
const shell = await container.openShell("zsh");
|
||||||
|
const result = await shell.runCommand('python3 -m pip install requests');
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
result.output.includes("no malicious packages found."),
|
||||||
|
`Output did not include expected text. Output was:\n${result.output}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`python3 -m pip download routes through safe-chain`, async () => {
|
||||||
|
const shell = await container.openShell("zsh");
|
||||||
|
const result = await shell.runCommand('python3 -m pip download requests');
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
result.output.includes("no malicious packages found."),
|
||||||
|
`Output did not include expected text. Output was:\n${result.output}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue