1package secrets
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/bnema/zerowrap"
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11
12 "gordon/internal/domain"
13)
14
15func TestValidatePath(t *testing.T) {
16 tests := []struct {
17 name string
18 path string
19 wantErr bool
20 errType error
21 }{
22 // Valid paths
23 {
24 name: "simple path",
25 path: "gordon/registry/secret",
26 wantErr: false,
27 },
28 {
29 name: "path with dots in name",
30 path: "my.secret.key",
31 wantErr: false,
32 },
33 {
34 name: "path with hyphens",
35 path: "my-app/api-key",
36 wantErr: false,
37 },
38 {
39 name: "path with underscores",
40 path: "my_app/api_key",
41 wantErr: false,
42 },
43 {
44 name: "nested path",
45 path: "org/team/app/secret",
46 wantErr: false,
47 },
48
49 // Invalid paths - path traversal
50 {
51 name: "path traversal with double dots",
52 path: "../../../etc/passwd",
53 wantErr: true,
54 errType: domain.ErrPathTraversal,
55 },
56 {
57 name: "path traversal in middle",
58 path: "foo/../../../etc/passwd",
59 wantErr: true,
60 errType: domain.ErrPathTraversal,
61 },
62 {
63 name: "path traversal at end",
64 path: "foo/bar/..",
65 wantErr: true,
66 errType: domain.ErrPathTraversal,
67 },
68 {
69 name: "encoded path traversal",
70 path: "..%2f..%2fetc/passwd",
71 wantErr: true, // % is not allowed by regex
72 },
73
74 // Invalid paths - absolute paths
75 {
76 name: "absolute path",
77 path: "/etc/passwd",
78 wantErr: true,
79 },
80 {
81 name: "absolute path with valid chars",
82 path: "/gordon/secret",
83 wantErr: true,
84 },
85
86 // Invalid paths - special characters (command injection)
87 {
88 name: "semicolon injection",
89 path: "secret;rm -rf /",
90 wantErr: true,
91 },
92 {
93 name: "pipe injection",
94 path: "secret|cat /etc/passwd",
95 wantErr: true,
96 },
97 {
98 name: "backtick injection",
99 path: "secret`whoami`",
100 wantErr: true,
101 },
102 {
103 name: "dollar injection",
104 path: "secret$(whoami)",
105 wantErr: true,
106 },
107 {
108 name: "ampersand injection",
109 path: "secret&rm -rf /",
110 wantErr: true,
111 },
112 {
113 name: "newline injection",
114 path: "secret\nrm -rf /",
115 wantErr: true,
116 },
117 {
118 name: "space in path",
119 path: "secret with space",
120 wantErr: true,
121 },
122 {
123 name: "null byte injection",
124 path: "secret\x00evil",
125 wantErr: true,
126 },
127
128 // Edge cases
129 {
130 name: "empty path",
131 path: "",
132 wantErr: true,
133 },
134 {
135 name: "single dot",
136 path: ".",
137 wantErr: false, // single dot is valid, not traversal
138 },
139 }
140
141 for _, tt := range tests {
142 t.Run(tt.name, func(t *testing.T) {
143 err := ValidatePath(tt.path)
144
145 if tt.wantErr {
146 require.Error(t, err, "expected error for path: %q", tt.path)
147 if tt.errType != nil {
148 assert.True(t, errors.Is(err, tt.errType), "expected error type %v, got %v", tt.errType, err)
149 }
150 } else {
151 assert.NoError(t, err, "unexpected error for path: %q", tt.path)
152 }
153 })
154 }
155}
156
157func testLogger() zerowrap.Logger {
158 return zerowrap.Default()
159}
160
161func TestPassProvider_GetSecret_PathValidation(t *testing.T) {
162 provider := NewPassProvider(testLogger())
163
164 tests := []struct {
165 name string
166 path string
167 wantErr bool
168 }{
169 {
170 name: "valid path rejected due to pass not installed",
171 path: "gordon/test/secret",
172 wantErr: true, // Will fail because pass command doesn't exist in test, but path is valid
173 },
174 {
175 name: "path traversal rejected before command execution",
176 path: "../../../etc/passwd",
177 wantErr: true,
178 },
179 {
180 name: "command injection rejected before command execution",
181 path: "secret;whoami",
182 wantErr: true,
183 },
184 }
185
186 for _, tt := range tests {
187 t.Run(tt.name, func(t *testing.T) {
188 _, err := provider.GetSecret(context.Background(), tt.path)
189
190 if tt.wantErr {
191 require.Error(t, err)
192 }
193
194 // For security-related rejections, verify the error is from validation, not command execution
195 if tt.path == "../../../etc/passwd" {
196 assert.True(t, errors.Is(err, domain.ErrPathTraversal))
197 }
198 if tt.path == "secret;whoami" {
199 assert.Contains(t, err.Error(), "invalid path")
200 }
201 })
202 }
203}