# Proposal: Dockerized Bash Sandbox for Dashback Agent Tools ## 1. Overview & Motivation Dashback currently equips LLM agents with code execution capabilities via `javascript.js` running inside a Node.js `worker_threads` VM with a virtualized file system (`vfs.js`). While effective for lightweight JavaScript transformations, this approach has limitations: 1. **Language & Tooling Constraints:** Agents are restricted to JavaScript. They cannot run shell scripts, Python, `jq`, `ripgrep`, `git`, or standard command-line tools. 2. **Isolation Boundaries:** Worker threads share host process memory and kernel resources. A native or non-VM shell tool running directly on the host would pose severe security and privilege escalation risks. 3. **Reproducibility:** A containerized environment guarantees identical toolchain versions and execution environments across development and production deployments. This proposal specifies an isolated, container-based **Bash Tool** (`bash.js`) powered by a lightweight Docker image with sandboxed access to the company's virtual file system (VFS). --- ## 2. Sandboxing Architecture ``` ┌────────────────────────────────────────┐ │ dashback Host │ │ │ │ 1. prepareVfs(googleAuthId) │ │ └─ hydrates DB -> data/vfs// │ └──────────────────┬─────────────────────┘ │ Mounts host path│ (scoped to company) data/vfs/ │ as /home/agent/workspace ▼ ┌────────────────────────────────────────────────────────────────────────┐ │ Docker Sandbox Container (`dashback-sandbox`) │ │ │ │ User: agent (UID 1000, non-root) │ │ Rootfs: Read-Only (no modifying /usr, /bin, /lib) │ │ Capabilities: None (--cap-drop=ALL) │ │ Privilege Escalation: Blocked (no-new-privileges) │ │ Tmpfs: /tmp (64M, nosuid), /home/agent/.cache (64M, nosuid) │ │ Limits: 512MB RAM, 1.0 CPU, 100 PIDs (fork-bomb proof) │ │ Network: None (--network none) or Internal Bridge │ │ │ │ /home/agent/workspace/ <─ (Shared VFS Mount) │ │ ├── package.json │ │ ├── script.js │ │ └── output.csv │ └────────────────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────┴─────────────────────┐ │ 3. syncVfsToDb(googleAuthId) │ │ └─ persists created/updated files │ │ back to modelvfs table │ │ 4. Return stdout/stderr to LLM │ └────────────────────────────────────────┘ ``` --- ## 3. Docker Image Specification ### Location `docker/sandbox/Dockerfile` ### `Dockerfile` ```dockerfile FROM node:22-bookworm-slim # Prevent interactive prompts during package install ENV DEBIAN_FRONTEND=noninteractive # Install essential CLI tools and dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ bash \ ca-certificates \ curl \ git \ jq \ ripgrep \ procps \ tree \ tar \ unzip \ gzip \ python3 \ bc \ sed \ gawk \ && rm -rf /var/lib/apt/lists/* # Set up unprivileged user ENV USER=agent ENV HOME=/home/agent ENV WORKSPACE=/home/agent/workspace RUN groupadd -g 1000 ${USER} && \ useradd -u 1000 -g ${USER} -d ${HOME} -s /bin/bash -m ${USER} && \ mkdir -p ${WORKSPACE} && \ chown -R ${USER}:${USER} ${HOME} # Switch context to agent WORKDIR ${WORKSPACE} USER ${USER} # Provide local node bin in PATH ENV PATH="${WORKSPACE}/node_modules/.bin:${HOME}/.local/bin:${PATH}" ENTRYPOINT ["/bin/bash"] CMD ["-l"] ``` --- ## 4. Security & Runtime Constraints Containers must never be invoked with default privileged parameters. The runner applies strict Linux namespace and cgroup boundaries: | Flag | Rationale | | :--- | :--- | | `--user 1000:1000` | Executes strictly as unprivileged user `agent`. Matches UID with host file permissions. | | `--cap-drop=ALL` | Drops all Linux capabilities (prevents raw network creation, kernel module loading, chroot, ptrace). | | `--security-opt=no-new-privileges:true` | Disallows gaining privileges via `setuid`/`setgid` binaries. | | `--read-only` | The root filesystem is mounted read-only. The container cannot modify OS binaries, libraries, or configs. | | `--tmpfs /tmp:rw,nosuid,nodev,size=64m` | Provides a volatile, memory-backed scratch space with `nosuid` and `nodev`. | | `--tmpfs /home/agent/.cache:rw,nosuid,size=64m` | Allows tools like npm or python to write temporary caches without disk persistence. | | `--pids-limit 100` | Protects host from fork bombs (`:(){ :|:& };:`). | | `--memory 512m` | Caps RAM usage; terminates process if memory leaks or runaway allocations occur. | | `--cpus 1.0` | Restricts CPU usage to a single core equivalent to avoid CPU starvation. | | `--network none` | Prevents unauthorized external data exfiltration by default (can be bridged if specifically required). | | `-v :/home/agent/workspace:rw` | Restricts file access solely to the company's workspace directory. | --- ## 5. Integration with Dashback ### 5.1 VFS Lifecycle Each execution turn synchronizes through the existing Dashback VFS pipeline: 1. **Preparation:** ```javascript const vfsRoot = await resolveVfsRoot(googleAuthId); await prepareVfs(googleAuthId); // Hydrates files from `modelvfs` table ``` 2. **Mounting:** The directory `data/vfs/` on the host is mounted to `/home/agent/workspace` inside the container. 3. **Execution:** Command is executed with a hard timeout (e.g. default 30s, max 120s). 4. **Synchronization:** ```javascript await syncVfsToDb(googleAuthId); // Syncs any new or edited files back to `modelvfs` ``` ### 5.2 Tool Implementation (`src/tools/bash.js`) ```javascript import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; import path from 'node:path'; import { defineTool, ToolError } from 'libllmorbiter'; import { resolveVfsRoot, prepareVfs, syncVfsToDb } from './vfs.js'; const execFileAsync = promisify(execFile); const IMAGE_NAME = 'dashback-sandbox:latest'; const DEFAULT_TIMEOUT_MS = 30_000; const MAX_BUFFER = 1024 * 1024; // 1 MB output cap export function createBashTool(googleAuthId) { return defineTool({ name: 'bash_execute', description: 'Execute a bash command in a secure sandboxed Linux environment with Node.js, Python, git, jq, and ripgrep. Working directory is the company workspace.', parameters: { type: 'object', properties: { command: { type: 'string', description: 'The bash command line to execute (e.g., "node script.js", "jq . data.json", "rg pattern").', }, timeout_ms: { type: 'integer', description: 'Execution timeout in milliseconds (default 30000, max 120000).', }, }, required: ['command'], additionalProperties: false, }, invoke: async ({ command, timeout_ms = DEFAULT_TIMEOUT_MS }) => { const vfsRoot = await resolveVfsRoot(googleAuthId); await prepareVfs(googleAuthId); const effectiveTimeout = Math.min(Math.max(timeout_ms, 1000), 120000); const dockerArgs = [ 'run', '--rm', '-i', '--user', '1000:1000', '--cap-drop=ALL', '--security-opt=no-new-privileges:true', '--read-only', '--tmpfs', '/tmp:rw,nosuid,nodev,size=64m', '--tmpfs', '/home/agent/.cache:rw,nosuid,size=64m', '--pids-limit', '100', '--memory', '512m', '--cpus', '1.0', '--network', 'none', '-v', `${path.resolve(vfsRoot)}:/home/agent/workspace:rw`, IMAGE_NAME, '-c', command, ]; try { const { stdout, stderr } = await execFileAsync('docker', dockerArgs, { timeout: effectiveTimeout, maxBuffer: MAX_BUFFER, }); await syncVfsToDb(googleAuthId); return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0, }; } catch (err) { // Ensure partial file modifications are synced even on exit error try { await syncVfsToDb(googleAuthId); } catch (_) {} if (err.killed || err.signal === 'SIGTERM') { throw new ToolError(`Execution timed out after ${effectiveTimeout}ms.`); } return { stdout: err.stdout ? err.stdout.trim() : '', stderr: err.stderr ? err.stderr.trim() : err.message, exitCode: err.code ?? 1, }; } }, }); } ``` --- ## 6. Execution Models: Ephemeral vs. Persistent Daemon | Consideration | Ephemeral (`docker run --rm`) | Persistent Container (`docker exec`) | | :--- | :--- | :--- | | **Startup Overhead** | ~200–400ms per command | ~20–50ms per command | | **State Cleanliness** | Completely fresh OS environment every call | Processes/background jobs remain running | | **Lifecycle Complexity** | None. Docker handles lifecycle and cleanup | Requires container pool management and reaper | | **Failure Recovery** | Automatic | If container crashes, must be recreated | | **Recommendation** | **Adopt for Phase 1** | Evaluate if high execution frequency warrants it | --- ## 7. Implementation Roadmap 1. **Phase 1: Docker Setup & Scripting** - Add `docker/sandbox/Dockerfile`. - Add build script: `npm run docker:sandbox:build` to `package.json`. - Verify image build and base tools (`node -v`, `python3 --version`, `rg --version`, `jq --version`). 2. **Phase 2: Bash Tool Implementation** - Create `src/tools/bash.js` conforming to `defineTool`. - Integrate with `prepareVfs` and `syncVfsToDb`. - Add unit tests validating command execution, timeouts, and file synchronization. 3. **Phase 3: Integration into Tool Registries** - Register `createBashTool` in the agent execution registry alongside existing tools (`javascript.js`, `read-file.js`, `patch.js`). 4. **Phase 4: Optional Network & API Bridge** - If commands inside the container need to invoke Dashback endpoints (`http://host.docker.internal:4208/api`), introduce an opt-in network mode using Docker custom bridge networks with short-lived JWT tokens.