feat(go): port FIM/pkgdb/rootcheck integrity engines and add go-sidecar dashboard consumer

Adds sidecar-only, --state-dir-gated FIM, package-DB-anchor, and rootcheck
engines to the Go migration sidecar, wired into agent.Sweep/Initialize
behind the existing baseline lifecycle. Adds a read-only, schema-checked
Python dashboard consumer (go_sidecar_state_dir, /api/go-sidecar/*, Sidecar
tab) that never starts, stops, or mutates the Go sidecar's state.

Also fixes drift found while reconciling this work: enodia_sentinel/web.py
had reinvented local schema constants instead of using the canonical
enodia_sentinel/schemas.py catalog (now registers enodia.go.sidecar.v1
there and reuses ALERT_SNAPSHOT_V1/INCIDENT_V1); docs/RULES.md had drifted
from what `rules docs` actually generates for SIDs 100069-100078 (ruleops.py
was missing metadata for four SIDs and had stale drill text for four more),
now back in sync with a regression test pinning them together.

Updates CLAUDE.md, README.md, go-agent/README.md, and docs/{ROADMAP,
GO_PORT_HANDOFF,SURICATA_ASSIMILATION,THREAT_MODEL,OPERATIONS,SCHEMAS,
COMMAND_REFERENCE}.md to reflect the landed work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQivSKBqQJsayz1xcYqWzZ
This commit is contained in:
Luna 2026-07-24 09:59:09 -07:00
parent 1d1dee7f6e
commit d835386381
No known key found for this signature in database
43 changed files with 3419 additions and 72 deletions

View file

@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package rootcheck
import (
"reflect"
"testing"
)
func setInts(values ...int) map[int]bool {
result := map[int]bool{}
for _, value := range values {
result[value] = true
}
return result
}
func setStrings(values ...string) map[string]bool {
result := map[string]bool{}
for _, value := range values {
result[value] = true
}
return result
}
func TestCheckIsQuietWhenCrossViewsAgree(t *testing.T) {
alerts := Check(Views{
VisiblePIDs: setInts(1, 2), AlivePIDs: setInts(1, 2),
PSPIDs: setInts(1, 2), PSAvailable: true,
PIDAlive: func(int) bool { return true }, ProcPIDExists: func(int) bool { return true },
ProcModules: setStrings("ext4"), SysLiveModules: setStrings("ext4"),
}, Options{})
if len(alerts) != 0 {
t.Fatalf("alerts = %+v, want none", alerts)
}
}
func TestCheckReportsRootkitCrossViewSignalsInPythonOrder(t *testing.T) {
alerts := Check(Views{
VisiblePIDs: setInts(1), AlivePIDs: setInts(1), PSPIDs: setInts(1), PSAvailable: true,
PIDAlive: func(int) bool { return true }, ProcPIDExists: func(int) bool { return true },
ProcModules: setStrings("ext4"), SysLiveModules: setStrings("ext4", "diamorphine"),
ModuleTaints: map[string]string{"vendor_gpu": "OE"}, KernelTaint: (1 << 12) | (1 << 13),
ProcTCPPorts: setInts(22, 31337), SSTCPPorts: setInts(22),
ProcUDPPorts: setInts(53, 4444), SSUDPPorts: setInts(53),
ProcRawProtocols: setInts(1, 58), SSRawProtocols: setInts(58),
ProcProtocolKinds: setStrings("sctp", "packet"), SSProtocolKinds: setStrings("packet"),
PromiscuousIfaces: []string{"eth0"},
}, Options{})
got := make([]int, len(alerts))
for index, alert := range alerts {
got[index] = alert.SID
}
want := []int{SIDHiddenModule, SIDKnownRootkitModule, SIDTaintedModule, SIDKernelTainted,
SIDHiddenPort, SIDHiddenUDPPort, SIDHiddenRawSocket, SIDRawICMPSocket,
SIDHiddenProtocolSocket, SIDPromiscuous}
if !reflect.DeepEqual(got, want) {
t.Fatalf("SIDs = %v, want %v", got, want)
}
if alerts[0].Severity != "CRITICAL" || alerts[1].Signature != "rootkit_known_module" ||
alerts[3].Severity != "HIGH" || alerts[7].Key != "rk:rawicmp:1" {
t.Fatalf("unexpected alert detail: %+v", alerts)
}
}
func TestCheckRechecksProcessCandidatesAndLimitsRenderedPIDs(t *testing.T) {
alive := map[int]bool{}
for pid := 1; pid <= 25; pid++ {
alive[pid] = true
}
alerts := Check(Views{AlivePIDs: alive, VisiblePIDs: map[int]bool{},
PIDAlive: func(pid int) bool { return pid != 1 }, ProcPIDExists: func(int) bool { return false },
}, Options{})
if len(alerts) != 1 {
t.Fatalf("alerts = %+v, want one", alerts)
}
if len(alerts[0].PIDs) != 20 || alerts[0].PIDs[0] != 2 || alerts[0].PIDs[19] != 21 {
t.Fatalf("pids = %v, want [2..21]", alerts[0].PIDs)
}
}
func TestCheckHonorsNormalizedModuleAllowlist(t *testing.T) {
alerts := Check(Views{ModuleTaints: map[string]string{"vendor-gpu": "OE"}}, Options{
AllowedModules: setStrings("vendor_gpu"),
})
if len(alerts) != 0 {
t.Fatalf("alerts = %+v, want none", alerts)
}
}