sboxd 0.1.9

Policy-driven command runner for sandboxed dependency installation
Documentation
# sbox Implementation Roadmap

## Objective

Build `sbox` as a Rust CLI that executes arbitrary project commands either on the host or inside a sandboxed container environment, with behavior resolved from configuration, profiles, dispatch rules, and safe defaults.

Status: v1 and v2 are both complete.

## Scope For v1 — Complete

- Config loading from `sbox.yaml`
- CLI surface for `init`, `run`, `exec`, `shell`, `plan`, `doctor`, and `clean`
- Host and sandbox execution modes
- Profile resolution and dispatch matching
- Workspace mount and cwd mapping
- Environment variable policy
- Extra mounts, caches, and secrets
- Podman backend
- Exit code and streaming behavior

## Spec Issues Resolved

1. Section numbering inconsistency — treated as implementation-level only; spec not updated.
2. Ports-with-network-off example — validator rejects this combination.
3. Strict validation ambiguity — resolved: strict mode is an explicit opt-in flag.
4. `status` and `logs` — deferred to post-v2; not implemented.
5. Rebuild detection for `image.build` — simple tag-based check; no content hashing in v1/v2.
6. Cache scoping — workspace-root-hashed volume names; one predictable rule.

## Architecture

Modules as implemented:

```text
src/
  main.rs
  cli.rs
  app.rs
  error.rs
  config/
    mod.rs
    model.rs
    load.rs
    validate.rs
  dispatch.rs
  plan.rs
  resolve.rs
  exec.rs
  shim.rs
  shell.rs
  doctor.rs
  clean.rs
  init.rs
  backend/
    mod.rs
    podman.rs
    docker.rs
```

Core design rules followed:
- CLI parsing is separate from execution logic
- All behavior resolves into a single `ExecutionPlan` before any execution
- Backend-specific command generation is an adapter boundary (`backend/podman.rs`, `backend/docker.rs`)
- Config validation and runtime validation are separate passes
- Host and sandbox execution share the same plan model

## v1 Delivery Phases — All Complete

### Phase 0: Bootstrap — Complete
### Phase 1: CLI And Config Loading — Complete
### Phase 2: Resolution Engine — Complete
### Phase 3: Host Execution — Complete
### Phase 4: Podman Sandbox Backend — Complete
### Phase 5: `init`, `doctor`, And `clean` — Complete
### Phase 6: Reuse And Interactive Shells — Complete
### Phase 7: Hardening And Compatibility — Complete

## Definition Of Done For v1 — Met

A Linux user can:
- initialize a project config
- inspect the execution plan for any command
- run arbitrary commands on host or in a Podman sandbox
- rely on profile and dispatch-based policy selection
- use mounts, caches, env vars, and secrets intentionally
- get predictable exit codes, output streaming, and working-directory behavior
- understand failures without reading the source

---

## v2 Delivery Phases — All Complete

### Phase 8: Trust And Verification — Complete

Implemented:
- `runtime.require_pinned_image: true` — global image trust enforcement across all sandbox profiles
- Per-profile `require_pinned_image: true` — profile-level digest requirement
- Config-load-time validation: if `require_pinned_image` is set but no digest is configured, sbox fails before execution with an actionable message
- `trusted_image_required` in `ExecutionAudit` reflects both profile-level and runtime-level flags
- Image trust level (`pinned-digest`, `mutable-reference`, `local-build`) visible in `sbox plan`
- Real signature verification via `skopeo` and containers policy (`verify_signature: true` on image config)
- `doctor` reports whether signature verification is usable on the current machine

### Phase 9: Automated Integration Coverage — Complete

Implemented:
- Gated real-Podman integration tests (`SBOX_RUN_PODMAN_TESTS=1`): workspace/cwd behavior, network-off enforcement, reusable sessions, cleanup, port mapping
- Golden tests for representative `sbox plan` output: `npm`, `uv`, `bun`, `poetry`
- Opt-in signature-verification integration test (`SBOX_SIGNATURE_POLICY` + `SBOX_SIGNED_TEST_IMAGE`)
- 213 unit and integration tests passing

### Phase 10: Package-Manager-Agnostic Security Hooks — Complete

Implemented (replacing earlier PM-specific approach):
- `role: install | run | build` on profiles — declares install-style semantics without hardcoding package manager names
- `lockfile_files: [...]` — per-profile list of lockfile filenames to check for presence
- `pre_run: [...]` — list of host-side commands run before the sandboxed command; failure aborts execution
- `require_lockfile: true` — refuses install-style commands in strict mode when lockfile is absent
- `sbox plan` audit section shows: `install_style`, `lockfile` state, `pre_run` commands
- Examples updated: `npm`, `uv`, `bun`, `poetry` all use the agnostic model

Previously implemented PM-specific items (`script_policy`, `audit_hooks`) have been replaced by the above.

### Phase 11: Compatibility Without Trust Regression — Complete

Implemented:
- Docker backend fully functional: `build_run_args`, reusable sessions, `exec`, `shell`
- Per-profile image overrides: `profile.image` accepts `ref`, `build`, `preset`, `digest`, `verify_signature`
- Backend auto-detection: `runtime.backend` is now optional; sbox probes PATH for `podman` then `docker` when not specified
- `doctor` uses the same auto-detection logic for backend health checks
- Transparent shim interception (`sbox shim`) for `npm`, `pnpm`, `yarn`, `bun`, `uv`, `pip`, `pip3`, `poetry`, `cargo`, `composer`
- Outbound network domain allow-listing (`network_allow`) with three supported entry forms:
  - Exact hostname (`registry.npmjs.org`) — DNS-resolved to IPs, injected as `--add-host`
  - Glob pattern (`*.npmjs.org`) — base domain resolved, pattern stored for display
  - Regex-prefix pattern (`.*\.npmjs\.org`) — base domain unescaped and resolved
- `sbox plan` shows resolved entries and raw patterns separately

### Phase 12: Stronger Isolation Backends — Deferred

Decision: not implemented in v2. The rootless Podman model (no-new-privileges, read-only rootfs, network-off, credential masking, env filtering) is sufficient for the stated threat model — malicious postinstall scripts. A microVM or gVisor backend would only add value if the threat model expands to "code that breaks out of a Linux container namespace," which is a different attacker profile. Deferred to a future track if that need emerges.

## Definition Of Done For v2 — Met

A Linux, macOS, or Windows user can:
- enforce global or per-profile image trust rules with config-load-time validation
- rely on automated coverage for the main Podman security paths
- use package-manager-agnostic install policy (`role`, `pre_run`, `lockfile_files`) without hardcoding PM names
- restrict outbound DNS to an explicit allow-list with glob/regex pattern support
- use either Podman or Docker with auto-detection when backend is not specified
- intercept package manager invocations transparently via shims

---

## v3 Roadmap

### Shipped in v0.1.9 (phases 13–15)

- `sbox shim --verify` — PATH-aware shim health check, exits 1 on problems
- `sbox doctor` shim health check — integrated summary in doctor report
- `composer` and `bundler` presets with security defaults (token denial, credential exclusion, env injection)
- `bundle` and `ruby` added to shim targets
- `sbox completions <shell>` — shell completion scripts via `clap_complete`
- `sbox init --from-lockfile` — auto-detects preset from existing lockfile; 13 lockfile types supported
- `sbox clean --global` — host-wide cleanup of all sbox-managed containers/volumes/images

### Shipped in v0.1.8

- macOS support: `current_uid_gid()` via `id -u`/`id -g`; `home_dir()` centralised in `platform.rs`
- Windows support: Docker Desktop paths normalised via `path_to_docker_str`; `.cmd` shims generated; `USERPROFILE` respected
- `sbox run --dry-run` — resolves and prints the plan without executing
- `sbox run -e NAME=VALUE` — inject extra environment variables at invocation time
- Docker `--user UID:GID` always injected for `ResolvedUser::Default` — fixes file ownership in non-rootless Docker
- `sbox doctor` new checks: rootless Docker warning, `root_command_dispatch_warnings`
- CI matrix: Linux / macOS / Windows; release targets for all five platform/arch combos
- musl aarch64 release now uses `cross` (genuine musl toolchain, not glibc cross-compiler)
- `detect_compose_image` heuristic: dynamic indent detection, prefers well-known app service names

---

### Phase 13: Shim Health And Doctor Integration — Complete

- **`sbox shim --verify`** — scans PATH, reports each target as `ok`/`shadowed`/`missing`/`inactive`; exits 1 if any problems
- **`sbox doctor` shim check** — calls `verify_shims()` and emits a single `pass`/`warn` line showing how many shims are active and correctly ordered

### Phase 14: Additional Presets — Complete

- **`composer` preset**`composer:2` image, `vendor/` writable, `packagist.org` allowed, `COMPOSER_AUTH` denied, `auth.json` excluded, `COMPOSER_NO_INTERACTION=1`
- **`bundler` preset**`ruby:3-slim` image, `vendor/bundle` + `.bundle` writable, `rubygems.org` allowed, `GEM_HOST_API_KEY` denied, `BUNDLE_PATH=vendor/bundle`
- **`bundle` and `ruby` added to `SHIM_TARGETS`**

### Phase 15: Developer Experience Polish — Complete

- **`sbox completions <bash|zsh|fish|powershell|elvish>`** — prints clap-generated completion script to stdout via `clap_complete`
- **`sbox init --from-lockfile`** — detects lockfile in CWD and auto-selects preset; priority order handles `uv.lock` over `requirements.txt`, `poetry.lock` over `requirements.txt`, etc.
- **`sbox clean --global`** — removes all `sbox-` prefixed containers, volumes, and images across every project on the host; no `sbox.yaml` required

### Phase 16: Distribution

- **Homebrew tap** (`homebrew-sbox`) — formula that installs the macOS binary from the GitHub release; makes `brew install aquilesorei/sbox/sbox` work without Cargo
- **`sbox audit` integrated into plan output** — surface audit results inside `sbox plan` so users see security findings before deciding to run

---

## Remaining Work (Post-v3)

- **`status` and `logs` subcommands** — monitor running reusable sessions
- **Rebuild detection for `image.build`** — hash the Dockerfile and build context to detect when a rebuild is needed
- **Remote runner mode** — execute plans on a remote host or in CI without a local container runtime
- **Microvm/gVisor backend** — stronger isolation for higher-risk workloads if the threat model expands
- **Wildcard DNS enforcement for unknown domains** — full enforcement for arbitrary `*.example.org` patterns requires a container-side DNS proxy (e.g. CoreDNS or dnsmasq); deferred
- **Per-secret profile conditions**`when_profiles` filtering is implemented; richer conditional logic (e.g. `when_command_matches`) is not