Skip to content

Commit dba903a

Browse files
Claude (via Conductor)claude
andcommitted
fix: Obfuscate home directory paths in vendor list output
Prevent leaking user home directory paths in `atmos list vendor` output by replacing them with `~` before printing. Security improvement that follows the existing pattern from auth_whoami.go. Changes: - Added obfuscateHomeDirInOutput() helper function - Applied obfuscation before printing vendor list output - Added comprehensive unit tests with 5 test cases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 40e8d64 commit dba903a

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

cmd/list/vendor.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package list
22

33
import (
44
"fmt"
5+
"os"
6+
"strings"
57

68
"github.com/spf13/cobra"
79
"github.com/spf13/viper"
810

911
"github.com/cloudposse/atmos/pkg/config"
12+
"github.com/cloudposse/atmos/pkg/config/homedir"
1013
"github.com/cloudposse/atmos/pkg/flags"
1114
"github.com/cloudposse/atmos/pkg/flags/global"
1215
l "github.com/cloudposse/atmos/pkg/list"
@@ -52,7 +55,9 @@ var vendorCmd = &cobra.Command{
5255
return err
5356
}
5457

55-
fmt.Println(output)
58+
// Obfuscate home directory paths before printing.
59+
obfuscatedOutput := obfuscateHomeDirInOutput(output)
60+
fmt.Println(obfuscatedOutput)
5661
return nil
5762
},
5863
}
@@ -95,3 +100,18 @@ func listVendorWithOptions(opts *VendorOptions) (string, error) {
95100

96101
return l.FilterAndListVendor(&atmosConfig, options)
97102
}
103+
104+
// obfuscateHomeDirInOutput replaces occurrences of the home directory with "~" to prevent leaking user paths.
105+
func obfuscateHomeDirInOutput(output string) string {
106+
homeDir, err := homedir.Dir()
107+
if err != nil || homeDir == "" {
108+
return output
109+
}
110+
111+
// Replace home directory with tilde at the start of paths.
112+
// Handle both absolute paths and paths with path separator.
113+
result := strings.ReplaceAll(output, homeDir+string(os.PathSeparator), "~"+string(os.PathSeparator))
114+
result = strings.ReplaceAll(result, homeDir, "~")
115+
116+
return result
117+
}

cmd/list/vendor_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package list
22

33
import (
4+
"os"
5+
"path/filepath"
6+
"runtime"
7+
"strings"
48
"testing"
59
)
610

@@ -13,3 +17,64 @@ func TestListVendorCmd_WithoutStacks(t *testing.T) {
1317
// No runtime test needed - this is enforced by code structure.
1418
t.Log("list vendor command uses InitCliConfig with processStacks=false")
1519
}
20+
21+
// TestObfuscateHomeDirInOutput verifies that home directory paths are properly obfuscated.
22+
func TestObfuscateHomeDirInOutput(t *testing.T) {
23+
// Determine expected home directory.
24+
homeDir := os.Getenv("HOME")
25+
if runtime.GOOS == "windows" {
26+
if userProfile := os.Getenv("USERPROFILE"); userProfile != "" {
27+
homeDir = userProfile
28+
}
29+
}
30+
31+
if homeDir == "" {
32+
t.Skip("Could not determine home directory for test")
33+
}
34+
35+
tests := []struct {
36+
name string
37+
input string
38+
expected string
39+
}{
40+
{
41+
name: "absolute path with home directory",
42+
input: filepath.Join(homeDir, "path", "to", "file"),
43+
expected: filepath.Join("~", "path", "to", "file"),
44+
},
45+
{
46+
name: "home directory only",
47+
input: homeDir,
48+
expected: "~",
49+
},
50+
{
51+
name: "path without home directory",
52+
input: "/var/lib/atmos/vendor",
53+
expected: "/var/lib/atmos/vendor",
54+
},
55+
{
56+
name: "mixed content with home directory",
57+
input: "Component: vpc\nManifest: " + filepath.Join(homeDir, ".atmos", "vendor.yaml"),
58+
expected: "Component: vpc\nManifest: " + filepath.Join("~", ".atmos", "vendor.yaml"),
59+
},
60+
{
61+
name: "multiple occurrences of home directory",
62+
input: homeDir + "/path1 and " + homeDir + "/path2",
63+
expected: "~/path1 and ~/path2",
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
result := obfuscateHomeDirInOutput(tt.input)
70+
if result != tt.expected {
71+
t.Errorf("obfuscateHomeDirInOutput() = %q, want %q", result, tt.expected)
72+
}
73+
74+
// Verify home directory is not present in output.
75+
if strings.Contains(result, homeDir) {
76+
t.Errorf("obfuscateHomeDirInOutput() still contains home directory: %q", result)
77+
}
78+
})
79+
}
80+
}

0 commit comments

Comments
 (0)