Pre-flight risk check before
npm install,pip install, orcargo add. Single static binary. Zero runtime dependencies. Runs offline-friendly.
safeinstall add lodashβ Score the package, prompt if risky, then run the underlying installer.
May 2026 has been a record month for npm supply-chain attacks:
- Mini Shai-Hulud worm hit 314 npm packages including TanStack, OpenAI, Mistral, Grafana
- Axios hijacked in March (100 million weekly downloads)
- Nx Console poisoned VS Code extension breached GitHub itself (3,800 internal repos exfiltrated)
- 454,600+ malicious npm packages catalogued in 2025; 99% on npm
npm install runs arbitrary code from the internet on your laptop. So does pip install. So does cargo add. There is no pre-flight check by default.
SafeInstall is the missing pre-flight. It scores a package against:
- Known-malicious advisories (OSV: hard block)
- Package age (very new = high risk)
- Weekly downloads (very low + high churn = suspicious)
- Maintainer count (1 maintainer = hijack risk)
- Recent change pattern (long-quiet package suddenly republished β the Axios pattern)
- Install scripts (
postinstall,preinstall) - Typosquatting against the top popular packages
- Known CVEs
- Missing source repository
If the score crosses your threshold, it prompts before installing. If it's a known-malicious package, it refuses outright.
go install github.com/ATOM00blue/safeinstall/cmd/safeinstall@latestOr download a prebuilt binary from Releases.
# Drop-in replacement for the install step
safeinstall add lodash
safeinstall add axios@1.7.2
# Just check; don't install
safeinstall check requests --ecosystem pip
# Audit every direct dependency in the current project
safeinstall auditThe ecosystem is auto-detected from package.json / requirements.txt / Cargo.toml in the current directory; override with --ecosystem.
$ safeinstall check loadash --ecosystem npm
SafeInstall report: loadash@1.0.0 (npm)
------------------------------------------------------------------------
SIGNAL POINTS DETAIL
------------------------------------------------------------------------
package_age 0/15 Package established (3529 days old).
weekly_downloads 0/15 Widely downloaded (38595/week).
maintainer_count 10/10 Single maintainer (bus factor / hijack risk).
recent_change 0/15 No suspicious recent activity.
install_scripts 0/10 No install scripts detected.
typosquat 10/10 Name is 1 edit(s) from popular package "lodash" - possible typosquat.
vulnerabilities 0/10 No known vulnerabilities.
missing_repository 0/5 Source repository declared.
------------------------------------------------------------------------
TOTAL: 20/100 (LOW)
safeinstall add <pkg>[@version]... Score, prompt, then install
safeinstall check <pkg>[@version] Score only (exit 1 if >= threshold)
safeinstall audit Score every direct dep in this project
safeinstall config [show|init] Show or initialize .safeinstall.json
safeinstall version Print version
safeinstall help Show usage
-e, --ecosystem <npm|pip|cargo> Override auto-detection
-t, --threshold <0-100> Risk threshold (default 60)
-y, --yes Skip prompt; auto-install if score < threshold
-q, --quiet Suppress non-essential output
--json Emit reports as JSON
--no-color Disable ANSI colors
-c, --config <path> Override config file path
| Code | Meaning |
|---|---|
| 0 | Success: installed, or check passed |
| 1 | Risky: user declined, threshold exceeded, hard block |
| 2 | Tool error (network, parse, config) |
| 3 | Underlying installer (npm/pip/cargo) failed |
| Signal | Weight | Triggers when |
|---|---|---|
| Malicious advisory (OSV) | HARD BLOCK | OSV flags the package as malicious |
| Denylist | HARD BLOCK | Package is in your config denylist |
| Package age | 15 | Created < 7d (15) / < 30d (10) / < 90d (5) |
| Weekly downloads | 15 | < 100 (15) / < 1k (10) / < 10k (5) |
| Maintainer count | 10 | 1 maintainer (10) / 2 (5) |
| Recent change | 15 | New version on >1yr-old package, published <7d ago (15) |
| Install scripts | 10 | preinstall / install / postinstall present |
| Typosquatting | 10 | Levenshtein distance β€ 2 from a popular package |
| Known vulnerabilities | 10 | CVEs by max severity (critical=10, high=7, etc) |
| Missing repository | 5 | No repository or homepage URL |
Allowlisted packages are capped at score 20 regardless of signals.
safeinstall config init creates this in your current directory:
{
"threshold": 60,
"prompt": "risk",
"allowlist": [],
"denylist": []
}| Key | Values | Default |
|---|---|---|
threshold |
0β100 | 60 |
prompt |
always / never / risk |
risk |
allowlist |
[]string of trusted package names |
[] |
denylist |
[]string of always-blocked packages |
[] |
Resolution order: built-in defaults β ~/.safeinstall.json β ./.safeinstall.json β --config flag.
prompt:
risk(default): prompt only when score β₯ thresholdalways: prompt on every installnever: never prompt; refuse install if score β₯ threshold (good for CI)
# .github/workflows/security.yml
- run: go install github.com/ATOM00blue/safeinstall/cmd/safeinstall@latest
- run: safeinstall audit --json --threshold 60 > audit.jsonExit code 1 if any direct dependency scores β₯ threshold.
| Tool | What it does | Open Source | Pre-install gate | Free |
|---|---|---|---|---|
| SafeInstall (this) | Pre-flight scoring across npm/pip/cargo | MIT | Yes | Yes |
| Socket.dev | SaaS supply-chain analysis | Partial | Yes (paid plans) | Free tier |
| Snyk | Vuln scanning + IDE | No | No | Freemium |
| OSV-Scanner | CVE scanning of installed deps | Yes (Apache 2.0) | No (post-install) | Yes |
npm audit |
Built-in vuln scanner | Yes | No (post-install) | Yes |
| Dependabot | Vuln alerts on installed deps | Yes | No | Yes |
The differentiator: most tools scan after you've already installed. SafeInstall scores before the network call to the registry.
- Not a sandbox. We don't actually isolate the install β we just decide whether to run it.
- Not full static analysis. We don't parse package source code.
- Best-effort network calls. OSV/registry timeouts degrade gracefully but reduce signal accuracy.
- The typosquat list is a curated subset. Add to
internal/typosquat/*_top.govia PR. - PyPI install-script detection is unimplemented. PyPI doesn't expose this in metadata; needs source inspection.
- Cargo build-script detection is unimplemented. Same reason.
- No transitive dep checking yet. v0.1 only scores the direct package you're installing.
- Maintainer-change detection is heuristic. npm doesn't expose maintainer history; we use "recently modified + old package" as a proxy.
- v0.1: scan, check, audit, config (npm + pip + cargo)
- v0.2:
safeinstall init-hookto wrap npm/pip/cargo via shell alias - v0.2: GitHub Action wrapper
- v0.3: Transitive dependency scoring
- v0.3: Maintainer history caching for true diff
- v0.4: Tarball inspection (suspicious file types, embedded binaries)
- v0.4: Lockfile drift detection
- v0.5: Custom rule plugins (YAML)
import (
"context"
"github.com/ATOM00blue/safeinstall/internal/registry"
"github.com/ATOM00blue/safeinstall/internal/osv"
"github.com/ATOM00blue/safeinstall/internal/scorer"
"github.com/ATOM00blue/safeinstall/internal/types"
)
ctx := context.Background()
info, _ := registry.New().Lookup(ctx, types.NPM, "axios", "1.7.2")
vulns, _ := osv.New().Query(ctx, types.NPM, "axios", "1.7.2")
report := scorer.Score(info, vulns, types.DefaultConfig())
fmt.Printf("score=%d level=%s\n", report.Score, report.Level)git clone https://github.com/ATOM00blue/safeinstall.git
cd safeinstall
go test ./...
go build -o sical ./cmd/safeinstall # local binary; rename if Windows AV gets twitchy
./sical check lodashWe especially want:
- Additions to the curated typosquat lists in
internal/typosquat/*_top.go - Real-world false-positive reports
- Detection rules for newly-disclosed campaigns (open an issue with the campaign name)
See CONTRIBUTING.md for the full guide.
MIT β use it, fork it, ship it in your enterprise toolchain.
Built in response to the May 2026 npm Mini Shai-Hulud campaign and the broader supply-chain attack wave. The package registries should do this for us. They don't. So here we are.