Skip to main content
A sandbox is a Kubernetes object. Creating one through the API writes a custom resource; a controller reconciles that resource into a pod; a status mirror converges what the cluster actually observed back into the product record. Nothing in the model hides a pod behind a private protocol, so an operator can inspect the whole lifecycle with kubectl.

The sandbox resource

Sandboxes are represented by the Sandbox kind in the agents.x-k8s.io API group, served at v1beta1 and scoped to a namespace. The type comes from the open-source agent-sandbox project maintained under Kubernetes SIG Apps, licensed Apache-2.0. hiloop runs the upstream controller from a pinned image and consumes its API types as a versioned module rather than forking them. The spec fields that carry a sandbox’s shape: Status is observation, not intent: conditions (Ready, Suspended, Finished), plus nodeName and podIPs once scheduled. The controller is the only writer of status.

The reconcile loop

For each sandbox resource that is not being deleted, the controller:
  1. Creates exactly one pod from podTemplate, with an owner reference back to the sandbox. One sandbox is always one pod. Observing more than one live child pod for a sandbox is a contradiction, and the product record reports it as needing attention rather than picking a winner.
  2. Re-propagates pod-template metadata onto the live pod on every reconcile, including scrubbing keys that were removed. This is how a control-plane annotation edit reaches a running pod without another controller in the path.
  3. Creates one claim per volumeClaimTemplates entry, owned by the sandbox rather than the pod. Because the claim’s owner is the sandbox, it outlives any single pod and is garbage-collected with the sandbox itself.
  4. Writes status from pod readiness.
Stop and start are the same mechanism: setting operatingMode: Suspended makes the controller delete the sandbox’s pod, and setting it back to Running makes the controller create a replacement. That is the entire lifecycle transition. Because storage owned by the sandbox is not owned by the pod, a filesystem with its own lifetime survives the gap and is presented again to the replacement pod. Processes do not: the replacement pod starts its image’s entrypoint from the beginning. The control plane reaches the cluster directly over the Kubernetes API and writes only merge patches, never a server-side apply, so it never takes ownership of fields the controller manages. Its write vocabulary against a sandbox is small: create, patch the operating mode and expiry, annotate, and delete.

State is mirrored, never inferred

A separate status-mirror controller watches sandbox resources and sandbox pods and writes what it observed into the product record. The API answers reads from that record. This is why a sandbox’s state reports evidence: a sandbox is running because a pod was observed ready, and out-of-band pod death converges the record to a terminal state instead of leaving it green. The mirror resolves contradictions explicitly rather than smoothing them. An unparseable organization label, a missing resource identity, more than one child pod, or a runtime receipt it cannot read all produce an attention state naming the anomaly, and a recorded capability contradiction produces quarantined. It reconciles state; it never writes a pod.

What a sandbox is at the pod level

One pod, with one container that matters:
  • The workload container runs your image’s entrypoint unmodified. No command, no arguments, no injected environment, and no security-context override are applied to it. What runs is what your image says runs, as the user your image declares.
  • restartPolicy: Never. A workload that exits has exited; it is not silently restarted underneath you.
  • No Kubernetes service-account token is mounted. A sandbox cannot talk to the Kubernetes API, because it holds no credential for it.
  • The pod requests the gVisor runtime class, which also carries a fixed per-pod memory overhead for the sandboxing layer so the scheduler accounts for it.
Platform-owned helper containers, where a deployment uses them, are hardened the opposite way from the workload: they run with a read-only root filesystem, every capability dropped, privilege escalation disabled, and the default seccomp profile. The asymmetry is deliberate. Your container is untrusted and unconstrained inside the boundary; the boundary itself is what is constrained. Sandbox identity stays opaque across the API: a sandbox id never exposes a pod name, container id, or node name.

Reaching into a sandbox

hiloop sandbox exec is brokered through the Kubernetes API as an exec into the workload container. The command’s real exit code is recovered from the API server’s terminating status frame rather than inferred from a closed stream, so a non-zero exit is reported as a non-zero exit and not as a transport error. Interactive access is served by a small static server that runs inside the workload container, so a shell sees your filesystem as your image’s own user. It shares the pod’s network namespace but not its mount namespace, which is why the exec path, not the session path, is what reaches the container’s own filesystem view. Port forwarding through the container runtime is deliberately not offered. Runtime port-forward implementations work by entering the pod’s host-side network namespace and dialing loopback, which cannot reach a workload whose network stack lives in a user-space kernel. Forwarding a port is done over the session instead, with the standard OpenSSH -L flag.

The isolation boundary

Sandboxes run arbitrary, untrusted images, so every control sits outside the workload.
  • gVisor. Each sandbox pod runs under the runsc runtime class: a user-space kernel that intercepts system calls instead of passing them to the host kernel.
  • Admission pins the boundary. A cluster admission policy evaluates sandbox resources at the API server and refuses any whose pod template does not request the gVisor runtime class, or which asks to share the host’s network, process, or IPC namespaces. The policy fails closed, and it binds the invariant to the API server rather than to the code that renders sandboxes, so neither a control-plane regression nor a stolen control-plane credential can place a customer workload on the host kernel.
  • Pod Security. The sandbox namespace enforces the baseline Pod Security Standard and audits against restricted. Baseline rather than restricted is a considered choice: arbitrary customer images legitimately run as root and declare no seccomp or capability posture, so they cannot satisfy restricted. Baseline still refuses privileged containers and host namespace escapes at the API server as defense in depth.
  • Network policy. Ingress to sandboxes is default-deny, with a single exception for the control plane’s session port. Egress allows cluster DNS and the public internet while denying private address ranges and the link-local range, so a workload can reach the internet but not the deployment’s own infrastructure or the cloud metadata endpoint. See network access.
  • An organization-scoped identity. Each organization’s sandboxes run under their own service account, with token mounting disabled.
  • An object-count ceiling. A resource quota on sandbox objects bounds the fleet transactionally at the API server, behind the per-organization quotas applied at admission.
Admission fails closed throughout: a request a deployment cannot enforce exactly, such as a storage class or transport it does not back, is refused with unsupported_capability rather than quietly downgraded.