v2.31.0-dev20 020cc00

pass_test.go RawHistoryFolder

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	"github.com/bnema/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:    "github.com/bnema/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:    "github.com/bnema/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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close