status: pre-release. The author is dogfooding it on real client work before launch.
Disposable dev environments for the truly paranoid.
You want to run an npm install, a build script, or an AI coding agent without handing it your keys. snib runs it in a throwaway Linux VM on your Mac, and the VM holds placeholders, not real credentials.
A broker outside the VM swaps a placeholder for the real value on the wire, only toward the destination you approved in the manifest, and records the request in a host-side audit log the VM can't reach. The enforce tier adds a host firewall that blocks the VM's direct egress.
$ snib run -- npm install # observe tier: no sudo, audited
$ snib run --enforce -- claude # enforce tier: host firewall on
what a compromised dependency sees
Say a postinstall script goes hunting, the way the nx "s1ngularity" malware did when it drove victims' own Claude Code CLIs to collect secrets. Inside a snib workspace:
- It reads
$GITHUB_TOKEN. Its config holds{{GITHUB_TOKEN}}— a placeholder. The real token exists only in the broker's memory on the host, resolved from 1Password at arm time. There is no file, no env var, no keychain inside the VM that contains it. - It tries to phone home. In the enforce tier, a direct connection out of the VM is blocked by the host firewall; a request that rides the sanctioned broker path is recorded host-side with the destination it asked for.
- Meanwhile your legitimate
git pushtoapi.github.comworks: the broker verifies the destination's TLS identity (not the spoofableHostheader), swaps placeholder for real value in transit, and scrubs the value back out of the response before the guest sees it.
If malware steals the placeholder, it has not stolen the credential.
the evidence
These seven rows are machine-checked on every gate run from the host, against a throwaway sentinel, never a real credential. Each absence row is gated on a positive control, so a broken flow fails red instead of passing green by accident.
| row | what it proves | result |
|---|---|---|
| cred.proxy_rewrite | placeholder→real fired upstream, over verified TLS | PASS |
| cred.scope_enforced | the same placeholder sent to an off-scope host stayed a placeholder | PASS |
| cred.scope_identity | a spoofed Host header over a raw-IP authority did not inject | PASS |
| cred.no_reflect.plain | a reflecting upstream's response reached the caller with the scanned encodings (literal, base64, hex, URL) redacted | PASS |
| cred.no_reflect.gzip | …including a gzip-compressed reflection | PASS |
| cred.secret_not_logged | the sentinel, in all scanned encodings, is absent from the checked host log surfaces | PASS |
| cred.dns_beacon_inert | the guest never held the value, so a DNS beacon can carry only the placeholder | PASS |
Run them yourself on a laptop, no VM, no sudo:
bash tools/gate/cred-gate.sh --driver host. The full gate runs
the same assertions with requests originating inside the enforced VM.
how it works
| layer | mechanism |
|---|---|
| Secrets | The manifest declares key, a ref into
1Password (or an env: value), and a required
to destination. snib resolves the ref host-side, arms an
auditing proxy, and the guest gets a placeholder. Injection happens on
the wire, bound to the verified TLS identity of the named destination.
Responses are scanned for the value in literal, base64, hex, and URL
encodings — compressed bodies included — and redacted before the guest
sees them. |
| Egress | The enforce tier backs the workspace with a host firewall chokepoint,
so a process that tries to leave without going through the broker is
dropped. A deny-by-default allowlist is rendered from the manifest's
egress.allow (strict host globs; schemes, ports, paths, and
non-ASCII are rejected, never coerced). Arming the broker with that
allowlist on run is the next enforcement step — see the
limits below. |
| Isolation | A NixOS guest on Apple's Virtualization framework (via vfkit):
separate kernel, separate network identity. Mounts default to read-only.
The manifest lives inside the project, so a compromised guest could edit
its own grants — which is why every grant change invalidates a
host-owned approval hash, and run refuses until you
re-approve from outside. |
snib itself is Bun + TypeScript with zero npm dependencies (the broker is mitmproxy). 200 unit tests cover the manifest parser, approval gate, allowlist renderer, and secrets path.
what it doesn't stop
Known limits, current as of this build:
- Egress allowlisting on
runis not automatic yet. The enforce tier's host firewall drops direct egress by the workspace, andsnib allowlistrenders a deny-by-default allowlist from your manifest — butrun/updon't yet arm the broker with it, so traffic through the broker currently reaches any host (and is logged). Wiring the allowlist intorunis the next step. - A colluding authorized upstream. If a destination you granted decides to reflect your secret back using a transform we don't scan for, it can hand the value to the guest. Redaction covers the encodings we know; it cannot cover encodings we don't.
- Guest DNS resolves via the host resolver (gvproxy hardcodes it), so DNS queries sidestep the per-user firewall rules. Fix identified (one-line resolver change), waiting on a self-build we haven't shipped.
- gvproxy exposes a hardcoded forwarder API on the virtual network's gateway, reachable from the guest. Same self-build.
- Secrets live in the broker's memory until
snib down. No TTL, no automatic rotation yet. - The audit log is host-side and out of the guest's reach, but not yet hash-chained. A host-level attacker could edit it. Guest-level attackers cannot.
- macOS on Apple Silicon (built and tested on macOS 15.5), and the enforce tier installs a small fixed-purpose sudo helper — argument-validated, audit-logged, the unprivileged CLI never gets root.
compared to
| Cloud agent sandboxes (E2B, Daytona, Modal) | The sandbox runs on your machine; the workspace is not uploaded to someone else's cloud. And the secrets stay outside the sandbox rather than inside it. |
|---|---|
| Short-window secret brokers | Some brokers hand the agent a real credential for a bounded window. snib's workspace gets a placeholder; the broker injects the real value outside the VM, only toward a verified destination. |
| OS sandboxes (seatbelt, sandbox-runtime) | A sandbox inside your host shares its kernel, DNS, and network identity. snib puts the run in a guest VM with its own kernel and its own network identity. |
| Docker | Containers share the host kernel. They also don't help when the real credential is available as an environment variable; snib gives the guest a placeholder instead. |
why now
2025–26 turned "malicious dependency steals developer credentials" from a hypothetical into a recurring failure mode: the tj-actions/changed-files compromise dumped CI secrets across 23,000+ repos; the nx "s1ngularity" attack drove victims' own AI CLIs to hunt their secrets; chalk/debug put attacker code in packages with ~2.6 billion weekly downloads; the Shai-Hulud worm self-replicated through npm twice. Each case depended on the same weakness: real credentials reachable from code that had just run.
install
Not yet. If you want to be told when the repo opens, email james@cloudship.co.uk — you'll get exactly one email, when it ships.