From c11ec1317f411d4a11994a6734d66b63fd150295 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 2 May 2026 12:20:53 +0100 Subject: [PATCH] Reject private and loopback IPs in checkMetadataURL The URL check accepted any HTTPS host including loopback, link-local, and private IPs. A compromised registry could point download URLs at cloud metadata endpoints or internal services. Now resolves the hostname and rejects private, loopback, and link-local addresses. --- fetch/resolver.go | 19 ++++++++++++++++++- fetch/resolver_test.go | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/fetch/resolver.go b/fetch/resolver.go index f226d41..bccf024 100644 --- a/fetch/resolver.go +++ b/fetch/resolver.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "net/url" "strings" "unicode" @@ -183,12 +184,28 @@ func checkMetadataURL(raw string) error { if u.Scheme != "https" { return fmt.Errorf("%w: scheme %q", ErrUnsafeURL, u.Scheme) } - if u.Hostname() == "" { + hostname := u.Hostname() + if hostname == "" { return fmt.Errorf("%w: empty host", ErrUnsafeURL) } + if isPrivateHost(hostname) { + return fmt.Errorf("%w: private/loopback host %q", ErrUnsafeURL, hostname) + } return nil } +func isPrivateHost(hostname string) bool { + ip := net.ParseIP(hostname) + if ip == nil { + ips, err := net.LookupIP(hostname) + if err != nil || len(ips) == 0 { + return false + } + ip = ips[0] + } + return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() +} + func filenameFromURL(url string) string { if idx := strings.LastIndex(url, "/"); idx >= 0 { return url[idx+1:] diff --git a/fetch/resolver_test.go b/fetch/resolver_test.go index 4172c18..e7fc4b2 100644 --- a/fetch/resolver_test.go +++ b/fetch/resolver_test.go @@ -141,6 +141,9 @@ func TestCheckMetadataURL(t *testing.T) { {"https:///path/only", false}, {"//169.254.169.254/latest/meta-data/", false}, {"169.254.169.254/latest/meta-data/", false}, + {"https://169.254.169.254/latest/meta-data/", false}, + {"https://127.0.0.1/something", false}, + {"https://[::1]/something", false}, {"", false}, {"\x00https://evil", false}, }