Once an agent can run code (a Python script to analyse a dataset, a command on a server), the old network perimeter stops protecting you. The code was written by a model, and you cannot treat it as trusted. A zero-trust design starts from one assumption: the model will eventually produce something buggy, unsafe or compromised, so the system must stay safe anyway.
In plain terms: Letting an agent run code on your server is like letting a stranger use your office computer. You would give them a locked guest room with no network cable, a time limit, and a visitor badge that opens nothing important.
Diagram: Under zero-trust principles you assume the model will eventually produce buggy, unsafe or compromised code. Generated code must never run on a host or production container. It runs inside a sandbox that limits what it can steal, escalate or change. See the animated version.
Running generated code directly on a host or in a production container risks data theft, privilege escalation and damaging changes. Instead:
Diagram: Agent-generated code runs inside layered isolation: a microVM or gVisor boundary, a read-only filesystem with a temporary in-memory disk, and kernel cgroup limits on CPU, memory and I/O. The whole environment is destroyed as soon as the task ends. See the animated version.
Diagram: A code-execution sandbox should have no network access by default. If a task needs an external API, allow that single destination and keep everything else, including the local network and the internet, blocked so data cannot leak out. See the animated version.
Diagram: Credentials given to an agent should be read-only, limited to non-sensitive schemas and short-lived. Administrative actions such as schema changes require an authenticated elevation step, and everything is logged. See the animated version.
Here is a practical starting point: a Python helper that runs untrusted code in a locked-down throwaway container. It assumes Docker is installed. For stronger isolation, add --runtime=runsc if you have gVisor set up, or use a microVM platform.
import subprocess, tempfile, pathlib
def run_untrusted(code: str, timeout_s: int = 10) -> str:
with tempfile.TemporaryDirectory() as d:
script = pathlib.Path(d, "task.py")
script.write_text(code)
cmd = [
"docker", "run", "--rm",
"--network", "none", # zero network access
"--read-only", # read-only root filesystem
"--tmpfs", "/tmp:size=64m", # small in-memory scratch space
"--memory", "256m", "--cpus", "0.5", # cgroup limits
"--pids-limit", "64", # no fork bombs
"--cap-drop", "ALL", # drop Linux capabilities
"--security-opt", "no-new-privileges",
"--user", "65534:65534", # unprivileged user
"-v", f"{script}:/task.py:ro",
"python:3.11-slim", "python", "/task.py",
]
try:
done = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout_s)
except subprocess.TimeoutExpired:
return "Stopped: time limit exceeded"
return (done.stdout + done.stderr)[:4000] # cap the output size too
Treat the agent's execution environment as an untrusted runtime boundary. That is what lets you give agents real capabilities without handing them the keys to the enterprise.
Day 27: Observability and tracing for agentic systems: telemetry, logs and metrics.
Originally published at https://sureshpallapothu.in/blog/day-26-zero-trust-ai, where this post includes animated diagrams.