Your AI agent installs packages.
SafeInstall decides which ones run.
The open, local-first install guard for Claude Code, Codex, and Cursor. Typosquats, hours-old malware releases, and provenance breaks are blocked before npm, pnpm, or bun ever executes — and the policy defends itself against the agent, too.
One command: starter config + guard hooks + tamper-evident baseline
Global install
Node >=20 · Open source · MIT license
Only 3 runtime dependencies · Sigstore and MCP load on demand
Hooked at the shell layer — whether or not the agent cooperates.
The guard intercepts every shell command before it executes. Each client gets the strongest behavior its hook protocol supports — no protocol is stretched past what it can actually enforce.
Rewritten in place: the hook returns the SafeInstall-routed command, and Claude's permission dialog shows the rewritten command — you approve safeinstall npm install axios, never the raw install. Verified end-to-end against Claude Code v2.1.206.
Ask — Claude's own approval prompt puts the decision in front of you before registry code is downloaded and executed.
Rewritten in place via updatedInput, same as Claude Code — the agent's install runs through SafeInstall without a round-trip.
Fail closed. Codex hooks support no ask decision, so runners are denied outright rather than silently allowed.
One-time step: review and trust the project hook with Codex /hooks — Codex skips unreviewed hooks by design.
Denied with the exact SafeInstall-routed command to run instead — a well-behaved agent self-corrects in one step.
Ask — Cursor prompts you before registry code runs. The hook is registered with failClosed: true, so a crashed hook blocks instead of silently allowing.
safeinstall init registers hooks for the clients actually present in the project (detected via .claude/, CLAUDE.md, .codex/, AGENTS.md, .cursor/, .cursorrules) — or pass --client claude,codex,cursor explicitly.
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.
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.
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.
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.
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.
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.
Eight protections. Four on by default, four opt-in.
SafeInstall evaluates policy before your package manager runs. Four checks run by default. Typo-squat detection, Sigstore provenance verification, transitive dependency evaluation, and provenance continuity are opt-in per project via safeinstall.config.json.
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.
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.
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.
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).
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.
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.
Transitive dependencies
Walks the full lockfile tree and flags transitive packages that declare lifecycle scripts or resolve from untrusted sources. Zero extra registry calls — reads directly from pnpm-lock.yaml or package-lock.json. The ua-parser-js attack class: a deeply nested dependency running code at install time. Opt-in via transitive.mode = 'warn' or 'block'.
Provenance continuity
Learns a per-package trust baseline from the provenance identity of recent versions, then blocks deviations: a version that arrives with no attestation where recent ones had it (downgrade), or one attested from a different source repository (identity discontinuity). The fingerprint of an account-compromise publish. Packages that never adopted provenance simply have no baseline, so the check stays silent — no false positives. Reads npm's attestation metadata, so it works without the optional sigstore package. Opt-in via continuity.mode = 'warn' or 'block'.
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.
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.
Pinning is the strongest form, but it needs you to declare the expected repository up front. Provenance continuity (new in 0.7.0) closes the gap for everything you have not pinned: it learns a per-package baseline from the provenance identity of recent versions and blocks the deviations npm itself does not — a version that suddenly arrives with no attestation (the fingerprint of a publish from a stolen token) or one attested from a different repositorythan the established baseline. It reads npm's published attestation metadata, so it works without the optional sigstore package. Packages that never adopted provenance have no baseline and the check stays silent — no false positives, no global “require provenance” sledgehammer. Opt in with continuity.mode.
SafeInstall verifies its own Sigstore attestation against its own GitHub repository via the public Sigstore transparency log.
Evaluate first. Execute second.
No registry proxy. No middleware on downloads. Policy runs locally, then the underlying tool runs as usual.
You run SafeInstall
Prefix your package manager command with safeinstall.
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.
Block or allow
Policy violations exit with code 2 and print Blocked: … lines. Clean runs print Allowed: policy checks passed. then invoke the manager.
Package manager runs
On allow, your exact command runs. SafeInstall appends defaults like --ignore-scripts per packageManagerDefaults unless you change them.
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 runs | After install | Before install |
| What it checks | Known CVEs in installed packages | Install policy: age, scripts, source |
| Output | Vulnerability report | Allow or block, with Blocked: … lines |
| Lifecycle scripts | Does not block install | Blocks if unallowed; default forwards --ignore-scripts to PM |
| Fresh release window | No | Yes (default 72h minimum age) |
| Registry metadata | Varies | Uses npm registry API when evaluating registry packages |
| Requires security expertise | To act on results: yes | Readable block reasons |
| Maintainer-compromise detection | No | Yes (via trusted publisher pinning) |
| Provenance verification | No | Yes (via Sigstore public trust root) |
| Typo-squat detection | No | Yes (embedded top-N list, Damerau-Levenshtein) |
| Transitive dependency checks | Limited (post-install only) | Yes (lockfile tree walk, zero registry calls) |
| GitHub Action for CI | No (manual setup) | Yes (Mickdownunder/SafeInstall@v1) |
Use both. They solve different problems. SafeInstall is pre-install; scanners are post-install.
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.
The MCP server (0.8.0) lets the agent consult the policy engine — point Claude Code, Codex, Cursor, Windsurf, or Cline at it and add the agent rule, and check_package is called before any install is suggested.
Enforcement (since 0.9.0). The guard hooks Claude Code, Codex, and Cursor at the shell layer. Every command the agent runs is intercepted before execution: raw installs are rewritten in place through SafeInstall (Claude Code and Codex) or denied with the exact routed command (Cursor), and package runners like npx require your approval — whether or not the agent cooperates.
Self-defending (since 0.10.0). The guard stops installs — the Agent Trust Surface stops the agent from rewriting the guard. It locks the files that configure SafeInstall and your agent, and re-verifies them in CI.
Works with every AI tool that suggests or runs installs
Typical vibe coding workflow
Enforce it — one command, per project
Registers pre-execution shell hooks for Claude Code, Codex, and Cursor — and locks the trust surface over them. Raw installs come back routed through SafeInstall, so the agent self-corrects in one step. Guard docs → · MCP setup →
Your agent's config files are the new crontab.
A prompt-injected agent doesn't have to fight the guard — it can just rewrite the rules: weaken safeinstall.config.json, delete the hook, plant an invisible instruction in CLAUDE.md, or register a malicious MCP server. Those files program the next session. In the agent era, they are where persistence lives.
safeinstall trust lock records a hash baseline of that surface. SafeInstall reconciles the real state against it before every guard decision and every install — so tampering shows up as drift, even a bypass the guard never sees.
And it knows the difference between git and tampering (0.12.0): the out-of-workspace ledger mirror reconciles by hash-chain containment, so a pull or rebase that brings reviewed, CI-verified trust entries fast-forwards silently — while rewrites and rollbacks of trust history still hard-block. Your normal workflow stays quiet; the alarms are reserved for tampering.
No SafeInstall tool at the market does this. Every other guard asks “is this package bad?” The trust surface asks the question the agent era added: did anyone quietly change the rules?
Lock it, and wire the CI anchor
The generated workflow re-hashes the committed baseline on a machine the agent does not control — the durable anchor. Trust Surface docs →
Three zones, proportionate responses
Honest scope. Locally this is tamper-evident against mistakes and non-targeted tampering, not tamper-proof against a scheme-aware agent in your own account. The durable guarantee is CI re-verifying the committed lock — which is exactly why trust lock --ci scaffolds it for you.
A security tool should meet its own standard.
Every claim on this page is backed by something you can check in the repository — an advisory, a workflow file, a test corpus, a diff you can run yourself.
We published a security advisory against our own guard.
Property-based fuzzing of our own shell parser found command forms the guard could miss. We filed the advisory ourselves, fixed and disclosed within 24 hours, and every captured bypass class now lives on as a permanent adversarial regression corpus in the repository — run on every test suite, alongside an independent reference detector.
Byte-reproducible, with Sigstore provenance.
Every release is published from CI via trusted-publisher OIDC with a Sigstore provenance attestation — cryptographically traceable to the tagged commit and workflow run. The tarball is byte-reproducible from that tag: run npm pack against the tagged commit and diff it against the registry yourself.
The thing that verifies trust is its own locked-down repo.
Trust verification runs from safeinstall-verifier — a separate, code-owner-locked public repository. It installs the CLI pinned by exact version and sha512 content hash and reads a candidate checkout strictly as data. A CI-required adversarial suite makes every tampered case fail and the clean fixture pass, so a vacuous verifier can never go green. Since 0.13, this repo invokes it by full commit SHA — pinned, not floating.
Every install decision leaves a record you can re-check.
In a git repository, each policy-evaluated install writes an actor-tagged, explicitly untrusted record into .safeinstall/decisions/ — bound by git blob OID to the manifest, lockfile, config, and trust lock, before and after the package manager ran. safeinstall decisions verify re-checks that a base..head delta is covered by schema-valid, linkage-continuous chains anchored at both committed ends. It verifies integrity and completeness; it never trusts a recorded verdict.
A catalogue of attacks against us that cannot lie about itself.
attack-lab/ is a machine-readable catalogue: every case carries its reproducible starting state, the expected SafeInstall layer, and a named regression test in the normal suite. The eval maps each to a status — an honest limit stays a known-gap and cannot be silently relabelled defended, and an unpatched bypass marked public fails the run as a discipline error. Run it yourself: node attack-lab/run.mjs.
trust approve has already refused a real coding agent.
Approving trust-surface changes requires a human at the controlling terminal (/dev/tty) — never stdin — and refuses CI, Claude Code, Cursor, and Codex sessions. The Codex refusal is not theoretical: the CODEX_SHELL gate shipped in 0.11.1 because a Codex session on the developer's own terminal was in a position to approve its own trust-surface changes. Now it cannot.
Same commands — SafeInstall in front.
Start with safeinstall init (config + guard hooks + trust baseline in one run). Also supported: safeinstall --json … and project installs that resolve direct deps from lockfiles (pnpm / npm).
Five lines of YAML. Policy on every pull request.
SafeInstall ships as a reusable GitHub Action. Add it to any workflow — no CLI installation, no configuration required. Defaults apply immediately. Blocked dependencies appear in the GitHub Actions job summary with the exact block reason.
Supports check mode (audit direct dependencies without installing) and install mode (enforce policy during the actual install step). Pin a specific CLI version for reproducible builds, or let it track latest.
And the trust-surface backstop scaffolds itself.
safeinstall trust lock --ci github writes a second workflow that re-verifies the committed Agent Trust Surface baseline on every pull request — on a machine the agent does not control.
Since 0.12.0 the scaffolded workflow is content-hash-pinned: it installs the verifier CLI from a tarball whose sha512 was recorded from the registry at scaffold time (trust on first use). A registry that later serves different bytes for the same version fails the check — the verifier cannot be silently swapped. Any registry error at scaffold time fails closed; a weaker, version-only workflow is never written.
Since 0.13 the verification logic itself lives in a separate, code-owner-locked repository — safeinstall-verifier — guarded by a CI-required adversarial suite, and this repo invokes it by full commit SHA.
Built for developers who ship fast.
A CLI guardrail, not a platform. Scope is intentional: pre-install policy only.
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
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
- —Add the GitHub Action to your CI pipeline — policy runs on every pull request without developers needing to remember a CLI prefix
- —Enable transitive.mode to catch risky install scripts buried deep in the dependency tree — the attack surface npm audit misses
Your AI writes the install command. You control the gate.
- —Using Claude Code, Codex, Cursor, Copilot, Cline, 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
- —GitHub Action catches risky installs even when the AI commits directly to a branch without human review
Free forever. Open source.
Protected in one command.
One run writes the config, hooks your agents, and locks the trust surface. No account, no sign-up, no registry auth. Read the docs · View source
Add to any GitHub Actions workflow · Node >=20 · MIT license