feat(go): advance validation sidecar toward production

This commit is contained in:
Luna 2026-07-22 01:52:19 -07:00
parent 6a06eba255
commit f85c2e831a
No known key found for this signature in database
92 changed files with 8881 additions and 91 deletions

View file

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Package correlation adds higher-confidence stories without hiding or
// replacing their raw alerts. It is transport- and persistence-independent.
package correlation
import "sort"
const SIDMultiStageIntrusion = 100080
type Incident struct {
FirstTimestamp float64 `json:"first_ts"`
LastTimestamp float64 `json:"last_ts"`
Signatures []string `json:"signatures"`
}
type Record struct {
SID int `json:"sid"`
Signature string `json:"signature"`
Classtype string `json:"classtype"`
Severity string `json:"severity"`
Summary string `json:"summary"`
WindowSeconds int `json:"window_seconds"`
MatchedSignatures []string `json:"matched_signatures"`
}
type Rule struct {
SID int
Signature string
Classtype string
Severity string
Summary string
RequiredAny []map[string]bool
MaxWindow int
}
var DefaultRules = []Rule{{
SID: SIDMultiStageIntrusion, Signature: "correlation.multi_stage_intrusion",
Classtype: "multi-stage-intrusion", Severity: "CRITICAL",
Summary: "Web/database service spawned a shell and the same incident showed " +
"suspicious egress or an unusual listener within the correlation window",
RequiredAny: []map[string]bool{
{"exec_rule.web-rce": true},
{"host_rule.suspicious-egress": true, "host_rule.suspicious-listener": true},
},
MaxWindow: 600,
}}
func Correlate(incident Incident, rules []Rule) []Record {
if rules == nil {
rules = DefaultRules
}
signatures := make(map[string]bool, len(incident.Signatures))
for _, signature := range incident.Signatures {
signatures[signature] = true
}
duration := incident.LastTimestamp - incident.FirstTimestamp
result := make([]Record, 0)
for _, rule := range rules {
if duration > float64(rule.MaxWindow) {
continue
}
matched := true
for _, choices := range rule.RequiredAny {
found := false
for choice := range choices {
if signatures[choice] {
found = true
break
}
}
if !found {
matched = false
break
}
}
if !matched {
continue
}
matchedSignatures := make([]string, 0, len(signatures))
for signature := range signatures {
matchedSignatures = append(matchedSignatures, signature)
}
sort.Strings(matchedSignatures)
result = append(result, Record{
SID: rule.SID, Signature: rule.Signature, Classtype: rule.Classtype,
Severity: rule.Severity, Summary: rule.Summary,
WindowSeconds: rule.MaxWindow, MatchedSignatures: matchedSignatures,
})
}
return result
}

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package correlation
import (
"reflect"
"testing"
)
func TestCorrelateMultiStageIntrusion(t *testing.T) {
records := Correlate(Incident{
FirstTimestamp: 1000, LastTimestamp: 1050,
Signatures: []string{"host_rule.suspicious-egress", "exec_rule.web-rce"},
}, nil)
if len(records) != 1 || records[0].SID != SIDMultiStageIntrusion ||
!reflect.DeepEqual(records[0].MatchedSignatures,
[]string{"exec_rule.web-rce", "host_rule.suspicious-egress"}) {
t.Fatalf("unexpected correlation: %#v", records)
}
if records := Correlate(Incident{
FirstTimestamp: 1000, LastTimestamp: 1601,
Signatures: []string{"exec_rule.web-rce", "host_rule.suspicious-listener"},
}, nil); len(records) != 0 {
t.Fatalf("expired incident correlated: %#v", records)
}
}
func TestLineageAssignAndSeverity(t *testing.T) {
parents := map[int]int{42: 20, 20: 10, 10: 1}
lineage := LineageOf([]int{42}, func(pid int) int { return parents[pid] }, 8)
if !lineage[42] || !lineage[20] || !lineage[10] || lineage[1] {
t.Fatalf("unexpected lineage: %#v", lineage)
}
index := []IndexIncident{
{ID: "old", Host: "h", LastTimestamp: 1010, Lineage: []int{20}},
{ID: "new", Host: "h", LastTimestamp: 1020, Lineage: []int{10}},
}
if got := Assign(index, lineage, 1030, "h", 60); got != "new" {
t.Fatalf("unexpected assignment: %s", got)
}
if MaxSeverity("HIGH", "CRITICAL") != "CRITICAL" || MaxSeverity("unknown", "MEDIUM") != "MEDIUM" {
t.Fatal("severity ordering drifted")
}
}

View file

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package correlation
// LineageOf walks each starting PID through parentOf, excluding pid 0/init so
// unrelated process trees never correlate merely because they share init.
func LineageOf(pids []int, parentOf func(int) int, depth int) map[int]bool {
seen := make(map[int]bool)
for _, start := range pids {
current := start
steps := 0
for current != 0 && current != 1 && steps <= depth {
if seen[current] {
break
}
seen[current] = true
current = parentOf(current)
steps++
}
}
return seen
}
type IndexIncident struct {
ID string `json:"id"`
Host string `json:"host"`
LastTimestamp float64 `json:"last_ts"`
Lineage []int `json:"lineage"`
}
// Assign returns the most recently active incident compatible with this host
// and lineage. Empty-lineage evidence may join any open incident on the host,
// reproducing Python's fallback for FIM/package evidence without a live PID.
func Assign(index []IndexIncident, lineage map[int]bool, when float64, host string, window int) string {
best := ""
bestTimestamp := -1.0
for _, incident := range index {
if incident.Host != host || when-incident.LastTimestamp > float64(window) {
continue
}
if len(lineage) > 0 && !intersects(incident.Lineage, lineage) {
continue
}
if incident.LastTimestamp > bestTimestamp {
best, bestTimestamp = incident.ID, incident.LastTimestamp
}
}
return best
}
func MaxSeverity(left, right string) string {
if severityRank(left) >= severityRank(right) {
return left
}
return right
}
func severityRank(value string) int {
switch value {
case "MEDIUM":
return 1
case "HIGH":
return 2
case "CRITICAL":
return 3
default:
return 0
}
}
func intersects(values []int, wanted map[int]bool) bool {
for _, value := range values {
if wanted[value] {
return true
}
}
return false
}