Skip to content

Per-workspace agent sandboxes: on-demand, internet-enabled, no-escape isolation on top of ComputePlane #54

Open

Description

@kvaps

Summary

Cozystack needs a per-workspace sandbox layer for agentic-coding and untrusted-code workloads: one fully isolated Linux environment per workspace, spun up on demand from an image, with public internet access, snapshot/resume, and no practical escape path.

This is the layer above ComputePlane. #17 (merged) gives us the no-escape boundary between untrusted workloads and the management plane. It does not give us the per-workspace unit that lives inside that boundary — that is what this issue tracks.

Motivation

The request comes from mattia-eleuteri, whose end customer runs an agentic-coding product and currently depends on a hosted Firecracker-based sandbox service. The commercial driver is sovereignty: the managed micro-VM offerings that fit the requirement cleanest are all outside the EU/CH, which rules them out for the customer. A self-hosted equivalent on Cozystack is the reason this is worth building rather than buying.

Requirements

The sandbox must let an agent read and write files, run shell commands, install dependencies from the public internet (npm, pnpm, pip, uv), run builds and tests, and use ordinary dev tooling (git, Node, Python, ripgrep). The file API and shell execution must share one workspace root.

Hard isolation requirements:

  • the sandbox may reach the public internet;
  • it must not reach the host, the orchestrator, the container-runtime socket, the metadata service, the private network, the platform database, application secrets, or model/API keys;
  • sandbox A must not reach sandbox B's files or its localhost.

Accepted risk: a sandbox can exfiltrate its own workspace data, because internet access is a requirement. Not accepted: escape to the host, the platform, or another tenant.

Prior art and substrate options already evaluated

  • bwrap — shared host kernel; an escape compromises the worker and potentially every workspace on it. Usable as a protocol, not as the final isolation boundary.
  • gVisor — a workable self-hosted fallback (user-space kernel boundary, per-workspace netns, egress firewall, cgroup limits), but operationally heavy: runsc/containerd lifecycle, cleanup, image patching, and DNS/firewall correctness all land on the operator.
  • Kata Containers / micro-VM runtimes — the cleanest boundary, but they need hardware virtualization on the host, which ordinary cloud VMs without nested virtualization do not expose. On bare metal this constraint does not apply, which is the common Cozystack deployment.
  • kubernetes-sigs/agent-sandbox — surfaced after the original discussion and covers a meaningful part of the design. Whether we adopt it as the substrate or keep our own abstraction is the main open question below.

Relationship to existing proposals

  • #17 — ComputePlane, merged. The isolation boundary this work sits inside.
  • #28 — VMSession, still open and untouched since 8 July: on-demand per-workspace VM cloned from a master image, no-escape networking, auto-teardown. It was written directly from this request and reviewed by Mattia Valzelli (@mattia).

Open questions

  1. Do we implement VMSession as proposed in docs(design-proposals): add ephemeral VM sessions (VMSession) proposal #28, adopt kubernetes-sigs/agent-sandbox as the substrate, or express docs(design-proposals): add ephemeral VM sessions (VMSession) proposal #28's contract on top of it? This decision blocks the rest and should be settled before implementation starts.
  2. Snapshot/resume: Mattia proposed Kata plus LINSTOR snapshots. With Blockstor replacing LINSTOR, what does the snapshot path look like?
  3. Does a sandbox get its own ComputePlane, or do many sandboxes share one? This drives both the security story and the cost per workspace.
  4. What is the intended tenant-facing API — a new kind, or a preset on an existing one? #26 pushed ComputePlane toward composable presets rather than a new kind; the same argument likely applies here.

Next step

Settle question 1, then either revive #28 or supersede it. Hidora offered to prototype on their lab.

Activity

  1. themoriarti commented on Nov 3, 2026

    @themoriarti

    Adding a Proxmox-substrate angle to this, since a few of the open questions look different there.

    Q1 — VMSession vs agent-sandbox. I'd keep #28's contract (master ref, Running | Paused | Stopped, networkIsolation, optional persistent workspace disk) and implement it on the primitives each substrate already has. agent-sandbox is a Pod-with-RuntimeClass orchestrator; it delegates isolation to gVisor/Kata inside the pod, which is exactly the boundary this issue rejects on hosts without nested virtualization. It is worth borrowing its vocabulary (SandboxTemplate, SandboxClaim, SandboxWarmPool) so a bridge is possible later, but it does not give us the VM boundary.

    Q2 — snapshot/resume. On a Proxmox-backed installation the snapshot path does not go through LINSTOR or Blockstor at all: it is the hypervisor's own snapshot on ZFS/Ceph. More importantly, Proxmox can snapshot a VM with RAM state and suspend a VM to disk, so the "warm resume with resources freed" gap that #28 lists as out of scope for KubeVirt is available there today: the guest resumes with its processes intact and holds no node resources while suspended. kubemox already exposes VirtualMachineSnapshot / SnapshotPolicy, so Paused can map to suspend-to-disk rather than a resident pause.

    Q3 — own ComputePlane per sandbox, and the nested-virt constraint. With substrate: proxmox the sandbox is a KVM guest on the hypervisor, not on a Kubernetes node. The Cozystack nodes themselves can be VMs; nothing in the data path needs nested virtualization. That also makes the per-sandbox cost question a scheduling question on the hypervisor pool rather than a per-node one.

    Isolation. The egress shape in #28 (allow world + DNS, egressDeny for RFC1918, link-local, pod/service CIDRs, apiserver, and sibling sessions) is the same shape we run for Proxmox-backed VMs behind a per-account router pod with a Cilium identity. We validated it on a live cluster with a positive/negative matrix: VM → own account OK, VM → other account dropped as Policy denied, apiserver only via the explicit label, account DNS returns NXDOMAIN for other accounts and for private PTRs. Deny rules win over the tenant's blanket allow, so it does not depend on a platform-wide default-deny.

    What already runs. We create VMs on Proxmox from kubemox templates (full and linked clones) for throwaway workloads today: CI build agents for a public repo and batch compute jobs, both with internet egress and automatic teardown. That is the clone → boot → reclaim loop of #28 without the resource. The Proxmox side of vm-disk / vm-instance is in draft cozystack#5177.

    Offer. We can prototype #28's loop on our Proxmox lab in parallel with Hidora's and report spin-up and suspend/resume timings, so the decision in Q1 is made with numbers from both substrates. The pieces to wire are a kubemox linked clone, the per-session CiliumNetworkPolicy, and cloud-init for SSH; the known gaps are on the kubemox side (object-name collisions, no repair after out-of-band delete), which I listed in #117.

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