summaryrefslogtreecommitdiff
path: root/internal/cli
diff options
context:
space:
mode:
authorClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 09:21:32 +0000
committerClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 09:21:32 +0000
commit767ddade57f189827fa956ff8081ca47404a4798 (patch)
treee2b44d0e943e1cc5cf2b11e0b826902a24b63802 /internal/cli
parent81ae7d598e7ec6afe8afb5bbdbfe807042298559 (diff)
feat(sandbox): add DockerSandbox + pre-tool-use guardrail hooks (Phase 3)
Gives native-API-driven agents (currently just the Phase 2 Anthropic adapter) real container isolation, decoupled from model invocation -- the model call happens in the Go process via provider.Provider, only tool execution happens in the container, unlike the CLI-subprocess ContainerRunner (left completely untouched) where the claude/gemini CLI runs inside the container. - internal/sandbox/dockersandbox.go: Sandbox via a long-lived `docker run -d ... sleep infinity` container (started once per execution, not per tool call), host-side git clone + bind-mount matching ContainerRunner's existing pattern, docker exec for read/write/bash/glob. Reuses images/agent-base (claudomator-agent:latest) rather than standing up a second image. WorkDir()/resume persists the host bind-mount directory (matching HostSandbox's contract); a resumed sandbox lazily starts a fresh container against that directory rather than trying to reattach to a possibly-gone one. - internal/sandbox/guard.go, hooks.go: Hook interface (CheckBash/CheckWrite), Guarded wrapper, DenylistBashHook (rm -rf /, force-push, curl|sh, sudo, chmod 777, dd if=) and ProtectedPathHook (.git/**, .env*, credentials/, .github/workflows/**). A rejection returns *RejectionError, which agentloop/tools.go now recognizes and feeds back to the model as a normal (non-fatal) tool-error result instead of aborting the run. - NativeRunner wraps whichever Sandbox it builds (Host or Docker) in Guarded{Hooks: DefaultHooks()} uniformly. The "anthropic" runner now uses DockerSandbox; "local" stays on HostSandbox by design (local models are the harness's more-trusted, lower-stakes-to-run tier). Docker is not installed in this dev environment (no docker/podman/containerd on PATH), so DockerSandbox's real container-lifecycle behavior is verified via mocked-command unit tests only -- go test -race ./... passes throughout, with the two real-daemon integration tests gated behind a dockerAvailable(t) check and skipping here. Live verification against an actual Docker host is a follow-up before relying on the "anthropic" agent type in production. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
Diffstat (limited to 'internal/cli')
-rw-r--r--internal/cli/run.go4
-rw-r--r--internal/cli/serve.go11
2 files changed, 14 insertions, 1 deletions
diff --git a/internal/cli/run.go b/internal/cli/run.go
index a5a6419..497ba6b 100644
--- a/internal/cli/run.go
+++ b/internal/cli/run.go
@@ -128,6 +128,10 @@ func runTasks(file string, parallel int, dryRun bool) error {
Logger: logger,
LogDir: cfg.LogDir,
DefaultModel: pc.DefaultModel,
+ // See internal/cli/serve.go: native cloud providers get
+ // sandbox.DockerSandbox rather than the weaker HostSandbox.
+ SandboxKind: "docker",
+ SandboxImage: cfg.SandboxImage,
}
}
diff --git a/internal/cli/serve.go b/internal/cli/serve.go
index d9bf609..8b6cc6a 100644
--- a/internal/cli/serve.go
+++ b/internal/cli/serve.go
@@ -147,6 +147,10 @@ func serve(addr, basePath string) error {
LogDir: cfg.LogDir,
DefaultTemperature: cfg.LocalModel.DefaultTemperature,
DefaultModel: cfg.LocalModel.Model,
+ // SandboxKind left at its zero value ("host" / sandbox.HostSandbox).
+ // Local models are presumed more trusted/lower-stakes than paid
+ // cloud providers, so they stay on the weaker but zero-overhead
+ // host sandbox — see NativeRunner.SandboxKind doc comment.
}
logger.Info("local runner registered", "endpoint", cfg.LocalModel.Endpoint, "model", cfg.LocalModel.Model)
}
@@ -161,8 +165,13 @@ func serve(addr, basePath string) error {
Logger: logger,
LogDir: cfg.LogDir,
DefaultModel: pc.DefaultModel,
+ // Native cloud providers get real container isolation
+ // (sandbox.DockerSandbox) rather than HostSandbox's host-side
+ // path-prefix confinement.
+ SandboxKind: "docker",
+ SandboxImage: cfg.SandboxImage,
}
- logger.Info("anthropic runner registered", "default_model", pc.DefaultModel)
+ logger.Info("anthropic runner registered", "default_model", pc.DefaultModel, "sandbox", "docker")
}
pool := executor.NewPool(cfg.MaxConcurrent, runners, store, logger)