package client import ( "encoding/base64" "fmt" "os" "path/filepath" "strings" "testing" ) func TestContainsPathTraversal(t *testing.T) { cases := []struct { path string want bool }{ {"", false}, {"/home/user/docs", false}, {"C:\\Users\\alice", false}, {"~/Downloads", false}, {"../etc/passwd", true}, {"/home/user/../../etc", true}, {"foo/../bar", true}, } for _, tc := range cases { if got := containsPathTraversal(tc.path); got != tc.want { t.Errorf("containsPathTraversal(%q) = %v, want %v", tc.path, got, tc.want) } } } func TestResolveListDirPathRejectsTraversal(t *testing.T) { _, err := resolveListDirPath("../outside") if err == nil { t.Fatal("expected traversal error") } } func TestResolveListDirPathEmptyUsesHome(t *testing.T) { home, err := os.UserHomeDir() if err != nil { t.Skip("no home dir") } for _, input := range []string{"", "~", "@home"} { got, err := resolveListDirPath(input) if err != nil { t.Fatalf("resolveListDirPath(%q): %v", input, err) } if got != filepath.Clean(home) { t.Fatalf("resolveListDirPath(%q) = %q, want %q", input, got, home) } } } func TestIsBlockedDeletePath(t *testing.T) { if !isBlockedDeletePath(`C:\Windows\System32\kernel32.dll`) { t.Fatal("expected Windows system path blocked") } if !isBlockedDeletePath(`/usr/bin/bash`) { t.Fatal("expected /usr blocked") } home, err := os.UserHomeDir() if err != nil { t.Skip("no home") } if !isBlockedDeletePath(home) { t.Fatal("expected home root blocked") } tmp := filepath.Join(home, "test_delete_guard.txt") if isBlockedDeletePath(tmp) { t.Fatalf("expected user file path allowed: %s", tmp) } } func TestReadFileCommandRejectsOversize(t *testing.T) { dir := t.TempDir() big := filepath.Join(dir, "huge.bin") if err := os.WriteFile(big, make([]byte, maxReadFileBytes+1), 0o644); err != nil { t.Fatal(err) } c := newTestClient(t) var gotOK bool var gotMsg string c.commandResultHook = func(_ string, success bool, message string) { gotOK = success gotMsg = message } c.handleCommand("read_file", 0, "", big, "", "") if gotOK { t.Fatal("oversize read_file should fail") } if !strings.Contains(gotMsg, "file too large") { t.Fatalf("message=%q", gotMsg) } } func TestReadFileCommandAcceptsWithinCap(t *testing.T) { dir := t.TempDir() small := filepath.Join(dir, "small.txt") want := "config-value" if err := os.WriteFile(small, []byte(want), 0o644); err != nil { t.Fatal(err) } c := newTestClient(t) var gotOK bool var gotMsg string c.commandResultHook = func(_ string, success bool, message string) { gotOK = success gotMsg = message } c.handleCommand("read_file", 0, "", small, "", "") if !gotOK || gotMsg != want { t.Fatalf("ok=%v msg=%q want %q", gotOK, gotMsg, want) } } func TestAgentConfigFileUploadReadRoundTrip(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "agent-config.json") payload := `{"server_url":"http://127.0.0.1:8989","worker_name":"test-node"}` encoded := base64.StdEncoding.EncodeToString([]byte(payload)) c := newTestClient(t) uploadDone := make(chan struct{}) c.commandResultHook = func(action string, success bool, message string) { if action == "upload" { if !success { t.Fatalf("upload failed: %s", message) } close(uploadDone) } } c.handleCommand("upload", 0, "", cfgPath, encoded, "") <-uploadDone readDone := make(chan struct{}) var readBody string c.commandResultHook = func(action string, success bool, message string) { if action == "read_file" { if !success { t.Fatalf("read_file failed: %s", message) } readBody = message close(readDone) } } c.handleCommand("read_file", 0, "", cfgPath, "", "") <-readDone if readBody != payload { t.Fatalf("round-trip mismatch:\nwant %q\ngot %q", payload, readBody) } } func TestReadFileCommandRejectsTraversal(t *testing.T) { c := newTestClient(t) var gotMsg string c.commandResultHook = func(_ string, success bool, message string) { if !success { gotMsg = message } } c.handleCommand("read_file", 0, "", "../../etc/passwd", "", "") if !strings.Contains(gotMsg, "path traversal") { t.Fatalf("message=%q", gotMsg) } } func TestReadDirectoryEntriesCapsCount(t *testing.T) { dir := t.TempDir() for i := 0; i < maxListDirEntries+10; i++ { name := filepath.Join(dir, fmt.Sprintf("file_%d.txt", i)) if err := os.WriteFile(name, []byte("x"), 0o644); err != nil { t.Fatal(err) } } entries, err := readDirectoryEntries(dir) if err != nil { t.Fatal(err) } if len(entries) != maxListDirEntries { t.Fatalf("entries len = %d, want cap %d", len(entries), maxListDirEntries) } }