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
52 changes: 48 additions & 4 deletions internal/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package paths

import (
"os"
"path/filepath"
"strings"

"github.com/step-security/dev-machine-guard/internal/config"
)
Expand All @@ -38,20 +40,62 @@ func SetOverride(s string) {

// Home returns the resolved install dir. Falls back to LegacyHome when
// nothing else is set. Empty string is possible only when the home
// directory itself cannot be resolved.
// directory itself cannot be resolved. A leading $HOME or ~ token in
// any source is expanded via expandHome so the returned path is
// canonical for the current OS, keeping the migration warning in main
// from misfiring on hand-edited values like "$HOME/.stepsecurity" that
// resolve to the legacy default.
//
// Note: this is a superset of resolveSearchDirs in internal/scan/scanner.go,
// which only expands the exact literal "$HOME" — the install dir comes
// from operator-edited config so it has to tolerate the "$HOME/foo" /
// "~/foo" forms our docs use; search_dirs come from --search-dirs flag
// values that operators don't combine with subpaths.
func Home() string {
if cliOverride != "" {
return cliOverride
return expandHome(cliOverride)
}
if v := os.Getenv(HomeEnvVar); v != "" {
return v
return expandHome(v)
}
if config.InstallDir != "" {
return config.InstallDir
return expandHome(config.InstallDir)
}
return LegacyHome()
}

// expandHome replaces a leading $HOME or ~ token with the resolved
// user home directory. Returns the input unchanged when the home
// directory cannot be resolved or no token is present. Callers that
// hand-edit config.json with "$HOME/.stepsecurity" — the literal value
// our docs use — get the same canonical form as LegacyHome(), which
// keeps the migration warning in main from misfiring on identical
// paths.
func expandHome(s string) string {
if s == "" {
return s
}
if !strings.HasPrefix(s, "$HOME") && !strings.HasPrefix(s, "~") {
return s
}
home, err := os.UserHomeDir()
if err != nil || home == "" {
return s
}
switch {
case s == "$HOME" || s == "~":
return home
case strings.HasPrefix(s, "$HOME/") || strings.HasPrefix(s, `$HOME\`):
// filepath.Join + Clean canonicalises separators so Windows
// gets C:\Users\me\.stepsecurity (not C:\Users\me/.stepsecurity)
// and the equality check against LegacyHome() can succeed.
return filepath.Join(home, s[len("$HOME"):])
case strings.HasPrefix(s, "~/") || strings.HasPrefix(s, `~\`):
return filepath.Join(home, s[len("~"):])
}
Comment thread
shubham-stepsecurity marked this conversation as resolved.
return s
}

// LegacyHome returns ~/.stepsecurity. Exposed for the migration check
// in main and for ShowConfigure displays. Mirrors config.LegacyDir but
// kept here so callers can grab the legacy path without taking a
Expand Down
60 changes: 60 additions & 0 deletions internal/paths/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package paths

import (
"os"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -86,6 +87,65 @@ func TestHome_CLIOverridesEnv(t *testing.T) {
}
}

func TestHome_ExpandsHomeTokenFromConfig(t *testing.T) {
withOverride(t, "")
withEnv(t, HomeEnvVar, "")
withConfigInstallDir(t, "$HOME/.stepsecurity")

home, err := os.UserHomeDir()
if err != nil || home == "" {
t.Skip("home dir unresolved in this environment")
}
want := filepath.Join(home, ".stepsecurity")
if got := Home(); got != want {
t.Errorf("Home() = %q, want %q (config $HOME should expand)", got, want)
}
// And the migration warning's equality check must now succeed.
if Home() != LegacyHome() {
t.Errorf("Home()=%q vs LegacyHome()=%q — expected equal after $HOME expansion", Home(), LegacyHome())
}
}

func TestHome_ExpandsTildeFromEnvVar(t *testing.T) {
withOverride(t, "")
withConfigInstallDir(t, "")
withEnv(t, HomeEnvVar, "~/agent")

home, err := os.UserHomeDir()
if err != nil || home == "" {
t.Skip("home dir unresolved in this environment")
}
want := filepath.Join(home, "agent")
if got := Home(); got != want {
t.Errorf("Home() = %q, want %q (env ~ should expand)", got, want)
}
}

func TestHome_ExpandsHomeFromCLIFlag(t *testing.T) {
withConfigInstallDir(t, "")
withEnv(t, HomeEnvVar, "")
withOverride(t, "$HOME/custom")

home, err := os.UserHomeDir()
if err != nil || home == "" {
t.Skip("home dir unresolved in this environment")
}
want := filepath.Join(home, "custom")
if got := Home(); got != want {
t.Errorf("Home() = %q, want %q (CLI $HOME should expand)", got, want)
}
}

func TestHome_AbsolutePathUnchanged(t *testing.T) {
withOverride(t, "")
withEnv(t, HomeEnvVar, "")
withConfigInstallDir(t, "/opt/stepsecurity")

if got := Home(); got != "/opt/stepsecurity" {
t.Errorf("Home() = %q, want /opt/stepsecurity (absolute path must not be modified)", got)
}
}

func TestSetOverride_Sticks(t *testing.T) {
withOverride(t, "")
SetOverride("/sticky")
Expand Down
Loading