// SPDX-License-Identifier: GPL-3.0-or-later package detectors import ( "fmt" "strconv" "strings" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" ) func stdioSocketInode(process model.Process) *int { // The Python oracle checks descriptors in 0,1,2 order and stops at the // first socket. That ordering avoids a map-iteration-dependent alert. for _, fd := range []string{"0", "1", "2"} { target := process.FDTargets[fd] if strings.HasPrefix(target, "socket:[") && strings.HasSuffix(target, "]") { value, err := strconv.Atoi(target[8 : len(target)-1]) if err == nil { return &value } } } return nil } func optionalPythonInt(value *int) string { // Python f-strings render a missing optional integer as "None". Keys are // stable machine contracts, so preserve that spelling rather than Go's // default representation. if value == nil { return "None" } return strconv.Itoa(*value) } func optionalDisplayInt(value *int) string { // Human-facing stealth-network details use "?" for unknown ownership even // though the corresponding dedup key uses Python's "None" spelling. if value == nil || *value == 0 { return "?" } return strconv.Itoa(*value) } func alertPIDs(value *int) []int { if value == nil || *value == 0 { return []int{} } return []int{*value} } func truncateRunes(value string, limit int) string { // Python slices Unicode code points, not UTF-8 bytes. Rune truncation keeps // command details identical for non-ASCII argv values. runes := []rune(value) if len(runes) <= limit { return value } return string(runes[:limit]) } func socketKey(kind string, socket model.Socket) string { return fmt.Sprintf("stealthnet:%s:%s:%s:%s", kind, optionalPythonInt(socket.PID), socket.Local, socket.Peer) }