enodia-sentinal/go-agent/internal/provenance/provenance.go

70 lines
1.7 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
// Package provenance answers "is this file shipped by an installed package?".
// Ownership raises confidence that a binary is legitimate; it never proves
// safety, so callers use it to suppress or rank alerts, not to delete evidence.
package provenance
import (
"context"
"os/exec"
"strings"
"sync"
"time"
)
var (
mu sync.Mutex
tool *string
cache = map[string]bool{}
)
func packageTool() string {
if tool != nil {
return *tool
}
found := ""
for _, name := range []string{"pacman", "dpkg", "rpm"} {
if _, err := exec.LookPath(name); err == nil {
found = name
break
}
}
tool = &found
return found
}
func query(name, path string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
switch name {
case "pacman":
output, err := exec.CommandContext(ctx, "pacman", "-Qo", path).Output()
return err == nil && strings.Contains(string(output), "owned by")
case "dpkg":
output, err := exec.CommandContext(ctx, "dpkg", "-S", path).Output()
return err == nil && strings.Contains(string(output), ":")
case "rpm":
output, err := exec.CommandContext(ctx, "rpm", "-qf", path).Output()
return err == nil && !strings.Contains(string(output), "not owned")
}
return false
}
// IsPackageOwned reports whether path belongs to an installed package.
// Results are cached because package ownership is stable within a sweep run.
func IsPackageOwned(path string) bool {
if path == "" || path == "(deleted)" {
return false
}
path = strings.ReplaceAll(path, " (deleted)", "")
mu.Lock()
defer mu.Unlock()
if owned, seen := cache[path]; seen {
return owned
}
name := packageTool()
owned := name != "" && query(name, path)
cache[path] = owned
return owned
}