Skip to content

Commit 85928c9

Browse files
committed
only accept valid git URL formats
1 parent 4ad93b8 commit 85928c9

File tree

4 files changed

+29
-32
lines changed

4 files changed

+29
-32
lines changed

git/git.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ func LogHostKeyCallback(logger func(string, ...any)) gossh.HostKeyCallback {
241241
// If SSH_KNOWN_HOSTS is not set, the SSH auth method will be configured
242242
// to accept and log all host keys. Otherwise, host key checking will be
243243
// performed as usual.
244+
//
245+
// Git URL formats may only consist of the following:
246+
// 1. A valid URL with a scheme
247+
// 2. An SCP-like URL (e.g. [email protected]:path/to/repo.git)
248+
// 3. Local filesystem paths (require `git` executable)
244249
func SetupRepoAuth(logf func(string, ...any), options *options.Options) transport.AuthMethod {
245250
if options.GitURL == "" {
246251
logf("❔ No Git URL supplied!")

git/git_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ func TestSetupRepoAuth(t *testing.T) {
397397
// Anything that is not https:// or http:// is treated as SSH.
398398
kPath := writeTestPrivateKey(t)
399399
opts := &options.Options{
400-
GitURL: "git://[email protected]:repo/path",
400+
GitURL: "git://[email protected]:12345/path",
401401
GitSSHPrivateKeyPath: kPath,
402402
}
403403
auth := git.SetupRepoAuth(t.Logf, opts)
@@ -420,7 +420,7 @@ func TestSetupRepoAuth(t *testing.T) {
420420
t.Run("SSH/PrivateKey", func(t *testing.T) {
421421
kPath := writeTestPrivateKey(t)
422422
opts := &options.Options{
423-
GitURL: "ssh://[email protected]:repo/path",
423+
GitURL: "ssh://[email protected]/repo/path",
424424
GitSSHPrivateKeyPath: kPath,
425425
}
426426
auth := git.SetupRepoAuth(t.Logf, opts)
@@ -434,7 +434,7 @@ func TestSetupRepoAuth(t *testing.T) {
434434

435435
t.Run("SSH/Base64PrivateKey", func(t *testing.T) {
436436
opts := &options.Options{
437-
GitURL: "ssh://[email protected]:repo/path",
437+
GitURL: "ssh://[email protected]/repo/path",
438438
GitSSHPrivateKeyBase64: base64EncodeTestPrivateKey(),
439439
}
440440
auth := git.SetupRepoAuth(t.Logf, opts)
@@ -450,7 +450,7 @@ func TestSetupRepoAuth(t *testing.T) {
450450

451451
t.Run("SSH/NoAuthMethods", func(t *testing.T) {
452452
opts := &options.Options{
453-
GitURL: "ssh://[email protected]:repo/path",
453+
GitURL: "[email protected]:repo/path",
454454
}
455455
auth := git.SetupRepoAuth(t.Logf, opts)
456456
require.Nil(t, auth) // TODO: actually test SSH_AUTH_SOCK

internal/ebutil/giturls.go

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@ package ebutil
22

33
import (
44
"fmt"
5-
"net/url"
65
"strings"
76

87
gittransport "github.com/go-git/go-git/v5/plumbing/transport"
98
)
109

10+
type InvalidRepoURLError struct {
11+
repoURL string
12+
inner error
13+
}
14+
15+
func (e *InvalidRepoURLError) Error() string {
16+
return fmt.Sprintf("invalid repository URL %q, please see https://github.com/coder/envbuilder/blob/main/docs/git-auth.md for supported formats: %v", e.repoURL, e.inner)
17+
}
18+
1119
type ParsedURL struct {
1220
Protocol string
1321
User string
@@ -21,22 +29,17 @@ type ParsedURL struct {
2129
// ParseRepoURL parses the given repository URL into its components.
2230
// We used to use chainguard-dev/git-urls for this, but its behaviour
2331
// diverges from the go-git URL parser. To ensure consistency, we now
24-
// use go-git directly with some tweaks.
32+
// use go-git directly.
2533
func ParseRepoURL(repoURL string) (*ParsedURL, error) {
26-
repoURL = fixupScheme(repoURL, "ssh://")
27-
repoURL = fixupScheme(repoURL, "git://")
28-
repoURL = fixupScheme(repoURL, "git+ssh://")
29-
parsed, err := gittransport.NewEndpoint(repoURL)
30-
if err != nil {
31-
return nil, fmt.Errorf("parse repo url %q: %w", repoURL, err)
32-
}
3334
// Trim #reference from path
3435
var reference string
35-
if len(parsed.Path) > 0 { // annoyingly, strings.Index returns 0 if len(s) == 0
36-
if idx := strings.Index(parsed.Path, "#"); idx > -1 {
37-
reference = parsed.Path[idx+1:]
38-
parsed.Path = parsed.Path[:idx]
39-
}
36+
if idx := strings.Index(repoURL, "#"); idx > -1 {
37+
reference = repoURL[idx+1:]
38+
repoURL = repoURL[:idx]
39+
}
40+
parsed, err := gittransport.NewEndpoint(repoURL)
41+
if err != nil {
42+
return nil, &InvalidRepoURLError{repoURL: repoURL, inner: err}
4043
}
4144
return &ParsedURL{
4245
Protocol: parsed.Protocol,
@@ -48,14 +51,3 @@ func ParseRepoURL(repoURL string) (*ParsedURL, error) {
4851
Reference: reference,
4952
}, nil
5053
}
51-
52-
func fixupScheme(repoURL, scheme string) string {
53-
// go-git tries to handle protocol:// URLs with url.Parse. This fails
54-
// in the case of e.g. (ssh|git)://git@host:user/path.git
55-
if cut, found := strings.CutPrefix(repoURL, scheme); found {
56-
if _, err := url.Parse(repoURL); err != nil && strings.Contains(err.Error(), "invalid port") {
57-
return cut
58-
}
59-
}
60-
return repoURL
61-
}

options/defaults_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ func TestDefaultWorkspaceFolder(t *testing.T) {
3434
expected: "/workspaces/envbuilder",
3535
},
3636
{
37-
name: "SSH with prefix",
37+
name: "SSH with scheme",
3838
baseDir: "/workspaces",
39-
gitURL: "ssh://[email protected]:coder/envbuilder.git",
39+
gitURL: "ssh://[email protected]/coder/envbuilder.git",
4040
expected: "/workspaces/envbuilder",
4141
},
4242
{
@@ -46,7 +46,7 @@ func TestDefaultWorkspaceFolder(t *testing.T) {
4646
expected: "/workspaces/envbuilder",
4747
},
4848
{
49-
name: "Git+SSH protocol is SSH",
49+
name: "Git+SSH",
5050
baseDir: "/workspaces",
5151
gitURL: "git+ssh://github.com/coder/envbuilder.git",
5252
expected: "/workspaces/envbuilder",

0 commit comments

Comments
 (0)