// SPDX-License-Identifier: GPL-3.0-or-later package detectors import ( "fmt" "path/filepath" "strings" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" ) var exactCredentialFiles = map[string]bool{ "/etc/shadow": true, "/etc/gshadow": true, "/etc/security/opasswd": true, } var browserSecretNames = map[string]bool{ "logins.json": true, "key4.db": true, "Login Data": true, "Cookies": true, } var browserPathMarkers = []string{ "/.mozilla/", "/firefox/", "/chromium/", "/google-chrome/", "/chrome/", "/brave", "/vivaldi/", "/edge/", } // CredentialAccess ports the Python credential_access detector (SID 100033). func CredentialAccess(state model.State, cfg config.Config) []model.Alert { alerts := make([]model.Alert, 0) for _, process := range state.Processes { comm := process.Comm if comm == "" { comm = "?" } if cfg.CredentialAccessAllowComms[comm] { continue } for _, fd := range sortedFDs(process.FDTargets) { target := strings.TrimSuffix(process.FDTargets[fd], " (deleted)") kind := sensitiveCredentialKind(target, cfg.CredentialAccessExtraPaths) if kind == "" { continue } alerts = append(alerts, model.Alert{ SID: 100033, Severity: "CRITICAL", Signature: "credential_access", Classtype: "credential-theft", Key: fmt.Sprintf("cred:%d:%s", process.PID, target), Detail: fmt.Sprintf( "pid=%d comm=%s fd=%s has %s open: %s", process.PID, comm, fd, kind, target), PIDs: []int{process.PID}, }) break } } return alerts } func sensitiveCredentialKind(path string, extraPaths []string) string { if !strings.HasPrefix(path, "/") { return "" } if exactCredentialFiles[path] { return "system credential database" } if matchesExtraPath(path, extraPaths) { return "configured credential path" } if strings.Contains(path, "/.ssh/") { name := filepath.Base(path) if strings.HasPrefix(name, "id_") || strings.HasSuffix(name, ".pem") || strings.HasSuffix(name, ".key") { return "private SSH key" } } if strings.HasPrefix(path, "/etc/NetworkManager/system-connections/") { return "NetworkManager secret profile" } if browserSecretNames[filepath.Base(path)] { lowered := strings.ToLower(path) for _, marker := range browserPathMarkers { if strings.Contains(lowered, marker) { return "browser credential store" } } } return "" } func matchesExtraPath(path string, entries []string) bool { for _, entry := range entries { if entry == "" { continue } if strings.HasSuffix(entry, "/") && strings.HasPrefix(path, entry) { return true } if path == entry || strings.HasPrefix(path, strings.TrimRight(entry, "/")+"/") { return true } } return false }