Skip to content

Commit 13ff63d

Browse files
committed
fix: harden credential helper against trailing newlines and malformed Basic credentials
Trim trailing \r\n from mounted secrets before use in both token and header auth paths, preventing broken credential protocol responses when secret files contain trailing whitespace. Validate that decoded Basic auth credentials contain a colon separator per RFC 7617, returning a clear error instead of silently passing an empty password. Signed-off-by: Brian Goff <cpuguy83@gmail.com> (cherry picked from commit ad3d032) Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent e4af9d2 commit 13ff63d

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

cmd/frontend/git_credential_helper.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func generateResponse(payload *gitPayload, secret []byte, kind string) (string,
245245
}
246246

247247
func handleSecretHeader(b []byte, payload *gitPayload) (string, error) {
248-
s := string(b)
248+
s := strings.TrimRight(string(b), "\r\n")
249249
authtype, credential, ok := strings.Cut(s, " ")
250250
if !ok {
251251
return "", fmt.Errorf("improperly formatted auth header")
@@ -261,7 +261,10 @@ func handleSecretHeader(b []byte, payload *gitPayload) (string, error) {
261261
if err != nil {
262262
return "", fmt.Errorf("could not decode basic auth credential: %w", err)
263263
}
264-
user, pass, _ := strings.Cut(string(decoded), ":")
264+
user, pass, ok := strings.Cut(string(decoded), ":")
265+
if !ok {
266+
return "", fmt.Errorf("improperly formatted basic auth credential")
267+
}
265268
payload.username = user
266269
payload.password = pass
267270
} else {
@@ -284,7 +287,7 @@ func handleSecretToken(token []byte, payload *gitPayload) (string, error) {
284287
// Pre-2.46 git does not support authtype/credential protocol fields.
285288
// Fall back to username/password which maps to HTTP Basic auth.
286289
payload.username = "x-access-token"
287-
payload.password = string(token)
290+
payload.password = strings.TrimRight(string(token), "\r\n")
288291
}
289292

290293
return printPayload(payload), nil

cmd/frontend/git_credential_helper_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ func TestHandleSecretToken(t *testing.T) {
4848
assertFieldAbsent(t, resp, keyUsername)
4949
assertFieldAbsent(t, resp, keyPassword)
5050
})
51+
52+
t.Run("trailing newline trimmed from token", func(t *testing.T) {
53+
// Secrets mounted from files may have trailing newlines.
54+
// These must be stripped to avoid breaking the credential protocol response.
55+
payload := &gitPayload{
56+
protocol: "https",
57+
host: "github.com",
58+
}
59+
60+
resp, err := handleSecretToken([]byte("ghp_abc123\n"), payload)
61+
if err != nil {
62+
t.Fatalf("unexpected error: %v", err)
63+
}
64+
65+
assertFieldEquals(t, resp, keyPassword, "ghp_abc123")
66+
})
5167
}
5268

5369
func TestHandleSecretHeader(t *testing.T) {
@@ -117,6 +133,38 @@ func TestHandleSecretHeader(t *testing.T) {
117133
t.Fatal("expected error for malformed header, got nil")
118134
}
119135
})
136+
137+
t.Run("trailing newline trimmed from header", func(t *testing.T) {
138+
// Secrets mounted from files may have trailing newlines.
139+
payload := &gitPayload{
140+
protocol: "https",
141+
host: "github.com",
142+
capability: []string{"authtype"},
143+
}
144+
145+
resp, err := handleSecretHeader([]byte("Bearer eyJhbGciOi\n"), payload)
146+
if err != nil {
147+
t.Fatalf("unexpected error: %v", err)
148+
}
149+
150+
assertFieldEquals(t, resp, keyAuthtype, "Bearer")
151+
assertFieldEquals(t, resp, keyCredential, "eyJhbGciOi")
152+
})
153+
154+
t.Run("malformed basic credential no colon", func(t *testing.T) {
155+
// A Basic credential that decodes to a string without a colon
156+
// is invalid per RFC 7617 and should be rejected.
157+
payload := &gitPayload{
158+
protocol: "https",
159+
host: "github.com",
160+
}
161+
162+
cred := base64.StdEncoding.EncodeToString([]byte("nocolonhere"))
163+
_, err := handleSecretHeader([]byte("Basic "+cred), payload)
164+
if err == nil {
165+
t.Fatal("expected error for basic credential without colon, got nil")
166+
}
167+
})
120168
}
121169

122170
func TestReadPayload(t *testing.T) {

0 commit comments

Comments
 (0)