8 Sandboxing and Security
Tactus is built for running untrusted or semi-trusted agent code safely. The core idea is capability control:
- Lua code runs in a restricted VM
- the runtime can run inside a Docker sandbox
- tools are the only way to “reach out” (filesystem, network, secrets)
8.1 Sandbox Defaults
In typical setups, procedures run in a Docker container with:
- no network access by default
- no secret environment variables inside the container
- a workspace mount that can be destroyed after the run
Configure sandbox behavior in .tactus/config.yml (or per-procedure via sidecar):
sandbox:
enabled: true
timeout: 3600
network: "none"
limits:
memory: "2g"
cpus: "2"8.2 Trust Zones (Tool Types)
Not all tools run in the same place:
- Lua tools: run in the runtime (inside the sandbox when enabled)
- MCP tools: subprocesses of the runtime (same trust zone unless isolated externally)
- Python plugin tools: loaded by the runtime (same trust zone)
- Brokered host tools: run outside the sandbox (best place for secrets)
8.3 Secrets: Where to Put Them
Practical guidance:
- keep API keys in host-side config / environment
- avoid passing secrets into the runtime container
- prefer brokered host tools for secret-bearing operations
local r = Host.call("host.ping", {value = 1})8.4 Sidecar Files Are Trusted
{procedure}.tac.yml can include file paths and command configuration (e.g., MCP servers). Treat sidecars as trusted inputs.
8.5 Volume Mount Trust Boundaries
Default behavior: Current directory mounted to /workspace:rw
Trust model: - .tac files: Sandboxed Lua code (safe for untrusted sources) - .yml files: Trusted configuration (volume mounts, network, etc.) - Volume mounts expose host filesystem to container
Safety with default mount: - Container isolation limits access to mounted directory only - Git provides version control and rollback - Use :ro (read-only) when possible for external data
Disable for untrusted procedures:
sandbox:
mount_current_dir: false8.6 Quick Checklist
- Default to
--sandboxfor anything you wouldn’t run from a random PR - Use
toolaccess (and per-turntools = {...}) as your capability boundary - Checkpoint nondeterministic operations (
Step.checkpoint) when durability matters