VYPR
High severity7.5NVD Advisory· Published Mar 11, 2026· Updated Mar 11, 2026

Argo Workflows has unauthorized access to Argo Workflows Template

CVE-2026-28229

Description

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to 4.0.2 and 3.7.11, Workflow templates endpoints allow any client to retrieve WorkflowTemplates (and ClusterWorkflowTemplates). Any request with a Authorization: Bearer nothing token can leak sensitive template content, including embedded Secret manifests. This vulnerability is fixed in 4.0.2 and 3.7.11.

Affected products

1

Patches

1
34afaf9c0c36

Merge commit from fork

https://github.com/argoproj/argo-workflowsAlan ClucasMar 11, 2026via ghsa
4 files changed · +75 0
  • server/clusterworkflowtemplate/cluster_workflow_template_server.go+8 0 modified
    @@ -6,6 +6,7 @@ import (
     	"sort"
     
     	"google.golang.org/grpc/codes"
    +	"google.golang.org/grpc/status"
     	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     
     	clusterwftmplpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/clusterworkflowtemplate"
    @@ -52,6 +53,13 @@ func (cwts *Server) CreateClusterWorkflowTemplate(ctx context.Context, req *clus
     }
     
     func (cwts *Server) GetClusterWorkflowTemplate(ctx context.Context, req *clusterwftmplpkg.ClusterWorkflowTemplateGetRequest) (*v1alpha1.ClusterWorkflowTemplate, error) {
    +	allowed, err := auth.CanI(ctx, "get", "clusterworkflowtemplates", "", req.Name)
    +	if err != nil {
    +		return nil, serverutils.ToStatusError(err, codes.Internal)
    +	}
    +	if !allowed {
    +		return nil, status.Error(codes.PermissionDenied, "permission denied")
    +	}
     	wfTmpl, err := cwts.getTemplateAndValidate(ctx, req.Name)
     	if err != nil {
     		return nil, serverutils.ToStatusError(err, codes.Internal)
    
  • server/clusterworkflowtemplate/cluster_workflow_template_server_test.go+27 0 modified
    @@ -7,7 +7,10 @@ import (
     	"github.com/go-jose/go-jose/v3/jwt"
     	"github.com/stretchr/testify/assert"
     	"github.com/stretchr/testify/require"
    +	authorizationv1 "k8s.io/api/authorization/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/client-go/kubernetes/fake"
    +	k8stesting "k8s.io/client-go/testing"
     
     	clusterwftmplpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/clusterworkflowtemplate"
     	"github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1"
    @@ -153,6 +156,11 @@ const userEmailLabel = "my-sub.at.your.org"
     func getClusterWorkflowTemplateServer(t *testing.T) (clusterwftmplpkg.ClusterWorkflowTemplateServiceServer, context.Context) {
     	t.Helper()
     	kubeClientSet := fake.NewSimpleClientset()
    +	kubeClientSet.PrependReactor("create", "selfsubjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) {
    +		return true, &authorizationv1.SelfSubjectAccessReview{
    +			Status: authorizationv1.SubjectAccessReviewStatus{Allowed: true},
    +		}, nil
    +	})
     	wfClientset := wftFake.NewSimpleClientset(&unlabelled, &cwftObj2, &cwftObj3)
     	ctx := context.WithValue(logging.TestContext(t.Context()), auth.WfKey, wfClientset)
     	ctx = context.WithValue(ctx, auth.KubeKey, kubeClientSet)
    @@ -207,6 +215,25 @@ func TestWorkflowTemplateServer_GetClusterWorkflowTemplate(t *testing.T) {
     		})
     		require.Error(t, err)
     	})
    +	t.Run("Unauthorized", func(t *testing.T) {
    +		kubeClientSet := fake.NewSimpleClientset()
    +		kubeClientSet.PrependReactor("create", "selfsubjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) {
    +			return true, &authorizationv1.SelfSubjectAccessReview{
    +				Status: authorizationv1.SubjectAccessReviewStatus{Allowed: false},
    +			}, nil
    +		})
    +		wfClientset := wftFake.NewSimpleClientset(&cwftObj2)
    +		ctx := context.WithValue(context.WithValue(context.WithValue(logging.TestContext(t.Context()),
    +			auth.WfKey, wfClientset),
    +			auth.KubeKey, kubeClientSet),
    +			auth.ClaimsKey, &types.Claims{Claims: jwt.Claims{Subject: "my-sub"}})
    +		server := NewClusterWorkflowTemplateServer(instanceid.NewService("my-instanceid"), nil, nil)
    +		_, err := server.GetClusterWorkflowTemplate(ctx, &clusterwftmplpkg.ClusterWorkflowTemplateGetRequest{
    +			Name: "cluster-workflow-template-whalesay-template2",
    +		})
    +		require.Error(t, err)
    +		assert.Contains(t, err.Error(), "PermissionDenied")
    +	})
     }
     
     func TestWorkflowTemplateServer_ListClusterWorkflowTemplates(t *testing.T) {
    
  • server/workflowtemplate/workflow_template_server.go+8 0 modified
    @@ -8,6 +8,7 @@ import (
     	"strings"
     
     	"google.golang.org/grpc/codes"
    +	"google.golang.org/grpc/status"
     	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     
     	workflowtemplatepkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/workflowtemplate"
    @@ -58,6 +59,13 @@ func (wts *Server) CreateWorkflowTemplate(ctx context.Context, req *workflowtemp
     }
     
     func (wts *Server) GetWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateGetRequest) (*v1alpha1.WorkflowTemplate, error) {
    +	allowed, err := auth.CanI(ctx, "get", "workflowtemplates", req.Namespace, req.Name)
    +	if err != nil {
    +		return nil, sutils.ToStatusError(err, codes.Internal)
    +	}
    +	if !allowed {
    +		return nil, status.Error(codes.PermissionDenied, "permission denied")
    +	}
     	wfTmpl, err := wts.getTemplateAndValidate(ctx, req.Namespace, req.Name)
     	if err != nil {
     		return nil, sutils.ToStatusError(err, codes.Internal)
    
  • server/workflowtemplate/workflow_template_server_test.go+32 0 modified
    @@ -9,8 +9,11 @@ import (
     	"github.com/go-jose/go-jose/v3/jwt"
     	"github.com/stretchr/testify/assert"
     	"github.com/stretchr/testify/require"
    +	authorizationv1 "k8s.io/api/authorization/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/client-go/kubernetes/fake"
    +	k8stesting "k8s.io/client-go/testing"
     
     	workflowtemplatepkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/workflowtemplate"
     	"github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1"
    @@ -174,6 +177,11 @@ func getWorkflowTemplateServer(t *testing.T) (workflowtemplatepkg.WorkflowTempla
     	v1alpha1.MustUnmarshal(wftStr2, &wftObj1)
     	v1alpha1.MustUnmarshal(wftStr3, &wftObj2)
     	kubeClientSet := fake.NewSimpleClientset()
    +	kubeClientSet.PrependReactor("create", "selfsubjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) {
    +		return true, &authorizationv1.SelfSubjectAccessReview{
    +			Status: authorizationv1.SubjectAccessReviewStatus{Allowed: true},
    +		}, nil
    +	})
     	wfClientset := wftFake.NewSimpleClientset(&unlabelledObj, &wftObj1, &wftObj2)
     	ctx := context.WithValue(context.WithValue(context.WithValue(logging.TestContext(t.Context()), auth.WfKey, wfClientset), auth.KubeKey, kubeClientSet), auth.ClaimsKey, &types.Claims{Claims: jwt.Claims{Subject: "my-sub"}, Email: "my-sub@your.org"})
     	wftmplStore := NewClientStore()
    @@ -229,6 +237,30 @@ func TestWorkflowTemplateServer_GetWorkflowTemplate(t *testing.T) {
     		_, err := server.GetWorkflowTemplate(ctx, &workflowtemplatepkg.WorkflowTemplateGetRequest{Name: "unlabelled", Namespace: "default"})
     		require.Error(t, err)
     	})
    +	t.Run("Unauthorized", func(t *testing.T) {
    +		kubeClientSet := fake.NewSimpleClientset()
    +		kubeClientSet.PrependReactor("create", "selfsubjectaccessreviews", func(action k8stesting.Action) (bool, runtime.Object, error) {
    +			return true, &authorizationv1.SelfSubjectAccessReview{
    +				Status: authorizationv1.SubjectAccessReviewStatus{Allowed: false},
    +			}, nil
    +		})
    +		var wftObj1 v1alpha1.WorkflowTemplate
    +		v1alpha1.MustUnmarshal(wftStr2, &wftObj1)
    +		wfClientset := wftFake.NewSimpleClientset(&wftObj1)
    +		ctx := context.WithValue(context.WithValue(context.WithValue(logging.TestContext(t.Context()),
    +			auth.WfKey, wfClientset),
    +			auth.KubeKey, kubeClientSet),
    +			auth.ClaimsKey, &types.Claims{Claims: jwt.Claims{Subject: "my-sub"}})
    +		wftmplStore := NewWorkflowTemplateClientStore()
    +		cwftmplStore := clusterworkflowtemplate.NewClusterWorkflowTemplateClientStore()
    +		server := NewWorkflowTemplateServer(instanceid.NewService("my-instanceid"), wftmplStore, cwftmplStore)
    +		_, err := server.GetWorkflowTemplate(ctx, &workflowtemplatepkg.WorkflowTemplateGetRequest{
    +			Name:      "workflow-template-whalesay-template2",
    +			Namespace: "default",
    +		})
    +		require.Error(t, err)
    +		assert.Contains(t, err.Error(), "PermissionDenied")
    +	})
     }
     
     func TestWorkflowTemplateServer_ListWorkflowTemplates(t *testing.T) {
    

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

6

News mentions

0

No linked articles in our index yet.