VYPR
Moderate severityNVD Advisory· Published Sep 7, 2023· Updated Sep 26, 2024

Denial of Service to Argo CD repo-server

CVE-2023-40584

Description

Argo CD is a declarative continuous deployment for Kubernetes. All versions of ArgoCD starting from v2.4 have a bug where the ArgoCD repo-server component is vulnerable to a Denial-of-Service attack vector. Specifically, the said component extracts a user-controlled tar.gz file without validating the size of its inner files. As a result, a malicious, low-privileged user can send a malicious tar.gz file that exploits this vulnerability to the repo-server, thereby harming the system's functionality and availability. Additionally, the repo-server is susceptible to another vulnerability due to the fact that it does not check the extracted file permissions before attempting to delete them. Consequently, an attacker can craft a malicious tar.gz archive in a way that prevents the deletion of its inner files when the manifest generation process is completed. A patch for this vulnerability has been released in versions 2.6.15, 2.7.14, and 2.8.3. Users are advised to upgrade. The only way to completely resolve the issue is to upgrade, however users unable to upgrade should configure RBAC (Role-Based Access Control) and provide access for configuring applications only to a limited number of administrators. These administrators should utilize trusted and verified Helm charts.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cd/v2Go
>= 2.4.0, < 2.6.152.6.15
github.com/argoproj/argo-cd/v2Go
>= 2.7.0, < 2.7.142.7.14
github.com/argoproj/argo-cd/v2Go
>= 2.8.0, < 2.8.32.8.3

Affected products

1

Patches

2
1391ba721496

Merge pull request from GHSA-fwr2-64vr-xv9m

https://github.com/argoproj/argo-cdAlexander MatyushentsevSep 7, 2023via ghsa
2 files changed · +40 0
  • util/db/cluster.go+5 0 modified
    @@ -345,6 +345,9 @@ func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
     	secret.Data = data
     
     	secret.Labels = c.Labels
    +	if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
    +		return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
    +	}
     	secret.Annotations = c.Annotations
     
     	if secret.Annotations == nil {
    @@ -403,6 +406,8 @@ func SecretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
     	annotations := map[string]string{}
     	if s.Annotations != nil {
     		annotations = collections.CopyStringMap(s.Annotations)
    +		// delete system annotations
    +		delete(annotations, apiv1.LastAppliedConfigAnnotation)
     		delete(annotations, common.AnnotationKeyManagedBy)
     	}
     
    
  • util/db/cluster_test.go+35 0 modified
    @@ -7,6 +7,8 @@ import (
     
     	"github.com/stretchr/testify/assert"
     	"github.com/stretchr/testify/require"
    +	"google.golang.org/grpc/codes"
    +	"google.golang.org/grpc/status"
     	v1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/client-go/kubernetes/fake"
    @@ -56,6 +58,24 @@ func Test_secretToCluster(t *testing.T) {
     	})
     }
     
    +func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
    +	secret := &v1.Secret{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name:        "mycluster",
    +			Namespace:   fakeNamespace,
    +			Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
    +		},
    +		Data: map[string][]byte{
    +			"name":   []byte("test"),
    +			"server": []byte("http://mycluster"),
    +			"config": []byte("{\"username\":\"foo\"}"),
    +		},
    +	}
    +	cluster, err := SecretToCluster(secret)
    +	require.NoError(t, err)
    +	assert.Len(t, cluster.Annotations, 0)
    +}
    +
     func TestClusterToSecret(t *testing.T) {
     	cluster := &appv1.Cluster{
     		Server:      "server",
    @@ -78,6 +98,21 @@ func TestClusterToSecret(t *testing.T) {
     	assert.Equal(t, cluster.Labels, s.Labels)
     }
     
    +func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
    +	cluster := &appv1.Cluster{
    +		Server:      "server",
    +		Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
    +		Name:        "test",
    +		Config:      v1alpha1.ClusterConfig{},
    +		Project:     "project",
    +		Namespaces:  []string{"default"},
    +	}
    +	s := &v1.Secret{}
    +	err := clusterToSecret(cluster, s)
    +	require.Error(t, err)
    +	require.Equal(t, codes.InvalidArgument, status.Code(err))
    +}
    +
     func Test_secretToCluster_NoConfig(t *testing.T) {
     	secret := &v1.Secret{
     		ObjectMeta: metav1.ObjectMeta{
    
b8f92c4ff226

Merge pull request from GHSA-g687-f2gx-6wm8

https://github.com/argoproj/argo-cdpasha-codefreshSep 7, 2023via ghsa
12 files changed · +115 11
  • cmd/argocd-repo-server/commands/argocd_repo_server.go+8 0 modified
    @@ -65,6 +65,8 @@ func NewCommand() *cobra.Command {
     		allowOutOfBoundsSymlinks          bool
     		streamedManifestMaxTarSize        string
     		streamedManifestMaxExtractedSize  string
    +		helmManifestMaxExtractedSize      string
    +		disableManifestMaxExtractedSize   bool
     	)
     	var command = cobra.Command{
     		Use:               cliName,
    @@ -103,6 +105,9 @@ func NewCommand() *cobra.Command {
     			streamedManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxExtractedSize)
     			errors.CheckError(err)
     
    +			helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
    +			errors.CheckError(err)
    +
     			askPassServer := askpass.NewServer()
     			metricsServer := metrics.NewMetricsServer()
     			cacheutil.CollectMetrics(redisClient, metricsServer)
    @@ -117,6 +122,7 @@ func NewCommand() *cobra.Command {
     				AllowOutOfBoundsSymlinks:                     allowOutOfBoundsSymlinks,
     				StreamedManifestMaxExtractedSize:             streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
     				StreamedManifestMaxTarSize:                   streamedManifestMaxTarSizeQuantity.ToDec().Value(),
    +				HelmManifestMaxExtractedSize:                 helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
     			}, askPassServer)
     			errors.CheckError(err)
     
    @@ -197,6 +203,8 @@ func NewCommand() *cobra.Command {
     	command.Flags().BoolVar(&allowOutOfBoundsSymlinks, "allow-oob-symlinks", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS", false), "Allow out-of-bounds symlinks in repositories (not recommended)")
     	command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
     	command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
    +	command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
    +	command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
     	tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
     	cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
     		redisClient = client
    
  • docs/operator-manual/server-commands/argocd-repo-server.md+2 0 modified
    @@ -16,7 +16,9 @@ argocd-repo-server [flags]
           --address string                                 Listen on given address for incoming connections (default "0.0.0.0")
           --allow-oob-symlinks                             Allow out-of-bounds symlinks in repositories (not recommended)
           --default-cache-expiration duration              Cache expiration default (default 24h0m0s)
    +      --disable-helm-manifest-max-extracted-size       Disable maximum size of helm manifest archives when extracted
           --disable-tls                                    Disable TLS on the gRPC endpoint
    +      --helm-manifest-max-extracted-size string        Maximum size of helm manifest archives when extracted (default "1G")
       -h, --help                                           help for argocd-repo-server
           --logformat string                               Set the logging format. One of: text|json (default "text")
           --loglevel string                                Set the logging level. One of: debug|info|warn|error (default "info")
    
  • manifests/base/repo-server/argocd-repo-server-deployment.yaml+12 0 modified
    @@ -150,6 +150,18 @@ spec:
                     key: reposerver.streamed.manifest.max.extracted.size
                     name: argocd-cmd-params-cm
                     optional: true
    +          - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +            valueFrom:
    +              configMapKeyRef:
    +                key: reposerver.helm.manifest.max.extracted.size
    +                name: argocd-cmd-params-cm
    +                optional: true
    +          - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +            valueFrom:
    +              configMapKeyRef:
    +                name: argocd-cmd-params-cm
    +                key: reposerver.disable.helm.manifest.max.extracted.size
    +                optional: true
               - name: ARGOCD_GIT_MODULES_ENABLED
                 valueFrom:
                   configMapKeyRef:
    
  • manifests/core-install.yaml+12 0 modified
    @@ -19174,6 +19174,18 @@ spec:
                   key: reposerver.streamed.manifest.max.extracted.size
                   name: argocd-cmd-params-cm
                   optional: true
    +        - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
    +        - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.disable.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
             - name: ARGOCD_GIT_MODULES_ENABLED
               valueFrom:
                 configMapKeyRef:
    
  • manifests/ha/install.yaml+12 0 modified
    @@ -20631,6 +20631,18 @@ spec:
                   key: reposerver.streamed.manifest.max.extracted.size
                   name: argocd-cmd-params-cm
                   optional: true
    +        - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
    +        - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.disable.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
             - name: ARGOCD_GIT_MODULES_ENABLED
               valueFrom:
                 configMapKeyRef:
    
  • manifests/ha/namespace-install.yaml+12 0 modified
    @@ -2131,6 +2131,18 @@ spec:
                   key: reposerver.streamed.manifest.max.extracted.size
                   name: argocd-cmd-params-cm
                   optional: true
    +        - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
    +        - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.disable.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
             - name: ARGOCD_GIT_MODULES_ENABLED
               valueFrom:
                 configMapKeyRef:
    
  • manifests/install.yaml+12 0 modified
    @@ -19688,6 +19688,18 @@ spec:
                   key: reposerver.streamed.manifest.max.extracted.size
                   name: argocd-cmd-params-cm
                   optional: true
    +        - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
    +        - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.disable.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
             - name: ARGOCD_GIT_MODULES_ENABLED
               valueFrom:
                 configMapKeyRef:
    
  • manifests/namespace-install.yaml+12 0 modified
    @@ -1188,6 +1188,18 @@ spec:
                   key: reposerver.streamed.manifest.max.extracted.size
                   name: argocd-cmd-params-cm
                   optional: true
    +        - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
    +        - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
    +          valueFrom:
    +            configMapKeyRef:
    +              key: reposerver.disable.helm.manifest.max.extracted.size
    +              name: argocd-cmd-params-cm
    +              optional: true
             - name: ARGOCD_GIT_MODULES_ENABLED
               valueFrom:
                 configMapKeyRef:
    
  • reposerver/repository/repository.go+4 2 modified
    @@ -106,6 +106,8 @@ type RepoServerInitConstants struct {
     	AllowOutOfBoundsSymlinks                     bool
     	StreamedManifestMaxExtractedSize             int64
     	StreamedManifestMaxTarSize                   int64
    +	HelmManifestMaxExtractedSize                 int64
    +	DisableHelmManifestMaxExtractedSize          bool
     }
     
     // NewService returns a new instance of the Manifest service
    @@ -345,7 +347,7 @@ func (s *Service) runRepoOperation(
     		if source.Helm != nil {
     			helmPassCredentials = source.Helm.PassCredentials
     		}
    -		chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials)
    +		chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize)
     		if err != nil {
     			return err
     		}
    @@ -2266,7 +2268,7 @@ func (s *Service) GetRevisionChartDetails(ctx context.Context, q *apiclient.Repo
     	if err != nil {
     		return nil, fmt.Errorf("helm client error: %v", err)
     	}
    -	chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, false)
    +	chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, false, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize)
     	if err != nil {
     		return nil, fmt.Errorf("error extracting chart: %v", err)
     	}
    
  • util/helm/client.go+19 6 modified
    @@ -8,6 +8,7 @@ import (
     	"encoding/json"
     	"errors"
     	"fmt"
    +	executil "github.com/argoproj/argo-cd/v2/util/exec"
     	"io"
     	"net/http"
     	"net/url"
    @@ -25,7 +26,6 @@ import (
     	"oras.land/oras-go/v2/registry/remote/auth"
     
     	"github.com/argoproj/argo-cd/v2/util/cache"
    -	executil "github.com/argoproj/argo-cd/v2/util/exec"
     	argoio "github.com/argoproj/argo-cd/v2/util/io"
     	"github.com/argoproj/argo-cd/v2/util/io/files"
     	"github.com/argoproj/argo-cd/v2/util/proxy"
    @@ -52,7 +52,7 @@ type indexCache interface {
     
     type Client interface {
     	CleanChartCache(chart string, version string) error
    -	ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error)
    +	ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error)
     	GetIndex(noCache bool) (*Index, error)
     	GetTags(chart string, noCache bool) (*TagsList, error)
     	TestHelmOCI() (bool, error)
    @@ -122,7 +122,21 @@ func (c *nativeHelmChart) CleanChartCache(chart string, version string) error {
     	return os.RemoveAll(cachePath)
     }
     
    -func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error) {
    +func untarChart(tempDir string, cachedChartPath string, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) error {
    +	if disableManifestMaxExtractedSize {
    +		cmd := exec.Command("tar", "-zxvf", cachedChartPath)
    +		cmd.Dir = tempDir
    +		_, err := executil.Run(cmd)
    +		return err
    +	}
    +	reader, err := os.Open(cachedChartPath)
    +	if err != nil {
    +		return err
    +	}
    +	return files.Untgz(tempDir, reader, manifestMaxExtractedSize, false)
    +}
    +
    +func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) {
     	// always use Helm V3 since we don't have chart content to determine correct Helm version
     	helmCmd, err := NewCmdWithVersion("", HelmV3, c.enableOci, c.proxy)
     
    @@ -196,15 +210,14 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent
     		if len(infos) != 1 {
     			return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
     		}
    +
     		err = os.Rename(filepath.Join(tempDest, infos[0].Name()), cachedChartPath)
     		if err != nil {
     			return "", nil, err
     		}
     	}
     
    -	cmd := exec.Command("tar", "-zxvf", cachedChartPath)
    -	cmd.Dir = tempDir
    -	_, err = executil.Run(cmd)
    +	err = untarChart(tempDir, cachedChartPath, manifestMaxExtractedSize, disableManifestMaxExtractedSize)
     	if err != nil {
     		_ = os.RemoveAll(tempDir)
     		return "", nil, err
    
  • util/helm/client_test.go+9 2 modified
    @@ -4,6 +4,7 @@ import (
     	"bytes"
     	"encoding/json"
     	"fmt"
    +	"math"
     	"os"
     	"strings"
     	"testing"
    @@ -71,17 +72,23 @@ func TestIndex(t *testing.T) {
     
     func Test_nativeHelmChart_ExtractChart(t *testing.T) {
     	client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
    -	path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
    +	path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
     	assert.NoError(t, err)
     	defer io.Close(closer)
     	info, err := os.Stat(path)
     	assert.NoError(t, err)
     	assert.True(t, info.IsDir())
     }
     
    +func Test_nativeHelmChart_ExtractChartWithLimiter(t *testing.T) {
    +	client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
    +	_, _, err := client.ExtractChart("argo-cd", "0.7.1", false, 100, false)
    +	assert.Error(t, err, "error while iterating on tar reader: unexpected EOF")
    +}
    +
     func Test_nativeHelmChart_ExtractChart_insecure(t *testing.T) {
     	client := NewClient("https://argoproj.github.io/argo-helm", Creds{InsecureSkipVerify: true}, false, "")
    -	path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
    +	path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
     	assert.NoError(t, err)
     	defer io.Close(closer)
     	info, err := os.Stat(path)
    
  • util/helm/mocks/Client.go+1 1 modified
    @@ -29,7 +29,7 @@ func (_m *Client) CleanChartCache(chart string, version string) error {
     }
     
     // ExtractChart provides a mock function with given fields: chart, version
    -func (_m *Client) ExtractChart(chart string, version string, passCredentials bool) (string, io.Closer, error) {
    +func (_m *Client) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, io.Closer, error) {
     	ret := _m.Called(chart, version)
     
     	var r0 string
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.