SafeInstall
Documentation

SafeInstall

Package safeinstall-cli (0.2.1). Local-first wrapper: policy is evaluated before npm, pnpm, or bun runs. Blocks risky installs by default and, opt-in, verifies the cryptographic provenance of registry packages via Sigstore trusted publisher pinning. Not a CVE scanner.

Overview

Prefix your package manager with safeinstall. The CLI resolves what would be installed, fetches registry metadata from the configured npm-compatible registry when needed (public npm registry by default), evaluates rules, then either exits with code 2 (blocked) or invokes the real tool—often with defaults such as ignore-scripts.

SafeInstall does not proxy the registry or modify tarballs. Network access is required to evaluate registry packages (publish time and declared scripts).

Local-first
Policy runs on your machine.
Lockfile-aware
npm/pnpm project installs use package-lock / pnpm-lock.
Fail-closed
Ambiguous or mismatched lockfile / packageManager states block.
Explicit outcomes
Exit 2 means blocked by policy.
Monorepo-safe
Workspace-wide flags blocked; target one package with -C / --prefix.
Honest scope
Not malware detection; transitive policy not complete.
Cryptographically verifiable
Registry packages can be verified against their Sigstore attestation and pinned to a specific owner/repo, refusing valid signatures from unexpected sources.
Typo-squat aware
Requested names are matched against a curated top-package list via Damerau-Levenshtein distance. Catches lodsh, raect, axois, and friends before they install.

Installation

  1. Use Node.js >=20.
  2. Install globally or invoke with npx — no account or registry auth required.
$npm install -g safeinstall-cli
# Without a global install:
$npx safeinstall-cli check

Open source · MIT licensed · Source on GitHub

Usage

Pass through the same arguments you would use with the underlying tool.

$safeinstall pnpm add axios
$safeinstall npm install
$safeinstall bun add zod
$safeinstall check
$safeinstall init
$safeinstall --json pnpm add axios

Project installs

For pnpm install and npm install / npm ci, direct dependency versions come from pnpm-lock.yaml or package-lock.json (or shrinkwrap)—not loose ranges in package.json alone. Stale, missing, or mismatched lockfile entries fail closed instead of guessing.

If packageManager is set in package.json, using a different CLI is blocked. Workspace-targeting flags that may touch multiple packages (e.g. pnpm --filter, npm --workspace) are blocked; use -C or --prefix for a single package directory.

bun install still uses manifest-oriented analysis; full lockfile parity with npm/pnpm is not implemented yet.

Policy (defaults)

These are the main gates. Adjust or exempt via config where documented below.

Release ageBlocked: release too new

Registry packages must be at least 72 hours old by default (minimumReleaseAgeHours).

Lifecycle scriptsBlocked: install script present

preinstall, install, and postinstall on the resolved registry version are blocked unless allowed per package via allowedScripts.

SourcesBlocked: untrusted source

allowedSources defaults to registry, workspace, file, and directory. Git, tarball, and URL installs are outside the default set.

Trust downgradeBlocked: trust level dropped

Blocks when a dependency moves from registry to git/url/tarball, or when a new registry version adds lifecycle scripts that the version already in node_modules did not declare (comparison needs that prior install present).

Typo-squat detectionBlocked: suspected typo-squat

Requested package names compared via Damerau-Levenshtein distance with transposition against a curated top-package list. Three modes: off (default), warn, block. Configure via typoSquat.mode. See Configuration.

Provenance verificationBlocked: publisher mismatch / attestation missing / attestation invalid / attestation unreachable

Cryptographically verifies Sigstore attestations for registry packages via the public Sigstore trust root and Rekor transparency log. Pins source repository via provenance.trustedPublishers. Three modes: off (default), warn, require. Configure via provenance.mode. See Configuration.

Configuration

Optional safeinstall.config.json is discovered by walking upward from the effective project directory; the nearest file wins.

safeinstall.config.json (shape)
{
"minimumReleaseAgeHours": 72,
"allowedScripts": { "esbuild": ["postinstall"] },
"allowedSources": ["registry", "workspace", "file", "directory"],
"allowedPackages": [],
"ciMode": false,
"packageManagerDefaults": {
"npm": { "ignoreScripts": true },
"pnpm": { "ignoreScripts": true },
"bun": { "ignoreScripts": true }
},
"typoSquat": {
"mode": "off",
"minNameLength": 4,
"ignore": []
},
"provenance": {
"mode": "off",
"requireFor": [],
"trustedPublishers": {},
"offlineBehavior": "fail-closed"
}
}
FieldRole
minimumReleaseAgeHoursMinimum age in hours for registry versions.
registryUrlnpm-compatible registry URL for metadata (mirrors, Artifactory, Verdaccio).
allowedScriptsPackage name → allowed script names. Does not remove ignore-scripts unless you change packageManagerDefaults.
allowedSourcesSource types permitted (add git, tarball, url only if you intend to allow them).
allowedPackagesNames that skip policy checks entirely (warning emitted).
ciModeStored in config; default merges CI="true". Install and check flows do not branch on it yet.
packageManagerDefaultsPer-manager flags forwarded when spawning the tool; ignoreScripts defaults to true.
typoSquat.mode"off" / "warn" / "block". Controls the typo-squat check. Defaults to "off".
typoSquat.minNameLengthMinimum length of a requested package name to participate in the typo-squat check. Names shorter than this are skipped. Defaults to 4.
typoSquat.ignoreArray of package names (lowercased on load) that will never be flagged as typo-squats. Use for known legitimate lookalikes.
provenance.mode"off" / "warn" / "require". Controls Sigstore provenance verification for registry packages. Defaults to "off".
provenance.requireForArray of package name patterns (exact or glob with * wildcard). Provenance is required for these packages even when mode is "warn".
provenance.trustedPublishersObject mapping package name patterns to expected owner/repo slug patterns. Example: { "axios": "axios/axios", "@acme/*": "acme/*" }. A verified attestation whose source repository does not match the pin is ALWAYS blocked, even in "warn" mode.
provenance.offlineBehavior"fail-closed" (default) or "allow-cached". Controls what happens when the attestation endpoint is unreachable.

CI and exit codes

0 — success or policy allowed. 1 — runtime or config error. 2 — blocked by policy. Use 2 like any other failing step in a pipeline.

GitHub Actions (illustrative)
- run: safeinstall pnpm install --frozen-lockfile
env:
CI: true

safeinstall check

Evaluates direct dependencies only. On success, stderr includes: Check passed: no direct dependency policy violations found.

$safeinstall check --json

safeinstall init

Writes a starter safeinstall.config.json in the current directory. Refuses if the file exists unless you pass --force.

$safeinstall init

JSON output

Put --json anywhere in argv. Structured output goes to stdout; human-oriented messages stay separate. Field details are stable in the CLI version you install.

$safeinstall --json pnpm add axios

Exceptions

allowedPackages — exact name match skips checks (with a warning). allowedScripts — per-package lifecycle phases you accept for policy; SafeInstall may still forward ignore-scripts by default via packageManagerDefaults.

{
"allowedPackages": ["internal-tooling"],
"allowedScripts": {
"esbuild": ["postinstall"]
}
}

Limitations

  • Not a CVE scanner — pair with npm audit or other tools for vulnerability data.
  • Transitive dependency policy is not fully implemented.
  • Trust script comparison needs the previous resolved tree under node_modules.
  • Bun project installs are not fully lockfile-aware.
  • peerDependencies are not evaluated unless also declared as direct dependencies.
  • Ambiguous metadata leads to a block, not a silent allow.
  • Typo-squat target list is curated and refreshed manually between releases; brand-new packages published in the last day may not yet appear on the list.
  • Provenance verification supports GitHub Actions trusted publishers on the public Sigstore root only. GitLab CI, self-hosted Sigstore, and other trust roots are out of scope for 0.2.x.
  • Git sources are identified by URL for allowlist and trusted-publisher purposes, not by inferred package name. This is intentional: conflating registry axios with github:any-fork/axios would be dangerous.

Out of scope

SafeInstall is a pre-install guard, not a full platform.

×Vulnerability databases / CVE scanning
×Registry proxy or tarball rewriting
×Hosted dashboards or cloud policy services
×Malware detection or package content analysis (pair with a content analyzer like Socket.dev or Phylum if you need that)
×GitLab CI / Bitbucket Pipelines trusted publishers (GitHub Actions only for 0.2.x)
×Self-hosted Sigstore instances (public Sigstore root only)
×Transitive dependency policy (still in progress for a future release)
×Selective lifecycle script execution

License

SafeInstall is open source, released under the MIT license. Source code is on GitHub. Free forever for individual use. Team features (shared policy, org-wide enforcement) are a planned commercial addition — not part of the current CLI.

Provided as-is under MIT. Not a security guarantee. SafeInstall is a policy tool. It does not guarantee the safety of any package, does not detect all supply-chain attacks, and does not replace professional security review.