Secure Sandboxed GRPO
Sandboxed GRPO runs untrusted environment code in an OpenSandbox pod, separate from the training pod. The sandbox needs two endpoints on the training pod: the episode broker and the vLLM generation server. Everything else on that pod, including Ray, is the training control plane and must stay unreachable from the sandbox.
This how-to separates the settings that make GRPO run from the settings that make the sandbox secure. A GRPO job starts and trains without the network-policy settings and without Kata. The OpenSandbox sidecar still denies other private addresses, but its exceptions are addresses with no port: the sandbox can reach every port on the training pod, including Ray, and every port on the cluster DNS resolver. Without Kata, a kernel exploit in the Gym can leave the sandbox. The worked example is a single-node minikube cluster. On another Kubernetes cluster, use the same Helm overlay and follow On a Kubernetes cluster.
What each layer does
The Helm chart sets rl.job_storage_pvc_claim to its job-storage PVC. The sandbox reads the environment and dataset from that claim. Leave it unset unless you are not using the chart, or job storage uses a different claim.
Prerequisites
- A source checkout of
NVIDIA-NeMo/nemo-helix - A Kubernetes cluster. The steps below use minikube; skip the minikube start when the cluster already exists.
kubectl,helm,jq, andopenssl. The minikube path also needsminikubeanddocker.- An NVIDIA GPU host with the NVIDIA container toolkit, for GPU training
NGC_API_KEYwith access to NeMo Helix chart and container images- Kubernetes 1.30 or later, or 1.28 or later with the ValidatingAdmissionPolicy beta API enabled. This is needed only for the secure configuration.
1. Start minikube with an enforcing CNI
minikube’s default CNI on the Docker driver accepts NetworkPolicy resources and does not enforce them. Use Cilium. The CNI cannot be changed on an existing profile, so delete and recreate a profile that was started without it.
The script starts minikube with the Docker driver, passes --gpus all when nvidia-smi works, and creates the platform pull Secrets in KUBE_NAMESPACE. Do not use --cni calico with the Docker driver: Calico pods stay in ContainerCreating there.
Confirm Cilium is running:
The NetworkPolicy smoke test checks the chart’s API, controller, and managed-job policies on Kind with Calico. It does not exercise the Gym sandbox policy described here.
On a Kubernetes cluster
Skip the minikube start when the cluster already exists. The sandbox settings in the Helm overlay below are the same. These parts differ:
The per-step policy is a standard ingress NetworkPolicy. The jobs controller writes each allowed port on its own, so a CNI that ignores port ranges still admits the broker and the vLLM span.
2. Install OpenSandbox
Install the shared-kernel OpenSandbox controller and server, and copy its API-key Secret into the platform namespace:
The server’s [kubernetes] namespace must be the platform release namespace, because sandbox pods remount the job-storage PVC and resolve pull Secrets there. See OpenSandbox for the namespace rule, the Secret copy, and installs outside minikube.
3. Install Kata
The server from the previous step is the shared-kernel runtime, which is enough to run GRPO. It is not enough for a secure sandbox: the Gym shares the node’s kernel, and a kernel exploit in untrusted environment code can leave the pod. Follow OpenSandbox with Kata and point the platform at the Kata server’s Service. One OpenSandbox server cannot mix shared-kernel and Kata sandboxes.
Kata is the kernel boundary. The network-policy values in the next step are the network boundary. A secure sandbox needs both.
4. Install the platform with sandboxing enabled
Install the chart with the minikube values:
Then apply this overlay. The first block makes sandboxed GRPO run. The networkPolicies block locks down the sandbox’s cluster access.
Notes on these values:
opensandbox.protocol: httpis required for the in-cluster Service. If it is unset, health checks usehttpsand the job stalls waiting for the sandbox.networkPolicies.enabled: truealso enables the chart’s API, controller, and managed-job policies. Keep them on. Turning them off is not part of this setup.- The per-step policy is created only when both
networkPolicies.enabledandnetworkPolicies.jobSteps.enabledare true. With either one off, GRPO still runs and the sandbox can reach every port on the training pod. rl.sandboxed_gym_defaultalready defaults totrue, andrl.job_storage_pvc_claimalready names the chart PVC.- With the policy flags on, the chart installs a ValidatingAdmissionPolicy and fails the install on clusters that do not serve that API.
Internet access
rl.sandbox_allow_internet defaults to false. It is an operator setting on the platform, under platformConfig.rl. The overlay above leaves it off, which is the setting for a complete wheels-v1 closure.
Turning internet on does not open a public address range, and it does not open the cluster. Private, link-local, and metadata addresses stay denied, including the platform database, the Files service, the platform API, and Ray.
How the sandbox is isolated
Two boundaries apply. OpenSandbox’s egress sidecar is on every Gym sandbox the documented server creates. The per-step NetworkPolicy is ingress to the training pod, and the jobs controller creates it only when both network-policy flags are on.
OpenSandbox egress
The documented OpenSandbox server runs its egress sidecar in dns+nft mode. The policy is deny by default, and it denies private, shared, link-local, and metadata ranges, including 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and 169.254.0.0/16. The platform database, the Files service, the platform API, and the rest of the cluster sit in those ranges, so the Gym cannot open them. The job-storage claim is still mounted into the sandbox so it can read its environment and dataset. That mount is not a connection to the Files service.
The addresses carved out of the deny are the job’s episode broker, the policy model endpoint, and the cluster DNS resolver. Public name resolution is the internet access setting. The platform builds the allow list.
The broker and the policy model are addresses on the training pod. An address carve-out has no port, so this layer alone still leaves the training pod’s other ports, including Ray, reachable. The ingress policy below closes those ports.
Training pod ingress
For each GRPO training step, the jobs controller creates an ingress NetworkPolicy:
- Target. The training step’s pods, selected by
nhx.nvidia.com/managed_by=jobs-controller,nhx.nvidia.com/job_id, andnhx.nvidia.com/job_step_id. - Same job. Pods of the same job reach every port. Ray traffic between training pods stays inside the job.
- Gym sandbox. Pods labelled
nemo-rl-job-id=<job id>reach only TCP 51234 (the episode broker) and TCP 3000–4998 (the span vLLM picks its generation port from). OpenSandbox turns sandbox metadata into pod labels, and the broker stamps the job id. The sandbox’sopensandbox.io/idlabel names the sandbox; it is not what the policy matches. - Everything else. No other ingress is allowed into the training pods.
Ray’s ports stay outside the allowed set: GCS 6379, client 10001, dashboard 8265, node manager and agent ports from 53001, and workers 54001–54257. If you override Ray ports with GCS_PORT, MIN_WORKER_PORT, or the other Ray port variables, keep them out of 3000–4998 and off 51234.
NetworkPolicy allows are additive. Another policy in the namespace that selects the training pods with a broader allow, such as an exception for the training pod’s IP with no ports, reopens every port on that pod, including Ray.
Verify
Before a job, confirm the admission policy is installed:
Submit a short GRPO job, as described in GRPO and Reward Environments. While it runs, set JOB_ID to the platform job id and inspect the step policy:
Expect:
spec.podSelectorselects one job step, andpolicyTypesis[Ingress].- One ingress rule allows pods with
nhx.nvidia.com/job_id: <job id>on all ports. - One ingress rule allows pods with
nemo-rl-job-id: <job id>on TCP 51234 and TCP 3000–4998, and no other ports.
Confirm the Gym sandbox pod carries the job id:
The expected connectivity from the sandbox is:
- The episode broker and the vLLM generation port are reachable.
- Ray ports on the training pod are not reachable.
- The platform database, the Files service, the platform API, and other private addresses are not reachable.
A completed GRPO job with training metrics and an output model confirms that the allowed ports are enough.
Next Steps
- Build an environment package in GRPO Environment Packages.
- Review OpenSandbox for installs outside minikube.
- Review Security for broader deployment hardening.