Skip to content

About

Invisible dependency isolation for local development and coding agents that routes risky package-manager operations through a disposable Docker-compatible container.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

safe-install

Invisible dependency isolation for local development and coding agents.

safe-install routes risky package-manager operations through a disposable Docker-compatible container. On macOS, OrbStack is the intended low-friction runtime. The goal is simple: keep normal pnpm install, npm ci, uv sync, and pip install workflows feeling ordinary while host secrets stay out of reach.

What It Protects

By default, install commands run with:

  • only the current project mounted at /work
  • container HOME=/tmp/safe-home
  • no host home directory
  • no SSH keys, browser profiles, npm tokens, PyPI tokens, GitHub tokens, or agent settings
  • dropped Linux capabilities
  • no-new-privileges
  • npm/pnpm lifecycle scripts disabled unless explicitly allowed
  • Python pip install commands install into a project .venv and the python/python3 shims auto-use that environment from inside the project

Install

Claude Code Plugin

Claude Code can load safe-install as a plugin. When enabled, the plugin's bin/ wrappers are available to Claude's Bash tool, so package-manager installs run through safe-install without shell rc setup.

For local testing from this checkout:

claude --plugin-dir .

To install the local development marketplace:

claude plugin marketplace add /path/to/safe-install --scope user
claude plugin marketplace update safe-install-dev
claude plugin install safe-install@safe-install-dev --scope user

In Claude Code, verify:

safe-install doctor
SAFE_INSTALL_DRY_RUN=1 pnpm install

The intended marketplace flow is:

/plugin install safe-install@<marketplace>
/reload-plugins

Shell Installer

From the project you want to protect, run:

curl -fsSL https://raw.githubusercontent.com/cachetronaut/safe-install/main/install.sh | bash

Then restart Terminal and Claude Code.

For the current terminal only:

source ~/.local/share/safe-install/activate.sh
safe-install doctor

Expected:

pnpm: protected
npm: protected
npx: protected
bun: protected
uv: protected
pip3: protected
python3: protected

If you already cloned the repo locally, run:

./bin/safe-install init --repo /path/to/your/project

init does four visible things:

  • adds shell activation to your shell rc file
  • installs a repo-local pre-commit guard when the current directory is a git repo
  • configures Claude Code when ~/.claude/settings.json exists
  • starts OrbStack/Docker when possible

Open a new terminal after init, or run source /path/to/safe-install/activate.sh once in the current terminal. Restart Claude Code after init, then run safe-install doctor inside Claude. For an already-open Codex or coding-agent shell, run eval "$($HOME/.local/share/safe-install/bin/safe-install reload)" so the current process prepends the installed wrappers without a restart.

Normal Use

Use your usual commands:

pnpm install
npm ci
bun install
uv sync --locked
uv pip install pytest
pip install -r requirements.txt

The wrappers intercept install-like operations and run them in the container. Other commands pass through to the real host package manager.

Python is the exception for local usability: bare pip install ... creates or reuses .venv in the current project, installs there with the host Python, and subsequent python or python3 commands automatically use that .venv through the safe-install shims. This avoids disappearing container-only installs and macOS externally-managed Python failures.

uv uses the official Debian uv image rather than a Python-minor-specific image, so project Python constraints such as Python 3.14 remain valid. The wrapper hides a host .venv behind a container-local volume during uv commands because virtual environments are platform-specific. It also moves uv's downloaded Python interpreters and cache out of /tmp/safe-home so uv run can execute them inside the container.

For packages that expose command-line tools, use the ecosystem launchers instead of guessing host paths. pnpm dlx/npx/bunx fetch-and-run remote packages and are sandboxed; pnpm exec/pnpm run/bun run run already-installed local code on the host:

pnpm exec <command>   # local, runs on host
pnpm dlx <package>    # remote, sandboxed
npx <package>         # remote, sandboxed
bunx <package>        # remote, sandboxed
bun run <command>     # local, runs on host
uv run <command>
uv tool run <tool>

Direct Use

safe-install --pm pnpm -- install
safe-install --pm npm -- ci
safe-install --pm bun -- install
safe-install --pm uv -- sync --locked
safe-install --pm pip -- install -r requirements.txt

Preview without running Docker:

SAFE_INSTALL_DRY_RUN=1 pnpm install
SAFE_INSTALL_DRY_RUN=1 pnpm dlx vite --version
SAFE_INSTALL_DRY_RUN=1 npx prettier --version
SAFE_INSTALL_DRY_RUN=1 bun install
SAFE_INSTALL_DRY_RUN=1 bunx cowsay hi
SAFE_INSTALL_DRY_RUN=1 uv run python --version
safe-install --pm pnpm --dry-run -- install
SAFE_INSTALL_DRY_RUN=1 pip install pdfplumber

Run without network for already-downloaded material:

safe-install --pm pnpm --no-network --readonly -- install

Allow npm/pnpm/bun lifecycle scripts only when you trust the dependency tree:

safe-install --pm pnpm --allow-scripts -- install
safe-install --pm bun --allow-scripts -- install

Agent Enforcement

Claude Code plugin sessions are protected when the plugin's bin/ directory is on Claude's Bash PATH. Shell and other coding-agent sessions are protected when their shell environment resolves package managers through safe-install/bin first.

Check any terminal or agent session:

safe-install doctor

Start the local container runtime when needed:

safe-install start

Real install commands also try to start OrbStack or Docker Desktop automatically on macOS when Docker is not reachable. Set SAFE_INSTALL_AUTO_START=0 to disable that behavior. If startup fails, wait until docker info succeeds in the same shell before retrying the install. Colima and other Docker-compatible runtimes are supported too, but they must expose a working Docker CLI/socket before safe-install can run the protected container.

For pnpm TypeScript/Node repos that commit lockfile changes from the protected Linux container, native optional dependency entries can differ from the macOS host unless the repo declares both platforms. Add a pnpm supportedArchitectures block when platform-specific optional packages matter:

{
  "pnpm": {
    "supportedArchitectures": {
      "os": ["current", "linux"],
      "cpu": ["current", "x64", "arm64"],
      "libc": ["current", "glibc"]
    }
  }
}

Install a repo-local pre-commit guard:

safe-install install-git-hook --repo /path/to/repo

The guard blocks commits that stage dependency files when safe-install is not active on PATH.

For Claude Code, prefer the plugin route. The shell installer still configures ~/.claude/settings.json automatically when that file exists. If Claude Code was already open, restart it and check:

safe-install doctor

Manual Claude Code setup is still available:

node ./scripts/install-claude-env.js

For Codex CLI sessions launched from your shell, shell activation is enough. For desktop apps that do not inherit your login shell, start them from an activated terminal or configure their launcher environment with:

PATH=/path/to/safe-install/bin:$PATH

For an already-open Codex worktree, run this inside that session after installing or updating:

eval "$($HOME/.local/share/safe-install/bin/safe-install reload)"
safe-install doctor

Wrapped Commands

  • pnpm: install, i, add, update, up, import, dlx, create
  • pnpx: package execution through pnpm dlx
  • npm: install, i, ci, update, up, exec, x, init
  • npx: package execution through npm exec
  • bun: install, i, add, remove, rm, update, up, x, create, init
  • bunx: package execution through bun x
  • uv: sync, add, pip, tool, python, run
  • pip and pip3: install, wheel, download
  • python and python3: auto-dispatch to a project .venv when one exists, otherwise pass through

pnpm exec, pnpm run, and bun run are intentionally not wrapped. They invoke already-installed, local, trusted code, so they run on the host. Sandboxing them would run the tool as root inside the container against the bind-mounted repo and corrupt git-aware tooling (for example, a lint-staged pre-commit hook whose stash/restore would rewrite the host index). Remote-fetching execution still goes through the container via pnpm dlx/pnpm create, npx/npm exec, and bunx/bun x/bun create.

Bypass is explicit:

SAFE_INSTALL_BYPASS=1 pnpm install

Resource Defaults

The default container budget is intentionally small for everyday local development:

  • memory: 2g
  • CPUs: 2
  • tmpfs: 512m

Override per command or session when a dependency tree needs more room:

SAFE_INSTALL_MEMORY=4g SAFE_INSTALL_CPUS=4 pnpm install
SAFE_INSTALL_TMPFS_SIZE=1g uv sync

Extending Backends

The first backend targets Docker-compatible runtimes. That covers OrbStack on macOS, Docker Desktop, Colima, Podman-compatible Docker sockets, and Linux Docker engines.

Future backends should preserve the same policy contract:

  • project-only mount
  • disposable home
  • no ambient secrets
  • optional network isolation
  • package-manager-specific lifecycle-script controls
  • safe-install doctor visibility

Python virtualenv management is intentionally host-side because Linux container virtualenvs and compiled wheels are not generally executable on macOS hosts. Use SAFE_INSTALL_PYTHON_CONTAINER=1 pip install ... only when you explicitly want the previous disposable-container behavior.

See CONTRIBUTING.md.

Security Model

This reduces risk from dependency installs. It does not make arbitrary untrusted code safe.

The mounted project directory is still writable by default. With network enabled, code inside the container can still reach the internet. Keep secrets out of project folders and use --no-network --readonly when inspecting suspicious material.

See SECURITY.md.

About

Invisible dependency isolation for local development and coding agents that routes risky package-manager operations through a disposable Docker-compatible container.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages