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

265 lines
12 KiB
Markdown

# 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`
```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:**
```javascript
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:**
```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.