docs: clarify sandbox bind mounts (#790)

This commit is contained in:
Peter Steinberger
2026-01-12 22:06:17 +00:00
parent 21405b0dfc
commit 59c8d2d17f
8 changed files with 81 additions and 11 deletions

View File

@@ -39,6 +39,14 @@ Sandboxing is controlled by `agents.defaults.sandbox.mode`:
See [Sandboxing](/gateway/sandboxing) for the full matrix (scope, workspace mounts, images).
### Bind mounts (security quick check)
- `docker.binds` *pierces* the sandbox filesystem: whatever you mount is visible inside the container with the mode you set (`:ro` or `:rw`).
- Default is read-write if you omit the mode; prefer `:ro` for source/secrets.
- `scope: "shared"` ignores per-agent binds (only global binds apply).
- Binding `/var/run/docker.sock` effectively hands host control to the sandbox; only do this intentionally.
- Workspace access (`workspaceAccess: "ro"`/`"rw"`) is independent of bind modes.
## Tool policy: which tools exist/are callable
Two layers matter:

View File

@@ -62,6 +62,41 @@ Format: `host:container:mode` (e.g., `"/home/user/source:/source:rw"`).
Global and per-agent binds are **merged** (not replaced). Under `scope: "shared"`, per-agent binds are ignored.
Example (read-only source + docker socket):
```json5
{
agents: {
defaults: {
sandbox: {
docker: {
binds: [
"/home/user/source:/source:ro",
"/var/run/docker.sock:/var/run/docker.sock"
]
}
}
},
list: [
{
id: "build",
sandbox: {
docker: {
binds: ["/mnt/cache:/cache:rw"]
}
}
}
]
}
}
```
Security notes:
- Binds bypass the sandbox filesystem: they expose host paths with whatever mode you set (`:ro` or `:rw`).
- Sensitive mounts (e.g., `docker.sock`, secrets, SSH keys) should be `:ro` unless absolutely required.
- Combine with `workspaceAccess: "ro"` if you only need read access to the workspace; bind modes stay independent.
- See [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) for how binds interact with tool policy and elevated exec.
## Images + setup
Default image: `clawdbot-sandbox:bookworm-slim`

View File

@@ -209,6 +209,10 @@ ClawdHub installs into `./skills` under your current directory; Clawdbot treats
Yes. See [Sandboxing](/gateway/sandboxing). For Docker-specific setup (full gateway in Docker or sandbox images), see [Docker](/install/docker).
### How do I bind a host folder into the sandbox?
Set `agents.defaults.sandbox.docker.binds` to `["host:path:mode"]` (e.g., `"/home/user/src:/src:ro"`). Global + per-agent binds merge; per-agent binds are ignored when `scope: "shared"`. Use `:ro` for anything sensitive and remember binds bypass the sandbox filesystem walls. See [Sandboxing](/gateway/sandboxing#custom-bind-mounts) and [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated#bind-mounts-security-quick-check) for examples and safety notes.
### How does memory work?
Clawdbot memory is just Markdown files in the agent workspace:

View File

@@ -1,6 +1,6 @@
import type { NativeCommandsSetting } from "./types.js";
import { normalizeProviderId } from "../providers/registry.js";
import type { ProviderId } from "../providers/plugins/types.js";
import { normalizeProviderId } from "../providers/registry.js";
import type { NativeCommandsSetting } from "./types.js";
function resolveAutoDefault(providerId?: ProviderId): boolean {
const id = normalizeProviderId(providerId);

View File

@@ -14,8 +14,11 @@ import {
type User,
} from "@buape/carbon";
import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
import type { APIAttachment } from "discord-api-types/v10";
import { ApplicationCommandOptionType, Routes } from "discord-api-types/v10";
import {
type APIAttachment,
ApplicationCommandOptionType,
Routes,
} from "discord-api-types/v10";
import {
resolveAckReaction,
@@ -49,12 +52,12 @@ import {
} from "../auto-reply/reply/reply-dispatcher.js";
import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import {
isNativeCommandsExplicitlyDisabled,
resolveNativeCommandsEnabled,
} from "../config/commands.js";
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { formatDurationSeconds } from "../infra/format-duration.js";

View File

@@ -109,7 +109,17 @@ describe("voice-call plugin", () => {
it("tool get_status returns json payload", async () => {
const { tools } = setup({ provider: "mock" });
const tool = tools[0] as { execute: (id: string, params: unknown) => any };
type VoiceTool = {
execute: (
id: string,
params: unknown,
) =>
| Promise<{ details: Record<string, unknown> }>
| {
details: Record<string, unknown>;
};
};
const tool = tools[0] as VoiceTool;
const result = await tool.execute("id", {
action: "get_status",
callId: "call-1",
@@ -119,7 +129,17 @@ describe("voice-call plugin", () => {
it("legacy tool status without sid returns error payload", async () => {
const { tools } = setup({ provider: "mock" });
const tool = tools[0] as { execute: (id: string, params: unknown) => any };
type VoiceTool = {
execute: (
id: string,
params: unknown,
) =>
| Promise<{ details: Record<string, unknown> }>
| {
details: Record<string, unknown>;
};
};
const tool = tools[0] as VoiceTool;
const result = await tool.execute("id", { mode: "status" });
expect(String(result.details.error)).toContain("sid required");
});

View File

@@ -38,13 +38,13 @@ import { createReplyDispatcherWithTyping } from "../auto-reply/reply/reply-dispa
import { getReplyFromConfig } from "../auto-reply/reply.js";
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import { resolveNativeCommandsEnabled } from "../config/commands.js";
import type {
ClawdbotConfig,
SlackReactionNotificationMode,
SlackSlashCommandConfig,
} from "../config/config.js";
import { loadConfig } from "../config/config.js";
import { resolveNativeCommandsEnabled } from "../config/commands.js";
import {
resolveSessionKey,
resolveStorePath,

View File

@@ -33,12 +33,12 @@ import {
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import {
isNativeCommandsExplicitlyDisabled,
resolveNativeCommandsEnabled,
} from "../config/commands.js";
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import {
resolveProviderGroupPolicy,
resolveProviderGroupRequireMention,