VYPR
High severity8.3GHSA Advisory· Published May 9, 2026· Updated May 15, 2026

CVE-2026-42297

CVE-2026-42297

Description

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. From version 4.0.0 to before version 4.0.5, the Sync Service's ConfigMap-backed provider (server/sync/sync_cm.go) performs zero authorization checks on all CRUD operations (create, read, update, delete). Any authenticated user — including those using fake Bearer tokens — can create, read, update, and delete Kubernetes ConfigMaps containing synchronization limits. This issue has been patched in version 4.0.5.

Affected products

1

Patches

1
09fff05e0830

Merge commit from fork

https://github.com/argoproj/argo-workflowsAlan ClucasApr 23, 2026via ghsa
3 files changed · +150 31
  • server/sync/sync_cm.go+30 0 modified
    @@ -6,12 +6,14 @@ import (
     	"strconv"
     
     	"google.golang.org/grpc/codes"
    +	"google.golang.org/grpc/status"
     	corev1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     
     	syncpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/sync"
     	"github.com/argoproj/argo-workflows/v4/server/auth"
     	sutils "github.com/argoproj/argo-workflows/v4/server/utils"
    +	authutil "github.com/argoproj/argo-workflows/v4/util/auth"
     )
     
     type configMapSyncProvider struct{}
    @@ -23,6 +25,10 @@ func (s *configMapSyncProvider) createSyncLimit(ctx context.Context, req *syncpk
     		return nil, sutils.ToStatusError(fmt.Errorf("limit must be greater than zero"), codes.InvalidArgument)
     	}
     
    +	if err := checkConfigMapPermission(ctx, "create", req.Namespace); err != nil {
    +		return nil, err
    +	}
    +
     	kubeClient := auth.GetKubeClient(ctx)
     
     	configmapGetter := kubeClient.CoreV1().ConfigMaps(req.Namespace)
    @@ -67,6 +73,10 @@ func (s *configMapSyncProvider) createSyncLimit(ctx context.Context, req *syncpk
     }
     
     func (s *configMapSyncProvider) getSyncLimit(ctx context.Context, req *syncpkg.GetSyncLimitRequest) (*syncpkg.SyncLimitResponse, error) {
    +	if err := checkConfigMapPermission(ctx, "get", req.Namespace); err != nil {
    +		return nil, err
    +	}
    +
     	kubeClient := auth.GetKubeClient(ctx)
     
     	configmapGetter := kubeClient.CoreV1().ConfigMaps(req.Namespace)
    @@ -100,10 +110,18 @@ func (s *configMapSyncProvider) updateSyncLimit(ctx context.Context, req *syncpk
     		return nil, sutils.ToStatusError(fmt.Errorf("limit must be greater than zero"), codes.InvalidArgument)
     	}
     
    +	if err := checkConfigMapPermission(ctx, "update", req.Namespace); err != nil {
    +		return nil, err
    +	}
    +
     	return s.handleUpdateSyncLimit(ctx, req, true)
     }
     
     func (s *configMapSyncProvider) deleteSyncLimit(ctx context.Context, req *syncpkg.DeleteSyncLimitRequest) (*syncpkg.DeleteSyncLimitResponse, error) {
    +	if err := checkConfigMapPermission(ctx, "update", req.Namespace); err != nil {
    +		return nil, err
    +	}
    +
     	kubeClient := auth.GetKubeClient(ctx)
     
     	configmapGetter := kubeClient.CoreV1().ConfigMaps(req.Namespace)
    @@ -123,6 +141,18 @@ func (s *configMapSyncProvider) deleteSyncLimit(ctx context.Context, req *syncpk
     	return &syncpkg.DeleteSyncLimitResponse{}, nil
     }
     
    +func checkConfigMapPermission(ctx context.Context, verb, namespace string) error {
    +	kubeClient := auth.GetKubeClient(ctx)
    +	allowed, err := authutil.CanI(ctx, kubeClient, []string{verb}, "", namespace, "configmaps")
    +	if err != nil {
    +		return sutils.ToStatusError(err, codes.Internal)
    +	}
    +	if !allowed {
    +		return status.Error(codes.PermissionDenied, fmt.Sprintf("Permission denied, you are not allowed to %s configmaps in namespace %q.", verb, namespace))
    +	}
    +	return nil
    +}
    +
     func (s *configMapSyncProvider) handleUpdateSyncLimit(ctx context.Context, req *syncpkg.UpdateSyncLimitRequest, shouldFieldExist bool) (*syncpkg.SyncLimitResponse, error) {
     	kubeClient := auth.GetKubeClient(ctx)
     
    
  • server/sync/sync_db.go+0 1 modified
    @@ -23,7 +23,6 @@ var _ SyncConfigProvider = &dbSyncProvider{}
     
     func (s *dbSyncProvider) createSyncLimit(ctx context.Context, req *syncpkg.CreateSyncLimitRequest) (*syncpkg.SyncLimitResponse, error) {
     	// since there's no permission system for db sync limits, we use the k8s RBAC check to see if the request is reasonable
    -	// configmap version is relying on the k8s RBAC so we don't need to check permissions
     	allowed, err := auth.CanI(ctx, "create", workflow.WorkflowPlural, req.Namespace, "")
     	if err != nil {
     		return nil, sutils.ToStatusError(err, codes.Internal)
    
  • server/sync/sync_server_cm_test.go+120 30 modified
    @@ -8,20 +8,36 @@ import (
     	"github.com/stretchr/testify/require"
     	"google.golang.org/grpc/codes"
     	"google.golang.org/grpc/status"
    +	authorizationv1 "k8s.io/api/authorization/v1"
     	corev1 "k8s.io/api/core/v1"
    -	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
    -	"k8s.io/apimachinery/pkg/runtime/schema"
     	"k8s.io/client-go/kubernetes/fake"
     	ktesting "k8s.io/client-go/testing"
     
     	syncpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/sync"
     	"github.com/argoproj/argo-workflows/v4/server/auth"
    +	"github.com/argoproj/argo-workflows/v4/util/logging"
     )
     
    -func withKubeClient(kubeClient *fake.Clientset) context.Context {
    -	return context.WithValue(context.Background(), auth.KubeKey, kubeClient)
    +func withAllowedKubeClient(t *testing.T, kubeClient *fake.Clientset) context.Context {
    +	t.Helper()
    +	kubeClient.PrependReactor("create", "selfsubjectaccessreviews", func(action ktesting.Action) (bool, runtime.Object, error) {
    +		return true, &authorizationv1.SelfSubjectAccessReview{
    +			Status: authorizationv1.SubjectAccessReviewStatus{Allowed: true},
    +		}, nil
    +	})
    +	return context.WithValue(logging.TestContext(t.Context()), auth.KubeKey, kubeClient)
    +}
    +
    +func withDeniedKubeClient(t *testing.T, kubeClient *fake.Clientset) context.Context {
    +	t.Helper()
    +	kubeClient.PrependReactor("create", "selfsubjectaccessreviews", func(action ktesting.Action) (bool, runtime.Object, error) {
    +		return true, &authorizationv1.SelfSubjectAccessReview{
    +			Status: authorizationv1.SubjectAccessReviewStatus{Allowed: false},
    +		}, nil
    +	})
    +	return context.WithValue(logging.TestContext(t.Context()), auth.KubeKey, kubeClient)
     }
     
     func Test_syncServer_CreateSyncLimit(t *testing.T) {
    @@ -45,18 +61,34 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     		require.Contains(t, statusErr.Message(), "limit must be greater than zero")
     	})
     
    +	t.Run("Permission denied", func(t *testing.T) {
    +		kubeClient := fake.NewClientset()
    +		ctx := withDeniedKubeClient(t, kubeClient)
    +		server := NewSyncServer(ctx, kubeClient, "", nil)
    +
    +		req := &syncpkg.CreateSyncLimitRequest{
    +			CmName:    "test-cm",
    +			Namespace: "test-ns",
    +			Key:       "test-key",
    +			Limit:     100,
    +		}
    +
    +		_, err := server.CreateSyncLimit(ctx, req)
    +
    +		require.Error(t, err)
    +		statusErr, ok := status.FromError(err)
    +		require.True(t, ok)
    +		require.Equal(t, codes.PermissionDenied, statusErr.Code())
    +	})
    +
     	t.Run("Error creating ConfigMap", func(t *testing.T) {
     		kubeClient := fake.NewSimpleClientset()
     
     		kubeClient.PrependReactor("create", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
    -			return true, nil, apierrors.NewForbidden(
    -				schema.GroupResource{Group: "", Resource: "configmaps"},
    -				"test-cm",
    -				errors.New("namespace not found"),
    -			)
    +			return true, nil, errors.New("create error")
     		})
     
    -		ctx := context.WithValue(context.Background(), auth.KubeKey, kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.CreateSyncLimitRequest{
    @@ -71,13 +103,13 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     		require.Error(t, err)
     		statusErr, ok := status.FromError(err)
     		require.True(t, ok)
    -		require.Equal(t, codes.PermissionDenied, statusErr.Code())
    -		require.Contains(t, statusErr.Message(), "namespace not found")
    +		require.Equal(t, codes.Internal, statusErr.Code())
    +		require.Contains(t, statusErr.Message(), "create error")
     	})
     
     	t.Run("Create new ConfigMap", func(t *testing.T) {
     		kubeClient := fake.NewSimpleClientset()
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.CreateSyncLimitRequest{
    @@ -107,7 +139,7 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.CreateSyncLimitRequest{
    @@ -136,7 +168,7 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     			Data: nil,
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.CreateSyncLimitRequest{
    @@ -165,7 +197,7 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.CreateSyncLimitRequest{
    @@ -186,9 +218,28 @@ func Test_syncServer_CreateSyncLimit(t *testing.T) {
     }
     
     func Test_syncServer_GetSyncLimit(t *testing.T) {
    +	t.Run("Permission denied", func(t *testing.T) {
    +		kubeClient := fake.NewClientset()
    +		ctx := withDeniedKubeClient(t, kubeClient)
    +		server := NewSyncServer(ctx, kubeClient, "", nil)
    +
    +		req := &syncpkg.GetSyncLimitRequest{
    +			CmName:    "test-cm",
    +			Namespace: "test-ns",
    +			Key:       "test-key",
    +		}
    +
    +		_, err := server.GetSyncLimit(ctx, req)
    +
    +		require.Error(t, err)
    +		statusErr, ok := status.FromError(err)
    +		require.True(t, ok)
    +		require.Equal(t, codes.PermissionDenied, statusErr.Code())
    +	})
    +
     	t.Run("ConfigMap doesn't exist", func(t *testing.T) {
     		kubeClient := fake.NewSimpleClientset()
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.GetSyncLimitRequest{
    @@ -218,7 +269,7 @@ func Test_syncServer_GetSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.GetSyncLimitRequest{
    @@ -248,7 +299,7 @@ func Test_syncServer_GetSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.GetSyncLimitRequest{
    @@ -277,7 +328,7 @@ func Test_syncServer_GetSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.GetSyncLimitRequest{
    @@ -317,9 +368,29 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     		require.Contains(t, statusErr.Message(), "limit must be greater than zero")
     	})
     
    +	t.Run("Permission denied", func(t *testing.T) {
    +		kubeClient := fake.NewClientset()
    +		ctx := withDeniedKubeClient(t, kubeClient)
    +		server := NewSyncServer(ctx, kubeClient, "", nil)
    +
    +		req := &syncpkg.UpdateSyncLimitRequest{
    +			CmName:    "test-cm",
    +			Namespace: "test-ns",
    +			Key:       "test-key",
    +			Limit:     100,
    +		}
    +
    +		_, err := server.UpdateSyncLimit(ctx, req)
    +
    +		require.Error(t, err)
    +		statusErr, ok := status.FromError(err)
    +		require.True(t, ok)
    +		require.Equal(t, codes.PermissionDenied, statusErr.Code())
    +	})
    +
     	t.Run("ConfigMap doesn't exist", func(t *testing.T) {
     		kubeClient := fake.NewSimpleClientset()
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.UpdateSyncLimitRequest{
    @@ -347,7 +418,7 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     			Data: nil,
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.UpdateSyncLimitRequest{
    @@ -378,7 +449,7 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.UpdateSyncLimitRequest{
    @@ -413,7 +484,7 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     			return true, nil, errors.New("update error")
     		})
     
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.UpdateSyncLimitRequest{
    @@ -443,7 +514,7 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.UpdateSyncLimitRequest{
    @@ -464,9 +535,28 @@ func Test_syncServer_UpdateSyncLimit(t *testing.T) {
     }
     
     func Test_syncServer_DeleteSyncLimit(t *testing.T) {
    +	t.Run("Permission denied", func(t *testing.T) {
    +		kubeClient := fake.NewClientset()
    +		ctx := withDeniedKubeClient(t, kubeClient)
    +		server := NewSyncServer(ctx, kubeClient, "", nil)
    +
    +		req := &syncpkg.DeleteSyncLimitRequest{
    +			CmName:    "test-cm",
    +			Namespace: "test-ns",
    +			Key:       "test-key",
    +		}
    +
    +		_, err := server.DeleteSyncLimit(ctx, req)
    +
    +		require.Error(t, err)
    +		statusErr, ok := status.FromError(err)
    +		require.True(t, ok)
    +		require.Equal(t, codes.PermissionDenied, statusErr.Code())
    +	})
    +
     	t.Run("ConfigMap doesn't exist", func(t *testing.T) {
     		kubeClient := fake.NewSimpleClientset()
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.DeleteSyncLimitRequest{
    @@ -493,7 +583,7 @@ func Test_syncServer_DeleteSyncLimit(t *testing.T) {
     			Data: nil,
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.DeleteSyncLimitRequest{
    @@ -516,7 +606,7 @@ func Test_syncServer_DeleteSyncLimit(t *testing.T) {
     			Data: map[string]string{},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.DeleteSyncLimitRequest{
    @@ -546,7 +636,7 @@ func Test_syncServer_DeleteSyncLimit(t *testing.T) {
     			return true, nil, errors.New("update error")
     		})
     
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.DeleteSyncLimitRequest{
    @@ -576,7 +666,7 @@ func Test_syncServer_DeleteSyncLimit(t *testing.T) {
     			},
     		}
     		kubeClient := fake.NewSimpleClientset(existingCM)
    -		ctx := withKubeClient(kubeClient)
    +		ctx := withAllowedKubeClient(t, kubeClient)
     		server := NewSyncServer(ctx, kubeClient, "", nil)
     
     		req := &syncpkg.DeleteSyncLimitRequest{
    

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

5

News mentions

0

No linked articles in our index yet.