feat(go): enrich snapshots asynchronously

This commit is contained in:
Luna 2026-07-22 03:10:26 -07:00
parent 1ab4add205
commit eff31b3a82
No known key found for this signature in database
7 changed files with 270 additions and 14 deletions

View file

@ -6,9 +6,14 @@
package enrich
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
"regexp"
"sort"
"strings"
"syscall"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/netutil"
@ -18,6 +23,7 @@ const (
maxAlerts = 128
maxProcesses = 64
maxIndicators = 128
MaxHashBytes = int64(8 << 20)
)
var (
@ -33,6 +39,58 @@ type Process struct {
PPIDComm string
}
// SlowResult contains bounded filesystem enrichment collected away from the
// alert-emission path. A file larger than MaxHashBytes is described but not
// hashed, avoiding unbounded worker time or memory use.
type SlowResult struct {
Processes map[int]map[string]any
Files map[string]map[string]any
}
func CollectSlow(processes []Process, paths []string) SlowResult {
result := SlowResult{Processes: map[int]map[string]any{}, Files: map[string]map[string]any{}}
for _, process := range processes {
exe := cleanPath(process.Exe)
if exe == "" {
continue
}
result.Processes[process.PID] = map[string]any{"exe_sha256": boundedHash(exe)}
}
for _, path := range paths {
result.Files[path] = fileMetadata(path)
}
return result
}
func boundedHash(path string) any {
info, err := os.Stat(path)
if err != nil || !info.Mode().IsRegular() || info.Size() > MaxHashBytes {
return nil
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, io.LimitReader(file, MaxHashBytes+1)); err != nil {
return nil
}
return hex.EncodeToString(hash.Sum(nil))
}
func fileMetadata(path string) map[string]any {
info, err := os.Lstat(path)
if err != nil {
return map[string]any{"exists": false, "mode": nil, "uid": nil, "gid": nil}
}
mode, uid, gid := int(info.Mode().Perm()), -1, -1
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
return map[string]any{"exists": true, "mode": mode, "uid": uid, "gid": gid}
}
// Build returns the stable Python enrichment keys without invoking any slow
// collectors. Input and indicator caps keep attacker-controlled details from
// causing unbounded snapshot work or output.