package client import ( "encoding/base64" "strings" "sync" "testing" "crypto-miner-agent/deploy" ) type commandResult struct { action string success bool message string } func captureCommandResult(t *testing.T, c *AgentClient) (done <-chan struct{}, result *commandResult) { t.Helper() ch := make(chan struct{}) var mu sync.Mutex out := &commandResult{} c.commandResultHook = func(action string, success bool, message string) { mu.Lock() out.action = action out.success = success out.message = message mu.Unlock() close(ch) } t.Cleanup(func() { c.commandResultHook = nil }) return ch, out } func TestUploadCommandRejectsPathTraversal(t *testing.T) { data := base64.StdEncoding.EncodeToString([]byte("payload")) cases := []struct { name string path string }{ {name: "unix_relative", path: "../../etc/passwd"}, {name: "windows_relative", path: `..\..\Windows\System32\config\sam`}, {name: "embedded_traversal", path: "uploads/../../outside.txt"}, {name: "absolute_with_traversal", path: "/var/log/../../etc/shadow"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := deploy.ResolveRemotePath(tc.path) if err == nil { t.Fatalf("ResolveRemotePath(%q) should reject traversal", tc.path) } if !strings.Contains(err.Error(), "path traversal") { t.Fatalf("ResolveRemotePath(%q) error = %q, want path traversal rejection", tc.path, err.Error()) } c := newTestClient(t) done, got := captureCommandResult(t, c) c.handleCommand("upload", 0, "", tc.path, data, "") <-done if got.action != "upload" { t.Fatalf("action = %q, want upload", got.action) } if got.success { t.Fatalf("upload with %q should fail (success=true, message=%q)", tc.path, got.message) } if !strings.Contains(got.message, "path traversal") { t.Fatalf("message = %q, want path traversal error from ResolveRemotePath", got.message) } }) } } func TestDownloadCommandRejectsPathTraversal(t *testing.T) { cases := []struct { name string path string }{ {name: "unix_relative", path: "../../etc/passwd"}, {name: "windows_relative", path: `..\..\Windows\System32\config\sam`}, {name: "embedded_traversal", path: "uploads/../../outside.txt"}, {name: "absolute_with_traversal", path: "/var/log/../../etc/shadow"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := deploy.ResolveRemotePath(tc.path) if err == nil { t.Fatalf("ResolveRemotePath(%q) should reject traversal", tc.path) } if !strings.Contains(err.Error(), "path traversal") { t.Fatalf("ResolveRemotePath(%q) error = %q, want path traversal rejection", tc.path, err.Error()) } c := newTestClient(t) done, got := captureCommandResult(t, c) c.handleCommand("download", 0, "", tc.path, "", "") <-done if got.action != "download" { t.Fatalf("action = %q, want download", got.action) } if got.success { t.Fatalf("download with %q should fail (success=true, message=%q)", tc.path, got.message) } if !strings.Contains(got.message, "path traversal") { t.Fatalf("message = %q, want path traversal error from ResolveRemotePath", got.message) } }) } } func TestUploadCommandAcceptsSafePath(t *testing.T) { dir := t.TempDir() dest := dir + "/notes.txt" data := base64.StdEncoding.EncodeToString([]byte("ok")) c := newTestClient(t) done, got := captureCommandResult(t, c) c.handleCommand("upload", 0, "", dest, data, "") <-done if !got.success { t.Fatalf("safe upload failed: %s", got.message) } if got.action != "upload" { t.Fatalf("action = %q, want upload", got.action) } }