CVE-2024-34079: Insufficient Information (NVD-CWE-noinfo)
value: octo-sts is a GitHub App that acts like a Security Token Service (STS) for the Github API. This vulnerability can spike the resource utilization of the STS service, and combined with a significant traffic volume could potentially lead to a denial of service. This vulnerability is fixed in 0.1.0
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-14T15:38Z
Repository: https://github.com/octo-sts/app
Language: Go
Files changed: pkg/maxsize/maxsize.go, pkg/maxsize/maxsize_test.go, pkg/provider/provider.go
Commit date: 2024-05-10 11:49:11 -0700
Commit message: Merge pull request from GHSA-75r6-6jg8-pfcq

This change adds some logic to limit the size of the HTTP responses allowed when performing OIDC discovery on a new provider.

Prior to this, Octo STS could have been manipulated into reading arbitrary amounts of data from the provided issuer endpoint.

Signed-off-by: Matt Moore <mattmoor@chainguard.dev>
Diff: diff --git a/pkg/maxsize/maxsize.go b/pkg/maxsize/maxsize.go
new file mode 100644
index 0000000..5a1f869
--- /dev/null
+++ b/pkg/maxsize/maxsize.go
@@ -0,0 +1,50 @@
+// Copyright 2024 Chainguard, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package maxsize
+
+import (
+	"io"
+	"net/http"
+)
+
+// NewRoundTripper creates a new http.RoundTripper that wraps the given
+// http.RoundTripper and limits the size of the response body to maxSize bytes.
+func NewRoundTripper(maxSize int64, inner http.RoundTripper) http.RoundTripper {
+	return &ms{
+		base:        inner,
+		maxBodySize: maxSize,
+	}
+}
+
+type ms struct {
+	base        http.RoundTripper // The underlying RoundTripper
+	maxBodySize int64             // Maximum allowed response body size in bytes
+}
+
+// RoundTrip implements http.RoundTripper
+func (rt *ms) RoundTrip(req *http.Request) (*http.Response, error) {
+	resp, err := rt.base.RoundTrip(req)
+	if err != nil {
+		return nil, err
+	}
+
+	resp.Body = &lr{
+		LimitedReader: io.LimitedReader{
+			R: resp.Body,
+			N: rt.maxBodySize,
+		},
+		close: resp.Body.Close,
+	}
+	return resp, nil
+}
+
+type lr struct {
+	io.LimitedReader
+	close func() error
+}
+
+// Close implements io.Closer
+func (r *lr) Close() error {
+	return r.close()
+}
diff --git a/pkg/maxsize/maxsize_test.go b/pkg/maxsize/maxsize_test.go
new file mode 100644
index 0000000..ec8d796
--- /dev/null
+++ b/pkg/maxsize/maxsize_test.go
@@ -0,0 +1,49 @@
+// Copyright 2024 Chainguard, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package maxsize
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/coreos/go-oidc/v3/oidc"
+)
+
+func TestCompile(t *testing.T) {
+	tests := []struct {
+		name    string
+		size    int64
+		wantErr bool
+	}{{
+		name:    "large size",
+		size:    1000000, // 1M bytes
+		wantErr: false,
+	}, {
+		name:    "medium size",
+		size:    10000, // 10000 bytes
+		wantErr: false,
+	}, {
+		name:    "tiny size",
+		size:    10, // 10 bytes
+		wantErr: true,
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := oidc.ClientContext(context.Background(), &http.Client{
+				Transport: NewRoundTripper(tt.size, http.DefaultTransport),
+			})
+			for _, issuer := range []string{
+				"https://accounts.google.com",
+				"https://token.actions.githubusercontent.com",
+				"https://issuer.enforce.dev",
+			} {
+				if _, err := oidc.NewProvider(ctx, issuer); (err != nil) != tt.wantErr {
+					t.Errorf("constructing %q provider: %v", issuer, err)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index 7b606fb..2c15a51 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -4,16 +4,26 @@
 package provider
 
 import (
 	"context"
 	"fmt"
+	"net/http"
 
 	"github.com/chainguard-dev/clog"
+	"github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics"
 	"github.com/coreos/go-oidc/v3/oidc"
 	lru "github.com/hashicorp/golang-lru/v2"
+	"github.com/octo-sts/app/pkg/maxsize"
 )
 
+// MaximumResponseSize is the maximum size of allowed responses from
+// OIDC providers.  Some anecdata
+//   - Google: needs around 1KiB
+//   - GitHub: needs around 5KiB
+//   - Chainguard: needs around 2KiB
+const MaximumResponseSize = 100 * 1024 // 100KiB
+
 var (
 	// providers is an LRU cache of recently used providers.
 	providers, _ = lru.New2Q[string, *oidc.Provider](100)
 )
 
@@ -23,10 +33,14 @@ func Get(ctx context.Context, issuer string) (provider *oidc.Provider, err error
 	if v, ok := providers.Get(issuer); ok {
 		clog.InfoContext(ctx, "found provider in cache")
 		return v, nil
 	}
 
+	ctx = oidc.ClientContext(ctx, &http.Client{
+		Transport: maxsize.NewRoundTripper(MaximumResponseSize, httpmetrics.Transport),
+	})
+
 	// Verify the token before we trust anything about it.
 	provider, err = oidc.NewProvider(ctx, issuer)
 	if err != nil {
 		return nil, fmt.Errorf("constructing %q provider: %w", issuer, err)
 	}

Fixed code: // Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package maxsize

import (
	"io"
	"net/http"
)

// NewRoundTripper creates a new http.RoundTripper that wraps the given
// http.RoundTripper and limits the size of the response body to maxSize bytes.
func NewRoundTripper(maxSize int64, inner http.RoundTripper) http.RoundTripper {
	return &ms{
		base:        inner,
		maxBodySize: maxSize,
	}
}

type ms struct {
	base        http.RoundTripper // The underlying RoundTripper
	maxBodySize int64             // Maximum allowed response body size in bytes
}

// RoundTrip implements http.RoundTripper
func (rt *ms) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := rt.base.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	resp.Body = &lr{
		LimitedReader: io.LimitedReader{
			R: resp.Body,
			N: rt.maxBodySize,
		},
		close: resp.Body.Close,
	}
	return resp, nil
}

type lr struct {
	io.LimitedReader
	close func() error
}

// Close implements io.Closer
func (r *lr) Close() error {
	return r.close()
}
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package maxsize

import (
	"context"
	"net/http"
	"testing"

	"github.com/coreos/go-oidc/v3/oidc"
)

func TestCompile(t *testing.T) {
	tests := []struct {
		name    string
		size    int64
		wantErr bool
	}{{
		name:    "large size",
		size:    1000000, // 1M bytes
		wantErr: false,
	}, {
		name:    "medium size",
		size:    10000, // 10000 bytes
		wantErr: false,
	}, {
		name:    "tiny size",
		size:    10, // 10 bytes
		wantErr: true,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := oidc.ClientContext(context.Background(), &http.Client{
				Transport: NewRoundTripper(tt.size, http.DefaultTransport),
			})
			for _, issuer := range []string{
				"https://accounts.google.com",
				"https://token.actions.githubusercontent.com",
				"https://issuer.enforce.dev",
			} {
				if _, err := oidc.NewProvider(ctx, issuer); (err != nil) != tt.wantErr {
					t.Errorf("constructing %q provider: %v", issuer, err)
				}
			}
		})
	}
}
	"net/http"
	"github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics"
	"github.com/octo-sts/app/pkg/maxsize"
// MaximumResponseSize is the maximum size of allowed responses from
// OIDC providers.  Some anecdata
//   - Google: needs around 1KiB
//   - GitHub: needs around 5KiB
//   - Chainguard: needs around 2KiB
const MaximumResponseSize = 100 * 1024 // 100KiB

	ctx = oidc.ClientContext(ctx, &http.Client{
		Transport: maxsize.NewRoundTripper(MaximumResponseSize, httpmetrics.Transport),
	})