14 Sandboxing Layers: Lua VM and Containers
In the last chapter we treated the agent like what it is in production: untrusted execution with the ability to take actions.
Capability control (tools + stages + approvals) is the first line of defense. That’s the principle of least privilege applied to agents: grant only the tools and context required for the current stage. But once you start giving agents real power—writing files, generating code, running programs—you also want a cage:
If the monkey has a razor blade, it shouldn’t be standing in your living room.
This chapter is about isolation boundaries that security professionals recognize:
- language sandboxing (reducing what code can do)
- OS-level sandboxing (constraining what code can touch)
- per-invocation isolation (reducing cross-run/tenant leakage)
Tactus uses defense in depth: Lua sandboxing + container isolation, and (in cloud deployments) per-invocation isolation.
On top of that, Tactus is converging on a brokered runtime model: the runtime container stays networkless and secretless while privileged operations happen behind a trusted broker boundary. The next chapter zooms in on that “where do the secrets live?” question.
14.1 Layer 1: Lua Sandboxing (Language-Level)
Lua is widely used in environments where you must execute untrusted code safely: game modding, embedded systems, nginx scripting, and more. The language is designed to be sandboxed.
Tactus uses that property directly:
.taccode runs in a restricted Lua VM- the standard library is limited to safe functionality
- there is no ambient filesystem/network/OS access
- all I/O goes through tools you provide
This is why Tactus can credibly talk about running user-contributed procedures. In Python, “just sandbox it” is not a real option; in Lua, it’s the default operating model.
14.1.1 What this protects against
Lua sandboxing primarily protects the host application from untrusted orchestration code:
- no arbitrary imports of privileged host libraries
- no
os.execute/ shell escapes - no direct file reads/writes
- no direct network calls
But it does not solve everything.
If you provide a filesystem tool and a network tool, then the agent can still cause harm through those capabilities. That’s why we need OS-level isolation.
14.2 Layer 2: Container Isolation (OS-Level)
Containerization is the practical way to make “agents that write files and run code” safe enough for development and production.
The principle is simple:
- Each procedure execution runs in its own container
- The container has an ephemeral filesystem
- Only explicit mounts are accessible
- Resource limits (CPU/memory/time) reduce blast radius
- Network access can be controlled (or disabled entirely)
This gives you the ability to hand an agent powerful tools—write files, run Python, compile code—without giving it access to your actual machine.
It’s important to separate two different kinds of protection:
- System safety: protecting the host machine and preventing cross-run leakage through the filesystem.
- Information security: preventing the agent from ever seeing secrets (API keys, credentials) that it could leak.
Containers are great for system safety and isolation—but they’re not a secret-hiding mechanism. If you pass credentials into the runtime container (or the process), the agent can often still exfiltrate them through tool misuse or prompt injection. That’s why Tactus pairs isolation with a stricter boundary: keep the runtime secretless, and broker privileged operations (model calls and credentialed tools) behind a narrow interface.
From a security standpoint, this is about:
- integrity: protecting the host from vandalism
- confidentiality: limiting what the runtime can read
- isolation: preventing cross-run/tenant leakage through shared state
14.2.1 Ephemeral by default (with explicit persistence)
An important “security smell” in agent systems is persistence-by-accident:
- an agent writes a file with sensitive data
- that file gets picked up by a later run
- now you have cross-session leakage and unclear provenance
Running each execution in a fresh container makes persistence explicit. By default, your current directory is mounted at /workspace, making it easy for procedures to read and write project files. This strikes a balance between convenience and safety:
- Procedures can naturally work with project files (read source code, write outputs)
- Container isolation ensures procedures can only access the project directory, not your entire filesystem
- Git provides version control, making all changes reviewable and reversible
- Additional data sources can be mounted via sidecar configuration
For workflows that need more restricted access (like output-only report generation or untrusted procedures), you can disable the default mount and explicitly configure only what’s needed. This explicitness is exactly what InfoSec teams want: clear boundaries about what data crosses into the runtime.
14.2.2 Brokered runtime (networkless by default)
Most agent systems need outbound network access to call model providers. Traditionally, that pushes you toward giving the runtime container network access—which is also the primary path for data exfiltration.
Tactus’s Docker sandbox is designed so the untrusted runtime can stay networkless by default (sandbox.network: none) while still supporting model calls: the runtime asks a host-side broker to do privileged work and streams results back into the trace.
14.3 Running with the Docker Sandbox
Tactus includes CLI support for managing the sandbox image and checking availability:
tactus sandbox status
tactus sandbox rebuildAnd you can run a procedure (the sandbox is used by default when Docker is available):
tactus run my-procedure.tacIf Docker is unavailable and sandboxing is required, Tactus will fail safe (it won’t silently fall back to running on your host). If you explicitly want to run without container isolation, you can opt out:
tactus run my-procedure.tac --no-sandboxIn production, “fail closed” is often the right default. In local development, you might opt out temporarily—but you should treat it like disabling seatbelts.
14.4 Network and Exfiltration Risk
Most agent systems need outbound network access to call model providers. That creates an unavoidable tension:
- outbound access is required for usefulness
- outbound access is also the primary path for data exfiltration
Container isolation lets you control network mode. With a brokered runtime, you can often keep the runtime container networkless while still making model calls. A common strategy is:
- run the untrusted runtime with no outbound network
- broker model calls and credentialed tools through a trusted service
If you do enable runtime networking (for example, to let a tool call an internal service directly), treat it as a conscious tradeoff: keep secrets out of the runtime, and constrain egress so the runtime can only reach approved endpoints.
That is the “secretless runtime” model, and it’s the topic of the next chapter.
14.5 Why This Matters for the IDE (Streaming + Parity)
A common worry is that sandboxing makes developer experience worse: “Do I lose streaming? Does this make iteration slow?”
Tactus is designed so you can keep the ergonomic parts (streaming traces, step-by-step visibility) while still enforcing isolation boundaries.
In practice, a good development setup looks like:
- press Run in the IDE
- the procedure executes inside a container
- you still see streaming output, tool calls, and stage transitions in real time
That parity is the point: you develop with the same isolation model you deploy with.
14.6 Looking Ahead
Lua sandboxing and containers put the monkey in a cage, but there’s one more problem:
If the runtime has secrets (provider keys, SMTP credentials), the agent can still leak them.
Next, we’ll add the final guardrail: secretless execution—moving secrets and privileged operations behind narrow broker interfaces, while keeping the .tac workflow code unchanged.