Files
fusion/docs/research/sandbox-options.md
Fusion 5b3455bba9 feat(FN-4635): add sandbox options research report
Adds a 219-line sandbox options research report documenting available sandboxing technologies and their tradeoffs for the Fusion project.

Fusion-Task-Id: FN-4635
2026-05-15 10:18:22 -07:00

12 KiB

FN-4635: Pluggable sandbox options for executor command isolation

1) Threat model

Fusion executor lanes currently run shell commands (testCommand, buildCommand, workflow script steps) as the host user inside a git worktree. This exposes the host if task content or agent-generated commands are malicious or buggy.

Primary threats:

  • Host filesystem access/exfiltration: command reads/writes outside task worktree, including secrets in $HOME, SSH keys, cloud creds, and unrelated repos.
  • Secret exfiltration over network: command uploads tokens, source, or .fusion metadata.
  • Persistence / host tampering: command installs startup hooks, modifies shell rc, mutates global package/tool state.
  • Runaway resource abuse: fork bombs, memory exhaustion, or very long-running scripts.
  • Cross-task contamination: command mutates shared caches or shared worktree state in unsafe ways.
  • Dashboard safety: accidental conflict with production dashboard process/port conventions (port 4040 must not be disrupted).

Non-goals for this phase:

  • Perfect VM-grade multi-tenant isolation.
  • Replacing host OS hardening strategy end-to-end.
  • Sandboxing every agent role immediately (triage/reviewer may remain unchanged initially).

2) Requirements

Functional/operational requirements:

  • Platform first target: macOS + Linux; document Windows path as follow-up.
  • Low startup overhead: ideally sub-second to a few seconds for frequent executor invocations.
  • Pluggable with WorktreeBackend plan: sandbox layer composes with current worktree lifecycle, not replacing it.
  • Opt-in settings model: project/global defaults with task-level override.
  • Action-gate friendly: policy-gated privileged/provisioning operations.
  • Command compatibility: must still run realistic pnpm test, pnpm build, git operations, and workflow scripts.
  • Filesystem policy controls: allowlist worktree + required caches + minimal runtime dirs.
  • Network policy controls: allow default egress profile for git/npm unless explicitly restricted.
  • Graceful availability handling: when backend is missing/unavailable, use configurable fail-hard vs fallback-native behavior.
  • Auditability: emit run-audit events for backend prepare/run/failure/fallback.

3) Candidate survey

3.1 Baseline matrix

Option Platforms Isolation primitives Startup cost Fit with worktrees Operational notes
Native (status quo) macOS/Linux/Windows None beyond cwd/user perms ~0 Excellent No added protection
macOS sandbox-exec macOS Seatbelt profile (fs/process/network constraints) Low Good Deprecated tool; still available on many macOS versions
Apple App Sandbox macOS app model Entitlement-driven app containerization N/A for CLI Poor Not realistic for arbitrary CLI shell commands
bubblewrap Linux mount/user/pid/net/uts/cgroup namespaces; seccomp via composition Low Excellent Strong for per-command filesystem isolation
firejail Linux namespaces + seccomp + profiles Low Good SUID/profile complexity trade-offs
Landlock Linux (kernel feature) unprivileged LSM policy (fs + TCP port control by ABI) Very low Good Additive policy layer; kernel/ABI variability
nsjail Linux namespaces+cgroups+rlimits+seccomp Low-med Good Powerful, but more ops complexity
Docker rootless Linux/macOS (Desktop VM on macOS) OCI containers + userns/rootless mode Med Good Daemon/service dependency; image lifecycle overhead
Podman rootless Linux/macOS (machine) OCI containers daemonless + userns Med Good Good local dev ergonomics; fewer daemon assumptions
gVisor (runsc) Linux (container runtime) userspace kernel boundary for containers Med-high Indirect Strong isolation, but compatibility/perf overhead
Firecracker microVM Linux + KVM microVM boundary High Indirect Strongest isolation, highest complexity/latency
WASM/WASI (Wasmtime) macOS/Linux/Windows runtime sandbox for wasm modules Low-med Limited Only works for specifically-built wasm workloads, not arbitrary shell

3.2 Evidence notes (sources)

3.3 Fusion-specific operational considerations

  • pnpm/node_modules/caches: sandbox policy must preserve usable package manager cache paths and workspace artifacts (or mount explicit cache volumes).
  • .fusion/fusion.db and task files: executor may need read/write to project-local .fusion metadata depending on callsite.
  • Git access: must permit git process execution and network egress for fetch/push where configured.
  • Network defaults: practical default is allow egress; stricter policy should be explicit opt-in to avoid breaking test/build workflows.
  • Port policy: no backend should force use of port 4040 or manage unrelated host processes.

4) Proposed pluggable abstraction

export type SandboxBackendId =
  | "native"
  | "sandbox-exec"
  | "bubblewrap"
  | "docker"
  | "podman"
  | "custom";

export type SandboxFailureMode = "fail-hard" | "fallback-native";

export interface SandboxPolicy {
  allowNetwork: boolean;
  allowedPaths: string[]; // absolute host paths mounted/readable in sandbox
  writablePaths?: string[];
  envAllowlist?: string[];
  cpuLimit?: { maxCores?: number; maxMs?: number };
  memoryLimitMb?: number;
}

export interface SandboxRunOptions {
  cwd: string;
  timeoutMs: number;
  maxBuffer: number;
  env?: NodeJS.ProcessEnv;
}

export interface SandboxPrepareContext {
  taskId: string;
  runId: string;
  worktreePath: string;
  policy: SandboxPolicy;
}

export interface SandboxRunResult {
  stdout: string;
  stderr: string;
  exitCode: number | null;
  signal: NodeJS.Signals | null;
}

export interface SandboxCapabilities {
  filesystemIsolation: "none" | "basic" | "strong";
  networkPolicy: "none" | "coarse" | "fine";
  resourceLimits: "none" | "basic" | "advanced";
  requiresInstall: boolean;
  supportsMac: boolean;
  supportsLinux: boolean;
  supportsWindows: boolean;
}

export interface SandboxBackend {
  id: SandboxBackendId;
  capabilities(): SandboxCapabilities;
  prepare(ctx: SandboxPrepareContext): Promise<void>;
  run(command: string, opts: SandboxRunOptions): Promise<SandboxRunResult>;
  dispose(): Promise<void>;
}

Integration shape:

  • Keep existing async command model (promisify(exec) / spawn-based command execution) and route through selected backend wrapper.
  • native backend is default for backward compatibility.
  • Backend selected from merged settings + optional per-task prompt override.

4.1 Settings proposal (not implemented here)

sandbox:
  backend: native | sandbox-exec | bubblewrap | docker | podman | custom
  policy:
    allowNetwork: true
    allowedPaths: []
    writablePaths: []
  failureMode: fail-hard | fallback-native

Per-task prompt override example:

  • **Sandbox:** podman

4.2 Audit proposal

Emit run-audit events:

  • sandbox:prepare
  • sandbox:run
  • sandbox:failure
  • sandbox:fallback

Include taskId, runId, backend id, policy hash/summary, and failure reason.

4.3 Action-gate / governance proposal

  • Reuse network_api gating for backends requiring outbound fetch/pull/bootstrap.
  • Add optional sandbox_provision gate for first-time install/provision actions (container runtime pull/setup, helper binary bootstrap).
  • Keep default native path ungated to preserve current behavior for existing projects.

4.4 Self-healing interaction notes

  • Sandbox startup failure should be classified similarly to recoverable/unrecoverable session-start failures:
    • backend missing and fallback-native => record fallback + continue.
    • backend missing and fail-hard => task failure/pause with explicit reason.
  • Must not break worktree liveness invariants; sandbox prepare should consume existing worktree path validated by current worktree-pool logic.
  • Stuck-task recovery remains authoritative; sandbox runs must preserve timeout/abort semantics so watchdog and restart recovery can reason about active subprocesses.

5) Recommendation

Recommended platform defaults (when backend family is enabled in future work):

  • macOS: native default, optional sandbox-exec backend for teams accepting deprecated-but-available Seatbelt CLI behavior.
  • Linux: bubblewrap recommended first non-native backend (best balance of strong fs isolation + low startup + worktree compatibility).
  • Windows: keep native initially; evaluate Windows-native sandboxing/container path in later phase.

Rationale:

  • bubblewrap is lightweight, namespace-native, and aligns with per-command short-lived executor workflow.
  • Container and microVM backends are valuable as optional high-isolation modes but carry higher latency and ops complexity.
  • sandbox-exec can be an interim macOS hardening option, but deprecation risk means it should not be mandatory default.

6) Phased rollout plan

  1. Phase A: land SandboxBackend abstraction + native only; no behavior change.
  2. Phase B: add macOS sandbox-exec backend behind explicit opt-in.
  3. Phase C: add Linux bubblewrap backend with policy templates for Fusion workloads.
  4. Phase D: optional container backend (podman first, docker optional) for stronger but heavier isolation.
  5. Phase E: docs, telemetry dashboards, and hardening guidance; then consider default changes.

7) Follow-up tasks and open questions

Follow-up implementation tasks (created from this research)

  • Add SandboxBackend abstraction and native passthrough backend (depends on FN-4635)
  • Implement macOS sandbox-exec backend with policy templates + fallback behavior (depends on abstraction task)
  • Implement Linux bubblewrap backend with path/network policy adapter (depends on abstraction task)
  • Add sandbox settings schema + prompt override parsing + validation (depends on abstraction task)
  • Add sandbox run-audit events and dashboard visibility for sandbox telemetry (depends on abstraction task)
  • Add action-gate support for sandbox provisioning/privileged backend setup (depends on abstraction task)

Open questions

  • Should sandboxing apply only to executor command lanes, or also merger verification commands/workflow post-merge scripts?
  • Is network default allow acceptable, with explicit deny profiles, or should stricter defaults be project-specific?
  • Should fallback from sandbox to native be allowed by default, or require explicit failureMode=fallback-native?
  • Are teams willing to accept sandbox-exec deprecation risk on macOS, or should macOS remain native until another path exists?
  • Should container backends be first-class, or treated as external/custom backend plugins only?
  • For Windows support, do we target native isolation primitives first or container runtime parity?