From acc867b88e68e5f9c074c1c8c584a57b3ae6c127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:33:14 +0000 Subject: [PATCH 1/2] chore(deps): bump oras.land/oras-go/v2 from 2.6.0 to 2.6.1 Bumps [oras.land/oras-go/v2](https://github.com/oras-project/oras-go) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/oras-project/oras-go/releases) - [Commits](https://github.com/oras-project/oras-go/compare/v2.6.0...v2.6.1) --- updated-dependencies: - dependency-name: oras.land/oras-go/v2 dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 83102d224..8ca9ab85e 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( k8s.io/client-go v0.35.1 k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.35.1 - oras.land/oras-go/v2 v2.6.0 + oras.land/oras-go/v2 v2.6.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 136675e29..7bab59d58 100644 --- a/go.sum +++ b/go.sum @@ -523,8 +523,8 @@ k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= +oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= From 2fb05f8a35f6c0d5760c03c12d60c46adf2b3d12 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 10 Jun 2026 18:53:09 -0600 Subject: [PATCH 2/2] fix(registry): keep credentials on plain-HTTP fallback with oras-go v2.6.1 oras-go v2.6.1 hardens the auth client to drop the Authorization header when a request's origin changes mid-flight (GHSA-vh4v-2xq2-g5cg). Helm's fallbackTransport reaches plain-HTTP registries by downgrading the connection from https to http inside a single round trip, which oras now treats as a credential-leaking origin change and refuses to authenticate. On login, detect when the transport has fallen back to plain HTTP and, in that case, set PlainHTTP explicitly and re-ping so requests are built as http from the start. The scheme no longer changes mid-request, credentials flow as before, and the new cross-origin protection is preserved for real https registries (forcedHTTP stays false, so the retry never triggers). Signed-off-by: Terry Howe --- pkg/registry/client.go | 43 ++++++++++++++++++++++++++++++++++------ pkg/registry/fallback.go | 8 ++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index b14b767b2..03ff2c9a9 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -296,15 +296,22 @@ func (c *Client) Login(host string, options ...LoginOption) error { } reg.PlainHTTP = c.plainHTTP cred := auth.Credential{Username: c.username, Password: c.password} - c.authorizer.ForceAttemptOAuth2 = true reg.Client = c.authorizer ctx := context.Background() - if err := reg.Ping(ctx); err != nil { - c.authorizer.ForceAttemptOAuth2 = false - if err := reg.Ping(ctx); err != nil { - return fmt.Errorf("authenticating to %q: %w", host, err) - } + err = c.ping(ctx, reg) + if err != nil && !reg.PlainHTTP && c.forcedHTTP() { + // The registry is plain HTTP: the fallback transport downgraded the + // connection from https to http. ORAS v2.6.1+ refuses to forward + // credentials across that implicit scheme change (GHSA-vh4v-2xq2-g5cg), + // so the credentialed ping above fails. Now that the fallback has been + // detected, set PlainHTTP explicitly and retry so requests are built as + // http from the start and the scheme no longer changes mid-request. + reg.PlainHTTP = true + err = c.ping(ctx, reg) + } + if err != nil { + return fmt.Errorf("authenticating to %q: %w", host, err) } // The credentialsStore loader does not handle empty files. So, there is a workaround. @@ -341,6 +348,30 @@ func (c *Client) Login(host string, options ...LoginOption) error { return nil } +// ping authenticates against the registry, first attempting the OAuth2 token +// flow and falling back to the basic/refresh token flow on failure. +func (c *Client) ping(ctx context.Context, reg *remote.Registry) error { + c.authorizer.ForceAttemptOAuth2 = true + err := reg.Ping(ctx) + if err != nil { + c.authorizer.ForceAttemptOAuth2 = false + err = reg.Ping(ctx) + } + return err +} + +// forcedHTTP reports whether the client's transport has fallen back to plain +// HTTP after a failed HTTPS attempt, indicating the registry is plain HTTP. +func (c *Client) forcedHTTP() bool { + if c.httpClient == nil { + return false + } + if ft, ok := c.httpClient.Transport.(*fallbackTransport); ok { + return ft.forcedHTTP() + } + return false +} + // LoginOptBasicAuth returns a function that sets the username/password settings on login func LoginOptBasicAuth(username string, password string) LoginOption { return func(o *loginOperation) { diff --git a/pkg/registry/fallback.go b/pkg/registry/fallback.go index 1db729576..a5ca6f72c 100644 --- a/pkg/registry/fallback.go +++ b/pkg/registry/fallback.go @@ -38,6 +38,14 @@ func newTransport(debug bool) *fallbackTransport { } } +// forcedHTTP reports whether the transport has fallen back to plain HTTP after +// a failed HTTPS attempt. Once this is true, the registry is known to be plain +// HTTP and callers should set PlainHTTP so requests are built as http from the +// start (see Client.Login). +func (t *fallbackTransport) forcedHTTP() bool { + return t.forceHTTP.Load() +} + // RoundTrip wraps base round trip with conditional insecure retry. func (t *fallbackTransport) RoundTrip(req *http.Request) (*http.Response, error) { if ok := t.forceHTTP.Load(); ok {