GHSA-h98r-wv3h-fr38 Argo CD: Stored XSS in application link annotations enables developer-to-admin privilege escalation
SummaryA user with **application write access (developer role)** can set `link.argocd.argoproj.io/*` annotations on any ArgoCD Application. These annotation values are rendered in the Summary tab's **URLs section** as `<a href>` elements without URL validation. Using the pipe-separator trick (`Display Text | javascript:...`), an attacker can inject a `javascript:` URI while displaying a legitimate-looking label (e.g. `GitHub Repo`). When a higher-privileged user (admin) clicks the link, **arbitrary JavaScript executes in the ArgoCD origin context** in the admin's authenticated session context, enabling API exfiltration and privilege escalation from developer to admin.Details**Vulnerable sink:** `ui/src/app/applications/components/application-summary/application-summary.tsx:277`The annotation value is split on `|`. `parts[0]` becomes the visible link label; `parts[1]` becomes the `href`. **No call to `isValidURL()` is made**, unlike the protected `ApplicationURLs` component (`application-urls.tsx:72,80`) which does validate URLs and blocks `javascript:`. The `target='_blank'` opens a new tab that inherits the ArgoCD origin, giving the injected script same-origin fetch access to all ArgoCD APIs using the victim's authenticated session (credentialed `fetch()` calls).**Root cause:** React 16.x does not block `javascript:` URIs in `href` attributes (this protection was added in React 19). The helper `isValidURL()` exists in `shared/utils.ts` but is **not applied** to this sink.**CSP:** ArgoCD's default Content Security Policy is `frame-ancestors 'self'` only — no `script-src`, no `connect-src`, no `default-src` — providing **zero XSS execution mitigation**.PoC**Prerequisites:** Developer role with application write access (e.g. RBAC: `p, role:developer, applications, *, */*, allow`).**Step 1 — Set malicious annotation as developer:**The URL section in the admin's Summary tab renders the link as **"GitHub Repo"** — the `javascript:` payload is invisible in the displayed text.**Step 2 — Admin opens Summary tab** of the annotated application and clicks the link.**Step 3 — JavaScript executes** at the ArgoCD origin and exfiltrates admin session data via out-of-band HTTP request. Tested with Burp Collaborator:**Step 4 — Burp Collaborator received the OOB HTTP interaction** containing the base64-encoded admin session data. Decoded response:**Tested on:** ArgoCD v3.3.8 (commit 0850e97), React 16.9.3.Impact**Stored XSS** — payload persists in the Kubernetes Application resource until manually removed**Privilege escalation** — developer role → admin session hijacking via authenticated API calls**Maximum stealth** — the injected link displays as any attacker-chosen text; the `javascript:` href is never visible to the victim**No server-side interaction required** — purely client-side exploit, no network egress needed for execution (exfiltration uses `no-cors` fetch, bypassed by absent `connect-src` CSP)Any admin or operator who views the Summary tab of the compromised application is affectedCreditsDiscovered and reported by **Jan Kahmen** ([jan@turingpoint.de](mailto:jan@turingpoint.de)) — [turingpoint.de](https://turingpoint.de)
CVSS v3.1 score: 7.3