
An enterprise assistant receives two confidential CSV exports and writes Python to reconcile them. The script runs in a fresh container, finishes successfully, and returns a report. That looks like a contained computation. It may also have downloaded a package, inherited a cloud credential, contacted an external endpoint, or embedded active content in the report.
None of those failures requires a kernel escape.
Treat agent-generated code as untrusted execution, with separate controls for what enters the sandbox, what it can reach, and what leaves it. The isolation runtime protects the host. Network policy, credential placement, resource limits, and artifact release determine whether the computation stays inside its business authority.
For the reconciliation job, a useful initial design is deliberately narrow: approved input snapshots, a prepared Python environment, no network during execution, no business credentials inside the worker, and a quarantined result inspected before delivery. This article develops that design for asynchronous enterprise analysis jobs. Interactive development and connected agents need different profiles, with their additional authority made explicit.
Separate host isolation from business authority
A sandbox can successfully isolate a process while allowing it to misuse everything intentionally exposed inside it. A mounted customer directory remains readable. An injected API token remains usable. An allowed upload endpoint remains a possible destination for data.
The gVisor security model makes this distinction concrete. Its Sentry implements the application’s system-call interface and limits interactions with the host. Applications can still use mapped files and permitted networking. The documentation also identifies network-accessible services as a route that does not require escaping the container.
Start the agent threat model with an uncomfortable assumption: the generated script, an imported package, or a document parser might behave maliciously. Then enumerate reachable resources. Reviewing the script is useful, but it cannot replace a boundary that also contains unexpected behavior.
For hostile native code, evaluate an application-kernel sandbox such as gVisor or a microVM deployment against the workload’s compatibility and isolation requirements. Do not infer the complete security posture from the runtime name. Ordinary containers share the host kernel; additional isolation changes the attack surface but still needs secure configuration and patching.
Firecracker’s production guidance requires attention to host and guest kernels, microcode, seccomp, and the jailer or equivalent restrictions. It also assigns operators responsibility for resource bounds, including guest-influenced output. A microVM without a maintained host and a controlled management interface is an incomplete deployment.
Qualify the actual workload: Python extensions, filesystem behavior, startup latency, cancellation, and memory overhead. Record the runtime and host versions used. If a library requires broader access, treat that as a profile change requiring review, rather than quietly enabling privileged mode.
Build a job profile before exposing an interpreter
Consider an illustrative reconciliation service. A user selects two authorized exports from one tenant and asks for mismatched invoice rows. The result goes back to a named reviewer. The worker needs the two files and computation; it does not need the source database, the user’s home directory, or permission to send email.
Use the following as a design record, not a ready-to-apply deployment manifest. The limits are example choices to validate with representative workloads.
| Boundary | Reconciliation profile |
|---|---|
| Input | Two authorized, immutable file snapshots; record tenant, version, digest, and classification. |
| Runtime | Approved image digest and dependency set; unprivileged worker with no host management socket. |
| Files | Read-only inputs and runtime; private scratch space; one bounded output area. |
| Network | No worker network access, including DNS, metadata, cluster APIs, and business systems. |
| Credentials | No user token, cloud credential, service-account token, or package credential in the worker. |
| Resources | 2 vCPUs, 2 GiB memory, 120 seconds, 64 processes, 512 MiB scratch, 1 MiB logs, 10 MiB results. |
| Release | Validate result content and recipient; publish only the inspected immutable object. |
| Disposal | Destroy the job instance and scratch state; retain only approved evidence and results. |
The launcher chooses the profile from trusted workflow state. The agent may request a capability; it cannot redefine the mounts, network rules, limits, or recipient by adding fields to a tool call. Reject unsupported requests instead of silently selecting a more permissive environment.
The tool contract should identify the profile version and input references. The launcher resolves those references under the caller’s current authorization and records the actual runtime configuration. A caller-provided tenant ID or a hash supplied by the script is not independent evidence of isolation.
Keep job identity distinct from tenant identity. Two simultaneous jobs in the same tenant can still have different authorized inputs and recipients. Avoid shared writable working directories and caches. Reuse a clean, verified base image if needed; do not reuse an interpreter, filesystem overlay, or memory snapshot that has processed a previous job’s data.
Acquire dependencies before confidential data arrives
Package installation is execution. It also creates a network channel at exactly the moment many designs assume they are only preparing an environment.
Split the lifecycle into three independently controlled stages:
1 | prepare approved environment, without business data |
The preparation stage resolves and reviews dependencies, produces an image or package bundle, and records its digest. It has no access to the confidential exports. The execution stage consumes that prepared environment. A missing dependency produces a controlled failure or a request for a new approved environment; it does not open internet access on the running job.
For Python, pip’s secure-install guidance describes hash checking with pinned requirements and hashes for all dependencies, plus excluding source distributions with --only-binary :all:. These mechanisms make acquisition more controlled. A matching hash proves that the bytes match the selected artifact; it does not establish that the package is benign. Dependency review, vulnerability handling, and rebuild ownership remain necessary.
An internal package mirror also needs a clear boundary. If the worker can request arbitrary names from a mirror that forwards misses to public registries, those requests can carry data outward. Pre-staging the permitted package set avoids giving the confidential-data stage a general package-query channel.
For more flexible analysis, maintain several approved environments and route jobs to one of them. The operational cost is maintaining that catalog. The benefit is a much smaller set of runtime permissions to investigate when a job behaves unexpectedly.
Make network exceptions explicit and enforceable
For this batch profile, enforce the network prohibition outside the generated process. A prompt instruction, an HTTP-client wrapper, or deleting curl does not restrict a program that can create its own sockets.
In Kubernetes, NetworkPolicy documentation describes IP/port controls enforced by a supporting network plugin. Policies combine additively, so a broad allow rule can reopen traffic. Standard NetworkPolicy is not an HTTP authorization layer, and loopback/node behavior has exceptions. A policy change also need not terminate existing connections; that behavior depends on the implementation.
Consequently, qualify effective reachability from the deployed worker, including host-local services, IPv4 and IPv6, DNS, cloud metadata, and control-plane endpoints. Install and verify isolation before admitting untrusted work. Do not place a privileged helper on the worker’s loopback interface and assume a Pod network policy separates them.
If a job must call an internal service, create a different profile. Permit a narrow broker interface whose identity and operations are checked independently. Keep upstream credentials in that broker, with task-scoped delegation and authorization on every operation. Removing the credential from the worker limits theft, but the broker still grants authority and must constrain it.
Avoid a generic URL-fetch method when a fixed business operation will do. Where fetching is necessary, the broker must constrain destinations, methods, paths, request bodies, redirects, and resolved addresses. An allowed domain can host many tenants or accept uploads. TLS proves the connection’s endpoint identity under its trust model; it does not prove that transmitting this document is authorized.
Treat any permitted request field as a potential output channel. Even a read operation can place sensitive bytes in a URL or query. For highly sensitive jobs, prefetching approved data into the offline profile is often easier to defend than trying to prove that unrestricted code will use an online broker innocently.
Budget the whole job, including cleanup
A two-vCPU limit constrains concurrent CPU consumption; it does not set a completion deadline. A memory limit does not bound disk, process count, or log volume. Configure these dimensions separately and measure their effect on neighboring jobs.
Run the deadline controller outside the worker’s authority. On expiry, terminate the entire execution instance, including descendants, close its remaining connections, revoke any job-scoped broker access, and mark its outputs unreleased. Killing only the parent interpreter can leave background work alive. Resource-exhaustion tests should demonstrate that termination and evidence collection still work when the worker is unhealthy.
Budget retries at the workflow level. Three attempts with 120 seconds each can reserve up to 720 vCPU-seconds at full two-vCPU utilization, excluding preparation and inspection. That is an illustrative capacity bound, not a cost estimate or benchmark. Retrying every failed analysis with a larger limit can remove the very containment the profile was meant to provide.
Persist lifecycle events outside the guest: admitted, started, execution stopped, inspection completed, released or rejected, and disposed. The service must be able to reconcile an abandoned job after a launcher restart. A guest message saying “cleanup complete” is insufficient evidence that its compute, storage, or broker authority has disappeared.
Treat results as untrusted files at a new boundary
A zero exit code says that the process reported success. It does not establish result correctness, authorization to disclose, or safety to open.
For reconciliation, prefer a narrow result schema over an arbitrary HTML report. A trusted validator can check invoice identifiers against the authorized input set, enforce expected columns and row limits, and recompute reconciliation invariants. Keep source data outside the worker for this validation, or use separately verified snapshots; do not ask the same generated script to certify its own answer.
The OWASP File Upload Cheat Sheet is applicable to generated outputs because they are untrusted bytes entering a more trusted service. It recommends layered type/content checks, size limits, safe storage, and authorization rather than trusting an extension or MIME declaration. The release workflow here applies those principles to computation results.
Stop the producer before inspection, then collect a bounded set of regular files into quarantine without following symlinks or accepting paths outside the output directory. Keep extraction and parsing isolated too: an archive, malformed document, or hostile filesystem structure can attack the collector. Do not mount an untrusted disk image directly into a privileged host merely to retrieve a report.
Inspect an immutable snapshot and bind the verdict to its digest. Publication must copy or expose those exact bytes. If the worker can alter a file between scanning and delivery, the scan says nothing about the delivered version. Failed inspection leaves the artifact quarantined; an unavailable scanner is not a reason to release it automatically.
Prefer generating the final presentation through a trusted renderer from validated data. If active documents are required, apply format-specific controls before a reviewer opens them. Spreadsheet formulas, HTML scripts, and external resource references deserve explicit handling. A clean malware scan cannot establish business correctness or detect every disclosure embedded in an otherwise valid report.
Apply the data-classification policy to results, stdout, errors, filenames, and previews. A shorter result can remain as sensitive as its source. Check the recipient again at delivery, and give result links a narrow audience and lifetime. Sending an error back to a cloud model is also a disclosure path; truncation alone does not make that error safe.
When tool output returns to the agent, preserve its status as untrusted data. A result that asks the assistant to change permissions or send another file must not modify the execution profile or authorize a subsequent action.
Prove containment with deliberate failures
Use synthetic confidential markers and controlled test destinations. The purpose is to observe the boundary, not to expose real records. Include a successful reconciliation as a positive control so a broken worker cannot pass merely by refusing everything.
| Test | Evidence required |
|---|---|
| Read outside the staged inputs | Host files, credentials, and other jobs’ canary files remain inaccessible. |
| Connect by IP, DNS, or alternate address family | Attempts fail at the intended boundary; controlled receivers see no data. |
| Reach metadata, loopback helpers, or cluster APIs | No credential or privileged management capability becomes available. |
| Request a new package after input staging | Execution stays offline; the request creates no hidden acquisition path. |
| Exhaust CPU, memory, processes, disk, or logs | Each bound holds; the external controller stops the entire instance. |
| Return a symlink, archive, or active document | Collection and inspection contain it; it cannot become a trusted download. |
| Change bytes after inspection; change the recipient | Release binds to the inspected digest and current recipient authorization. |
| Crash the launcher; start a later job | Orphans are reconciled; no previous data, authority, or writable state is inherited. |
Run these checks against the deployed profile after runtime, host, network-plugin, image, broker, or collector changes. Observe both sides of a denied operation where practical. A worker-side timeout alone cannot distinguish a firewall denial from a failed test server.
The platform team should own the launcher, runtime maintenance, quotas, and cleanup. Security engineering should own containment testing and review network exceptions. The process and data owners should define acceptable inputs, validation rules, and recipients. Keep the approved profile version and the latest test evidence together.
Start with the offline reconciliation profile. Expand its authority only when a specific workload demonstrates the need and the new boundary can be tested. The useful deliverable is an execution service whose reachable resources and releasable outputs can be explained precisely, even when the generated program behaves badly.
