Repository: sigstore/cosign
Pull request #3661 (2024-04-10 21:29:41)

Fixes for GHSA-88jx-383q-w4qc and GHSA-95pr-fxf5-86gv
<!--
Thanks for opening a pull request! Please do not just delete this text.  The three fields below are mandatory.

Please remember to:
- This PR requires an issue. If it is a new feature, the issue should proceed the PR and will have allowed sufficent time for discussions to take place. Please use
issue tags such as "Closes #XYZ" or "Resolves sigstore/repo-name#XYZ".
  [documentation](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
- ensure your commits are signed-off, as sigstore uses the [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) using `git commit -s`, or `git commit -s --amend` if you want to amend already existing commits
- lastly, ensure there are no merge commits!

Thank you :)
-->

#### Summary
<!--
 Explain the **motivation** for making this change. What existing problem does the pull request solve? How can reviewers test this PR?
-->

#### Release Note
<!--
Add a release note for each of the following conditions:

* Config changes (additions, deletions, updates)
* API additions—new endpoint, new response fields, or newly accepted request parameters
* Database changes (any)
* Websocket additions or changes
* Anything noteworthy to an administrator running private sigstore instances (err on the side of over-communicating)
* New features and improvements, including behavioural changes, UI changes and CLI changes
* Bug fixes and fixes of previous known issues
* Deprecation warnings, breaking changes, or compatibility notes

If no release notes are required write NONE. Use past-tense.

-->

#### Documentation
<!--

Does this change require an update to documentation? How will users implement your new feature?

Please reference a PR within https://docs.sigstore.dev

-->



Patch:
diff --git a/go.mod b/go.mod
index 05cfbe83..8dcf0400 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
 	github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46
 	github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936
 	github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7
+	github.com/dustin/go-humanize v1.0.1
 	github.com/go-openapi/runtime v0.28.0
 	github.com/go-openapi/strfmt v0.23.0
 	github.com/go-openapi/swag v0.23.0
diff --git a/internal/pkg/cosign/payload/size/errors.go b/internal/pkg/cosign/payload/size/errors.go
new file mode 100644
index 00000000..5a7e0559
--- /dev/null
+++ b/internal/pkg/cosign/payload/size/errors.go
@@ -0,0 +1,31 @@
+// Copyright 2024 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package payload
+
+import "fmt"
+
+// MaxLayerSizeExceeded is an error indicating that the layer is too big to read into memory and cosign should abort processing it.
+type MaxLayerSizeExceeded struct {
+	value   uint64
+	maximum uint64
+}
+
+func NewMaxLayerSizeExceeded(value, maximum uint64) *MaxLayerSizeExceeded {
+	return &MaxLayerSizeExceeded{value, maximum}
+}
+
+func (e *MaxLayerSizeExceeded) Error() string {
+	return fmt.Sprintf("size of layer (%d) exceeded the limit (%d)", e.value, e.maximum)
+}
diff --git a/internal/pkg/cosign/payload/size/size.go b/internal/pkg/cosign/payload/size/size.go
new file mode 100644
index 00000000..f867477c
--- /dev/null
+++ b/internal/pkg/cosign/payload/size/size.go
@@ -0,0 +1,38 @@
+// Copyright 2024 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package payload
+
+import (
+	"github.com/dustin/go-humanize"
+	"github.com/sigstore/cosign/v2/pkg/cosign/env"
+)
+
+const defaultMaxSize = uint64(134217728) // 128MiB
+
+func CheckSize(size uint64) error {
+	maxSize := defaultMaxSize
+	maxSizeOverride, exists := env.LookupEnv(env.VariableMaxAttachmentSize)
+	if exists {
+		var err error
+		maxSize, err = humanize.ParseBytes(maxSizeOverride)
+		if err != nil {
+			maxSize = defaultMaxSize
+		}
+	}
+	if size > maxSize {
+		return NewMaxLayerSizeExceeded(size, maxSize)
+	}
+	return nil
+}
diff --git a/pkg/cosign/env/env.go b/pkg/cosign/env/env.go
index 5c26d4f5..a2960e08 100644
--- a/pkg/cosign/env/env.go
+++ b/pkg/cosign/env/env.go
@@ -51,6 +51,7 @@ const (
 	VariablePKCS11ModulePath        Variable = "COSIGN_PKCS11_MODULE_PATH"
 	VariablePKCS11IgnoreCertificate Variable = "COSIGN_PKCS11_IGNORE_CERTIFICATE"
 	VariableRepository              Variable = "COSIGN_REPOSITORY"
+	VariableMaxAttachmentSize       Variable = "COSIGN_MAX_ATTACHMENT_SIZE"
 
 	// Sigstore environment variables
 	VariableSigstoreCTLogPublicKeyFile Variable = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE"
@@ -113,6 +114,11 @@ var (
 			Expects:     "string with a repository",
 			Sensitive:   false,
 		},
+		VariableMaxAttachmentSize: {
+			Description: "maximum attachment size to download (default 128MiB)",
+			Expects:     "human-readable unit of memory, e.g. 5120, 20K, 3M, 45MiB, 1GB",
+			Sensitive:   false,
+		},
 
 		VariableSigstoreCTLogPublicKeyFile: {
 			Description: "overrides what is used to validate the SCT coming back from Fulcio",
diff --git a/pkg/oci/errors.go b/pkg/oci/errors.go
new file mode 100644
index 00000000..aa0c2985
--- /dev/null
+++ b/pkg/oci/errors.go
@@ -0,0 +1,31 @@
+// Copyright 2024 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package oci
+
+import "fmt"
+
+// MaxLayersExceeded is an error indicating that the artifact has too many layers and cosign should abort processing it.
+type MaxLayersExceeded struct {
+	value   int64
+	maximum int64
+}
+
+func NewMaxLayersExceeded(value, maximum int64) *MaxLayersExceeded {
+	return &MaxLayersExceeded{value, maximum}
+}
+
+func (e *MaxLayersExceeded) Error() string {
+	return fmt.Sprintf("number of layers (%d) exceeded the limit (%d)", e.value, e.maximum)
+}
diff --git a/pkg/oci/internal/signature/layer.go b/pkg/oci/internal/signature/layer.go
index 176fe3ce..d92af61c 100644
--- a/pkg/oci/internal/signature/layer.go
+++ b/pkg/oci/internal/signature/layer.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
 	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/sigstore/pkg/cryptoutils"
@@ -58,6 +59,14 @@ func (s *sigLayer) Annotations() (map[string]string, error) {
 
 // Payload implements oci.Signature
 func (s *sigLayer) Payload() ([]byte, error) {
+	size, err := s.Layer.Size()
+	if err != nil {
+		return nil, err
+	}
+	err = payloadsize.CheckSize(uint64(size))
+	if err != nil {
+		return nil, err
+	}
 	// Compressed is a misnomer here, we just want the raw bytes from the registry.
 	r, err := s.Layer.Compressed()
 	if err != nil {
diff --git a/pkg/oci/layout/signatures.go b/pkg/oci/layout/signatures.go
index c9f24866..1b0d4c02 100644
--- a/pkg/oci/layout/signatures.go
+++ b/pkg/oci/layout/signatures.go
@@ -21,6 +21,8 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/oci/internal/signature"
 )
 
+const maxLayers = 1000
+
 type sigs struct {
 	v1.Image
 }
@@ -33,7 +35,11 @@ func (s *sigs) Get() ([]oci.Signature, error) {
 	if err != nil {
 		return nil, err
 	}
-	signatures := make([]oci.Signature, 0, len(manifest.Layers))
+	numLayers := int64(len(manifest.Layers))
+	if numLayers > maxLayers {
+		return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
+	}
+	signatures := make([]oci.Signature, 0, numLayers)
 	for _, desc := range manifest.Layers {
 		l, err := s.Image.LayerByDigest(desc.Digest)
 		if err != nil {
diff --git a/pkg/oci/mutate/signatures.go b/pkg/oci/mutate/signatures.go
index 4f3bd98f..2a7c077e 100644
--- a/pkg/oci/mutate/signatures.go
+++ b/pkg/oci/mutate/signatures.go
@@ -23,6 +23,8 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/oci"
 )
 
+const maxLayers = 1000
+
 // AppendSignatures produces a new oci.Signatures with the provided signatures
 // appended to the provided base signatures.
 func AppendSignatures(base oci.Signatures, recordCreationTimestamp bool, sigs ...oci.Signature) (oci.Signatures, error) {
@@ -106,5 +108,9 @@ func (sa *sigAppender) Get() ([]oci.Signature, error) {
 	if err != nil {
 		return nil, err
 	}
+	sumLayers := int64(len(sl) + len(sa.sigs))
+	if sumLayers > maxLayers {
+		return nil, oci.NewMaxLayersExceeded(sumLayers, maxLayers)
+	}
 	return append(sl, sa.sigs...), nil
 }
diff --git a/pkg/oci/remote/remote.go b/pkg/oci/remote/remote.go
index 7827407c..eab4e1f9 100644
--- a/pkg/oci/remote/remote.go
+++ b/pkg/oci/remote/remote.go
@@ -26,6 +26,7 @@ import (
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
 	"github.com/google/go-containerregistry/pkg/v1/types"
+	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
 	ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 )
@@ -226,6 +227,15 @@ func (f *attached) FileMediaType() (types.MediaType, error) {
 
 // Payload implements oci.File
 func (f *attached) Payload() ([]byte, error) {
+	size, err := f.layer.Size()
+	if err != nil {
+		return nil, err
+	}
+	err = payloadsize.CheckSize(uint64(size))
+	if err != nil {
+		return nil, err
+	}
+
 	// remote layers are believed to be stored
 	// compressed, but we don't compress attachments
 	// so use "Compressed" to access the raw byte
diff --git a/pkg/oci/remote/signatures.go b/pkg/oci/remote/signatures.go
index 635b5e9e..f8a53e0f 100644
--- a/pkg/oci/remote/signatures.go
+++ b/pkg/oci/remote/signatures.go
@@ -27,6 +27,8 @@ import (
 	"github.com/sigstore/cosign/v2/pkg/oci/internal/signature"
 )
 
+const maxLayers = 1000
+
 // Signatures fetches the signatures image represented by the named reference.
 // If the tag is not found, this returns an empty oci.Signatures.
 func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) {
@@ -58,6 +60,10 @@ func (s *sigs) Get() ([]oci.Signature, error) {
 	if err != nil {
 		return nil, err
 	}
+	numLayers := int64(len(m.Layers))
+	if numLayers > maxLayers {
+		return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
+	}
 	signatures := make([]oci.Signature, 0, len(m.Layers))
 	for _, desc := range m.Layers {
 		layer, err := s.Image.LayerByDigest(desc.Digest)
diff --git a/pkg/oci/signature/layer.go b/pkg/oci/signature/layer.go
index 176fe3ce..d92af61c 100644
--- a/pkg/oci/signature/layer.go
+++ b/pkg/oci/signature/layer.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
 	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/sigstore/pkg/cryptoutils"
@@ -58,6 +59,14 @@ func (s *sigLayer) Annotations() (map[string]string, error) {
 
 // Payload implements oci.Signature
 func (s *sigLayer) Payload() ([]byte, error) {
+	size, err := s.Layer.Size()
+	if err != nil {
+		return nil, err
+	}
+	err = payloadsize.CheckSize(uint64(size))
+	if err != nil {
+		return nil, err
+	}
 	// Compressed is a misnomer here, we just want the raw bytes from the registry.
 	r, err := s.Layer.Compressed()
 	if err != nil {
diff --git a/pkg/oci/static/file.go b/pkg/oci/static/file.go
index c3435be4..18ec65c3 100644
--- a/pkg/oci/static/file.go
+++ b/pkg/oci/static/file.go
@@ -22,6 +22,7 @@ import (
 	"github.com/google/go-containerregistry/pkg/v1/empty"
 	"github.com/google/go-containerregistry/pkg/v1/mutate"
 	"github.com/google/go-containerregistry/pkg/v1/types"
+	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
 	"github.com/sigstore/cosign/v2/internal/pkg/now"
 	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/cosign/v2/pkg/oci/signed"
@@ -82,6 +83,14 @@ func (f *file) FileMediaType() (types.MediaType, error) {
 
 // Payload implements oci.File
 func (f *file) Payload() ([]byte, error) {
+	size, err := f.layer.Size()
+	if err != nil {
+		return nil, err
+	}
+	err = payloadsize.CheckSize(uint64(size))
+	if err != nil {
+		return nil, err
+	}
 	rc, err := f.layer.Uncompressed()
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go
index 0d9c9b77..63983eb4 100644
--- a/cmd/cosign/cli/verify/verify_blob_attestation.go
+++ b/cmd/cosign/cli/verify/verify_blob_attestation.go
@@ -34,6 +34,7 @@ import (
 	"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
 	"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
 	internal "github.com/sigstore/cosign/v2/internal/pkg/cosign"
+	payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size"
 	"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
 	"github.com/sigstore/cosign/v2/pkg/blob"
 	"github.com/sigstore/cosign/v2/pkg/cosign"
@@ -117,6 +118,14 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
 			return err
 		}
 		defer f.Close()
+		fileInfo, err := f.Stat()
+		if err != nil {
+			return err
+		}
+		err = payloadsize.CheckSize(uint64(fileInfo.Size()))
+		if err != nil {
+			return err
+		}
 
 		payload = internal.NewHashReader(f, sha256.New())
 		if _, err := io.ReadAll(&payload); err != nil {
diff --git a/cmd/cosign/cli/verify/verify_blob_attestation_test.go b/cmd/cosign/cli/verify/verify_blob_attestation_test.go
index 35c9940d..2d87efeb 100644
--- a/cmd/cosign/cli/verify/verify_blob_attestation_test.go
+++ b/cmd/cosign/cli/verify/verify_blob_attestation_test.go
@@ -32,6 +32,7 @@ gZPFIp557+TOoDxf14FODWc+sIPETk0OgCplAk60doVXbCv33IU4rXZHrg==
 const (
 	blobContents                         = "some-payload"
 	anotherBlobContents                  = "another-blob"
+	hugeBlobContents                     = "hugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayload"
 	blobSLSAProvenanceSignature          = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJalkxT0RjNE1XTmtOR1ZrT1dKallUWXdaR0ZqWkRBNVpqZGlZamt4TkdKaU5URTFNREpsT0dJMVpEWXhPV1kxTjJZek9XRXhaRFkxTWpVNU5tTmpNalFpZlgxZExDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJaklpZlN3aVluVnBiR1JVZVhCbElqb2llQ0lzSW1sdWRtOWpZWFJwYjI0aU9uc2lZMjl1Wm1sblUyOTFjbU5sSWpwN2ZYMTlmUT09Iiwic2lnbmF0dXJlcyI6W3sia2V5aWQiOiIiLCJzaWciOiJNRVVDSUE4S2pacWtydDkwZnpCb2pTd3d0ajNCcWI0MUU2cnV4UWs5N1RMbnB6ZFlBaUVBek9Bak9Uenl2VEhxYnBGREFuNnpocmc2RVp2N2t4SzVmYVJvVkdZTWgyYz0ifV19"
 	dssePredicateEmptySubject            = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHRkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FWUNJUUNrTEV2NkhZZ0svZDdUK0N3NTdXbkZGaHFUTC9WalAyVDA5Q2t1dk1nbDRnSWhBT1hBM0lhWWg1M1FscVk1eVU4cWZxRXJma2tGajlEakZnaWovUTQ2NnJSViJ9XX0="
 	dssePredicateMissingSha256           = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdmWDFkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FVUNJQysvM2M4RFo1TGFZTEx6SFZGejE3ZmxHUENlZXVNZ2tIKy8wa2s1cFFLUEFpRUFqTStyYnBBRlJybDdpV0I2Vm9BYVZPZ3U3NjRRM0JKdHI1bHk4VEFHczNrPSJ9XX0="
@@ -46,6 +47,7 @@ func TestVerifyBlobAttestation(t *testing.T) {
 
 	blobPath := writeBlobFile(t, td, blobContents, "blob")
 	anotherBlobPath := writeBlobFile(t, td, anotherBlobContents, "other-blob")
+	hugeBlobPath := writeBlobFile(t, td, hugeBlobContents, "huge-blob")
 	keyRef := writeBlobFile(t, td, pubkey, "cosign.pub")
 
 	tests := []struct {
@@ -53,6 +55,7 @@ func TestVerifyBlobAttestation(t *testing.T) {
 		blobPath      string
 		signature     string
 		predicateType string
+		env           map[string]string
 		shouldErr     bool
 	}{
 		{
@@ -98,11 +101,20 @@ func TestVerifyBlobAttestation(t *testing.T) {
 			signature:     dssePredicateMultipleSubjectsInvalid,
 			blobPath:      blobPath,
 			shouldErr:     true,
+		}, {
+			description: "override file size limit",
+			signature:   blobSLSAProvenanceSignature,
+			blobPath:    hugeBlobPath,
+			env:         map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "128"},
+			shouldErr:   true,
 		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.description, func(t *testing.T) {
+			for k, v := range test.env {
+				t.Setenv(k, v)
+			}
 			decodedSig, err := base64.StdEncoding.DecodeString(test.signature)
 			if err != nil {
 				t.Fatal(err)
diff --git a/internal/pkg/cosign/payload/size/size_test.go b/internal/pkg/cosign/payload/size/size_test.go
new file mode 100644
index 00000000..7feba402
--- /dev/null
+++ b/internal/pkg/cosign/payload/size/size_test.go
@@ -0,0 +1,110 @@
+// Copyright 2024 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package payload
+
+import (
+	"testing"
+)
+
+func TestCheckSize(t *testing.T) {
+	tests := []struct {
+		name    string
+		input   uint64
+		setting string
+		wantErr bool
+	}{
+		{
+			name:    "size is within default limit",
+			input:   1000,
+			wantErr: false,
+		},
+		{
+			name:    "size exceeds default limit",
+			input:   200000000,
+			wantErr: true,
+		},
+		{
+			name:    "size is within overridden limit (bytes)",
+			input:   1000,
+			setting: "1024",
+			wantErr: false,
+		},
+		{
+			name:    "size is exceeds overridden limit (bytes)",
+			input:   2000,
+			setting: "1024",
+			wantErr: true,
+		},
+		{
+			name:    "size is within overridden limit (megabytes, short form)",
+			input:   1999999,
+			setting: "2M",
+			wantErr: false,
+		},
+		{
+			name:    "size exceeds overridden limit (megabytes, short form)",
+			input:   2000001,
+			setting: "2M",
+			wantErr: true,
+		},
+		{
+			name:    "size is within overridden limit (megabytes, long form)",
+			input:   1999999,
+			setting: "2MB",
+			wantErr: false,
+		},
+		{
+			name:    "size exceeds overridden limit (megabytes, long form)",
+			input:   2000001,
+			setting: "2MB",
+			wantErr: true,
+		},
+		{
+			name:    "size is within overridden limit (mebibytes)",
+			input:   2097151,
+			setting: "2MiB",
+			wantErr: false,
+		},
+		{
+			name:    "size exceeds overridden limit (mebibytes)",
+			input:   2097153,
+			setting: "2MiB",
+			wantErr: true,
+		},
+		{
+			name:    "size is negative results in default",
+			input:   5121,
+			setting: "-5KiB",
+			wantErr: false,
+		},
+		{
+			name:    "invalid setting results in default",
+			input:   5121,
+			setting: "five kilobytes",
+			wantErr: false,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.setting != "" {
+				t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", test.setting)
+			}
+			got := CheckSize(test.input)
+			if (got != nil) != (test.wantErr) {
+				t.Errorf("CheckSize() = %v, expected %v", got, test.wantErr)
+			}
+		})
+	}
+}
diff --git a/pkg/oci/internal/signature/layer_test.go b/pkg/oci/internal/signature/layer_test.go
index e6700366..d3895f9c 100644
--- a/pkg/oci/internal/signature/layer_test.go
+++ b/pkg/oci/internal/signature/layer_test.go
@@ -20,6 +20,8 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
+	"io"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -50,6 +52,7 @@ func TestSignature(t *testing.T) {
 	tests := []struct {
 		name           string
 		l              *sigLayer
+		env            map[string]string
 		wantPayloadErr error
 		wantSig        string
 		wantSigErr     error
@@ -222,10 +225,39 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 		},
 		wantSig:   "blah",
 		wantChain: 1,
+	}, {
+		name: "payload size exceeds default limit",
+		l: &sigLayer{
+			Layer: &mockLayer{size: 134217728 + 42}, // 128MB + 42 bytes
+		},
+		wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"),
+	}, {
+		name: "payload size exceeds overridden limit",
+		l: &sigLayer{
+			Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes
+		},
+		env:            map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"},
+		wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"),
+	}, {
+		name: "payload size is within overridden limit",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "blah",
+				},
+			},
+		},
+		env:     map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"},
+		wantSig: "blah",
 	}}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			for k, v := range test.env {
+				t.Setenv(k, v)
+			}
 			b, err := test.l.Payload()
 			switch {
 			case (err != nil) != (test.wantPayloadErr != nil):
@@ -239,6 +271,9 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
 				}
 			}
+			if err != nil {
+				return
+			}
 
 			switch got, err := test.l.Base64Signature(); {
 			case (err != nil) != (test.wantSigErr != nil):
@@ -453,3 +488,20 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
 		})
 	}
 }
+
+type mockLayer struct {
+	size int64
+}
+
+func (m *mockLayer) Size() (int64, error) {
+	return m.size, nil
+}
+
+func (m *mockLayer) Compressed() (io.ReadCloser, error) {
+	return io.NopCloser(strings.NewReader("data")), nil
+}
+
+func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
+func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
diff --git a/pkg/oci/layout/signatures_test.go b/pkg/oci/layout/signatures_test.go
new file mode 100644
index 00000000..6df93ceb
--- /dev/null
+++ b/pkg/oci/layout/signatures_test.go
@@ -0,0 +1,62 @@
+// Copyright 2024 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package layout
+
+import (
+	"errors"
+	"testing"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/fake"
+)
+
+func TestGet(t *testing.T) {
+	tests := []struct {
+		name      string
+		layers    int
+		wantError error
+	}{
+		{
+			name:      "within limit",
+			layers:    23,
+			wantError: nil,
+		},
+		{
+			name:      "exceeds limit",
+			layers:    4242,
+			wantError: errors.New("number of layers (4242) exceeded the limit (1000)"),
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			s := sigs{
+				Image: &fake.FakeImage{
+					ManifestStub: func() (*v1.Manifest, error) {
+						return &v1.Manifest{
+							Layers: make([]v1.Descriptor, test.layers),
+						}, nil
+					},
+				},
+			}
+			_, err := s.Get()
+			if test.wantError != nil && test.wantError.Error() != err.Error() {
+				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
+			}
+			if test.wantError == nil && err != nil {
+				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
+			}
+		})
+	}
+}
diff --git a/pkg/oci/mutate/signatures_test.go b/pkg/oci/mutate/signatures_test.go
index b5f2b10c..195780e3 100644
--- a/pkg/oci/mutate/signatures_test.go
+++ b/pkg/oci/mutate/signatures_test.go
@@ -16,8 +16,11 @@
 package mutate
 
 import (
+	"errors"
 	"testing"
 
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/sigstore/cosign/v2/pkg/oci"
 	"github.com/sigstore/cosign/v2/pkg/oci/empty"
 	"github.com/sigstore/cosign/v2/pkg/oci/static"
 )
@@ -83,3 +86,63 @@ func TestAppendSignatures(t *testing.T) {
 		t.Errorf("Date of Signature was Zero")
 	}
 }
+
+func TestGet(t *testing.T) {
+	tests := []struct {
+		name         string
+		baseLayers   int
+		appendLayers int
+		wantError    error
+	}{
+		{
+			name:         "within limit",
+			baseLayers:   1,
+			appendLayers: 1,
+			wantError:    nil,
+		},
+		{
+			name:         "base exceeds limit",
+			baseLayers:   2000,
+			appendLayers: 1,
+			wantError:    errors.New("number of layers (2001) exceeded the limit (1000)"),
+		},
+		{
+			name:         "append exceeds limit",
+			baseLayers:   1,
+			appendLayers: 1300,
+			wantError:    errors.New("number of layers (1301) exceeded the limit (1000)"),
+		},
+		{
+			name:         "sum exceeds limit",
+			baseLayers:   666,
+			appendLayers: 666,
+			wantError:    errors.New("number of layers (1332) exceeded the limit (1000)"),
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			sa := sigAppender{
+				base: &mockOCISignatures{
+					signatures: make([]oci.Signature, test.baseLayers),
+				},
+				sigs: make([]oci.Signature, test.appendLayers),
+			}
+			_, err := sa.Get()
+			if test.wantError != nil && test.wantError.Error() != err.Error() {
+				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
+			}
+			if test.wantError == nil && err != nil {
+				t.Fatalf("Get() = %v, wanted %v", err, test.wantError)
+			}
+		})
+	}
+}
+
+type mockOCISignatures struct {
+	v1.Image
+	signatures []oci.Signature
+}
+
+func (m *mockOCISignatures) Get() ([]oci.Signature, error) {
+	return m.signatures, nil
+}
diff --git a/pkg/oci/remote/remote_test.go b/pkg/oci/remote/remote_test.go
index 90449ae7..1d21792d 100644
--- a/pkg/oci/remote/remote_test.go
+++ b/pkg/oci/remote/remote_test.go
@@ -17,11 +17,14 @@ package remote
 
 import (
 	"errors"
+	"io"
+	"strings"
 	"testing"
 
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
+	"github.com/google/go-containerregistry/pkg/v1/types"
 )
 
 func TestTagMethods(t *testing.T) {
@@ -203,3 +206,70 @@ func TestDockercontentDigest(t *testing.T) {
 		})
 	}
 }
+
+func TestPayload(t *testing.T) {
+	tests := []struct {
+		name      string
+		size      int64
+		env       map[string]string
+		wantError error
+	}{
+		{
+			name:      "within default limit",
+			size:      1000,
+			wantError: nil,
+		},
+		{
+			name:      "excceds default limit",
+			size:      1073741824,
+			wantError: errors.New("size of layer (1073741824) exceeded the limit (134217728)"),
+		},
+		{
+			name:      "exceeds overridden limit",
+			size:      5120,
+			env:       map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1KB"},
+			wantError: errors.New("size of layer (5120) exceeded the limit (1000)"),
+		},
+		{
+			name: "within overridden limit",
+			size: 5120,
+			env:  map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "10KB"},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			for k, v := range test.env {
+				t.Setenv(k, v)
+			}
+			a := attached{
+				layer: &mockLayer{
+					size: test.size,
+				},
+			}
+			_, err := a.Payload()
+			if test.wantError != nil && test.wantError.Error() != err.Error() {
+				t.Fatalf("Payload() = %v, wanted %v", err, test.wantError)
+			}
+			if test.wantError == nil && err != nil {
+				t.Fatalf("Payload() = %v, wanted %v", err, test.wantError)
+			}
+		})
+	}
+}
+
+type mockLayer struct {
+	size int64
+}
+
+func (m *mockLayer) Compressed() (io.ReadCloser, error) {
+	return io.NopCloser(strings.NewReader("test payload")), nil
+}
+
+func (m *mockLayer) Size() (int64, error) {
+	return m.size, nil
+}
+
+func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
+func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
diff --git a/pkg/oci/remote/signatures_test.go b/pkg/oci/remote/signatures_test.go
index 91e7c7a0..c2367003 100644
--- a/pkg/oci/remote/signatures_test.go
+++ b/pkg/oci/remote/signatures_test.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/fake"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
 )
@@ -75,4 +76,25 @@ func TestSignaturesErrors(t *testing.T) {
 			t.Fatalf("Signatures() = %v, wanted %v", err, want)
 		}
 	})
+
+	t.Run("too many layers", func(t *testing.T) {
+		remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) {
+			return &fake.FakeImage{
+				ManifestStub: func() (*v1.Manifest, error) {
+					return &v1.Manifest{
+						Layers: make([]v1.Descriptor, 10000),
+					}, nil
+				},
+			}, nil
+		}
+		sigs, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig"))
+		if err != nil {
+			t.Fatalf("Signatures() = %v", err)
+		}
+		want := errors.New("number of layers (10000) exceeded the limit (1000)")
+		_, err = sigs.Get()
+		if err == nil || want.Error() != err.Error() {
+			t.Fatalf("Get() = %v", err)
+		}
+	})
 }
diff --git a/pkg/oci/signature/layer_test.go b/pkg/oci/signature/layer_test.go
index 9a610d35..e88157d2 100644
--- a/pkg/oci/signature/layer_test.go
+++ b/pkg/oci/signature/layer_test.go
@@ -20,6 +20,8 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
+	"io"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -292,6 +294,7 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
 	tests := []struct {
 		name           string
 		l              *sigLayer
+		env            map[string]string
 		wantPayloadErr error
 		wantSig        string
 		wantSigErr     error
@@ -397,10 +400,39 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
 		wantBundle: &bundle.RFC3161Timestamp{
 			SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
 		},
+	}, {
+		name: "payload size exceeds default limit",
+		l: &sigLayer{
+			Layer: &mockLayer{size: 134217728 + 42}, // 128MiB + 42 bytes
+		},
+		wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"),
+	}, {
+		name: "payload size exceeds overridden limit",
+		l: &sigLayer{
+			Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes
+		},
+		env:            map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"},
+		wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"),
+	}, {
+		name: "payload size is within overridden limit",
+		l: &sigLayer{
+			Layer: layer,
+			desc: v1.Descriptor{
+				Digest: digest,
+				Annotations: map[string]string{
+					sigkey: "blah",
+				},
+			},
+		},
+		env:     map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"},
+		wantSig: "blah",
 	}}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			for k, v := range test.env {
+				t.Setenv(k, v)
+			}
 			b, err := test.l.Payload()
 			switch {
 			case (err != nil) != (test.wantPayloadErr != nil):
@@ -414,6 +446,9 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
 					t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
 				}
 			}
+			if err != nil {
+				return
+			}
 
 			switch got, err := test.l.Base64Signature(); {
 			case (err != nil) != (test.wantSigErr != nil):
@@ -453,3 +488,20 @@ func TestSignatureWithTSAAnnotation(t *testing.T) {
 		})
 	}
 }
+
+type mockLayer struct {
+	size int64
+}
+
+func (m *mockLayer) Size() (int64, error) {
+	return m.size, nil
+}
+
+func (m *mockLayer) Compressed() (io.ReadCloser, error) {
+	return io.NopCloser(strings.NewReader("data")), nil
+}
+
+func (m *mockLayer) Digest() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) DiffID() (v1.Hash, error)             { panic("not implemented") }
+func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
+func (m *mockLayer) MediaType() (types.MediaType, error)  { panic("not implemented") }
diff --git a/pkg/oci/static/file_test.go b/pkg/oci/static/file_test.go
index 0866f25d..226335b4 100644
--- a/pkg/oci/static/file_test.go
+++ b/pkg/oci/static/file_test.go
@@ -16,6 +16,7 @@
 package static
 
 import (
+	"errors"
 	"io"
 	"strings"
 	"testing"
@@ -27,7 +28,7 @@ import (
 
 func TestNewFile(t *testing.T) {
 	payload := "this is the content!"
-	file, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"}))
+	f, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"}))
 	if err != nil {
 		t.Fatalf("NewFile() = %v", err)
 	}
@@ -38,7 +39,7 @@ func TestNewFile(t *testing.T) {
 		t.Fatalf("NewFile() = %v", err)
 	}
 
-	layers, err := file.Layers()
+	layers, err := f.Layers()
 	if err != nil {
 		t.Fatalf("Layers() = %v", err)
 	} else if got, want := len(layers), 1; got != want {
@@ -59,7 +60,7 @@ func TestNewFile(t *testing.T) {
 
 	t.Run("check media type", func(t *testing.T) {
 		wantMT := types.MediaType("foo")
-		gotMT, err := file.FileMediaType()
+		gotMT, err := f.FileMediaType()
 		if err != nil {
 			t.Fatalf("MediaType() = %v", err)
 		}
@@ -118,7 +119,7 @@ func TestNewFile(t *testing.T) {
 			t.Errorf("Uncompressed() = %s, wanted %s", got, want)
 		}
 
-		gotPayload, err := file.Payload()
+		gotPayload, err := f.Payload()
 		if err != nil {
 			t.Fatalf("Payload() = %v", err)
 		}
@@ -128,7 +129,7 @@ func TestNewFile(t *testing.T) {
 	})
 
 	t.Run("check date", func(t *testing.T) {
-		fileCfg, err := file.ConfigFile()
+		fileCfg, err := f.ConfigFile()
 		if err != nil {
 			t.Fatalf("ConfigFile() = %v", err)
 		}
@@ -145,7 +146,7 @@ func TestNewFile(t *testing.T) {
 	})
 
 	t.Run("check annotations", func(t *testing.T) {
-		m, err := file.Manifest()
+		m, err := f.Manifest()
 		if err != nil {
 			t.Fatalf("Manifest() = %v", err)
 		}
@@ -154,4 +155,39 @@ func TestNewFile(t *testing.T) {
 			t.Errorf("Annotations = %s, wanted %s", got, want)
 		}
 	})
+
+	t.Run("huge file payload", func(t *testing.T) {
+		// default limit
+		f := file{
+			layer: &mockLayer{200000000},
+		}
+		want := errors.New("size of layer (200000000) exceeded the limit (134217728)")
+		_, err = f.Payload()
+		if err == nil || want.Error() != err.Error() {
+			t.Errorf("Payload() = %v, wanted %v", err, want)
+		}
+		// override limit
+		t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", "512MiB")
+		_, err = f.Payload()
+		if err != nil {
+			t.Errorf("Payload() = %v, wanted nil", err)
+		}
+	})
 }
+
+type mockLayer struct {
+	size int64
+}
+
+func (m *mockLayer) Size() (int64, error) {
+	return m.size, nil
+}
+
+func (m *mockLayer) Uncompressed() (io.ReadCloser, error) {
+	return io.NopCloser(strings.NewReader("data")), nil
+}
+
+func (m *mockLayer) Digest() (v1.Hash, error)            { panic("not implemented") }
+func (m *mockLayer) DiffID() (v1.Hash, error)            { panic("not implemented") }
+func (m *mockLayer) Compressed() (io.ReadCloser, error)  { panic("not implemented") }
+func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") }