Skip to content

Opt-in container wrapper for crewmate launches (config/crew-container) #1

Description

Summary

Crewmates today are plain same-UID host processes launched with every harness safeguard off (--dangerously-skip-permissions, --dangerously-bypass-approvals-and-sandbox, --yolo; the muse adapter even disables muse's built-in sandbox so git/network work). The only isolation between tasks is a treehouse git worktree, which prevents branch collisions but nothing else. bin/fm-spawn.sh says so itself in the launch-env header:

This is an exec environment boundary, not a sandbox for the pane's startup shell, credential files, same-user processes, or later shell initialization.

Upstream's position (kunchenguid#559, kunchenguid#1426) is that safety "needs to be established in the environment where we run our agents", not by reviewing tool calls. We agree — and want to actually build that environment: keep full bypass inside a container, so the blast radius of a crewmate becomes its worktree plus what we deliberately mount.

Proposal: a default-off config/crew-container that wraps the existing per-harness launch string in a container runtime exec. Nothing changes when the file is absent (byte-identical launch), so this is class=opt-in under upstream's triage rules.

What a crewmate can reach today that a worktree does not protect

All crewmates share the captain's UID with unrestricted tool execution:

Surface Consequence
~/.ssh, ~/.config/gh token, ~/.gitconfig, ~/.claude, ~/.aws, … read/alter credentials every other crewmate and the captain use
the project clone's common .git dir (objects/, refs/, config, worktrees/) a stray git gc/prune/worktree remove/config in one slot hits all siblings and the primary checkout
sibling worktrees (../<other-slot>/) cd ../other && edit — the cd-guard is inert inside worktrees by design
$FM_ROOT/state/, the watcher process, other panes can rewrite any task's status, kill the watcher, type into siblings
global package caches, Docker daemon, launchd, anything under $HOME rm -rf outside cwd, install/mutate shared toolchain

Prior art (upstream)

The four host couplings a wrapper must preserve

A crewmate is not a black box; firstmate talks to it through the host filesystem and process table. From bin/fm-spawn.sh on upstream main:

  1. Linked git worktree. The task slot's .git is a file pointing at the project clone's .git/worktrees/<slot> (spawn resolves --git-common-dir, ~:2508). The container must mount both the slot and the common git dir at the same absolute paths (git stores absolute gitdir/commondir pointers).
  2. Hook callbacks by absolute host path. The per-task $WT/.claude/settings.local.json (~:3514) wires UserPromptSubmit/Stop/StopFailure/SessionEnd to $FM_ROOT/bin/fm-busy-event.sh apply $STATE_REAL $ID … and touch $TURNEND. Other harnesses have equivalents (grok .grok/hooks, gemini .gemini/settings.json, opencode plugin, kimi Stop hook). Inside the container $FM_ROOT/bin (ro) and $FM_ROOT/state (rw, ideally only this task's files) must resolve.
  3. Agent-state classification walks the pane's process tree with ps (bin/fm-backend.sh ~:214-221; relaunch path in bin/fm-control.sh). With docker run -it in the pane the tree is the docker CLI, not claude. The classifier must learn the wrapper shape or relaunch/liveness will misclassify — this is the one non-additive change.
  4. Delivery needs network + scoped creds. git push, gh pr create, no-mistakes. Inject a scoped GH_TOKEN and a git credential helper via the env allowlist; never mount ~/.ssh or ~/.config/gh.

Secondary: a per-project image carrying the toolchain (node, gh, no-mistakes, the harness CLI), cold-start cost, macOS bind-mount performance.

Proposed shape

  • config/crew-container (one token: docker | podman | container; absent = unchanged). Possibly config/crew-container-image / config/crew-container-args for the image and extra mounts.
  • bin/fm-container-lib.sh: given the already-composed adapter launch string, emit <runtime> run --rm -it <mounts> <env> <image> /bin/sh -c '<launch>'. Reuse config/launch-env-allowlist for the env contract.
  • bin/fm-backend.sh: recognise the container CLI as a transparent wrapper when walking the process tree (or classify via docker inspect/docker top of a task-labelled container).
  • Applies to ship and scout spawns and relaunch. Secondmates out of scope for v1 (they already have a separate-home/separate-host boundary).
  • Not a new backend: backends are session providers (tmux/herdr/zellij/cmux/orca); containment is orthogonal to where the pane lives.
  • Tests: fake-docker behavioral coverage in tests/, asserting (a) absent config → unchanged launch bytes, (b) mounts and env for each coupling above, (c) classifier recognises the wrapper.

Estimated size: roughly PR kunchenguid#3380's footprint (~400–800 lines incl. tests), an order of magnitude smaller than kunchenguid#2241.

Plan

  1. Shape-A experiment (no code): fm-spawn.sh accepts a raw launch command as the harness arg (~:1723, "unverified-adapter escape hatch"). Run a crewmate with a hand-written docker run … -v $WT:$WT -v <common-gitdir>:<same> -v $FM_ROOT/bin:…:ro -v $FM_ROOT/state:… -e GH_TOKEN … claude --dangerously-skip-permissions and record which of the four couplings hold. Expected: 1, 2, 4 work with same-path mounts; 3 breaks; --model/--effort threading is lost because raw launch bypasses the adapters.
  2. Implement the opt-in wrapper on this fork behind config/crew-container.
  3. Once it works end-to-end (task → PR via no-mistakes) with evidence, open an upstream issue referencing this one, citing Crews launch with --dangerously-skip-permissions hardcoded; propose --permission-mode auto kunchenguid/firstmate#559's quote, Enforce crew Claude sandbox posture at spawn kunchenguid/firstmate#3380, Document and bound unintended secret inheritance at worker launch kunchenguid/firstmate#3742, and why feat: integrate Docker Sandbox placement and Crabbox execution kunchenguid/firstmate#2241's shape was too large — and ask upstream which design they prefer before raising the PR.

Out of scope

  • Containerising the primary firstmate session (it keeps its guarded write powers on the host).
  • Changing any default launch posture.
  • Secondmates, herdr/zellij/cmux/orca-specific placement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions