Skip to main content

Sandbox & Security

Workflow steps that execute arbitrary code or shell commands — the code node and the agent's terminal/write_file/read_file/list_files tools — never run on the API server or worker host. They run inside a dedicated, isolated Docker container per execution, managed by internal/sandbox (sandbox.Manager).

Lifecycle

  1. Lazy creation — the first time an execution needs the sandbox, ensureSandbox (in internal/tools/terminal.go) calls Manager.Create, which starts a container from the goagents-sandbox:latest image with sleep infinity as its entrypoint and a working directory of /workspace.
  2. Reuse within an execution — the container is tracked in-memory by execution ID (sandboxes map[string]string), so every code/terminal/file-tool call within the same workflow execution reuses the same container and shares its /workspace filesystem — useful for multi-step pipelines (e.g. download a file, then process it, then read the result).
  3. TeardownDestroyExecution stops and force-removes the container once the workflow completes (CompleteWorkflowActivity). CleanupStale runs on worker startup and removes any containers left over from a previous crash, identified by the goagents.execution_id label.

Isolation

Each container is created with:

SettingValueEffect
Imagegoagents-sandbox:latestA purpose-built image (build it via the sandbox Makefile target) — not the worker's own filesystem.
Memory512 MBHard memory limit; OOM kills the container, not the worker process.
CPU quota100000 (1 full CPU)Caps CPU usage per container.
PIDs limit256Prevents fork-bombs from exhausting host resources.
Network modebridgeOutbound internet access (for pip install, curl, etc.) but no access to the host network namespace.
Labelsgoagents.execution_id, goagents.tenant_idUsed for tracking and stale-container cleanup; also makes containers attributable per tenant for auditing.

:::info Per-plan limits internal/billing/plans.go defines sandbox CPU/memory ceilings per plan tier — Enterprise deployments can raise the per-container limits beyond the defaults above. :::

Command execution

Manager.Exec / ExecStreaming run bash -c "<command>" inside the container via the Docker exec API:

  • Timeout: every command has a timeout (default 30s, capped at 300s/5 minutes). On timeout, the partial output is returned with a [TIMEOUT: command exceeded time limit] marker rather than hanging the workflow indefinitely.
  • Output limit: stdout and stderr are each capped at 1 MB (MaxOutputBytes) — additional output is silently discarded so a runaway command can't blow up step output storage.
  • Streaming: ExecStreaming calls a progressFn callback for each output chunk, used to stream live terminal output to the UI via SSE during long-running agent/code steps.

File access

WriteFile/ReadFile/ListFiles operate purely through bash -c commands (cat > file << EOF, cat file, find /workspace -type f | head -100) inside the container — there's no direct host bind-mount, so the only way in or out of the sandbox filesystem is through these tool calls or the workspace export step below.

Workspace export

When an execution finishes, ExportWorkspace uses docker cp (CopyFromContainer) to tar up /workspace, then extracts each file (up to 50 MB each) and uploads it to workspace storage (S3/MinIO), making files created by code/terminal steps (reports, generated images, processed CSVs, etc.) downloadable from the execution detail page. See Executions & Monitoring — Workspace files.

Other security measures

  • Secrets & OAuth tokens are encrypted at rest with AES-256-GCM, keyed from JWT_SECRET (see Secrets & Credentials) — they're decrypted only at the point of use and are redacted from execution step input/output before being returned over the API.
  • Temporal payload encryption — set TEMPORAL_ENCRYPTION_KEY to encrypt step inputs/outputs as stored by Temporal (see Temporal Execution Model).
  • Tenant isolation — every database query is scoped by tenant_id; see Multi-Tenancy & Data Model.
  • Public routes (/webhooks/{id}, /run/{id}) are gated by either an unguessable workflow UUID, a workflow-scoped API key, or both.

Next