diff --git a/Hub/internal/models/models.go b/Hub/internal/models/models.go index 58751cc..50c089c 100644 --- a/Hub/internal/models/models.go +++ b/Hub/internal/models/models.go @@ -1,5 +1,7 @@ package models +import "encoding/json" + // HubVersion defines the backend protocol version for resolving compatible sensors and authenticating Agents. const HubVersion = "2.0.0" @@ -34,7 +36,7 @@ type Event struct { Severity string `json:"severity"` Source string `json:"source"` Target string `json:"target"` - Details map[string]interface{} `json:"details"` + Details json.RawMessage `json:"details"` IsRead bool `json:"isRead"` IsArchived bool `json:"isArchived"` Count int `json:"count"` diff --git a/Hub/internal/services/event/service.go b/Hub/internal/services/event/service.go index d499dfb..f1ca25c 100644 --- a/Hub/internal/services/event/service.go +++ b/Hub/internal/services/event/service.go @@ -2,7 +2,6 @@ package event import ( "context" - "encoding/json" "fmt" "log" "strconv" @@ -79,10 +78,13 @@ func (s *Service) ProcessEvent(e *models.Event, nodeID string) error { } } - detailsJSON, _ := json.Marshal(e.Details) + detailsStr := "{}" + if len(e.Details) > 0 { + detailsStr = string(e.Details) + } e.NodeID = nodeID - lastInsertID, err := s.store.InsertEvent(e, nowStr, string(detailsJSON)) + lastInsertID, err := s.store.InsertEvent(e, nowStr, detailsStr) if err != nil { if strings.Contains(err.Error(), "FOREIGN KEY") { return fmt.Errorf("sensor_not_registered") diff --git a/Hub/internal/services/siem/service.go b/Hub/internal/services/siem/service.go index 537bdb4..4e4f7fe 100644 --- a/Hub/internal/services/siem/service.go +++ b/Hub/internal/services/siem/service.go @@ -2,7 +2,6 @@ package siem import ( "context" - "encoding/json" "fmt" "log" "math" @@ -317,8 +316,8 @@ func (s *Service) formatSyslog(event models.Event) string { timestamp := t.UTC().Format(time.RFC3339Nano) - detailsJSON, err := json.Marshal(event.Details) - if err != nil { + detailsJSON := event.Details + if len(detailsJSON) == 0 { detailsJSON = []byte("{}") } diff --git a/Hub/internal/services/siem/service_test.go b/Hub/internal/services/siem/service_test.go index 3db2124..20ca1df 100644 --- a/Hub/internal/services/siem/service_test.go +++ b/Hub/internal/services/siem/service_test.go @@ -3,6 +3,7 @@ package siem import ( "bufio" "context" + "encoding/json" "net" "regexp" "testing" @@ -41,7 +42,7 @@ func TestSyslogForwardingRFC5424(t *testing.T) { Source: "192.168.1.100", Target: "10.0.0.5", SensorID: "sensor-01", - Details: map[string]interface{}{"file": "/etc/shadow"}, + Details: json.RawMessage(`{"file": "/etc/shadow"}`), } // Send the event diff --git a/Hub/internal/store/events.go b/Hub/internal/store/events.go index 9f6330c..4a3c1c6 100644 --- a/Hub/internal/store/events.go +++ b/Hub/internal/store/events.go @@ -77,7 +77,11 @@ func (s *SQLiteStore) GetEvents(isArchived int, nodeID string, sensorID string) e.IsRead = isReadInt == 1 e.IsArchived = isArchivedInt == 1 - json.Unmarshal([]byte(detailsStr), &e.Details) + if detailsStr != "" { + e.Details = json.RawMessage(detailsStr) + } else { + e.Details = json.RawMessage("{}") + } events = append(events, e) } diff --git a/SDKs/go-honeywire/sdk.go b/SDKs/go-honeywire/sdk.go index ababe51..945d1f0 100644 --- a/SDKs/go-honeywire/sdk.go +++ b/SDKs/go-honeywire/sdk.go @@ -148,7 +148,7 @@ type Sensor struct { testTrigger string testSource string testTarget string - testDetails map[string]any + testDetails EventDetails } func NewSensor() (*Sensor, error) { @@ -176,7 +176,7 @@ func NewSensor() (*Sensor, error) { } // SetTestPayload allows community sensors to define a realistic, protocol-specific mock payload -func (s *Sensor) SetTestPayload(trigger, source, target string, details map[string]any) { +func (s *Sensor) SetTestPayload(trigger, source, target string, details EventDetails) { s.testPayload = map[string]any{ "eventTrigger": trigger, "source": source, @@ -214,7 +214,7 @@ func (s *Sensor) listenForSignals() { trigger := "test_mode_synthetic_alert" source := "Wizard Live Test" target := "Mock Hub" - details := map[string]any{"test_message": "Wizard triggered a live test event firedrill."} + details := EventDetails{{"test_message", "Wizard triggered a live test event firedrill."}} if s.testTrigger != "" { trigger = s.testTrigger @@ -241,7 +241,7 @@ func (s *Sensor) RunTestMode() bool { trigger := "test_mode_synthetic_alert" source := "CI/CD Runner" target := "Mock Hub" - details := map[string]any{"test_message": "Automated CI/CD check."} + details := EventDetails{{"test_message", "Automated CI/CD check."}} if s.testTrigger != "" { trigger = s.testTrigger @@ -275,7 +275,33 @@ func (s *Sensor) RunTestMode() bool { // PIPELINE A: EVENT WORKER // ========================================== -func (s *Sensor) ReportEvent(trigger, source, target string, details map[string]any) bool { +// EventDetail represents an ordered key-value pair for event details +type EventDetail struct { + Key string + Value any +} + +// EventDetails is an ordered slice of event details that marshals to a JSON object +type EventDetails []EventDetail + +func (ed EventDetails) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + buf.WriteString("{") + for i, d := range ed { + if i > 0 { + buf.WriteString(",") + } + key, _ := json.Marshal(d.Key) + val, _ := json.Marshal(d.Value) + buf.Write(key) + buf.WriteString(":") + buf.Write(val) + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +func (s *Sensor) ReportEvent(trigger, source, target string, details EventDetails) bool { payload := map[string]any{ "contractVersion": s.hubContractVersion, "sensorId": s.SensorID, diff --git a/SDKs/go-honeywire/sdk_test.go b/SDKs/go-honeywire/sdk_test.go index 2c3f1c6..d02f849 100644 --- a/SDKs/go-honeywire/sdk_test.go +++ b/SDKs/go-honeywire/sdk_test.go @@ -264,7 +264,7 @@ func TestReportEvent_Serialization(t *testing.T) { trigger := "test_serialization" source := "192.168.1.10" target := "8.8.8.8:53" - details := map[string]any{"protocol": "dns"} + details := EventDetails{{"protocol", "dns"}} // Report the event, which places it on the channel if !s.ReportEvent(trigger, source, target, details) { @@ -287,8 +287,9 @@ func TestReportEvent_Serialization(t *testing.T) { if got, want := receivedPayload["target"], target; got != want { t.Errorf("target mismatch: got %v, want %v", got, want) } - if !reflect.DeepEqual(receivedPayload["details"], details) { - t.Errorf("details mismatch: got %v, want %v", receivedPayload["details"], details) + expectedDetails := map[string]any{"protocol": "dns"} + if !reflect.DeepEqual(receivedPayload["details"], expectedDetails) { + t.Errorf("details mismatch: got %v, want %v", receivedPayload["details"], expectedDetails) } case <-time.After(2 * time.Second): t.Fatal("Timed out waiting for server to receive event payload") diff --git a/Sensors/official/FileCanary/main.go b/Sensors/official/FileCanary/main.go index f233c72..0b35e4d 100644 --- a/Sensors/official/FileCanary/main.go +++ b/Sensors/official/FileCanary/main.go @@ -167,10 +167,10 @@ func reportFileEvent( trigger, "Local OS", filepath.Base(path), - map[string]any{ - "category": category, - "action": action, - "path": path, + sdk.EventDetails{ + {"category", category}, + {"action", action}, + {"path", path}, }, ) @@ -195,11 +195,11 @@ func main() { "decoy_file_tampered", "Wizard Firedrill", "Mock Canary File", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "category": "tamper", - "action": "Decoy file modified", - "path": "/canaries/mock_passwords.txt", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"category", "tamper"}, + {"action", "Decoy file modified"}, + {"path", "/canaries/mock_passwords.txt"}, }, ) diff --git a/Sensors/official/IcmpCanary/main.go b/Sensors/official/IcmpCanary/main.go index ddff701..2fe770b 100644 --- a/Sensors/official/IcmpCanary/main.go +++ b/Sensors/official/IcmpCanary/main.go @@ -23,12 +23,12 @@ func main() { "icmp_ping_received", "Wizard Firedrill", "ICMP Listener", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "packet_size": 64, - "icmp_id": 1337, - "icmp_seq": 1, - "action_taken": "logged", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"packet_size", 64}, + {"icmp_id", 1337}, + {"icmp_seq", 1}, + {"action_taken", "logged"}, }, ) @@ -99,11 +99,11 @@ func listenICMP(conn *icmp.PacketConn, hw *sdk.Sensor) { "icmp_ping_received", sourceIP, "ICMP Listener", - map[string]any{ - "packet_size": n, - "icmp_id": echo.ID, - "icmp_seq": echo.Seq, - "action_taken": "logged", + sdk.EventDetails{ + {"packet_size", n}, + {"icmp_id", echo.ID}, + {"icmp_seq", echo.Seq}, + {"action_taken", "logged"}, }, ) } diff --git a/Sensors/official/NetworkScanDetector/main.go b/Sensors/official/NetworkScanDetector/main.go index 5f8c82a..20b92ab 100644 --- a/Sensors/official/NetworkScanDetector/main.go +++ b/Sensors/official/NetworkScanDetector/main.go @@ -42,12 +42,12 @@ func main() { "network_scan_detected", "Wizard Firedrill", "Multiple Ports", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "ports_hit": []uint16{22, 80, 443, 3306, 8080}, - "count": 5, - "window_sec": 5.0, - "action_taken": "logged", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"ports_hit", []uint16{22, 80, 443, 3306, 8080}}, + {"count", 5}, + {"window_sec", 5.0}, + {"action_taken", "logged"}, }, ) @@ -149,11 +149,11 @@ func processHit(hw *sdk.Sensor, srcIP string, dstPort uint16) { "network_scan_detected", srcIP, "Multiple Ports", - map[string]any{ - "ports_hit": uniquePortsList, - "count": len(uniquePortsList), - "window_sec": window.Seconds(), - "action_taken": "logged", + sdk.EventDetails{ + {"ports_hit", uniquePortsList}, + {"count", len(uniquePortsList)}, + {"window_sec", window.Seconds()}, + {"action_taken", "logged"}, }, ) state.history = nil diff --git a/Sensors/official/TcpTarpit/main.go b/Sensors/official/TcpTarpit/main.go index 416683b..4bd736c 100644 --- a/Sensors/official/TcpTarpit/main.go +++ b/Sensors/official/TcpTarpit/main.go @@ -35,11 +35,11 @@ func main() { "tcp_connection", "Wizard Firedrill", "Mock Tarpit Port", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "payload": []string{"SSH-2.0-Firedrill-Test\r\n"}, - "duration_sec": 5.2, - "action_taken": "hold", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"payload", []string{"SSH-2.0-Firedrill-Test\r\n"}}, + {"duration_sec", 5.2}, + {"action_taken", "hold"}, }, ) @@ -165,10 +165,10 @@ func handleConnection(hw *sdk.Sensor, conn net.Conn, port int) { "tcp_connection", srcIP, fmt.Sprintf("Port %d", port), - map[string]any{ - "duration_sec": duration, - "payload": payload, - "action_taken": tarpitMode, + sdk.EventDetails{ + {"duration_sec", duration}, + {"payload", payload}, + {"action_taken", tarpitMode}, }, ) } diff --git a/Sensors/official/WebRouterDecoy/main.go b/Sensors/official/WebRouterDecoy/main.go index 306cfd6..532fa5e 100644 --- a/Sensors/official/WebRouterDecoy/main.go +++ b/Sensors/official/WebRouterDecoy/main.go @@ -251,12 +251,12 @@ func main() { "web_login_attempt", "Wizard Firedrill", "Web Interface", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "user_agent": "HoneyWire-Firedrill/1.0", - "attempted_username": "admin", - "attempted_password": "password123", - "action_taken": "logged", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"user_agent", "HoneyWire-Firedrill/1.0"}, + {"attempted_username", "admin"}, + {"attempted_password", "password123"}, + {"action_taken", "logged"}, }, ) @@ -350,11 +350,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { "web_login_attempt", srcIP, "Web Interface", - map[string]any{ - "user_agent": userAgent, - "attempted_username": username, - "attempted_password": password, - "action_taken": "logged", + sdk.EventDetails{ + {"user_agent", userAgent}, + {"attempted_username", username}, + {"attempted_password", password}, + {"action_taken", "logged"}, }, ) diff --git a/Sensors/templates/go-sensor/main.go b/Sensors/templates/go-sensor/main.go index 2d257f5..8e37b5f 100644 --- a/Sensors/templates/go-sensor/main.go +++ b/Sensors/templates/go-sensor/main.go @@ -27,9 +27,9 @@ func main() { "custom_anomaly_detected", "Wizard Firedrill", "Mock Custom Target", - map[string]any{ - "test_message": "Wizard triggered a synthetic event firedrill.", - "action_taken": "logged", + sdk.EventDetails{ + {"test_message", "Wizard triggered a synthetic event firedrill."}, + {"action_taken", "logged"}, }, ) @@ -84,10 +84,10 @@ func runSensor(ctx context.Context, hw *sdk.Sensor) { "custom_anomaly_detected", // 1. Event Trigger sourceIP, // 2. Source target, // 3. Target - map[string]any{ // 4. Details - "attack_type": "example_probe", - "raw_payload": "GET /etc/passwd HTTP/1.1", - "action_taken": "logged", + sdk.EventDetails{ // 4. Details + {"attack_type", "example_probe"}, + {"raw_payload", "GET /etc/passwd HTTP/1.1"}, + {"action_taken", "logged"}, }, ) }