Summary
pkg/name/registry.go uses unanchored regular expressions to detect
loopback addresses for the purpose of choosing HTTP vs HTTPS scheme.
Because regexp.QuoteMeta only escapes metacharacters and does not
add ^/$ anchors, the regex matches the literal substring
127.0.0.1 (or ::1) anywhere in the hostname. Any registry hostname
that embeds either substring is misclassified as loopback and
contacted over plaintext HTTP instead of HTTPS.
This was reported to the Google OSS-VRP and acknowledged as a valid
security concern (TLS downgrade / potential data exposure) but
classified as a product vulnerability under OT2/OT3 — Google
suggested I file it here directly. Hence this issue.
Affected code
pkg/name/registry.go lines 30-34:
// Detect the loopback IP (127.0.0.1)
var reLoopback = regexp.MustCompile(regexp.QuoteMeta("127.0.0.1"))
// Detect the loopback IPV6 (::1)
var reipv6Loopback = regexp.MustCompile(regexp.QuoteMeta("::1"))
Compare the sibling reLocal on the same file, which IS properly
anchored — so the existing convention in this file is to anchor:
var reLocal = regexp.MustCompile(`.*\.localhost(?::\d{1,5})?$`)
Reproducer
package main
import (
"fmt"
"github.com/google/go-containerregistry/pkg/name"
)
func main() {
for _, h := range []string{
"127.0.0.1", // intended loopback, should be http
"127.0.0.1:5000", // intended loopback, should be http
"[::1]:5000", // intended loopback, should be http
"127.0.0.1.evil.com", // NOT loopback, should be https
"127.0.0.1.evil.com:5000", // NOT loopback, should be https
"my127.0.0.1registry.io", // NOT loopback, should be https
"evil.com.127.0.0.1.nip.io", // NOT loopback, should be https
"x127.0.0.1x.com", // NOT loopback, should be https
"[2001:db8::1]:5000", // global IPv6, should be https
} {
r, err := name.NewRegistry(h)
if err != nil {
fmt.Printf("%q rejected: %v\n", h, err)
continue
}
fmt.Printf("%q -> %s\n", h, r.Scheme())
}
}
Output against v0.21.6:
"127.0.0.1" -> http
"127.0.0.1:5000" -> http
"[::1]:5000" -> http
"127.0.0.1.evil.com" -> http <-- BUG (should be https)
"127.0.0.1.evil.com:5000" -> http <-- BUG
"my127.0.0.1registry.io" -> http <-- BUG
"evil.com.127.0.0.1.nip.io" -> http <-- BUG
"x127.0.0.1x.com" -> http <-- BUG
"[2001:db8::1]:5000" -> http <-- BUG
Versions confirmed vulnerable
- v0.21.6 (latest released at time of report)
- master @ commit
d807d512681c6b18391a8fa0ae0fb27c36ba5f4d (2026-05-19)
Impact
Any consumer of pkg/name that uses Registry.Scheme() to construct
outbound HTTP requests — which includes crane, cosign, kpack,
ko, skaffold, and any tool that takes user-supplied image
references. An attacker controlling a hostname that embeds the
substring (trivial via wildcard DNS or owning any domain such as
127.0.0.1.attacker.tld) can force the client to use plaintext HTTP,
exposing bearer tokens / basic-auth credentials on the wire and
enabling on-path tampering of manifest/layer responses.
For IPv6 the issue is broader: any address whose zero-compression
places ::1 anywhere in the textual form (2001:db8::1, fe80::1,
fec0::1, etc.) is misclassified.
Suggested fix
Either anchor the regexes:
var reLoopback = regexp.MustCompile(`^127\.0\.0\.1(?::\d{1,5})?$`)
var reipv6Loopback = regexp.MustCompile(`^\[::1\](?::\d{1,5})?$`)
Or, preferably, drop the regex and use structured parsing — this
also fixes a related weakness in isRFC1918() which uses
strings.Split(r.Name(), ":")[0] for port stripping and does not
handle bracketed IPv6 hosts:
func (r Registry) isLoopback() bool {
h := r.Name()
// Strip port and IPv6 brackets, then parse as IP.
if i := strings.LastIndex(h, ":"); i >= 0 && !strings.HasSuffix(h[:i], "]") {
h = h[:i]
}
h = strings.TrimSuffix(strings.TrimPrefix(h, "["), "]")
if ip := net.ParseIP(h); ip != nil {
return ip.IsLoopback()
}
return false
}
Summary
pkg/name/registry.gouses unanchored regular expressions to detectloopback addresses for the purpose of choosing HTTP vs HTTPS scheme.
Because
regexp.QuoteMetaonly escapes metacharacters and does notadd
^/$anchors, the regex matches the literal substring127.0.0.1(or::1) anywhere in the hostname. Any registry hostnamethat embeds either substring is misclassified as loopback and
contacted over plaintext HTTP instead of HTTPS.
This was reported to the Google OSS-VRP and acknowledged as a valid
security concern (TLS downgrade / potential data exposure) but
classified as a product vulnerability under OT2/OT3 — Google
suggested I file it here directly. Hence this issue.
Affected code
pkg/name/registry.golines 30-34:Compare the sibling
reLocalon the same file, which IS properlyanchored — so the existing convention in this file is to anchor:
Reproducer
Output against
v0.21.6:Versions confirmed vulnerable
d807d512681c6b18391a8fa0ae0fb27c36ba5f4d(2026-05-19)Impact
Any consumer of
pkg/namethat usesRegistry.Scheme()to constructoutbound HTTP requests — which includes
crane,cosign,kpack,ko,skaffold, and any tool that takes user-supplied imagereferences. An attacker controlling a hostname that embeds the
substring (trivial via wildcard DNS or owning any domain such as
127.0.0.1.attacker.tld) can force the client to use plaintext HTTP,exposing bearer tokens / basic-auth credentials on the wire and
enabling on-path tampering of manifest/layer responses.
For IPv6 the issue is broader: any address whose zero-compression
places
::1anywhere in the textual form (2001:db8::1,fe80::1,fec0::1, etc.) is misclassified.Suggested fix
Either anchor the regexes:
Or, preferably, drop the regex and use structured parsing — this
also fixes a related weakness in
isRFC1918()which usesstrings.Split(r.Name(), ":")[0]for port stripping and does nothandle bracketed IPv6 hosts: