SafeInstall
safeinstall-cli · 0.2.1

Stop risky package installs before they run.

SafeInstall wraps npm, pnpm, and bun: install policy runs first, then the real tool. Open source, MIT licensed. No account required. Six policy checks including Sigstore provenance verification and typo-squat detection.

Global install

$npm install -g safeinstall-cli

One-off / CI

$npx safeinstall-cli check

Node >=20 · Open source · MIT license

allowed — stderr before pnpm runs
$safeinstall pnpm add axios
Using config: built-in defaults
Allowed: policy checks passed.
# then pnpm output follows on stdout
blocked — policy output (exit 2)
$safeinstall pnpm add compromised-pkg@9.9.9
Using config: built-in defaults
Install blocked.
- compromised-pkg@9.9.9
Blocked: release too new (…; minimum is 72 hours).
Suggestion: Retry later or lower minimumReleaseAgeHours …
Blocked: install script present (… has postinstall).
Suggestion: Allow this package explicitly in allowedScripts …
Why this exists

Package install is now an attack surface.

Supply-chain attacks through npm and similar registries are increasing. The way developers install packages has changed. The tooling has not kept up.

01

Package installs are an attack surface.

Lifecycle scripts run arbitrary code at install time. A single malicious dependency can exfiltrate tokens, modify files, or establish persistence — before your app even starts.

02

AI coding makes this worse.

When an AI assistant suggests a package, most developers install it immediately. There is no pause for research, no audit, no second opinion. The speed that makes AI coding powerful also removes the friction that used to catch bad packages.

03

Scanners alert after the fact.

npm audit and most security tools tell you about known vulnerabilities in packages you already installed. They do nothing about install-time script execution. They do not prevent the package from running during install.

04

The fix is not more dashboards.

Developers need a guardrail that blocks many risky installs before they run—policy first, then the package manager. Not only a report after the fact.

05

Valid signatures are not enough.

A compromised npm maintainer can publish a malicious version that cryptographically verifies — signed by a GitHub Actions workflow the attacker controls in a fork of the real repository. Scanners and signature-only checks accept it. Install-time policy with trusted publisher pinning refuses it.

What it blocks

Six protections. Four on by default, two opt-in.

SafeInstall evaluates policy before your package manager runs. Four checks run by default. Typo-squat detection and Sigstore provenance verification are opt-in per project via safeinstall.config.json.

Blocked: release too new

Fresh registry releases

By default, registry versions newer than 72 hours are blocked (configurable via minimumReleaseAgeHours). That window is where many supply-chain publishes get noticed late.

Blocked: install script present

Lifecycle scripts

Packages with preinstall, install, or postinstall scripts are blocked unless you allow those scripts per package in allowedScripts. SafeInstall still forwards --ignore-scripts to the package manager by default so installs stay non-blind.

Blocked: untrusted source

Non-registry sources

Git, tarball, and direct URL installs are blocked unless that source type is in allowedSources. Registry, workspace, file, and directory sources are allowed by default.

Blocked: trust level dropped

Trust downgrades

Two cases: a registry dependency moves to git/url/tarball, or a new registry version introduces lifecycle scripts where the installed version had none (compared using local node_modules when present).

Blocked: suspected typo-squat

Typo-squat detection

Requested package names are compared against a curated list of popular npm packages via Damerau-Levenshtein distance with transposition. Close-but-not-exact matches like raect, lodsh, and axois are flagged. Opt-in via typoSquat.mode = 'warn' or 'block'. Minimum name length and per-project ignore list are configurable.

Blocked: publisher mismatch

Provenance verification

Fetches the Sigstore attestation bundle for registry packages, verifies signatures against the public Sigstore trust root, checks Rekor transparency log inclusion, and extracts the source repository from the SLSA v1 provenance statement. Pin the expected owner/repo per package via trustedPublishers. Mismatches always block, even in warn mode, because a valid signature from the wrong source is what maintainer-account compromise looks like.

The unique property

Catching a maintainer-compromise attack.

A valid Sigstore signature is not enough. An attacker who compromises an npm maintainer account can publish a malicious version of a package you already trust, and the attestation on that malicious version will cryptographically verify — signed by a GitHub Actions workflow the attacker controls in a fork of the real repository. SafeInstall catches this. Pin the expected source repository with provenance.trustedPublishers and any build that comes from anywhere else is blocked, even if the signature is valid.

publisher mismatch — blocked
$safeinstall check
Using config: ./safeinstall.config.json
Check blocked.
- axios@1.99.0
Blocked: publisher mismatch for axios (expected axios/axios, got evil-org/axios).
Suggestion: Verify the package source. Update provenance.trustedPublishers only if the change is intentional.

This is the only install-time policy gate that enforces the source-repository property of an npm provenance attestation. CVE scanners look for known vulnerabilities. Content analyzers look for suspicious code. SafeInstall verifies that the cryptographic chain of trust points at the repository you agreed to trust — and refuses anything else.

provenance verified — dogfood
$safeinstall check
Using config: ./safeinstall.config.json
Info: safeinstall-cli: provenance verified from Mickdownunder/SafeInstall via .github/workflows/release.yml.
Check passed: no direct dependency policy violations found.

SafeInstall verifies its own Sigstore attestation against its own GitHub repository via the public Sigstore transparency log.

How it works

Evaluate first. Execute second.

No registry proxy. No middleware on downloads. Policy runs locally, then the underlying tool runs as usual.

1

You run SafeInstall

Prefix your package manager command with safeinstall.

$safeinstall pnpm install
2

SafeInstall evaluates

For registry packages it uses public npm registry metadata (publish time, scripts). Project installs for pnpm/npm use the lockfile so versions match what the repo will install.

3

Block or allow

Policy violations exit with code 2 and print Blocked: … lines. Clean runs print Allowed: policy checks passed. then invoke the manager.

4

Package manager runs

On allow, your exact command runs. SafeInstall appends defaults like --ignore-scripts per packageManagerDefaults unless you change them.

stderr — allow
$safeinstall pnpm add zod
Using config: built-in defaults
Allowed: policy checks passed.
stderr — untrusted source
$safeinstall npm install git+https://github.com/foo/bar
Install blocked.
- git+https://github.com/foo/bar
Blocked: untrusted source (git).
vs. scanners

Scanners report. SafeInstall runs first.

npm audit and similar tools report on known issues in dependencies you already installed. SafeInstall evaluates policy before the package manager: it can refuse installs that violate age, script, or source rules, and it forwards --ignore-scripts by default unless you change defaults — see docs for behavior.

Scanners (npm audit, Snyk…)SafeInstall
When it runsAfter installBefore install
What it checksKnown CVEs in installed packagesInstall policy: age, scripts, source
OutputVulnerability reportAllow or block, with Blocked: … lines
Lifecycle scriptsDoes not block installBlocks if unallowed; default forwards --ignore-scripts to PM
Fresh release windowNoYes (default 72h minimum age)
Registry metadataVariesUses npm registry API when evaluating registry packages
Requires security expertiseTo act on results: yesReadable block reasons
Maintainer-compromise detectionNoYes (via trusted publisher pinning)
Provenance verificationNoYes (via Sigstore public trust root)
Typo-squat detectionNoYes (embedded top-N list, Damerau-Levenshtein)

Use both. They solve different problems. SafeInstall is pre-install; scanners are post-install.

Built for AI coding

Vibe coding is fast.
Blind installs are the cost.
SafeInstall is the gate.

AI assistants suggest packages in seconds. They don't check publish dates. They don't read install scripts. They don't verify the source. You type “yes” and move on.

SafeInstall adds one layer between suggestion and execution: policy. Release age, lifecycle scripts, source type, trust signals — checked before the package manager runs. You keep the speed. You lose the blind spot.

If you vibe code, this is the safety net your workflow is missing.

Works with every AI tool that suggests or runs installs

CursorGitHub CopilotClineClaude CodeWindsurfAiderDevinContinue

Typical vibe coding workflow

You"add a PDF generation library"
AIpnpm add pdf-maker@0.1.2 — "try this"
Beforepnpm add pdf-maker@0.1.2
Nowsafeinstall pnpm add pdf-maker@0.1.2
SafeInstallInstall blocked. Blocked: release too new.
CLI examples

Same commands — SafeInstall in front.

Also supported: safeinstall init, safeinstall --json …, and project installs that resolve direct deps from lockfiles (pnpm / npm).

pnpm add — allowed
$safeinstall pnpm add axios
Using config: built-in defaults
Allowed: policy checks passed.
Release too new (hours threshold)
$safeinstall npm install react@latest
Install blocked.
- react@…
Blocked: release too new (…; minimum is 72 hours).
Suggestion: Retry later or lower minimumReleaseAgeHours…
Git / URL source blocked
$safeinstall bun add github:owner/repo
Install blocked.
- …
Blocked: untrusted source (git).
safeinstall check — direct deps
$safeinstall check
Using config: ./safeinstall.config.json
Check passed: no direct dependency policy violations found.
Typo-squat caught
$safeinstall pnpm add raect
Using config: ./safeinstall.config.json
Install blocked.
- raect
Blocked: Suspected typo-squat: "raect" is 1 edit(s) away from popular package "react".
Suggestion: Verify you meant to install "react". If this package is intentional, add "raect" to typoSquat.ignore.
Provenance verified
$safeinstall check
Using config: ./safeinstall.config.json
Info: safeinstall-cli: provenance verified from Mickdownunder/SafeInstall via .github/workflows/release.yml.
Check passed: no direct dependency policy violations found.
Who it is for

Built for developers who ship fast.

A CLI guardrail, not a platform. Scope is intentional: pre-install policy only.

Solo developers

Your machine, your responsibility.

  • AI-assisted workflows mean more installs and less package review
  • No security team to catch risky publishes for you
  • One global install — policy runs locally, no account needed
  • Use safeinstall check on direct dependencies before you ship
Startup engineering teams

Move fast. Don't run installs blind.

  • Small teams ship quickly; one bad install can hit everyone
  • Commit safeinstall.config.json so policy matches across machines
  • Exit code 2 on block is CI-friendly when you wrap installs in pipelines
  • pnpm and npm project installs honor lockfiles — fewer surprise versions
AI-first teams

Your AI writes the install command. You control the gate.

  • Using Cursor, Copilot, Cline, Claude Code, or Windsurf
  • AI suggestions skip the usual review instincts — SafeInstall does not
  • Same prefix (safeinstall) for npm, pnpm, and bun
  • JSON mode (--json) fits automation around agents and scripts
Get started

Free forever. Open source. Install in 10 seconds.

Install the CLI globally or try it with npx. No account, no sign-up, no registry auth. Read the docs · View source

$npm install -g safeinstall-cli
$npx safeinstall-cli check

Node >=20 · MIT license