mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
fix: harden doctor PATH checks and attribute cloud events by OS user
Doctor now verifies every installed package manager against the shim directory, and system-install validation only requires a safe parent directory. Cloud sync records username/uid on invocation context for multi-user hosts sharing one endpoint. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+55
-44
@@ -304,67 +304,78 @@ func pathIsUnderDir(path, dir string) bool {
|
||||
return strings.HasPrefix(cleanPath, prefix)
|
||||
}
|
||||
|
||||
func checkShimInPathResult() doctor.CheckResult {
|
||||
pathEntries := filepath.SplitList(os.Getenv("PATH"))
|
||||
systemDir := shim.SystemBinDir()
|
||||
userDir, userDirErr := shim.UserBinDir()
|
||||
resolved, lookErr := exec.LookPath("npm")
|
||||
func classifyPackageManagerResolutions(packageManagers []string, shimDir string, lookPath func(string) (string, error)) (underShim, shadowed []string) {
|
||||
for _, pm := range packageManagers {
|
||||
resolved, err := lookPath(pm)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if lookErr == nil {
|
||||
if shim.SystemShimsInstalled() && pathIsUnderDir(resolved, systemDir) {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: "npm resolves to system shim",
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
}
|
||||
if userDirErr == nil && pathIsUnderDir(resolved, userDir) {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: "npm resolves to PMG shim",
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
if pathIsUnderDir(resolved, shimDir) {
|
||||
underShim = append(underShim, pm)
|
||||
continue
|
||||
}
|
||||
shadowed = append(shadowed, pm)
|
||||
}
|
||||
return underShim, shadowed
|
||||
}
|
||||
|
||||
if shim.SystemShimsInstalled() && pathContainsDir(pathEntries, systemDir) {
|
||||
if lookErr == nil {
|
||||
func checkShimDirResolution(shimDir, pathLabel string, pathEntries []string) doctor.CheckResult {
|
||||
underShim, shadowed := classifyPackageManagerResolutions(
|
||||
alias.DefaultConfig().PackageManagers,
|
||||
shimDir,
|
||||
exec.LookPath,
|
||||
)
|
||||
|
||||
if len(shadowed) > 0 {
|
||||
if pathContainsDir(pathEntries, shimDir) || len(underShim) > 0 {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusWarn,
|
||||
Message: fmt.Sprintf("System shim directory is in PATH, but npm resolves to %s", resolved),
|
||||
Message: fmt.Sprintf("%s resolved outside %s", strings.Join(shadowed, ", "), pathLabel),
|
||||
}
|
||||
}
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: "System shim directory is in PATH",
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
}
|
||||
if userDirErr == nil && pathContainsDir(pathEntries, userDir) {
|
||||
if lookErr == nil {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusWarn,
|
||||
Message: fmt.Sprintf("Shim directory is in PATH, but npm resolves to %s", resolved),
|
||||
}
|
||||
}
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: "Shim directory is in PATH",
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
}
|
||||
if shim.SystemShimsInstalled() {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusFail,
|
||||
Message: "System shim directory not in PATH",
|
||||
Message: fmt.Sprintf("%s not in PATH", pathLabel),
|
||||
}
|
||||
}
|
||||
if len(underShim) > 0 {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: fmt.Sprintf("Package managers resolve to %s", pathLabel),
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
}
|
||||
if pathContainsDir(pathEntries, shimDir) {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusPass,
|
||||
Message: fmt.Sprintf("%s is in PATH", pathLabel),
|
||||
ImpliesInterception: true,
|
||||
}
|
||||
}
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusFail,
|
||||
Message: "Shim directory not in PATH",
|
||||
Message: fmt.Sprintf("%s not in PATH", pathLabel),
|
||||
}
|
||||
}
|
||||
|
||||
func checkShimInPathResult() doctor.CheckResult {
|
||||
pathEntries := filepath.SplitList(os.Getenv("PATH"))
|
||||
|
||||
if shim.SystemShimsInstalled() {
|
||||
return checkShimDirResolution(shim.SystemBinDir(), "System shim directory", pathEntries)
|
||||
}
|
||||
|
||||
userDir, err := shim.UserBinDir()
|
||||
if err != nil {
|
||||
return doctor.CheckResult{
|
||||
Status: doctor.StatusWarn,
|
||||
Message: fmt.Sprintf("Could not resolve shim directory: %v", err),
|
||||
}
|
||||
}
|
||||
return checkShimDirResolution(userDir, "Shim directory", pathEntries)
|
||||
}
|
||||
|
||||
func runProtectionChecks(coreResults []doctor.CheckResult) []doctor.CheckResult {
|
||||
if !isInterceptionActive(coreResults) {
|
||||
var results []doctor.CheckResult
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/safedep/pmg/internal/doctor"
|
||||
@@ -47,10 +48,34 @@ func TestShimInPathImpliesInterception(t *testing.T) {
|
||||
{
|
||||
Name: checkShimInPath,
|
||||
Status: doctor.StatusPass,
|
||||
Message: "npm resolves to system shim",
|
||||
Message: "Package managers resolve to System shim directory",
|
||||
ImpliesInterception: true,
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, isInterceptionActive(results))
|
||||
}
|
||||
|
||||
func TestClassifyPackageManagerResolutions(t *testing.T) {
|
||||
shimDir := "/usr/local/lib/pmg/bin"
|
||||
lookPath := func(name string) (string, error) {
|
||||
switch name {
|
||||
case "npm":
|
||||
return shimDir + "/npm", nil
|
||||
case "pip":
|
||||
return "/usr/bin/pip", nil
|
||||
case "uv":
|
||||
return "", exec.ErrNotFound
|
||||
default:
|
||||
return "", exec.ErrNotFound
|
||||
}
|
||||
}
|
||||
|
||||
under, shadowed := classifyPackageManagerResolutions(
|
||||
[]string{"npm", "pip", "uv"},
|
||||
shimDir,
|
||||
lookPath,
|
||||
)
|
||||
assert.Equal(t, []string{"npm"}, under)
|
||||
assert.Equal(t, []string{"pip"}, shadowed)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user