npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pi-better-sandbox

v0.3.0

Published

Pi extension that confines foreground shell execution to the project directory with a kernel-enforced write sandbox.

Readme

pi-better-sandbox

An opt-in write sandbox for Pi's foreground tools.

It is installed by default with pi-better-harness, and can be installed on its own:

pi install npm:pi-better-sandbox

Either way you keep starting Pi the way you always have — pi. There is no launcher or wrapper command. The sandbox starts inactive. Use /sandbox on for the current session or /sandbox default on to persist opt-in. While enabled, Pi's built-in bash tool and the ! / !! commands you type yourself run inside an OS sandbox that lets them write only under the directory you launched Pi from, and the built-in write and edit tools are held to the same policy.

Read:       every filesystem path
Write:      the canonical launch directory and everything under it
Exceptions: .git/hooks, .env, .env.local
Network:    unchanged

For shell commands the denial is done by the kernel, not by inspecting command text: macOS uses Seatbelt (sandbox-exec) and Linux uses Bubblewrap (bwrap). A crafted command cannot talk its way past it, because the write syscall itself is refused.

write and edit never start a child process — they change files inside Pi's own process — so there is no child to wrap. They are confined by a containment check on the canonical target instead, run inside Pi's own file-mutation queue, immediately before the filesystem call it guards. A refused mutation leaves nothing behind on disk.

What is confined, and what is not

Reads and network access are never restricted. Every path on the filesystem stays readable and network behaviour is exactly what it was. This sandbox limits writes, and nothing else.

Writes are confined for the integrated first-party execution paths, and only those:

  • Pi's built-in bash tool.
  • User-entered ! and !! commands.
  • Pi's built-in write and edit tools.
  • Local pi-better-background-tasks spawns and watches, which capture this policy at launch.
  • pi-better-subagents children, through the same shared mechanism.

Not confined:

  • Pi's own process.
  • pi.exec calls made by extensions.
  • Unrelated third-party extension code.
  • Another first-party surface's control plane. Each surface denies its own — the files naming what it will run next — but not every other surface's, so confinement is per surface rather than global.

This is a tool-execution sandbox. It limits accidental damage from commands the model or you run through Pi's shell; it is not a boundary around Pi itself.

Overriding write and edit changes nothing you can see: the parameter schemas, prompt guidance, call rendering, write previews, edit diffs, result details, mutation queueing, and cancellation are Pi's own. Only the filesystem operations underneath them are replaced.

Commands

/sandbox                     show the effective status
/sandbox on                  enable protection for operations started from now on
/sandbox off                 turn protection off for this session (interactive confirmation)
/sandbox default on          persist opt-in and enable it now
/sandbox default off         persist opt-out (interactive confirmation)
/sandbox deny list           show the write-denied paths
/sandbox deny add <path>     stop allowing writes to a path
/sandbox deny remove <path>  allow writes to a path again
/sandbox deny reset          drop your changes and restore the packaged defaults
/sandbox rules               open the write-denied paths editor

The footer shows sandbox · available when a backend is available but inactive, sandbox · inactive when inactive without a backend, and sandbox · on · <project> while protection is active. Explicitly enabled sessions report UNAVAILABLE or FAILED when protection cannot be applied. Both surfaces report what the runtime actually resolved — which backend, which executable — never what was merely configured.

/sandbox off and /sandbox default off need interactive confirmation and are refused outright when there is no interactive UI. There is no tool for changing sandbox state or its rules, so the model cannot change confinement or edit the paths it is confined away from.

Write-denied paths

Three paths are denied out of the box — .git/hooks, .env, and .env.local, relative to whichever project you are in. They live in the package's source, so installing writes no settings file anywhere.

Rules are paths, not patterns. Write one of three ways:

| You type | It means | | ------------------- | --------------------------------------------------- | | build/artifacts | that path inside every project you open | | ~/.aws | that path under your home directory | | /etc/hosts | exactly that path |

A relative rule is stored as a template and resolved against each project, which is why one global rule set is enough — there is no per-project database. Lists and the editor always show the canonical absolute path a rule currently resolves to. A directory denies its whole subtree; a file denies that exact file, whether or not it exists yet.

/sandbox rules opens a compact keyboard-driven editor over the same rules: arrow keys to move, enter to remove the highlighted rule, or pick Add to type a new one and Restore the packaged defaults to start over. The slash commands and the editor are two front ends over one validation and persistence module, so they cannot disagree.

Changes take effect for shell commands and file mutations started after them. A command already running keeps the rules it launched with.

Where your rules live

Your changes are written to ~/.pi/agent/extensions/pi-better-sandbox.json (under $PI_CODING_AGENT_DIR when you set one):

{
  "version": 1,
  "denyWrite": [".env", ".env.local", ".git/hooks", "build/artifacts"]
}

That file appears the first time you add or remove a rule, never at install time. /sandbox deny reset deletes it and puts the defaults shipped by the installed package version back in force — so an upgrade that changes the defaults is picked up by a reset rather than being masked by a stale copy.

If the file cannot be read, the packaged defaults stay in force, the problem is reported, and rule changes are refused until you fix the file or reset it — a typo is never quietly turned into a lost rule set.

What is refused, and why

  • Empty entries and patterns (*.pem, src/**/x) — rules are concrete paths; a pattern would silently match nothing.
  • Duplicates, however they are spelled: .env, ./.env, the absolute path, or a symlink pointing at the same file all resolve to one canonical path.
  • Overlaps, in both directions. A path already inside a denied directory would change nothing; a directory that would swallow a narrower rule names that rule so you can remove it deliberately instead of losing it silently.
  • A rule that contains the project root., .., /, or ~ when your project lives under home. Denying it would make every write in the project fail. /sandbox off is the thing you actually want there.

A global rule that turns out to contain the root of a different project stays in your rule set but is held out in that project, with a message saying so.

Lifecycle

The foreground sandbox is inactive by default. Session overrides do not survive startup, new session, resume, fork, or reload. /sandbox default on|off stores the default for those future sessions in ~/.pi/agent/extensions/pi-better-sandbox-preferences.json.

Toggles apply to operations launched after the change. A command already running keeps the policy it launched with.

Fail-closed behaviour

While the sandbox is explicitly or persistently enabled and a backend cannot be applied, protected commands and file mutations are blocked rather than run unprotected:

  • No backend on this platform (unavailable).
  • A launch directory too broad to confine — / or your home directory (failed). Relaunch Pi from the directory you are actually working in, or turn the sandbox off on purpose.
  • A backend that was selected but failed to start. It is never retried directly.

Paths, symlinks and denied files

The launch directory is canonicalized at session start, so reaching a project through a symlink does not widen what is writable. Denied entries are canonicalized the same way: a directory denies its whole subtree, a file denies that exact file, and an alias pointing at a denied file is denied too.

Platform support

| Platform | Backend | Requirement | | -------- | --------------------------- | ---------------------------- | | macOS | Seatbelt (sandbox-exec) | ships with the OS | | Linux | Bubblewrap (bwrap) | install bubblewrap | | Other | none | protected commands are blocked |

For other extensions

The effective policy is published as a frozen snapshot on Pi's extension event bus. It carries policy and status only — never a way to run anything.

import {
  FOREGROUND_SANDBOX_POLICY_CHANNEL,
  FOREGROUND_SANDBOX_POLICY_REQUEST_CHANNEL,
  type ForegroundSandboxPolicyEvent,
} from "pi-better-sandbox";

pi.events.on(FOREGROUND_SANDBOX_POLICY_CHANNEL, (policy) => {
  // Snapshot it at launch time; a running operation keeps its launch policy.
});

// Loaded late and missed the last publication? Ask for the current one.
pi.events.emit(FOREGROUND_SANDBOX_POLICY_REQUEST_CHANNEL, undefined);