Commit 0c7b382159
0c7b382159eb8ab7f8c6840626d0fce3fa15eb13
parent: fad7a633f4
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-29 02:27 UTC
control: mirror add refuses a numerically written host
Ref #298
Layout: unified · split
CHANGELOG.org
+3
| @@ -19,6 +19,9 @@ anything beyond "replace the binary and restart" is needed. |
| 19 | global gitconfig are ignored. A source that redirects (a renamed |
19 | global gitconfig are ignored. A source that redirects (a renamed |
| 20 | repository) fails; import from the URL it redirects to. Needs git |
20 | repository) fails; import from the URL it redirects to. Needs git |
| 21 | 2.37 or later on the server (#298). |
21 | 2.37 or later on the server (#298). |
| |
22 | - =repo mirror add= refuses a host written numerically (=127.1=, |
| |
23 | =2130706433=, =0x7f.1=) when the mirror is added, rather than at its |
| |
24 | first sync (#298). |
| 22 | - A browser session creates credentials and grants access — keys, PGP |
25 | - A browser session creates credentials and grants access — keys, PGP |
| 23 | keys, tokens, verified addresses, org and repository roles, transfers, |
26 | keys, tokens, verified addresses, org and repository roles, transfers, |
| 24 | webhooks, secrets, mirrors, and the admin promote/enable actions — |
27 | webhooks, secrets, mirrors, and the admin promote/enable actions — |
internal/control/mirrorcmd.go
+9
| @@ -5,9 +5,11 @@ import ( |
| 5 | "errors" |
5 | "errors" |
| 6 | "fmt" |
6 | "fmt" |
| 7 | "io" |
7 | "io" |
| |
8 | "net/url" |
| 8 | "strconv" |
9 | "strconv" |
| 9 | "strings" |
10 | "strings" |
| 10 | |
11 | |
| |
12 | "gitbay.org/gitbay/internal/gitpin" |
| 11 | "gitbay.org/gitbay/internal/policy" |
13 | "gitbay.org/gitbay/internal/policy" |
| 12 | "gitbay.org/gitbay/internal/protocol" |
14 | "gitbay.org/gitbay/internal/protocol" |
| 13 | "gitbay.org/gitbay/internal/store" |
15 | "gitbay.org/gitbay/internal/store" |
| @@ -54,6 +56,13 @@ func runMirrorAdd(c *Ctx, args []string) int { |
| 54 | if path == "" || urlArg == "" || (direction != "push" && direction != "pull") { |
56 | if path == "" || urlArg == "" || (direction != "push" && direction != "pull") { |
| 55 | return c.usage() |
57 | return c.usage() |
| 56 | } |
58 | } |
| |
59 | // Sync refuses a numerically written host; say so now, before a |
| |
60 | // resolver gets to read it. |
| |
61 | if u, err := url.Parse(urlArg); err == nil { |
| |
62 | if err := gitpin.CheckHost(u.Hostname()); err != nil { |
| |
63 | return c.failInput(err) |
| |
64 | } |
| |
65 | } |
| 57 | // The worker's git process dials this URL from the server: same SSRF |
66 | // The worker's git process dials this URL from the server: same SSRF |
| 58 | // surface as a webhook target, same rules. |
67 | // surface as a webhook target, same rules. |
| 59 | if err := webhook.ValidateURL(urlArg, c.Cfg.Webhooks.AllowLocal); err != nil { |
68 | if err := webhook.ValidateURL(urlArg, c.Cfg.Webhooks.AllowLocal); err != nil { |
internal/control/mirrorcmd_test.go
added
+29
| @@ -0,0 +1,29 @@ |
| |
1 | package control |
| |
2 | |
| |
3 | import ( |
| |
4 | "strings" |
| |
5 | "testing" |
| |
6 | |
| |
7 | "gitbay.org/gitbay/internal/protocol" |
| |
8 | ) |
| |
9 | |
| |
10 | // mirror add refuses a host sync would refuse as numeric, on an |
| |
11 | // instance that allows local targets or not, and stores nothing (#298). |
| |
12 | func TestMirrorAddRefusesNumericHost(t *testing.T) { |
| |
13 | for _, allowLocal := range []bool{true, false} { |
| |
14 | for _, host := range []string{"127.1", "2130706433", "0x7f.1"} { |
| |
15 | c, errOut, st, _ := importCtx(t, allowLocal) |
| |
16 | code := Dispatch(c, []string{"repo", "mirror", "add", "alice/app", "https://" + host + "/x.git", "--direction", "pull"}) |
| |
17 | if code != protocol.ExitUsage || !strings.Contains(errOut.String(), "numeric address") { |
| |
18 | t.Fatalf("%s (allow_local %v): exit %d, %q", host, allowLocal, code, errOut.String()) |
| |
19 | } |
| |
20 | repo, err := st.RepoByPath("alice/app") |
| |
21 | if err != nil { |
| |
22 | t.Fatal(err) |
| |
23 | } |
| |
24 | if ms, err := st.ListMirrors(repo.ID); err != nil || len(ms) != 0 { |
| |
25 | t.Fatalf("%s: mirrors %v, %v", host, ms, err) |
| |
26 | } |
| |
27 | } |
| |
28 | } |
| |
29 | } |
internal/gitpin/gitpin.go
+13 −4
| @@ -46,10 +46,8 @@ func Resolve(ctx context.Context, lookup Lookup, raw string, allowLocal bool) (R |
| 46 | if host == "" { |
46 | if host == "" { |
| 47 | return Remote{}, fmt.Errorf("URL has no host") |
47 | return Remote{}, fmt.Errorf("URL has no host") |
| 48 | } |
48 | } |
| 49 | if net.ParseIP(host) == nil && numericHost(host) { |
49 | if err := CheckHost(host); err != nil { |
| 50 | // 127.1, 2130706433 and 0x7f.1 are loopback to curl's parser |
50 | return Remote{}, err |
| 51 | // but not to Go's; refuse rather than leave them to a resolver. |
| |
| 52 | return Remote{}, fmt.Errorf("host %q is a numeric address in a form other than dotted decimal; write it as a.b.c.d", host) |
| |
| 53 | } |
51 | } |
| 54 | ips, err := lookup(ctx, host) |
52 | ips, err := lookup(ctx, host) |
| 55 | if err != nil { |
53 | if err != nil { |
| @@ -65,6 +63,17 @@ func Resolve(ctx context.Context, lookup Lookup, raw string, allowLocal bool) (R |
| 65 | return Remote{URL: u, IPs: ips}, nil |
63 | return Remote{URL: u, IPs: ips}, nil |
| 66 | } |
64 | } |
| 67 | |
65 | |
| |
66 | // CheckHost refuses a host written as a number in a form other than |
| |
67 | // an IP literal: 127.1, 2130706433 and 0x7f.1 are loopback to curl's |
| |
68 | // parser but not to Go's, so they are refused rather than left to a |
| |
69 | // resolver. |
| |
70 | func CheckHost(host string) error { |
| |
71 | if net.ParseIP(host) == nil && numericHost(host) { |
| |
72 | return fmt.Errorf("host %q is a numeric address in a form other than dotted decimal; write it as a.b.c.d", host) |
| |
73 | } |
| |
74 | return nil |
| |
75 | } |
| |
76 | |
| 68 | // numericHost reports whether every label of host is a decimal, octal |
77 | // numericHost reports whether every label of host is a decimal, octal |
| 69 | // or hex number, the shapes inet_aton reads as an IPv4 address. |
78 | // or hex number, the shapes inet_aton reads as an IPv4 address. |
| 70 | func numericHost(host string) bool { |
79 | func numericHost(host string) bool { |