CVE-2023-33957: Uncontrolled Resource Consumption (CWE-400)
value: notation is a CLI tool to sign and verify OCI artifacts and container images. An attacker who has compromised a registry and added a high number of signatures to an artifact can cause denial of service of services on the machine, if a user runs notation inspect command on the same machine. The problem has been fixed in the release v1.0.0-rc.6. Users should upgrade their notation packages to v1.0.0-rc.6 or above. Users are advised to upgrade. Users unable to upgrade may restrict container registries to a set of secure and trusted container registries.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
CVSS v3 base score: 5.7
Published: 2023-06-06T19:15Z
Repository: https://github.com/notaryproject/notation
Language: Go
Files changed: cmd/notation/inspect.go, cmd/notation/inspect_test.go, cmd/notation/internal/errors/errors.go, cmd/notation/list.go, cmd/notation/list_test.go, cmd/notation/verify.go, cmd/notation/verify_test.go, specs/commandline/inspect.md, specs/commandline/list.md, specs/commandline/verify.md
Commit date: 2023-05-25 14:37:35 +0800
Commit message: Merge pull request from GHSA-9m3v-v4r5-ppx7

* added max number of signatures check

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* fix

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* fix: optimize listSignatures

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update listOpts fields order

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* fix: update code

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>

* updated per code review

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

* minor fix on error message

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>

---------

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Co-authored-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Junjie Gao <junjiegao@microsoft.com>
Diff: diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go
index 8b737c0..6f67886 100644
--- a/cmd/notation/inspect.go
+++ b/cmd/notation/inspect.go
@@ -12,10 +12,11 @@ import (
 	"time"
 
 	"github.com/notaryproject/notation-core-go/signature"
 	"github.com/notaryproject/notation-go/plugin/proto"
 	"github.com/notaryproject/notation-go/registry"
+	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
 	"github.com/notaryproject/notation/cmd/notation/internal/experimental"
 	"github.com/notaryproject/notation/internal/cmd"
 	"github.com/notaryproject/notation/internal/envelope"
 	"github.com/notaryproject/notation/internal/ioutil"
 	"github.com/notaryproject/notation/internal/tree"
@@ -27,10 +28,11 @@ type inspectOpts struct {
 	cmd.LoggingFlagOpts
 	SecureFlagOpts
 	reference         string
 	outputFormat      string
 	allowReferrersAPI bool
+	maxSignatures     int
 }
 
 type inspectOutput struct {
 	MediaType  string `json:"mediaType"`
 	Signatures []signatureOutput
@@ -86,17 +88,21 @@ Example - [Experimental] Inspect signatures on an OCI artifact identified by a d
 		},
 		PreRunE: func(cmd *cobra.Command, args []string) error {
 			return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api")
 		},
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if opts.maxSignatures <= 0 {
+				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
+			}
 			return runInspect(cmd, opts)
 		},
 	}
 
 	opts.LoggingFlagOpts.ApplyFlags(command.Flags())
 	opts.SecureFlagOpts.ApplyFlags(command.Flags())
 	cmd.SetPflagOutput(command.Flags(), &opts.outputFormat, cmd.PflagOutputUsage)
+	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
 	cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "inspect"))
 	experimental.HideFlags(command, experimentalExamples, []string{"allow-referrers-api"})
 	return command
 }
 
@@ -118,76 +124,78 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error {
 	if err != nil {
 		return err
 	}
 	output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []signatureOutput{}}
 	skippedSignatures := false
-	err = sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
-		for _, sigManifestDesc := range signatureManifests {
-			sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
-			if err != nil {
-				fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
-				skippedSignatures = true
-				continue
-			}
+	err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error {
+		sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
+			skippedSignatures = true
+			return nil
+		}
 
-			sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
-			if err != nil {
-				logSkippedSignature(sigManifestDesc, err)
-				skippedSignatures = true
-				continue
-			}
+		sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
+		if err != nil {
+			logSkippedSignature(sigManifestDesc, err)
+			skippedSignatures = true
+			return nil
+		}
 
-			envelopeContent, err := sigEnvelope.Content()
-			if err != nil {
-				logSkippedSignature(sigManifestDesc, err)
-				skippedSignatures = true
-				continue
-			}
+		envelopeContent, err := sigEnvelope.Content()
+		if err != nil {
+			logSkippedSignature(sigManifestDesc, err)
+			skippedSignatures = true
+			return nil
+		}
 
-			signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
-			if err != nil {
-				logSkippedSignature(sigManifestDesc, err)
-				skippedSignatures = true
-				continue
-			}
+		signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
+		if err != nil {
+			logSkippedSignature(sigManifestDesc, err)
+			skippedSignatures = true
+			return nil
+		}
 
-			signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
-			if err != nil {
-				logSkippedSignature(sigManifestDesc, err)
-				skippedSignatures = true
-				continue
-			}
+		signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
+		if err != nil {
+			logSkippedSignature(sigManifestDesc, err)
+			skippedSignatures = true
+			return nil
+		}
 
-			sig := signatureOutput{
-				MediaType:             sigDesc.MediaType,
-				Digest:                sigManifestDesc.Digest.String(),
-				SignatureAlgorithm:    string(signatureAlgorithm),
-				SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
-				UserDefinedAttributes: signedArtifactDesc.Annotations,
-				UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
-				Certificates:          getCertificates(opts.outputFormat, envelopeContent),
-				SignedArtifact:        *signedArtifactDesc,
-			}
+		sig := signatureOutput{
+			MediaType:             sigDesc.MediaType,
+			Digest:                sigManifestDesc.Digest.String(),
+			SignatureAlgorithm:    string(signatureAlgorithm),
+			SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
+			UserDefinedAttributes: signedArtifactDesc.Annotations,
+			UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
+			Certificates:          getCertificates(opts.outputFormat, envelopeContent),
+			SignedArtifact:        *signedArtifactDesc,
+		}
 
-			// clearing annotations from the SignedArtifact field since they're already
-			// displayed as UserDefinedAttributes
-			sig.SignedArtifact.Annotations = nil
+		// clearing annotations from the SignedArtifact field since they're already
+		// displayed as UserDefinedAttributes
+		sig.SignedArtifact.Annotations = nil
+
+		output.Signatures = append(output.Signatures, sig)
 
-			output.Signatures = append(output.Signatures, sig)
-		}
 		return nil
 	})
-
-	if err != nil {
+	var errorExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
+	if err != nil && !errors.As(err, &errorExceedMaxSignatures) {
 		return err
 	}
 
-	err = printOutput(opts.outputFormat, resolvedRef, output)
-	if err != nil {
+	if err := printOutput(opts.outputFormat, resolvedRef, output); err != nil {
 		return err
 	}
 
+	if errorExceedMaxSignatures.MaxSignatures > 0 {
+		fmt.Println("Warning:", errorExceedMaxSignatures)
+	}
+
 	if skippedSignatures {
 		return errors.New("at least one signature was skipped and not displayed")
 	}
 
 	return nil
diff --git a/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go
index 20234ad..bdad263 100644
--- a/cmd/notation/inspect_test.go
+++ b/cmd/notation/inspect_test.go
@@ -14,11 +14,12 @@ func TestInspectCommand_SecretsFromArgs(t *testing.T) {
 		SecureFlagOpts: SecureFlagOpts{
 			Password:         "password",
 			InsecureRegistry: true,
 			Username:         "user",
 		},
-		outputFormat: cmd.OutputPlaintext,
+		outputFormat:  cmd.OutputPlaintext,
+		maxSignatures: 100,
 	}
 	if err := command.ParseFlags([]string{
 		"--password", expected.Password,
 		expected.reference,
 		"-u", expected.Username,
@@ -42,11 +43,12 @@ func TestInspectCommand_SecretsFromEnv(t *testing.T) {
 		reference: "ref",
 		SecureFlagOpts: SecureFlagOpts{
 			Password: "password",
 			Username: "user",
 		},
-		outputFormat: cmd.OutputJSON,
+		outputFormat:  cmd.OutputJSON,
+		maxSignatures: 100,
 	}
 	command := inspectCommand(opts)
 	if err := command.ParseFlags([]string{
 		expected.reference,
 		"--output", "json"}); err != nil {
diff --git a/cmd/notation/internal/errors/errors.go b/cmd/notation/internal/errors/errors.go
index 3445d0a..6d56cb2 100644
--- a/cmd/notation/internal/errors/errors.go
+++ b/cmd/notation/internal/errors/errors.go
@@ -1,7 +1,9 @@
 package errors
 
+import "fmt"
+
 // ErrorReferrersAPINotSupported is used when the target registry does not
 // support the Referrers API
 type ErrorReferrersAPINotSupported struct {
 	Msg string
 }
@@ -23,5 +25,15 @@ func (e ErrorOCILayoutMissingReference) Error() string {
 	if e.Msg != "" {
 		return e.Msg
 	}
 	return "reference is missing either digest or tag"
 }
+
+// ErrorExceedMaxSignatures is used when the number of signatures has surpassed
+// the maximum limit that can be evaluated.
+type ErrorExceedMaxSignatures struct {
+	MaxSignatures int
+}
+
+func (e ErrorExceedMaxSignatures) Error() string {
+	return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures)
+}
diff --git a/cmd/notation/list.go b/cmd/notation/list.go
index f934dab..ba7ac1a 100644
--- a/cmd/notation/list.go
+++ b/cmd/notation/list.go
@@ -4,10 +4,11 @@ import (
 	"context"
 	"errors"
 	"fmt"
 
 	notationregistry "github.com/notaryproject/notation-go/registry"
+	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
 	"github.com/notaryproject/notation/cmd/notation/internal/experimental"
 	"github.com/notaryproject/notation/internal/cmd"
 	"github.com/opencontainers/go-digest"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/spf13/cobra"
@@ -18,10 +19,11 @@ type listOpts struct {
 	SecureFlagOpts
 	reference         string
 	allowReferrersAPI bool
 	ociLayout         bool
 	inputType         inputType
+	maxSignatures     int
 }
 
 func listCommand(opts *listOpts) *cobra.Command {
 	if opts == nil {
 		opts = &listOpts{
@@ -45,18 +47,22 @@ func listCommand(opts *listOpts) *cobra.Command {
 				opts.inputType = inputTypeOCILayout
 			}
 			return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout")
 		},
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if opts.maxSignatures <= 0 {
+				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
+			}
 			return runList(cmd.Context(), opts)
 		},
 	}
 	opts.LoggingFlagOpts.ApplyFlags(command.Flags())
 	opts.SecureFlagOpts.ApplyFlags(command.Flags())
 	cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "list"))
 	command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] list signatures stored in OCI image layout")
 	experimental.HideFlags(command, "", []string{"allow-referrers-api", "oci-layout"})
+	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
 	return command
 }
 
 func runList(ctx context.Context, opts *listOpts) error {
 	// set log level
@@ -71,52 +77,66 @@ func runList(ctx context.Context, opts *listOpts) error {
 	targetDesc, resolvedRef, err := resolveReferenceWithWarning(ctx, opts.inputType, reference, sigRepo, "list")
 	if err != nil {
 		return err
 	}
 	// print all signature manifest digests
-	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef)
+	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef, opts.maxSignatures)
 }
 
 // printSignatureManifestDigests returns the signature manifest digests of
 // the subject manifest.
-func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string) error {
+func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string, maxSigs int) error {
 	titlePrinted := false
 	printTitle := func() {
 		if !titlePrinted {
 			fmt.Println(ref)
 			fmt.Printf("└── %s\n", notationregistry.ArtifactTypeNotation)
 			titlePrinted = true
 		}
 	}
 
 	var prevDigest digest.Digest
-	err := sigRepo.ListSignatures(ctx, targetDesc, func(signatureManifests []ocispec.Descriptor) error {
-		for _, sigManifestDesc := range signatureManifests {
-			if prevDigest != "" {
-				// check and print title
-				printTitle()
-
-				// print each signature digest
-				fmt.Printf("    ├── %s\n", prevDigest)
-			}
-			prevDigest = sigManifestDesc.Digest
+	err := listSignatures(ctx, sigRepo, targetDesc, maxSigs, func(sigManifestDesc ocispec.Descriptor) error {
+		// print the previous signature digest
+		if prevDigest != "" {
+			printTitle()
+			fmt.Printf("    ├── %s\n", prevDigest)
 		}
+		prevDigest = sigManifestDesc.Digest
 		return nil
 	})
-
-	if err != nil {
-		return err
-	}
-
+	// print the last signature digest
 	if prevDigest != "" {
-		// check and print title
 		printTitle()
-
-		// print last signature digest
 		fmt.Printf("    └── %s\n", prevDigest)
 	}
+	if err != nil {
+		var errExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
+		if !errors.As(err, &errExceedMaxSignatures) {
+			return err
+		}
+		fmt.Println("Warning:", errExceedMaxSignatures)
+	}
 
 	if !titlePrinted {
 		fmt.Printf("%s has no associated signature\n", ref)
 	}
 	return nil
 }
+
+// listSignatures lists signatures associated with manifestDesc with number of
+// signatures limited by maxSig
+func listSignatures(ctx context.Context, sigRepo notationregistry.Repository, manifestDesc ocispec.Descriptor, maxSig int, fn func(sigManifest ocispec.Descriptor) error) error {
+	numOfSignatureProcessed := 0
+	return sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
+		for _, sigManifestDesc := range signatureManifests {
+			if numOfSignatureProcessed >= maxSig {
+				return cmderr.ErrorExceedMaxSignatures{MaxSignatures: maxSig}
+			}
+			numOfSignatureProcessed++
+			if err := fn(sigManifestDesc); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}
diff --git a/cmd/notation/list_test.go b/cmd/notation/list_test.go
index e60b326..20d2f0c 100644
--- a/cmd/notation/list_test.go
+++ b/cmd/notation/list_test.go
@@ -12,10 +12,11 @@ func TestListCommand_SecretsFromArgs(t *testing.T) {
 		SecureFlagOpts: SecureFlagOpts{
 			Password:         "password",
 			InsecureRegistry: true,
 			Username:         "user",
 		},
+		maxSignatures: 100,
 	}
 	if err := cmd.ParseFlags([]string{
 		"--password", expected.Password,
 		expected.reference,
 		"-u", expected.Username,
@@ -38,10 +39,11 @@ func TestListCommand_SecretsFromEnv(t *testing.T) {
 		reference: "ref",
 		SecureFlagOpts: SecureFlagOpts{
 			Password: "password",
 			Username: "user",
 		},
+		maxSignatures: 100,
 	}
 	cmd := listCommand(opts)
 	if err := cmd.ParseFlags([]string{
 		expected.reference}); err != nil {
 		t.Fatalf("Parse Flag failed: %v", err)
diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go
index 1056166..6e17c3b 100644
--- a/cmd/notation/verify.go
+++ b/cmd/notation/verify.go
@@ -1,11 +1,10 @@
 package main
 
 import (
 	"errors"
 	"fmt"
-	"math"
 	"os"
 	"reflect"
 
 	"github.com/notaryproject/notation-go"
 	"github.com/notaryproject/notation-go/verifier"
@@ -14,22 +13,21 @@ import (
 	"github.com/notaryproject/notation/internal/cmd"
 	"github.com/notaryproject/notation/internal/ioutil"
 	"github.com/spf13/cobra"
 )
 
-const maxSignatureAttempts = math.MaxInt64
-
 type verifyOpts struct {
 	cmd.LoggingFlagOpts
 	SecureFlagOpts
-	reference         string
-	pluginConfig      []string
-	userMetadata      []string
-	allowReferrersAPI bool
-	ociLayout         bool
-	trustPolicyScope  string
-	inputType         inputType
+	reference            string
+	pluginConfig         []string
+	userMetadata         []string
+	allowReferrersAPI    bool
+	ociLayout            bool
+	trustPolicyScope     string
+	inputType            inputType
+	maxSignatureAttempts int
 }
 
 func verifyCommand(opts *verifyOpts) *cobra.Command {
 	if opts == nil {
 		opts = &verifyOpts{
@@ -72,17 +70,21 @@ Example - [Experimental] Verify a signature on an OCI artifact identified by a t
 				opts.inputType = inputTypeOCILayout
 			}
 			return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout", "scope")
 		},
 		RunE: func(cmd *cobra.Command, args []string) error {
+			if opts.maxSignatureAttempts <= 0 {
+				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatureAttempts)
+			}
 			return runVerify(cmd, opts)
 		},
 	}
 	opts.LoggingFlagOpts.ApplyFlags(command.Flags())
 	opts.SecureFlagOpts.ApplyFlags(command.Flags())
 	command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values")
 	cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage)
+	command.Flags().IntVar(&opts.maxSignatureAttempts, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
 	cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "verify"))
 	command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] verify the artifact stored as OCI image layout")
 	command.Flags().StringVar(&opts.trustPolicyScope, "scope", "", "[Experimental] set trust policy scope for artifact verification, required and can only be used when flag \"--oci-layout\" is set")
 	command.MarkFlagsRequiredTogether("oci-layout", "scope")
 	experimental.HideFlags(command, experimentalExamples, []string{"allow-referrers-api", "oci-layout", "scope"})
@@ -122,15 +124,13 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error {
 	if err != nil {
 		return err
 	}
 	intendedRef := resolveArtifactDigestReference(resolvedRef, opts.trustPolicyScope)
 	verifyOpts := notation.VerifyOptions{
-		ArtifactReference: intendedRef,
-		PluginConfig:      configs,
-		// TODO: need to change MaxSignatureAttempts as a user input flag or
-		// a field in config.json
-		MaxSignatureAttempts: maxSignatureAttempts,
+		ArtifactReference:    intendedRef,
+		PluginConfig:         configs,
+		MaxSignatureAttempts: opts.maxSignatureAttempts,
 		UserMetadata:         userMetadata,
 	}
 	_, outcomes, err := notation.Verify(ctx, sigVerifier, sigRepo, verifyOpts)
 	err = checkVerificationFailure(outcomes, resolvedRef, err)
 	if err != nil {
diff --git a/cmd/notation/verify_test.go b/cmd/notation/verify_test.go
index f1ac69b..cee0c3c 100644
--- a/cmd/notation/verify_test.go
+++ b/cmd/notation/verify_test.go
@@ -12,11 +12,12 @@ func TestVerifyCommand_BasicArgs(t *testing.T) {
 		reference: "ref",
 		SecureFlagOpts: SecureFlagOpts{
 			Username: "user",
 			Password: "password",
 		},
-		pluginConfig: []string{"key1=val1"},
+		pluginConfig:         []string{"key1=val1"},
+		maxSignatureAttempts: 100,
 	}
 	if err := command.ParseFlags([]string{
 		expected.reference,
 		"--username", expected.Username,
 		"--password", expected.Password,
@@ -37,11 +38,12 @@ func TestVerifyCommand_MoreArgs(t *testing.T) {
 	expected := &verifyOpts{
 		reference: "ref",
 		SecureFlagOpts: SecureFlagOpts{
 			InsecureRegistry: true,
 		},
-		pluginConfig: []string{"key1=val1", "key2=val2"},
+		pluginConfig:         []string{"key1=val1", "key2=val2"},
+		maxSignatureAttempts: 100,
 	}
 	if err := command.ParseFlags([]string{
 		expected.reference,
 		"--insecure-registry",
 		"--plugin-config", "key1=val1",
diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md
index 1849d05..33db065 100644
--- a/specs/commandline/inspect.md
+++ b/specs/commandline/inspect.md
@@ -35,10 +35,11 @@ Usage:
 Flags:
       --allow-referrers-api   [Experimental] use the Referrers API to inspect signatures, if not supported (returns 404), fallback to the Referrers tag schema
   -d, --debug                 debug mode
   -h, --help                  help for inspect
       --insecure-registry     use HTTP protocol while connecting to registries. Should be used only for testing
+      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
   -o, --output string         output format, options: 'json', 'text' (default "text")
   -p, --password string       password for registry operations (default to $NOTATION_PASSWORD if not specified)
   -u, --username string       username for registry operations (default to $NOTATION_USERNAME if not specified)
   -v, --verbose               verbose mode
 ```
diff --git a/specs/commandline/list.md b/specs/commandline/list.md
index 4057cf6..4972672 100644
--- a/specs/commandline/list.md
+++ b/specs/commandline/list.md
@@ -29,10 +29,11 @@ Aliases:
 Flags:
       --allow-referrers-api   [Experimental] use the Referrers API to list signatures, if not supported (returns 404), fallback to the Referrers tag schema
   -d, --debug                 debug mode
   -h, --help                  help for list
       --insecure-registry     use HTTP protocol while connecting to registries. Should be used only for testing
+      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
       --oci-layout            [Experimental] list signatures stored in OCI image layout
   -p, --password string       password for registry operations (default to $NOTATION_PASSWORD if not specified)
   -u, --username string       username for registry operations (default to $NOTATION_USERNAME if not specified)
   -v, --verbose               verbose mode
 ```
diff --git a/specs/commandline/verify.md b/specs/commandline/verify.md
index 819b106..3576fbf 100644
--- a/specs/commandline/verify.md
+++ b/specs/commandline/verify.md
@@ -37,10 +37,11 @@ Usage:
 Flags:
        --allow-referrers-api         [Experimental] use the Referrers API to verify signatures, if not supported (returns 404), fallback to the Referrers tag schema
   -d,  --debug                       debug mode
   -h,  --help                        help for verify
        --insecure-registry           use HTTP protocol while connecting to registries. Should be used only for testing
+       --max-signatures int          maximum number of signatures to evaluate or examine (default 100)
        --oci-layout                  [Experimental] verify the artifact stored as OCI image layout
   -p,  --password string             password for registry operations (default to $NOTATION_PASSWORD if not specified)
        --plugin-config stringArray   {key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values
        --scope string                [Experimental] set trust policy scope for artifact verification, required and can only be used when flag "--oci-layout" is set
   -u,  --username string             username for registry operations (default to $NOTATION_USERNAME if not specified)

Vulnerable code: 	err = sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
				skippedSignatures = true
				continue
			}
			sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			envelopeContent, err := sigEnvelope.Content()
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
			if err != nil {
				logSkippedSignature(sigManifestDesc, err)
				skippedSignatures = true
				continue
			}
			sig := signatureOutput{
				MediaType:             sigDesc.MediaType,
				Digest:                sigManifestDesc.Digest.String(),
				SignatureAlgorithm:    string(signatureAlgorithm),
				SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
				UserDefinedAttributes: signedArtifactDesc.Annotations,
				UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
				Certificates:          getCertificates(opts.outputFormat, envelopeContent),
				SignedArtifact:        *signedArtifactDesc,
			}
			// clearing annotations from the SignedArtifact field since they're already
			// displayed as UserDefinedAttributes
			sig.SignedArtifact.Annotations = nil
			output.Signatures = append(output.Signatures, sig)
		}

	if err != nil {
	err = printOutput(opts.outputFormat, resolvedRef, output)
	if err != nil {
		outputFormat: cmd.OutputPlaintext,
		outputFormat: cmd.OutputJSON,
	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef)
func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string) error {
	err := sigRepo.ListSignatures(ctx, targetDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			if prevDigest != "" {
				// check and print title
				printTitle()

				// print each signature digest
				fmt.Printf("    ├── %s\n", prevDigest)
			}
			prevDigest = sigManifestDesc.Digest

	if err != nil {
		return err
	}

		// check and print title

		// print last signature digest
	"math"
const maxSignatureAttempts = math.MaxInt64

	reference         string
	pluginConfig      []string
	userMetadata      []string
	allowReferrersAPI bool
	ociLayout         bool
	trustPolicyScope  string
	inputType         inputType
		ArtifactReference: intendedRef,
		PluginConfig:      configs,
		// TODO: need to change MaxSignatureAttempts as a user input flag or
		// a field in config.json
		MaxSignatureAttempts: maxSignatureAttempts,
		pluginConfig: []string{"key1=val1"},
		pluginConfig: []string{"key1=val1", "key2=val2"},
Fixed code: 	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
	maxSignatures     int
			if opts.maxSignatures <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
			}
	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
	err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error {
		sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Warning: unable to fetch signature %s due to error: %v\n", sigManifestDesc.Digest.String(), err)
			skippedSignatures = true
			return nil
		}
		sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		envelopeContent, err := sigEnvelope.Content()
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm)
		if err != nil {
			logSkippedSignature(sigManifestDesc, err)
			skippedSignatures = true
			return nil
		}
		sig := signatureOutput{
			MediaType:             sigDesc.MediaType,
			Digest:                sigManifestDesc.Digest.String(),
			SignatureAlgorithm:    string(signatureAlgorithm),
			SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
			UserDefinedAttributes: signedArtifactDesc.Annotations,
			UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
			Certificates:          getCertificates(opts.outputFormat, envelopeContent),
			SignedArtifact:        *signedArtifactDesc,
		}
		// clearing annotations from the SignedArtifact field since they're already
		// displayed as UserDefinedAttributes
		sig.SignedArtifact.Annotations = nil

		output.Signatures = append(output.Signatures, sig)
	var errorExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
	if err != nil && !errors.As(err, &errorExceedMaxSignatures) {
	if err := printOutput(opts.outputFormat, resolvedRef, output); err != nil {
	if errorExceedMaxSignatures.MaxSignatures > 0 {
		fmt.Println("Warning:", errorExceedMaxSignatures)
	}

		outputFormat:  cmd.OutputPlaintext,
		maxSignatures: 100,
		outputFormat:  cmd.OutputJSON,
		maxSignatures: 100,
import "fmt"


// ErrorExceedMaxSignatures is used when the number of signatures has surpassed
// the maximum limit that can be evaluated.
type ErrorExceedMaxSignatures struct {
	MaxSignatures int
}

func (e ErrorExceedMaxSignatures) Error() string {
	return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures)
}
	cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors"
	maxSignatures     int
			if opts.maxSignatures <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatures)
			}
	command.Flags().IntVar(&opts.maxSignatures, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
	return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef, opts.maxSignatures)
func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string, maxSigs int) error {
	err := listSignatures(ctx, sigRepo, targetDesc, maxSigs, func(sigManifestDesc ocispec.Descriptor) error {
		// print the previous signature digest
		if prevDigest != "" {
			printTitle()
			fmt.Printf("    ├── %s\n", prevDigest)
		prevDigest = sigManifestDesc.Digest
	// print the last signature digest
	if err != nil {
		var errExceedMaxSignatures cmderr.ErrorExceedMaxSignatures
		if !errors.As(err, &errExceedMaxSignatures) {
			return err
		}
		fmt.Println("Warning:", errExceedMaxSignatures)
	}

// listSignatures lists signatures associated with manifestDesc with number of
// signatures limited by maxSig
func listSignatures(ctx context.Context, sigRepo notationregistry.Repository, manifestDesc ocispec.Descriptor, maxSig int, fn func(sigManifest ocispec.Descriptor) error) error {
	numOfSignatureProcessed := 0
	return sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
		for _, sigManifestDesc := range signatureManifests {
			if numOfSignatureProcessed >= maxSig {
				return cmderr.ErrorExceedMaxSignatures{MaxSignatures: maxSig}
			}
			numOfSignatureProcessed++
			if err := fn(sigManifestDesc); err != nil {
				return err
			}
		}
		return nil
	})
}
		maxSignatures: 100,
		maxSignatures: 100,
	reference            string
	pluginConfig         []string
	userMetadata         []string
	allowReferrersAPI    bool
	ociLayout            bool
	trustPolicyScope     string
	inputType            inputType
	maxSignatureAttempts int
			if opts.maxSignatureAttempts <= 0 {
				return fmt.Errorf("max-signatures value %d must be a positive number", opts.maxSignatureAttempts)
			}
	command.Flags().IntVar(&opts.maxSignatureAttempts, "max-signatures", 100, "maximum number of signatures to evaluate or examine")
		ArtifactReference:    intendedRef,
		PluginConfig:         configs,
		MaxSignatureAttempts: opts.maxSignatureAttempts,
		pluginConfig:         []string{"key1=val1"},
		maxSignatureAttempts: 100,
		pluginConfig:         []string{"key1=val1", "key2=val2"},
		maxSignatureAttempts: 100,
      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
      --max-signatures int    maximum number of signatures to evaluate or examine (default 100)
       --max-signatures int          maximum number of signatures to evaluate or examine (default 100)