CVE-2023-33957 (published 2023-06-06T19:15Z)

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.

CVSS v3 base score: 5.7, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
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.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/notaryproject/notation
commit_details: repo_url: https://github.com/notaryproject/notation
author: Patrick Zheng
author_date: 2023-05-25 14:37:35+08:00
msg: 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>
repository: repo_url: https://github.com/notaryproject/notation
repo_name: notaryproject/notation
description: A CLI tool to sign and verify artifacts
repo_language: Go
owner: notaryproject
file_changes: filename: inspect.go
change_type: ModificationType.MODIFY
diff: @@ -14,6 +14,7 @@ import (
 	"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"
@@ -29,6 +30,7 @@ type inspectOpts struct {
 	reference         string
 	outputFormat      string
 	allowReferrersAPI bool
+	maxSignatures     int
 }
 
 type inspectOutput struct {
@@ -88,6 +90,9 @@ Example - [Experimental] Inspect signatures on an OCI artifact identified by a d
 			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)
 		},
 	}
@@ -95,6 +100,7 @@ Example - [Experimental] Inspect signatures on an OCI artifact identified by a d
 	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
@@ -120,72 +126,74 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error {
 	}
 	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")
 	}

programming_language: Go
filename: inspect_test.go
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,8 @@ func TestInspectCommand_SecretsFromArgs(t *testing.T) {
 			InsecureRegistry: true,
 			Username:         "user",
 		},
-		outputFormat: cmd.OutputPlaintext,
+		outputFormat:  cmd.OutputPlaintext,
+		maxSignatures: 100,
 	}
 	if err := command.ParseFlags([]string{
 		"--password", expected.Password,
@@ -44,7 +45,8 @@ func TestInspectCommand_SecretsFromEnv(t *testing.T) {
 			Password: "password",
 			Username: "user",
 		},
-		outputFormat: cmd.OutputJSON,
+		outputFormat:  cmd.OutputJSON,
+		maxSignatures: 100,
 	}
 	command := inspectCommand(opts)
 	if err := command.ParseFlags([]string{

programming_language: Go
filename: errors.go
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,7 @@
 package errors
 
+import "fmt"
+
 // ErrorReferrersAPINotSupported is used when the target registry does not
 // support the Referrers API
 type ErrorReferrersAPINotSupported struct {
@@ -25,3 +27,13 @@ func (e ErrorOCILayoutMissingReference) Error() string {
 	}
 	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)
+}

programming_language: Go
filename: list.go
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,7 @@ import (
 	"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"
@@ -20,6 +21,7 @@ type listOpts struct {
 	allowReferrersAPI bool
 	ociLayout         bool
 	inputType         inputType
+	maxSignatures     int
 }
 
 func listCommand(opts *listOpts) *cobra.Command {
@@ -47,6 +49,9 @@ func listCommand(opts *listOpts) *cobra.Command {
 			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)
 		},
 	}
@@ -55,6 +60,7 @@ func listCommand(opts *listOpts) *cobra.Command {
 	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
 }
 
@@ -73,12 +79,12 @@ func runList(ctx context.Context, opts *listOpts) error {
 		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 {
@@ -89,34 +95,48 @@ func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descr
 	}
 
 	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
+	})
+}

programming_language: Go
filename: list_test.go
change_type: ModificationType.MODIFY
diff: @@ -14,6 +14,7 @@ func TestListCommand_SecretsFromArgs(t *testing.T) {
 			InsecureRegistry: true,
 			Username:         "user",
 		},
+		maxSignatures: 100,
 	}
 	if err := cmd.ParseFlags([]string{
 		"--password", expected.Password,
@@ -40,6 +41,7 @@ func TestListCommand_SecretsFromEnv(t *testing.T) {
 			Password: "password",
 			Username: "user",
 		},
+		maxSignatures: 100,
 	}
 	cmd := listCommand(opts)
 	if err := cmd.ParseFlags([]string{

programming_language: Go
filename: verify.go
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,6 @@ package main
 import (
 	"errors"
 	"fmt"
-	"math"
 	"os"
 	"reflect"
 
@@ -16,18 +15,17 @@ import (
 	"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 {
@@ -74,6 +72,9 @@ Example - [Experimental] Verify a signature on an OCI artifact identified by a t
 			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)
 		},
 	}
@@ -81,6 +82,7 @@ Example - [Experimental] Verify a signature on an OCI artifact identified by a t
 	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")
@@ -124,11 +126,9 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error {
 	}
 	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)

programming_language: Go
filename: verify_test.go
change_type: ModificationType.MODIFY
diff: @@ -14,7 +14,8 @@ func TestVerifyCommand_BasicArgs(t *testing.T) {
 			Username: "user",
 			Password: "password",
 		},
-		pluginConfig: []string{"key1=val1"},
+		pluginConfig:         []string{"key1=val1"},
+		maxSignatureAttempts: 100,
 	}
 	if err := command.ParseFlags([]string{
 		expected.reference,
@@ -39,7 +40,8 @@ func TestVerifyCommand_MoreArgs(t *testing.T) {
 		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,

programming_language: Go
filename: inspect.md
change_type: ModificationType.MODIFY
diff: @@ -37,6 +37,7 @@ Flags:
   -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)

programming_language: Markdown
filename: list.md
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,7 @@ Flags:
   -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)

programming_language: Markdown
filename: verify.md
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,7 @@ Flags:
   -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

programming_language: Markdown