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.
1919 global gitconfig are ignored. A source that redirects (a renamed
2020 repository) fails; import from the URL it redirects to. Needs git
2121 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).
2225- A browser session creates credentials and grants access — keys, PGP
2326 keys, tokens, verified addresses, org and repository roles, transfers,
2427 webhooks, secrets, mirrors, and the admin promote/enable actions —
internal/control/mirrorcmd.go +9
@@ -5,9 +5,11 @@ import (
55 "errors"
66 "fmt"
77 "io"
8 "net/url"
89 "strconv"
910 "strings"
1011
12 "gitbay.org/gitbay/internal/gitpin"
1113 "gitbay.org/gitbay/internal/policy"
1214 "gitbay.org/gitbay/internal/protocol"
1315 "gitbay.org/gitbay/internal/store"
@@ -54,6 +56,13 @@ func runMirrorAdd(c *Ctx, args []string) int {
5456 if path == "" || urlArg == "" || (direction != "push" && direction != "pull") {
5557 return c.usage()
5658 }
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 }
5766 // The worker's git process dials this URL from the server: same SSRF
5867 // surface as a webhook target, same rules.
5968 if err := webhook.ValidateURL(urlArg, c.Cfg.Webhooks.AllowLocal); err != nil {
internal/control/mirrorcmd_test.go added +29
@@ -0,0 +1,29 @@
1package control
2
3import (
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).
12func 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
4646 if host == "" {
4747 return Remote{}, fmt.Errorf("URL has no host")
4848 }
49 if net.ParseIP(host) == nil && numericHost(host) {
50 // 127.1, 2130706433 and 0x7f.1 are loopback to curl's parser
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)
49 if err := CheckHost(host); err != nil {
50 return Remote{}, err
5351 }
5452 ips, err := lookup(ctx, host)
5553 if err != nil {
@@ -65,6 +63,17 @@ func Resolve(ctx context.Context, lookup Lookup, raw string, allowLocal bool) (R
6563 return Remote{URL: u, IPs: ips}, nil
6664}
6765
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.
70func 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
6877// numericHost reports whether every label of host is a decimal, octal
6978// or hex number, the shapes inet_aton reads as an IPv4 address.
7079func numericHost(host string) bool {