fix: harden system-install review findings

Require root-owned, non-group/other-writable pmg for --system install;
allow remove without that validation. Doctor checks npm resolution for
PATH precedence, uses ImpliesInterception instead of message matching,
and documents version-manager shadowing. Pass profile bin dir from the
shim manager and note that system config ignores per-user files.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Sahilb315
2026-07-13 22:20:57 +05:30
co-authored by Cursor
parent 0d0399f665
commit 1c9b16f1fa
14 changed files with 277 additions and 57 deletions
+81 -32
View File
@@ -3,7 +3,9 @@ package setup
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/internal/alias"
@@ -139,8 +141,9 @@ func runCoreChecks(cfg *config.RuntimeConfig) []doctor.CheckResult {
}
if installed {
return doctor.CheckResult{
Status: doctor.StatusPass,
Message: aliasesInstalledMessage,
Status: doctor.StatusPass,
Message: aliasesInstalledMessage,
ImpliesInterception: true,
}
}
if shim.SystemShimsInstalled() {
@@ -189,32 +192,7 @@ func runCoreChecks(cfg *config.RuntimeConfig) []doctor.CheckResult {
{
Name: checkShimInPath,
Category: "Shell Integration",
Run: func() doctor.CheckResult {
pathEntries := filepath.SplitList(os.Getenv("PATH"))
systemDir := shim.SystemBinDir()
if shim.SystemShimsInstalled() && pathContainsDir(pathEntries, systemDir) {
return doctor.CheckResult{
Status: doctor.StatusPass,
Message: "System shim directory is in PATH",
}
}
if userDir, err := shim.UserBinDir(); err == nil && pathContainsDir(pathEntries, userDir) {
return doctor.CheckResult{
Status: doctor.StatusPass,
Message: "Shim directory is in PATH",
}
}
if shim.SystemShimsInstalled() {
return doctor.CheckResult{
Status: doctor.StatusFail,
Message: "System shim directory not in PATH",
}
}
return doctor.CheckResult{
Status: doctor.StatusFail,
Message: "Shim directory not in PATH",
}
},
Run: checkShimInPathResult,
},
{
Name: checkProxyMode,
@@ -313,6 +291,80 @@ func pathContainsDir(pathEntries []string, dir string) bool {
return false
}
func pathIsUnderDir(path, dir string) bool {
if path == "" || dir == "" {
return false
}
cleanPath := filepath.Clean(path)
cleanDir := filepath.Clean(dir)
if cleanPath == cleanDir {
return true
}
prefix := cleanDir + string(os.PathSeparator)
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")
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 shim.SystemShimsInstalled() && pathContainsDir(pathEntries, systemDir) {
if lookErr == nil {
return doctor.CheckResult{
Status: doctor.StatusWarn,
Message: fmt.Sprintf("System shim directory is in PATH, but npm resolves to %s", resolved),
}
}
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",
}
}
return doctor.CheckResult{
Status: doctor.StatusFail,
Message: "Shim directory not in PATH",
}
}
func runProtectionChecks(coreResults []doctor.CheckResult) []doctor.CheckResult {
if !isInterceptionActive(coreResults) {
var results []doctor.CheckResult
@@ -344,10 +396,7 @@ func runProtectionChecks(coreResults []doctor.CheckResult) []doctor.CheckResult
func isInterceptionActive(coreResults []doctor.CheckResult) bool {
for _, r := range coreResults {
if r.Name == checkShimInPath && r.Status == doctor.StatusPass {
return true
}
if r.Name == checkShellAliases && r.Status == doctor.StatusPass && r.Message == aliasesInstalledMessage {
if r.ImpliesInterception {
return true
}
}
+25 -1
View File
@@ -13,6 +13,12 @@ func TestPathContainsDir(t *testing.T) {
assert.False(t, pathContainsDir([]string{"/usr/bin"}, ""))
}
func TestPathIsUnderDir(t *testing.T) {
assert.True(t, pathIsUnderDir("/usr/local/lib/pmg/bin/npm", "/usr/local/lib/pmg/bin"))
assert.False(t, pathIsUnderDir("/usr/local/bin/npm", "/usr/local/lib/pmg/bin"))
assert.False(t, pathIsUnderDir("/usr/local/lib/pmg/bin-extra/npm", "/usr/local/lib/pmg/bin"))
}
func TestSystemInstallAliasesPassDoesNotActivateInterception(t *testing.T) {
results := []doctor.CheckResult{
{Name: checkShellAliases, Status: doctor.StatusPass, Message: "No aliases (system install)"},
@@ -24,9 +30,27 @@ func TestSystemInstallAliasesPassDoesNotActivateInterception(t *testing.T) {
func TestAliasesInstalledActivatesInterception(t *testing.T) {
results := []doctor.CheckResult{
{Name: checkShellAliases, Status: doctor.StatusPass, Message: aliasesInstalledMessage},
{
Name: checkShellAliases,
Status: doctor.StatusPass,
Message: aliasesInstalledMessage,
ImpliesInterception: true,
},
{Name: checkShimInPath, Status: doctor.StatusFail},
}
assert.True(t, isInterceptionActive(results))
}
func TestShimInPathImpliesInterception(t *testing.T) {
results := []doctor.CheckResult{
{
Name: checkShimInPath,
Status: doctor.StatusPass,
Message: "npm resolves to system shim",
ImpliesInterception: true,
},
}
assert.True(t, isInterceptionActive(results))
}
+4 -4
View File
@@ -105,7 +105,7 @@ func install(system bool) error {
}
func installSystem() error {
if err := errIfSystemInstallAllowed(); err != nil {
if err := requireSystemInstallSupported(); err != nil {
return err
}
@@ -190,11 +190,11 @@ func remove(system, removeConfig bool) error {
}
func removeSystem(removeConfig bool) error {
if err := errIfSystemInstallAllowed(); err != nil {
if err := requireSystemInstallSupported(); err != nil {
return err
}
shimMgr, err := shim.NewSystemShimManager()
shimMgr, err := shim.NewSystemShimManagerForRemove()
if err != nil {
return fmt.Errorf("failed to create system shim manager: %w", err)
}
@@ -212,7 +212,7 @@ func removeSystem(removeConfig bool) error {
return nil
}
func errIfSystemInstallAllowed() error {
func requireSystemInstallSupported() error {
if runtime.GOOS != "linux" {
return usefulerror.NewUsefulError().
WithCode(errcodes.UnsupportedPlatform).
+2 -2
View File
@@ -15,7 +15,7 @@ func TestErrIfSystemInstallAllowed(t *testing.T) {
t.Cleanup(func() { setupGeteuid = orig })
setupGeteuid = func() int { return 0 }
err := errIfSystemInstallAllowed()
err := requireSystemInstallSupported()
if runtime.GOOS == "linux" {
assert.NoError(t, err)
} else {
@@ -26,7 +26,7 @@ func TestErrIfSystemInstallAllowed(t *testing.T) {
}
setupGeteuid = func() int { return 1000 }
err = errIfSystemInstallAllowed()
err = requireSystemInstallSupported()
require.Error(t, err)
usefulErr, ok := usefulerror.AsUsefulError(err)
require.True(t, ok)