Skip to content

Pyronewbic/Sluice

Repository files navigation

sluice

sluice logo

CI Release License

Run any project - or a coding agent in full YOLO mode - in a locked-down container that can't read your secrets, reach outside the repo, or phone home. sluice runs it as a non-root user, seeing only that project directory, behind a default-DROP egress firewall (only the hosts you allow, by name, are reachable) - and ends every run with a receipt of exactly what it reached and what the firewall blocked.

sluice agent lists nine sandboxed coding-agent presets with auth status read live from the host env (the claude row green, key set); inside the box 'cat .env' prints nothing because SLUICE_MASK shadows the secret; and the egress receipt shows api.anthropic.com reached in green and pypi.org blocked in red - one command, the agent caged

Drop a sluice.config.sh in a directory and run sluice, or just run sluice and let it detect the stack, scaffold the config, and build + run it sandboxed.

Runs entirely on your machine - no account, no telemetry, nothing uploaded. The only network call sluice itself makes is an opt-out check to GitHub for a newer release (SLUICE_NO_UPDATE_CHECK=1 to disable).

Install

brew install Pyronewbic/tap/sluice
# dev stream (latest main commit):  brew install --HEAD Pyronewbic/tap/sluice
# or:  curl -fsSL https://raw.githubusercontent.com/Pyronewbic/Sluice/main/install.sh | sh
# or, from a checkout:  ./install.sh

The installer symlinks bin/sluice into ~/.local/bin (ensure it's on PATH). Needs a running docker or rootless podman (Docker Desktop or podman machine on macOS; rootful podman is unsupported - its network backend breaks the box's DNS). sluice init needs no engine at all.

Quickstart

Sandbox a coding agent - non-root, sees only this repo, behind the egress firewall, so YOLO mode is defensible:

export ANTHROPIC_API_KEY=sk-ant-...     # or CLAUDE_CODE_OAUTH_TOKEN; forwarded, never baked
cd your-project
sluice agent claude                     # also: codex, gemini, cursor, aider, opencode, amp, qwen, crush

Or run any project sandboxed, then see where its egress hit a wall:

cd your-project
sluice            # detect the stack, scaffold a config, build + run it sandboxed
sluice egress     # what it reached vs. what the firewall blocked

The first build bakes the sandbox image (a few minutes); reruns reuse it. Done with a project? sluice rm removes its container, image, and overlay volumes.

Updating

Update the sluice CLI itself (not to be confused with sluice update, which rebuilds your project's sandbox) via your installer:

brew upgrade sluice                                # stable
brew upgrade --fetch-HEAD Pyronewbic/tap/sluice    # dev stream (latest main)
curl -fsSL https://raw.githubusercontent.com/Pyronewbic/Sluice/main/install.sh | sh   # re-run to repin main's latest; SLUICE_REF=<sha> pins a commit
# stable -> dev stream needs a reinstall:  brew uninstall sluice && brew install --HEAD Pyronewbic/tap/sluice

sluice version flags a newer release when one is out.

Use

From anywhere inside a project with a sluice.config.sh (found by walking up, like git finds .git), sluice builds and runs it sandboxed. No config yet? A bare sluice detects the stack (Node, Python, Deno, Ruby/Rails, Rust, Go, Java, PHP, .NET, Elixir, Dart - anything else runs via the generic base), scaffolds the config, and on confirm builds + runs it.

a config-less node project: bare sluice finds no config, detects the stack (node/npm), scaffolds sluice.config.sh and previews the knobs it chose (ports, run command, an empty allowlist pointing at sluice learn), asks [Y/n], then builds and runs it sandboxed - ending on the egress receipt with registry.npmjs.org reached in green and a first-run nudge to sluice learn and sluice lock

When a run exits, sluice prints an egress receipt: the hosts it reached and any it tried but the firewall blocked. sluice learn then turns the blocked list into your allowlist with a per-host review - allow / skip / collapse to a .domain wildcard - applied live, no rebuild. sluice doctor is the one-screen health check: engine, image freshness, allowlist, persisted state, blocked hosts - plus warnings for what would silently misbehave in-box (unmasked secret-looking files, symlinks that resolve outside the mount). Full firewall + learn walkthrough: examples/.

The firewall is not just a claim - an allowlisted host gets through, an exfil POST and a raw-IP bypass are blocked, and the run's audit log is tamper-evident (egress --verify flips from green to red if a record is altered):

a run reaches an allowlisted host (api.github.com, green OK) but an exfil POST to a non-allowlisted host and a raw-IP connection are both blocked; sluice egress ledgers what got through; sluice egress --verify reports the hash chain intact, then after a tampered record it flips to red 'egress log TAMPERED, prev-link broken' with a non-zero exit

When a host you actually need is blocked, sluice learn allows it from the receipt, live:

a real run is blocked by the firewall: curl fails and the egress receipt shows pypi.org in red as 'blocked, not allowlisted'; sluice learn then reviews the host with an allow/skip/domain/quit prompt, 'a' allows it and the running box is reloaded live with no rebuild; the rerun returns HTTP 200 and the receipt flips to green 'reached pypi.org, all egress was allowlisted'

sluice                 # build (if needed) + run SLUICE_RUN_CMD in the sandbox
sluice agent <name>    # run a coding agent (no name lists them) - see docs/agents.md
sluice init            # scaffold sluice.config.sh from the detected stack (--force | --update)
sluice learn           # allowlist the blocked hosts you pick (--all | --print | --apply | --audit)
sluice run <cmd...>    # an ad-hoc command instead of SLUICE_RUN_CMD
sluice doctor          # health check + in-box hazard warnings (--json)
sluice lock            # supply-chain inventory to sluice.lock (--check | --sbom | --scan) - see docs/supply-chain.md
sluice diff | apply    # review / write back changes from a protected workspace (SLUICE_WORKSPACE=overlay)

Plus build / rebuild / update / stop / rm / prune for lifecycle and shell / ls / egress / logs / smoke to inspect - sluice help lists them all. ls, doctor, and egress take --json for scripting; egress --export/--verify dump and integrity-check the run's append-only audit log; sluice -b <name> <cmd> targets any box from anywhere. Full fleet view + egress-audit reference: docs/operations.md.

What it looks like

sluice doctor
  engine     Docker version 27.4.0
  config     ~/code/blog/sluice.config.sh
  mount      ~/code/blog
  image      sluice-blog built (config current)
  lock       in sync (142 pkgs)
  allowlist  api.anthropic.com
             base: github.com api.github.com registry.npmjs.org ...
  egress     1 host(s) blocked (last run) - run 'sluice learn' to allow:
             cdn.tracking.example

sluice ls shows every box on this machine, which one you're in (*), and its posture:

sluice ls
  NAME         STATUS    STACK       ALLOW  PORTS  LOCK    PATH         DESCRIPTION
* sluice-blog  running   node/astro  3      4321   locked  ~/code/blog  personal blog
  sluice-api   built     python      7      8000   -       ~/code/api   internal API

Run a coding agent

sluice agent <name> drops you into a coding agent that's non-root, sees only this repo, and can only reach its own model API. Nine presets ship (claude, codex, gemini, aider, cursor, opencode, amp, qwen, crush); each runs YOLO by default (the sandbox is the gate), masks .env* files from the box, and persists its sessions across rebuilds. The scaffold also allowlists your stack's package registry, so the agent's first install doesn't trip the firewall. Auth, parallel agents via git worktrees, and the preset list: docs/agents.md.

Configure

Everything is driven by sluice.config.sh - copy sluice.config.example.sh and edit. Every knob, with live-vs- rebuild semantics: docs/configuration.md. The ones you'll reach for most:

knob purpose
SLUICE_RUN_CMD the command a bare sluice runs (default: a shell)
SLUICE_EXTRA_PKGS extra apk packages baked in at build time
SLUICE_ALLOW_DOMAINS runtime egress domains, on top of the base allowlist
SLUICE_ALLOW_IPS runtime egress IPs/CIDRs for non-HTTP services
SLUICE_PORTS TCP ports to publish, bound to host loopback only
SLUICE_ENV host env var names to forward into the session
SLUICE_MASK in-repo secret globs shadowed from the box (agent presets default .env*)
SLUICE_OVERLAY_DIRS project dirs given a box-local volume (e.g. node_modules) - host contents untouched

The config contract is POSIX sh: space/newline-separated strings, no bash arrays.

Security model

The guardrails, one line each - full guarantees, trust boundaries, and known weaknesses in THREAT_MODEL.md:

  • Default-DROP egress, hostname-filtered: all HTTP/HTTPS goes through an in-box proxy that allows by Host/TLS-SNI (spliced, never decrypted); DNS is scoped to the allowlist; IPv6 and direct-IP are blocked; the firewall self-tests at boot and fails closed.
  • Non-root, capability-stripped: sessions run uid 1000; the container drops ALL capabilities and the root entrypoint keeps only what boot needs, with no-new-privileges and pids/memory caps.
  • Filesystem isolation: only the project dir is mounted (plus its git common dir for worktrees); SLUICE_MASK shadows in-repo secrets from the box.
  • Published ports bind 127.0.0.1 - reachable from your machine only.
  • Opt-in hardening: a seccomp denylist that supersets the engine default (SLUICE_SECCOMP), immutable rootfs (SLUICE_READONLY_ROOT), a read-only protected workspace with sluice diff/apply (SLUICE_WORKSPACE=overlay), and an own-kernel micro-VM runtime (SLUICE_RUNTIME=kata) - see docs/hardening.md.
  • Supply chain: the sandbox builds on Chainguard's wolfi-base; an opt-in cosign-signed, multi-arch (amd64 + arm64) base image with an SBOM attestation replaces the local core build (SLUICE_BASE_IMAGE, enforced by SLUICE_REQUIRE_SIGNED=1), and sluice lock records what's inside - see docs/supply-chain.md.
  • Centralized policy: an org can enforce a deny-capable egress policy (deny hosts, forbid loosening knobs, cap SLUICE_ALLOW_IPS) that a developer's local config can't override - see docs/policy.md.
  • The honest caveat: the allowlist is host-granular, so data can still be laundered through an allowed host - sluice flags such hosts and SLUICE_EGRESS_MAX_BYTES caps run volume, but read THREAT_MODEL.md before trusting it with anything that matters.

Build-time setup runs before the firewall (free egress for dependency installs); the running container is locked down.

Stability

sluice follows Semantic Versioning. The public API is: the documented commands and flags (sluice help), the SLUICE_* config knobs and the sluice.config.sh contract (sourced as POSIX sh - space/newline-separated strings, no bash arrays), and the runtime guarantees in THREAT_MODEL.md (default-DROP egress, non-root, project-directory-only mount).

The surface is frozen ahead of 1.0 and stays backward-compatible within a major: new commands, flags, knobs, detected stacks, agent presets, and --json fields may be added, but nothing in the public API is removed, renamed, or has its default changed in a breaking way without a major bump. Anything slated to change is deprecated first (a warning for at least one minor release) before removal in the next major.

Not part of the stable API (free to change in any release): the core/ internals (Dockerfile, squid / firewall / entrypoint), the image layout and base-image contents, and exact log/console text.

Examples

The gallery is self-contained demos, everyday tasks first: serve an app and learn its one upstream live, let a tool edit a copy you review with diff/apply, the firewall/exfil block made visible, a database over SLUICE_ALLOW_IPS, and a Nix toolchain baked at build and contained at runtime.

Layout

bin/sluice                the CLI launcher (one file; generated from src/ by `make build`)
src/                      the launcher in ordered slices - edit here, then `make build`
core/                     the sandbox image: Dockerfile + squid / firewall / entrypoint
agents/                   coding-agent presets + the preset contract (agents/README.md)
docs/                     configuration, agents, hardening, supply-chain references
examples/                 self-contained gallery demos
test/                     gate + nightly bats suites
completion/               bash + zsh shell completion
install.sh                curl|sh + local installer
sluice.config.example.sh  copyable config template

Shell completion auto-installs via brew install / install.sh. For own-kernel isolation, SLUICE_RUNTIME=kata runs the box as a Kata micro-VM (Linux + containerd/nerdctl). CI runs the gate on Linux Docker + rootless Podman (acceptance.yml).

License

Apache-2.0 - permissive, use it however you like. Found a sandbox escape or egress bypass? See SECURITY.md.

About

Run any project or coding agent in a sandboxed, egress-firewalled, non-root container. Drop in a config, run sluice.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages