Skip to content

Docker Sandboxes ship behind a preview flag and create on a stock install #177

Description

@gannonh

Status

Implemented

Goal

A maintainer turns Docker Sandboxes on with one switch and creates a sandbox on a stock install with no environment variables, no digest, and no local image build. With the switch off, the feature does not exist anywhere in the product. The Docker end-to-end test runs on every pull request.

Context

Slice 1 of #176. Today the deployment service and its HTTP routes mount unconditionally (apps/server/src/server.ts) with no request-time gate, and apps/server/src/kataSandbox/SandboxBootstrapManifest.ts reads KATACODE_SANDBOX_SERVER_ARTIFACT_SHA256, KATACODE_SANDBOX_CODEX_VERSION, and KATACODE_SANDBOX_CODEX_ARTIFACT_SHA256 from the host environment. Nothing in the CLI, desktop (apps/desktop/src/backend/DesktopBackendConfiguration.ts forwards process.env and scrubs a fixed list; the three names are simply never set), or release pipeline sets those values, so Create throws on every real install. The same facts are already known to the image build (packages/kata-sandbox-docker/src/imageBuild.ts) and to the release job, which writes them into sandbox-image.json.

ServerSettings already carries product toggles such as enableAgentBrowserAccess (packages/contracts/src/settings.ts), read per request in apps/server/src/provider/Layers/ProviderService.ts. The Add Environment dialog already hides the Sandboxes card when the server advertises no providers (apps/web/src/features/kataSandbox/AddEnvironmentDialog.logic.ts). The Connections section is gated on scope only (ConnectionsSettings.tsx, the canManageHostSandboxes check) and needs one more gate. Those mechanisms are the whole flag.

The sandbox's own server exposes POST /api/kata-sandbox/bootstrap-pairing-token (apps/server/src/kataSandbox/http.ts), which the control server calls to mint a pairing credential. The container runs the same build with the default setting, so that route must stay outside the gate.

The release workflow already pushes the exact-version image tag and verifies it (.github/workflows/release.yml), but publish_cli does not depend on build_sandbox_image, which is how 0.0.42 shipped to npm with no image.

apps/server/src/kataSandbox/DockerSandboxE2E.test.ts is skipped unless six env vars are set. CI (.github/workflows/ci.yml) runs on ubuntu-24.04, where Docker is preinstalled.

Constraints and non-goals

  • One boolean setting plus one env override, provider-neutral: enableSandboxes, KATACODE_SANDBOXES, label "Sandboxes (preview)". No flag registry, no remote flags, no per-client flags.
  • Gate at the request boundary, not by layer composition. server.ts composes routes statically, and the cited precedent reads its setting per request.
  • Bootstrap facts come from the image, not the host. Delete the three env reads and the "copy values into your environment" section of docs/user/docker-sandboxes.md.
  • Keep the digest-pinned profile, exact-SHA source, and secret handling from Create and attach Docker Sandbox environments #159 unchanged.
  • Do not change the create form, progress, or Connections presentation here. Slices 2 and 3 own those.
  • Do not add the flag to ExecutionEnvironmentDescriptor or any shared environment consumer.

Acceptance criteria

  • ServerSettings.enableSandboxes exists with default false. KATACODE_SANDBOXES=1 or =0 in ServerConfig overrides the stored setting for that process. The desktop backend inherits it from the app process; a test asserts the name is not added to DESKTOP_BACKEND_ENV_NAMES, which would strip it.
  • With the flag off, every /api/kata-sandbox route except the in-container bootstrap-pairing-token route returns 404 and the provider catalog is empty, evaluated per request. Connections renders no Sandboxes section (a new gate next to canManageHostSandboxes) and Add Environment shows no Sandboxes card, on web and desktop. Existing deployments and profiles in SQLite are untouched.
  • The CI Docker job proves create succeeds with the control server's setting on and the container's default off, so the exempt route is exercised.
  • An administrative session sees a "Sandboxes (preview)" switch in Settings alongside the other server toggles. Turning it on or off takes effect on the next request with no server restart. Standard and read-only sessions see no switch.
  • validateProfile returns typed bootstrap facts (Kata version, server artifact SHA-256, provider CLI versions and digests) alongside the daemon version, and the deployment service builds the bootstrap manifest from them. The Docker driver sources those facts from OCI labels that the image build stamps from the build args it already receives, read through the GET /images/<digest>/json request it already makes. The three KATACODE_SANDBOX_* env reads are deleted. A profile whose image lacks the labels is reported unavailable with the diagnostic "image was not built by Kata Code".
  • The in-container entrypoint still verifies the manifest against the shipped tarballs, so a tampered label fails before katacode serve starts.
  • Before any form input, the Docker step reports an unreachable Docker daemon (the existing daemon-unavailable diagnostic) or a missing managed image for the running server version, names the version, and offers the Advanced local image field. Neither case fails at the last step.
  • KATACODE_SANDBOX_IMAGE_REPOSITORY moves from a process.env read inside the service into ServerConfig.
  • The published sandbox port binds to 127.0.0.1 unless the control server is bound to a non-loopback host and the maintainer opted in; the current hostIpForEndpoint default of 0.0.0.0 is removed.
  • publish_cli in release.yml depends on build_sandbox_image, so no CLI version reaches npm without its matching managed image tag. The existing tag push and anonymous verification stay.
  • A new Docker end-to-end job runs in CI on pull requests with its own timeout-minutes (20 is the starting budget) and GH_TOKEN for source resolution. It builds the host-platform image from the checkout with buildx cache flags added to imageBuild.ts (--cache-from and --cache-to keyed on source-manifest.json and the lockfile), starts an isolated server with the flag on, and drives the authenticated HTTP boundary through profile upsert, create, receipt, handoff mint, delete, and empty final owned-container inventory. This is a new harness, not a flag flip on DockerSandboxE2E.test.ts, which drives the driver directly. Client onboarding is asserted in Create a Docker Sandbox from one form and land in a ready project #178.
  • Focused tests cover flag-off route absence, flag-on registration, env override precedence, label parsing and the missing-label diagnostic, and settings persistence.
  • User docs describe the preview switch, the env override, and the zero-config create. The dev-only env-var section is removed.
  • Focused tests also cover the loopback port default and the ServerConfig image-repository read.

Architecture

The sandbox HttpApi stays composed. A middleware or per-handler guard reads enableSandboxes from the settings service on each request, answers 404 when off, and exempts the in-container bootstrap pairing route. The list handler returns an empty provider catalog when off, which is what hides the Add Environment card. The web Connections section adds a flag check next to its scope check.

The image build adds LABEL com.katacode.sandbox.* values next to the existing org.opencontainers.image.source label, from the build args the Dockerfile already receives. The Docker driver's image inspection returns those labels as typed bootstrap facts on the validateProfile result, and SandboxBootstrapManifest is built from them instead of process.env. The in-container entrypoint keeps hashing the shipped tarballs against the manifest, so a forged label fails before serve.

Delivery slices

  1. Flag on, stock install, one Docker Sandbox created and attached through the existing dialog, proven by the CI Docker job and one desktop walkthrough.

Demonstration

  • Consumer: A maintainer on a fresh desktop build with Docker running.
  • Action or input: Open Settings, turn on "Docker sandboxes (preview)", open Add Environment, choose Sandboxes, Docker, pick a repository, and create.
  • Observable result: The sandbox attaches with no environment variable set anywhere. Turning the switch off hides every sandbox surface and leaves the deployment in SQLite.
  • Evidence: Screenshots of Settings with the switch off and on, Add Environment with and without the card, the attached environment, and the green CI Docker job on the pull request.

Verification

vp test run \
  packages/kata-sandbox-docker/src/driver.test.ts \
  packages/kata-sandbox-docker/src/imageBuild.test.ts \
  apps/server/src/kataSandbox/SandboxDeploymentService.test.ts \
  apps/web/src/features/kataSandbox/AddEnvironmentDialog.logic.test.tsx \
  apps/web/src/components/settings/ConnectionsSettings.sandbox.test.tsx   # new file

CI Docker job green on the pull request. One verify-katacode desktop pass with the screenshots above. Browser or computer use requires the maintainer's permission.

Risks and mitigations

  • CI image builds add minutes. The job gets its own timeout and buildx cache flags; the Build report records the measured duration.
  • Labels can drift from tarball contents. The entrypoint's SHA-256 check is the authority and stays.

Build handoff

  • Approved scope: the Goal, constraints, acceptance criteria, and delivery slice in this issue.
  • Non-goals: form redesign, progress redesign, Connections redesign, provider parity, env vars.
  • Ordered slices: this slice first; Docker Sandboxes: preview flag and usable end to end #176 children 2 and 4 depend on it.
  • Required verification commands: the focused list above plus the CI Docker job and one desktop walkthrough.
  • Fixtures or credentials needed: Docker or OrbStack, host gh authenticated, a disposable public repository, a sandbox-safe Codex credential, and a GH_TOKEN repository secret for the CI job.
  • Blocking open questions: None

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

    enhancementRequested improvement or new capability.kind:sub-specChild spec produced by decomposing an epic.phase:verifyCurrently in the Verify phase.status:implementedBuilt and reported. Ready to verify.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions