Switching clouds? Get up to $10K in credits + hands-on help.

Apply now
Platform

What are sandboxes, and why your agents should use them

When an agent writes code and immediately runs it, that code reaches your infrastructure without anyone having reviewed it. A sandbox is the isolation boundary between that untrusted execution and your trusted host, and it works because the boundary enforces the limits rather than asking the code to respect them. Where the boundary sits varies by mechanism, and that difference decides how much of your infrastructure a compromise reaches. Here is where it sits, and how to run agent-generated code on Render without over-trusting the container.

Agent-generated code changes from one run to the next

The same prompt can yield different code across runs, which makes three risks concrete. Generated code may read, write, or delete files outside its intended scope. It can reach arbitrary endpoints, exfiltrating data or calling services you never intended for it to touch. And an unbounded loop or a runaway allocation can exhaust the host's CPU and memory.

Run that code on trusted infrastructure and your blast radius equals your environment. Accidental exhaustion and malicious intent land in the same place.

The sandbox as an isolation boundary

That boundary is easiest to reason about as two zones. The trusted zone holds your credentials, and its central piece is the host that orchestrates work and decides what runs. The untrusted zone is where generated code runs under constraint, holding nothing you would mind losing. Those two names carry through the rest of this article.

createSandbox stands in for whichever mechanism you pick. The shape that matters is that every constraint is enforced by the boundary rather than requested of the model, because a model ignoring your instructions is the case you are defending against.

The kernel determines the strength of the sandbox

The title of "sandbox" is applied to mechanisms with very different strengths, and the difference is the kernel. A container shares the host kernel, so a kernel vulnerability puts the host inside your trust boundary. A microVM gives untrusted code its own guest kernel behind hardware virtualization, so a compromise has to defeat the hypervisor before it reaches the host. Many arguments about what counts as a sandbox are really arguments about whether the host kernel should sit inside the boundary.

Roughly in order of increasing isolation strength:

Mechanism
Kernel boundary
Fits
Limited process
Shared host kernel, shared namespaces
Code you wrote and trust
Container
Shared host kernel
Code your agents generate against your own tools
Secure runtime (gVisor, Kata Containers)
A separate kernel: user space for gVisor, a lightweight VM for Kata
Untrusted input, stronger isolation needed
microVM (Firecracker)
Guest kernel behind hardware virtualization
Code you expect to attack you

For code you consider actively hostile, the guardrails guide recommends a secure runtime or a microVM rather than a plain container.

Container-grade isolation for the untrusted zone

Render services are containers, which puts them mid-ladder: a reasonable fit for code your agents generate against your own tools, not designed for code submitted by untrusted users. For the second case, call a purpose-built microVM sandbox and let Render be the trusted host.

Within that fit, split the work across three roles, each in its own service: a trusted host that orchestrates, an untrusted zone that runs generated code, and an egress proxy that holds the provider credential and brokers the outbound calls the zone is allowed to make. The proxy sits in the trusted zone alongside the host. It is a separate service so that the credential lives in exactly one place and the untrusted zone is not that place.

Resource ceilings bound the service, not the task

The untrusted service runs on an instance type with defined RAM and CPU, which gives the zone a resource ceiling. That ceiling bounds the service rather than each task, so concurrent tasks share it and one runaway task can starve its siblings. Add per-task limits with ulimit or your language's own resource controls, plus an execution timeout.

If per-task bounds matter more than a long-lived service, Render Workflows sets the instance type and a timeout on each task rather than on the service, and every run gets its own instance that Render deprovisions as the run completes. It is still a container, so the kernel boundary does not move, but it gives you the per-task ceiling and the teardown that a shared service cannot. You define those tasks in code with the Render SDK for Python or TypeScript, so this is a change to how you write the zone rather than a field you flip.

Three details shape how you size a task:

  • Instance type. Tasks run on the Standard instance (1 CPU, 2 GB RAM) unless you override that per task. Every workspace can pick among Starter, Standard, and Pro, with the larger types available on request.
  • Timeout. It defaults to two hours, and the range you can set runs from 30 seconds to 24 hours, so the default bounds a stuck task rather than a tight inner loop.
  • Ingress. A workflow task can send private network requests but cannot receive them, so the host triggers a run through the SDK or the API rather than calling the zone at a host and port.

Workflows is in public beta.

The service type settles ingress

A private service is reachable only by your other Render services in the same region and workspace, and a background worker is not reachable even there, because workers can send private network requests but never receive them. Either one keeps the untrusted zone off the public internet entirely, though the worker changes how you reach it: you hand it work through a queue rather than a request.

IP rules are the one inbound filter you configure rather than build, and setting them per service, environment, or workspace takes a Scale or Enterprise workspace. Among services they apply only to web services and static sites, because private services and background workers have no public traffic to filter in the first place. You can set them on Render Postgres and Render Key Value on any workspace.

State survives between tasks on a long-lived service

The filesystem is ephemeral unless you attach a disk, so changes are lost every time the service redeploys or restarts and nothing the agent writes survives either event. Watch the granularity, though: a long-lived service neither deploys nor restarts between tasks, so whatever one task writes is still sitting there for the next one. Clearing that state is your job: give each task its own working directory and delete it when the task returns, or trigger a restart of the zone between tasks. Because the untrusted zone is a separate service, restarting it leaves the orchestrating host untouched.

Egress is an application-layer problem

Render distinguishes two kinds of outbound traffic. Private networking between your own services is something Render controls directly: on a Pro workspace or higher, you can block private traffic from entering or leaving an environment. Internet egress works differently. Render doesn't offer a single switch to lock it down. Instead, you implement it yourself by specifying which destinations a given task is allowed to reach, then routing the untrusted process through a proxy in your trusted zone that enforces that allowlist. This approach has a real limit: nothing forces code inside the container to actually use the proxy. Blocking every other path out would require kernel-level network controls, which need privileges an unprivileged container doesn't have.

The proxy still earns its keep. If it's the only place holding valid credentials, anything that bypasses it has network access but can't authenticate. It can still open a socket and send whatever's already in the zone, so the practical mitigation is keeping each task's inputs as narrow as the task allows, not chasing airtight containment. If a workload truly needs its network access blocked outright (not just rendered harmless), that's a sign you should isolate it in a microVM rather than running it alongside your container-based service.

Wiring the three roles in a Blueprint

Defining those three roles in a Blueprint writes the separation into your infrastructure.

Read it by what is absent. sandbox-runner has no provider credential in its environment at all, so the worst it can do by going around the proxy is reach the internet unauthenticated. Both untrusted-zone and proxy services are pserv, so neither takes public traffic. sync: false keeps the real token out of the repository and prompts you for it once at Blueprint creation. plan sets the resource ceiling discussed above, and Starter gives each service 512 MB of RAM and 0.5 CPU.

The hostport variables are also the call path. Once the services are separate, sandbox.execute from the earlier sketch becomes a request the orchestrator sends to SANDBOX_HOSTPORT over the private network, and the untrusted zone runs the code in a process it starts locally. The boundary you get is the one between two services, so nothing the zone does reaches the orchestrator except through that response.

Scoping the tool instead of the credential defeats isolation

The highest-impact mistake is scoping the tool instead of the credential. The write-up of what Polsia learned running autonomous companies shows how that plays out. Polsia gave an agent a Postmark key so it could send email on a user's behalf, and the agent discovered the same key allowed it to rename the company's main Postmark server and manipulate webhooks. A sandbox constrains where code runs, not what a credential inside it can do.

Three smaller mistakes compound it. Over-trusting the boundary ignores that no mechanism guarantees complete security and that a container guarantees less than most teams assume. Capping per service instead of per task leaves a ceiling shared by all concurrent work, which is not a per-task cap. And expecting platform-level egress controls skips the step of verifying what the platform actually filters.

Decide whether the host kernel belongs inside your trust boundary, pick the mechanism that matches, then scope the credentials the code can reach. For the surrounding controls on input validation and tool scoping, see security best practices for AI agents.

Frequently asked questions