Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions internal/detector/nodedist_global.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,15 @@ func pnpmGlobalHomes(exec executor.Executor, home string) []string {
}

// nodeHomeDir returns the user's home directory via the platform-appropriate
// environment variable. Uses the env rather than user.Current so that, under a
// root daemon delegating to a logged-in user, callers that pre-set HOME resolve
// the user's tree.
// source: macOS resolves the console user; Windows keeps USERPROFILE;
// other platforms keep HOME.
func nodeHomeDir(exec executor.Executor) string {
if exec.GOOS() == model.PlatformWindows {
switch exec.GOOS() {
case model.PlatformWindows:
return exec.Getenv("USERPROFILE")
case model.PlatformDarwin:
return executor.ResolveHome(exec)
default:
return exec.Getenv("HOME")
}
return exec.Getenv("HOME")
}
74 changes: 74 additions & 0 deletions internal/detector/nodedist_global_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package detector

import (
"context"
"os/user"
"path/filepath"
"slices"
"testing"

"github.com/step-security/dev-machine-guard/internal/executor"
"github.com/step-security/dev-machine-guard/internal/model"
"github.com/step-security/dev-machine-guard/internal/progress"
)

Expand Down Expand Up @@ -41,6 +44,77 @@ func TestNodeGlobalRoots_PrefixOverride(t *testing.T) {
}
}

type nodeConsoleExecutor struct {
*executor.Mock
consoleHome string
}

func (e nodeConsoleExecutor) LoggedInUser() (*user.User, error) {
return &user.User{HomeDir: e.consoleHome}, nil
}

func TestNodeHomeDir_Linux(t *testing.T) {
for _, tc := range []struct {
name, home, accountHome, want string
root bool
}{
{"user home", "/home/testuser", "/home/testuser", "/home/testuser", false},
{"user override", "/custom/home", "/home/testuser", "/custom/home", false},
{"root override", "/home/testuser", "/root", "/home/testuser", true},
{"root home", "/root", "/root", "/root", true},
{"user unset home", "", "/home/testuser", "", false},
{"root unset home", "", "/root", "", true},
} {
t.Run(tc.name, func(t *testing.T) {
mock := executor.NewMock()
mock.SetGOOS(model.PlatformLinux)
mock.SetIsRoot(tc.root)
mock.SetEnv("HOME", tc.home)
mock.SetHomeDir(tc.accountHome)
if got := nodeHomeDir(mock); got != tc.want {
t.Fatalf("nodeHomeDir() = %q, want %q", got, tc.want)
}
})
}
}

func TestNodeGlobalRoots_MacOSUsesLoggedInUserHome(t *testing.T) {
serviceHome := "/root"
userHome := "/home/testuser"
npmRoot := filepath.Join(userHome, ".npm-global", "lib", "node_modules")
pnpmRoot := filepath.Join(userHome, "Library", "pnpm", "global", "5", "node_modules")
yarnRoot := filepath.Join(userHome, ".config", "yarn", "global", "node_modules")
serviceNPMRoot := filepath.Join(serviceHome, ".npm-global", "lib", "node_modules")
servicePNPMRoot := filepath.Join(serviceHome, "Library", "pnpm", "global", "5", "node_modules")
serviceYarnRoot := filepath.Join(serviceHome, ".config", "yarn", "global", "node_modules")
want := []nodeGlobalRoot{
{pm: "npm", dir: npmRoot},
{pm: "pnpm", dir: pnpmRoot},
{pm: "yarn", dir: yarnRoot},
}
mock := executor.NewMock()
mock.SetGOOS(model.PlatformDarwin)
mock.SetIsRoot(true)
mock.SetEnv("HOME", serviceHome)
mock.SetHomeDir(serviceHome)
for _, dir := range []string{
npmRoot,
pnpmRoot,
yarnRoot,
serviceNPMRoot,
servicePNPMRoot,
serviceYarnRoot,
} {
mock.SetDir(dir)
}
mock.SetGlob(filepath.Join(userHome, "Library", "pnpm", "global", "*", "node_modules"), []string{pnpmRoot})
mock.SetGlob(filepath.Join(serviceHome, "Library", "pnpm", "global", "*", "node_modules"), []string{servicePNPMRoot})

if got := NodeGlobalRoots(nodeConsoleExecutor{Mock: mock, consoleHome: userHome}); !slices.Equal(got, want) {
t.Fatalf("NodeGlobalRoots() = %+v, want %+v", got, want)
}
}

// Enterprise disk mode: ScanProjects emits structured packages with no raw
// output and no package-manager invocation.
func TestNodeScanner_DiskMode_Project(t *testing.T) {
Expand Down
Loading