mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* fix: avoid resolving symlinks in shim PMG binary path Shims hardcoded the resolved Cellar path (e.g. /opt/homebrew/Cellar/pmg/0.16.0/bin/pmg) instead of the stable Homebrew symlink. This broke all shims after `brew upgrade` since the old Cellar directory is removed. Fixes #302 * fix pip test pkg version
137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package doctor
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/safedep/dry/log"
|
|
)
|
|
|
|
type ProtectionTestCase struct {
|
|
PackageManager string
|
|
Package string
|
|
InstallArgs []string
|
|
NeedsVenv bool
|
|
}
|
|
|
|
func ProtectionTestCases() []ProtectionTestCase {
|
|
return []ProtectionTestCase{
|
|
{
|
|
PackageManager: "npm",
|
|
Package: "safedep-test-pkg@0.1.3",
|
|
InstallArgs: []string{"npm", "install", "--no-cache", "--prefer-online", "safedep-test-pkg@0.1.3"},
|
|
},
|
|
{
|
|
PackageManager: "pip",
|
|
Package: "safedep-test-pkg==0.0.4",
|
|
InstallArgs: []string{"pip", "install", "--no-cache-dir", "safedep-test-pkg==0.0.4"},
|
|
NeedsVenv: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func RunProtectionCheck(tc ProtectionTestCase, pmgBinary string) CheckResult {
|
|
if _, err := exec.LookPath(tc.PackageManager); err != nil {
|
|
return CheckResult{
|
|
Status: StatusWarn,
|
|
Message: fmt.Sprintf("%s not available — skipping protection test for %s", tc.PackageManager, tc.Package),
|
|
}
|
|
}
|
|
|
|
tmpDir, err := os.MkdirTemp("", "pmg-doctor-*")
|
|
if err != nil {
|
|
return CheckResult{
|
|
Status: StatusWarn,
|
|
Message: fmt.Sprintf("Could not create temp dir for %s test: %v", tc.PackageManager, err),
|
|
}
|
|
}
|
|
defer func() {
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
log.Warnf("failed to clean up temp dir %s: %v", tmpDir, err)
|
|
}
|
|
}()
|
|
|
|
env := os.Environ()
|
|
|
|
if tc.NeedsVenv {
|
|
venvDir, venvErr := setupVenv(tmpDir)
|
|
if venvErr != nil {
|
|
return CheckResult{
|
|
Status: StatusWarn,
|
|
Message: fmt.Sprintf("Could not create venv for %s test: %v", tc.PackageManager, venvErr),
|
|
}
|
|
}
|
|
venvBin := filepath.Join(venvDir, "bin")
|
|
env = prependPath(env, venvBin)
|
|
}
|
|
|
|
cmd := exec.Command(pmgBinary, tc.InstallArgs...)
|
|
cmd.Dir = tmpDir
|
|
cmd.Env = env
|
|
|
|
_, runErr := cmd.CombinedOutput()
|
|
return evaluateProtectionResult(tc.PackageManager, tc.Package, runErr)
|
|
}
|
|
|
|
func setupVenv(baseDir string) (string, error) {
|
|
venvDir := filepath.Join(baseDir, "venv")
|
|
cmd := exec.Command("python3", "-m", "venv", venvDir)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return "", fmt.Errorf("venv creation failed: %w\n%s", err, string(output))
|
|
}
|
|
return venvDir, nil
|
|
}
|
|
|
|
func prependPath(env []string, dir string) []string {
|
|
result := make([]string, 0, len(env))
|
|
for _, e := range env {
|
|
if strings.HasPrefix(e, "PATH=") {
|
|
e = fmt.Sprintf("PATH=%s%c%s", dir, filepath.ListSeparator, e[5:])
|
|
}
|
|
result = append(result, e)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func evaluateProtectionResult(pm string, pkg string, err error) CheckResult {
|
|
if err != nil {
|
|
if isExecutableNotFound(err) {
|
|
return CheckResult{
|
|
Status: StatusWarn,
|
|
Message: fmt.Sprintf("%s not available — skipping protection test for %s", pm, pkg),
|
|
}
|
|
}
|
|
return CheckResult{
|
|
Status: StatusPass,
|
|
Message: fmt.Sprintf("Malicious package blocked (%s/%s)", pm, pkg),
|
|
}
|
|
}
|
|
return CheckResult{
|
|
Status: StatusFail,
|
|
Message: fmt.Sprintf("Failed to block %s/%s — package was installed instead of blocked", pm, pkg),
|
|
}
|
|
}
|
|
|
|
func isExecutableNotFound(err error) bool {
|
|
if execErr, ok := err.(*exec.Error); ok {
|
|
return execErr.Err == exec.ErrNotFound
|
|
}
|
|
return false
|
|
}
|
|
|
|
func CheckShimScripts(shimDir string, managers []string) (found []string, missing []string) {
|
|
for _, pm := range managers {
|
|
shimPath := filepath.Join(shimDir, pm)
|
|
info, err := os.Stat(shimPath)
|
|
if err != nil || info.Mode()&0o111 == 0 {
|
|
missing = append(missing, pm)
|
|
continue
|
|
}
|
|
found = append(found, pm)
|
|
}
|
|
return found, missing
|
|
}
|