package sandbox_test import ( "encoding/json" "os" "path/filepath" "testing" "github.com/VictorVargas/rony-llm-agent/pkg/llm" "github.com/VictorVargas/rony-llm-agent/pkg/tools" "github.com/VictorVargas/rony-llm-agent/pkg/tools/sandbox" ) func TestNewSandbox(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } if sb == nil { t.Fatal("expected non-nil sandbox") } } func TestValidatePath_Allowed(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } // Create a file inside the sandbox testFile := filepath.Join(dir, "test.txt") if err := os.WriteFile(testFile, []byte("hello"), 0644); err != nil { t.Fatalf("creating test file: %v", err) } call := llm.ToolCall{ Name: "read_file", Arguments: json.RawMessage(`{"path": "test.txt"}`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err != nil { t.Fatalf("expected no error, got: %v", err) } } func TestValidatePath_EscapesSandbox(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } call := llm.ToolCall{ Name: "read_file", Arguments: json.RawMessage(`{"path": "/etc/passwd"}`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err == nil { t.Fatal("expected sandbox violation error") } } func TestValidatePath_SymlinkEscape(t *testing.T) { dir := t.TempDir() // Create a symlink that escapes the sandbox escapeDir := t.TempDir() symlinkPath := filepath.Join(dir, "link") if err := os.Symlink(escapeDir, symlinkPath); err != nil { t.Fatalf("creating symlink: %v", err) } sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } // The sandbox should allow symlinks inside the root call := llm.ToolCall{ Name: "read_file", Arguments: json.RawMessage(`{"path": "link"}`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err != nil { t.Fatalf("expected symlink to be allowed (path is inside sandbox), got: %v", err) } } func TestValidatePath_NonExistentFile(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } // Non-existent file inside sandbox should be allowed call := llm.ToolCall{ Name: "read_file", Arguments: json.RawMessage(`{"path": "nonexistent.txt"}`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err != nil { t.Fatalf("expected no error for non-existent file, got: %v", err) } } func TestValidatePath_InvalidJSON(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } call := llm.ToolCall{ Name: "read_file", Arguments: json.RawMessage(`not json`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err == nil { t.Fatal("expected error for invalid JSON") } } func TestValidatePath_NoPathsInArgs(t *testing.T) { dir := t.TempDir() sb, err := sandbox.NewSandbox(dir) if err != nil { t.Fatalf("unexpected error: %v", err) } // Arguments with no paths should not cause errors call := llm.ToolCall{ Name: "math_add", Arguments: json.RawMessage(`{"a": 5, "b": 10}`), } err = sb.ValidateToolCall(tools.Tool{}, call) if err != nil { t.Fatalf("expected no error, got: %v", err) } }