gomcp/internal/domain/soc/correlation_test.go

145 lines
4.6 KiB
Go

package soc
import (
"testing"
"time"
)
func TestCorrelateMultistageJailbreak(t *testing.T) {
now := time.Now()
events := []SOCEvent{
{ID: "e1", Source: SourceSentinelCore, Category: "jailbreak", Severity: SeverityHigh, Timestamp: now.Add(-2 * time.Minute)},
{ID: "e2", Source: SourceSentinelCore, Category: "tool_abuse", Severity: SeverityHigh, Timestamp: now.Add(-1 * time.Minute)},
}
rules := DefaultSOCCorrelationRules()
matches := CorrelateSOCEvents(events, rules)
found := false
for _, m := range matches {
if m.Rule.ID == "SOC-CR-001" {
found = true
if len(m.Events) < 2 {
t.Errorf("expected at least 2 matched events, got %d", len(m.Events))
}
}
}
if !found {
t.Error("SOC-CR-001 (Multi-stage Jailbreak) should have matched")
}
}
func TestCorrelateOutsideWindow(t *testing.T) {
now := time.Now()
// Events too far apart — outside 5-minute window.
events := []SOCEvent{
{ID: "e1", Source: SourceSentinelCore, Category: "jailbreak", Severity: SeverityHigh, Timestamp: now.Add(-10 * time.Minute)},
{ID: "e2", Source: SourceSentinelCore, Category: "tool_abuse", Severity: SeverityHigh, Timestamp: now.Add(-1 * time.Minute)},
}
rules := []SOCCorrelationRule{DefaultSOCCorrelationRules()[0]} // SOC-CR-001 only
matches := CorrelateSOCEvents(events, rules)
if len(matches) != 0 {
t.Error("should not match events outside the time window")
}
}
func TestCorrelateCoordinatedAttack(t *testing.T) {
now := time.Now()
events := []SOCEvent{
{ID: "e1", Source: SourceSentinelCore, Category: "jailbreak", Timestamp: now.Add(-3 * time.Minute)},
{ID: "e2", Source: SourceSentinelCore, Category: "injection", Timestamp: now.Add(-2 * time.Minute)},
{ID: "e3", Source: SourceSentinelCore, Category: "exfiltration", Timestamp: now.Add(-1 * time.Minute)},
}
rules := DefaultSOCCorrelationRules()
matches := CorrelateSOCEvents(events, rules)
found := false
for _, m := range matches {
if m.Rule.ID == "SOC-CR-002" {
found = true
if len(m.Events) < 3 {
t.Errorf("expected at least 3 matched events, got %d", len(m.Events))
}
}
}
if !found {
t.Error("SOC-CR-002 (Coordinated Attack) should have matched")
}
}
func TestCorrelateCoordinatedAttackDifferentSources(t *testing.T) {
now := time.Now()
// Events from different sources — should NOT match coordinated attack.
events := []SOCEvent{
{ID: "e1", Source: SourceSentinelCore, Category: "jailbreak", Timestamp: now.Add(-3 * time.Minute)},
{ID: "e2", Source: SourceShield, Category: "injection", Timestamp: now.Add(-2 * time.Minute)},
{ID: "e3", Source: SourceImmune, Category: "exfiltration", Timestamp: now.Add(-1 * time.Minute)},
}
rules := []SOCCorrelationRule{DefaultSOCCorrelationRules()[1]} // SOC-CR-002 only
matches := CorrelateSOCEvents(events, rules)
if len(matches) != 0 {
t.Error("coordinated attack should only match same-source events")
}
}
func TestCorrelatePrivilegeEscalation(t *testing.T) {
now := time.Now()
events := []SOCEvent{
{ID: "e1", Source: SourceGoMCP, Category: "auth_bypass", Timestamp: now.Add(-10 * time.Minute)},
{ID: "e2", Source: SourceGoMCP, Category: "exfiltration", Timestamp: now.Add(-2 * time.Minute)},
}
rules := DefaultSOCCorrelationRules()
matches := CorrelateSOCEvents(events, rules)
found := false
for _, m := range matches {
if m.Rule.ID == "SOC-CR-003" {
found = true
}
}
if !found {
t.Error("SOC-CR-003 (Privilege Escalation Chain) should have matched")
}
}
func TestCorrelateSortsBySeverity(t *testing.T) {
now := time.Now()
events := []SOCEvent{
{ID: "e1", Source: SourceGoMCP, Category: "prompt_injection", Timestamp: now.Add(-2 * time.Minute)},
{ID: "e2", Source: SourceGoMCP, Category: "jailbreak", Timestamp: now.Add(-1 * time.Minute)},
{ID: "e3", Source: SourceGoMCP, Category: "tool_abuse", Timestamp: now.Add(-1 * time.Minute)},
}
rules := DefaultSOCCorrelationRules()
matches := CorrelateSOCEvents(events, rules)
if len(matches) < 2 {
t.Fatalf("expected at least 2 matches, got %d", len(matches))
}
// First match should be CRITICAL (highest severity).
if matches[0].Rule.Severity != SeverityCritical {
t.Errorf("first match should be CRITICAL, got %s", matches[0].Rule.Severity)
}
}
func TestCorrelateEmptyInput(t *testing.T) {
if matches := CorrelateSOCEvents(nil, DefaultSOCCorrelationRules()); matches != nil {
t.Error("nil events should return nil")
}
if matches := CorrelateSOCEvents([]SOCEvent{}, nil); matches != nil {
t.Error("nil rules should return nil")
}
}
func TestDefaultRuleCount(t *testing.T) {
rules := DefaultSOCCorrelationRules()
if len(rules) != 15 {
t.Errorf("expected 15 default rules, got %d", len(rules))
}
}