Install-time hardening for package managers. Reduces credential exposure during pip install, npm install, and other package operations by isolating builds in Docker containers and scanning source for exfiltration patterns.
Status: Alpha (v0.1.x). pip and npm ecosystems are the most mature. Cargo, Go, Gem, and Docker adapters are experimental. See the maturity table below.
Every time you run pip install or npm install, any package in the dependency tree can execute arbitrary code during the build/install phase. That code runs with your full user permissions and can read SSH keys, cloud credentials, API tokens, browser passwords, and anything else accessible to your account.
This is not theoretical. Real-world attacks exploiting install-time code execution include compromised maintainer accounts, typosquatting campaigns, and dependency confusion attacks across pip, npm, cargo, and gem ecosystems.
On March 24, 2026, attackers published malicious versions of litellm (v1.82.7 and v1.82.8) after compromising PyPI publishing credentials through a poisoned Trivy security scanner in LiteLLM's CI/CD pipeline. The malicious versions were live for approximately three hours — on a package with ~3.4 million daily downloads.
The payload operated in three stages:
- Credential harvesting — collected SSH keys, cloud tokens, and Kubernetes configs
- Encrypted exfiltration — sent stolen data to attacker infrastructure using AES-256 + RSA
- Persistence — deployed systemd services and Kubernetes pods for lateral movement
Because the attacker used legitimate PyPI credentials, hash verification and signature checks would not have caught this. However, safe-install's defenses directly address multiple layers of this attack:
| Defense Layer | Would it help? | Why |
|---|---|---|
| Docker Sandbox | Yes | Credentials, SSH keys, and cloud tokens are invisible inside the container — nothing to steal |
| Source Inspection | Yes | The payload contained detectable patterns: HTTP exfiltration, env var access, file reads targeting ~/.ssh/ and ~/.aws/ |
| Credential Vault | Yes | Sensitive files and env vars would have been temporarily hidden during install |
.pth file detection |
Yes | v0.1.1 specifically detects .pth file injection, the persistence technique used in v1.82.8 |
The one gap: the .pth persistence technique is an import-time attack — it executes on every Python startup, not just during install. The Docker sandbox protects install-time only. See Limitations for more on this boundary.
This incident directly motivated the detection patterns added in safe-install v0.1.1.
safe-install interposes between you and your package manager. Its primary defense is Docker-based build isolation: packages are downloaded and built inside a locked-down container with no access to your filesystem, credentials, or environment variables. The resulting artifacts (wheels, tarballs) are copied out and installed locally without executing any code.
When Docker is unavailable, safe-install falls back to a credential vault that temporarily hides sensitive files and clears sensitive environment variables during the install.
Additionally, safe-install runs heuristic source inspection that scans package source code for patterns commonly associated with exfiltration (HTTP requests in setup.py, environment variable access in build scripts, etc.).
- Import-time attacks: The sandbox protects install-time only. A malicious
__init__.pystill runs when youimport the_packagein your real environment. - Obfuscated payloads: Source inspection uses pattern matching. Encrypted payloads, steganography, and multi-stage loaders can evade it.
- Compiled native extensions: Binary code in wheels (
.so,.dll) can contain anything. Source inspection cannot analyze compiled code. - Build tool compromise: If pip, npm, or cargo themselves are compromised, safe-install cannot help.
- Registry infrastructure attacks: If PyPI or npm registry infrastructure is compromised at the server level.
- Complete protection: This tool reduces exposure and adds friction to attacks. It is not a guarantee.
The Docker sandbox is the only defense layer that does not depend on detecting malicious behavior. It works by removing the attack surface entirely: the container has nothing to steal, regardless of how sophisticated or obfuscated the malicious code is.
Your machine Docker container
+------------------+ +------------------+
| ~/.ssh/ | | (empty) |
| ~/.aws/ | -- INVISIBLE -> | No home dir |
| ~/.gitconfig | | No env vars |
| $GITHUB_TOKEN | | No volume mounts |
| Chrome passwords | | --cap-drop=ALL |
+------------------+ | --read-only |
| --memory=2g |
pip install pkg | --no-new-priv |
| +------------------+
v |
Download + build in container |
| |
Copy .whl files out <-------------------+
|
pip install --no-deps *.whl (just unzips, no code runs)
Docker isolation is strong but not absolute. Theoretical risks include container escapes (mitigated by --cap-drop=ALL and --security-opt=no-new-privileges), DNS-based exfiltration from within the container, and resource exhaustion despite limits.
- Reduce exposure: Minimize what malicious code can access during install.
- Contain risk: Isolate builds so that even successful exploitation has limited impact.
- Add friction and visibility: Make attacks harder and more detectable, not impossible.
- Defense in depth: Multiple independent layers, each with known limitations.
- Not a guarantee: No security tool can promise complete protection. safe-install shifts the odds.
pip install safe-installcurl -sSL https://raw.githubusercontent.com/Khaeldur/safe-install/main/install.sh | bashNote: piping curl to bash has its own supply chain risks. Consider cloning the repo and reviewing the script first.
| Ecosystem | Dep Resolution | Source Scan | Docker Sandbox | Local Install | Overall |
|---|---|---|---|---|---|
| pip | Full tree via --dry-run --report |
Python patterns (comprehensive) | Builds wheels in container | pip install --no-deps *.whl |
Strong |
| npm | Direct deps via npm view |
JS patterns (comprehensive) | npm pack in container |
npm install --ignore-scripts |
Strong |
| cargo | Top-level only (no transitive) | Rust patterns (basic) | cargo fetch in container |
Manual (prints instructions) | Experimental |
| go | Top-level module only | Go patterns (basic) | go mod download in container |
Manual (prints instructions) | Experimental |
| gem | Direct deps only | Ruby patterns (basic) | gem fetch in container |
gem install --local (still runs extconf.rb) |
Experimental |
| docker | Image layers only | Dockerfile patterns (not wired in) | N/A (is Docker) | docker load |
Experimental |
"Experimental" means: code exists and may provide some protection, but has not been tested against adversarial inputs, has incomplete dependency resolution, and may have non-functional code paths.
| Threat | Docker Sandbox | Credential Vault | Source Scan |
|---|---|---|---|
| Credential theft in setup.py/postinstall | Isolated | Hidden | Detected (if not obfuscated) |
| Cryptominer during build | Isolated (resource-limited) | N/A | Detected (heuristic) |
| RAM/CPU bomb | Limited (--memory, --cpus) | N/A | N/A |
| Environment variable exfiltration | Isolated (no env vars in container) | Cleared | Detected (heuristic) |
- Typosquatting via edit-distance comparison against popular package names
- Suspicious patterns in build scripts (HTTP requests, env access, file reads)
- Unexpected network connections during install
- Young packages, recent maintainer changes, low download counts
- Import-time code execution (
__init__.py,conftest.py) - Compiled native extensions with embedded payloads
- Obfuscated or encrypted malicious code that evades pattern matching
- Time-delayed payloads that activate long after install
- Attacks through the package manager itself
| Layer | Method | Confidence | Notes |
|---|---|---|---|
| Docker Sandbox | OS-level container isolation | High | Primary defense. No host access. |
| Binary-only mode | Wheels only, no setup.py | High (pip) | Some packages lack wheels. |
| Hash lockfile | SHA256 verification | High (when lockfile exists) | Lockfile generation not yet implemented. |
| Typosquat detection | Edit distance + popularity DB | Moderate | Unvalidated accuracy. |
| Package intelligence | Registry metadata analysis | Moderate | Queries real APIs. No malicious-package DB. |
| Source inspection | Regex pattern matching | Low-Moderate | Bypassable via obfuscation. Useful as early warning. |
| Credential vault | Temporarily hide files/env vars | Moderate | Fallback when Docker unavailable. Known bypasses exist. |
| Network monitor | Connection polling during install | Low-Moderate | 1s polling interval. Fast exfil can slip through. |
# Check what's exposed on your machine right now
safe-install check-env
# Install a package with full protection
safe-install install requests
# Audit a package without installing
safe-install audit litellm
# Scan a local project for suspicious patterns
safe-install scan ./my-project/# Auto-detects ecosystem from package name
safe-install install requests # pip
safe-install install -e npm express # explicit ecosystem
safe-install install -e cargo serde # experimental
safe-install install -e go github.com/gin-gonic/gin # experimental
# Docker sandbox (default if Docker is available)
safe-install install flask
# Binary-only (refuse source distributions)
safe-install install flask --binary-only
# Fallback to credential vault (when Docker unavailable)
safe-install install flask --no-sandbox
# Dry run (audit everything, install nothing)
safe-install install flask --dry-run
# Audit
safe-install audit requests
safe-install audit -e npm lodash
safe-install audit flask --deep # includes intelligence + binary analysisCreate ~/.config/safe-install/config.toml (global) or ./safe-install.toml (per-project):
[sandbox]
enabled = true
memory_limit = "2g"
cpu_limit = "2"
timeout = 600
[vault]
extra_paths = [
"~/.custom-secrets",
]
extra_env_vars = [
"MY_SECRET_API_KEY",
]
[network]
allowed_hosts = [
"my-private-registry.com",
]-
Import-time attacks: The sandbox protects install-time. A malicious
__init__.pystill runs when youimport the_package. Mitigation: use virtual environments, consider thesafe-install guardcommand (experimental). -
Obfuscated code: Source inspection uses pattern matching. Sophisticated obfuscation evades it. The Docker sandbox does not care about obfuscation, but it only protects install-time.
-
Without Docker: The credential vault fallback is imperfect. An attacker who knows about safe-install could look for the vault temp directory, or access paths not in the sensitive list.
-
Compiled extensions: Native C/C++ extensions in wheels can contain anything. Source inspection cannot analyze compiled code.
-
Experimental ecosystems: cargo, go, gem, and docker adapters have incomplete dependency resolution and may have non-functional code paths. Do not rely on them for security-critical workflows.
See CONTRIBUTING.md.
See SECURITY.md.
MIT