Files
dashback/SANDBOX.md
2026-10-14 13:44:53 +02:00

12 KiB

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/<cid>/  │
                  └──────────────────┬─────────────────────┘
                                     │
                     Mounts host path│ (scoped to company)
                     data/vfs/<cid>  │ 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

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 <vfs_root>:/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:
    const vfsRoot = await resolveVfsRoot(googleAuthId);
    await prepareVfs(googleAuthId); // Hydrates files from `modelvfs` table
    
  2. Mounting: The directory data/vfs/<companyId> 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:
    await syncVfsToDb(googleAuthId); // Syncs any new or edited files back to `modelvfs`
    

5.2 Tool Implementation (src/tools/bash.js)

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.