GitOps to PwnOps: Deconstructing the Argo CD Stored XSS (CVE-2025-47933)
Jan 6, 2026·6 min read
Executive Summary (TL;DR)
Improper protocol sanitization in Argo CD's repository management UI allows a malicious actor to inject `javascript:` payloads into repository URLs. When an administrator clicks the link, the payload executes, granting the attacker full control over the Argo CD API and the managed Kubernetes clusters. Patch immediately to 2.13.8+, 2.14.13+, or 3.0.4+.
A critical Stored Cross-Site Scripting (XSS) vulnerability in the Argo CD web interface allows attackers with repository edit permissions to execute arbitrary JavaScript in the context of other users, potentially leading to full Kubernetes cluster compromise via the API.
The Hook: When GitOps Becomes a Backdoor
Argo CD is the darling of the Kubernetes world. It’s the "Source of Truth" engine that ensures what you have in Git is exactly what is running in your cluster. It holds the keys to the kingdom—literally. It stores credentials, manages secrets, and orchestrates deployments. So, when we find a vulnerability in Argo CD, we aren't just talking about a defaced website; we are talking about the potential for a complete infrastructure takeover.
CVE-2025-47933 is a Stored Cross-Site Scripting (XSS) vulnerability that feels almost nostalgic. In an era of complex heap corruptions and race conditions, sometimes the most devastating bugs are the simplest: a failure to sanitize a URL. But don't let the simplicity fool you. This isn't just about popping an alert(1) box. This is about leveraging the trust placed in the Argo CD UI to pivot from a low-privilege developer account to a full cluster administrator.
The vulnerability resides in the repository management page. If you have enough permission to add or edit a git repository source—a common permission for developers—you can plant a landmine that waits for an administrator to step on it. Once they do, their browser becomes your remote control for the entire cluster.
The Flaw: Trusting the Protocol
The root cause of this vulnerability is a classic case of "Trust, but don't verify." The flaw lives in the frontend code responsible for rendering repository links, specifically within ui/src/app/shared/components/urls.ts. The application uses a library called git-url-parse to decompose repository URLs into their constituent parts (protocol, resource, owner, name).
When the UI renders the repository list, it takes these parsed components and reconstructs the URL string to create a clickable anchor tag (<a>). The fatal mistake? The code assumed that if git-url-parse successfully parsed the URL, it was safe to render. It failed to validate the protocol (scheme) of the URL.
This means that while the developers expected https://github.com/org/repo, the parser was perfectly happy accepting javascript:alert(document.domain)//. The application would then obediently reconstruct this into <a href="javascript:...">, turning a data field into an execution context. This is a DOM-based XSS that persists in the database, meaning every time an admin loads the repo list, the trap is armed.
The Code: The Smoking Gun
Let's look at the vulnerable code. The function repoUrl was responsible for generating the link string. Notice how it blindly interpolates the protocol without checking what it actually is.
// Vulnerable Code in ui/src/app/shared/components/urls.ts
export function repoUrl(url: string): string {
try {
const parsed = GitUrlParse(url);
if (!parsed) {
return null;
}
// The vulnerability: Direct interpolation of parsed.protocol
return `${protocol(parsed.protocol)}://${parsed.resource}/${parsed.owner}/${parsed.name}`;
} catch {
return null;
}
}The fix, applied in commit a5b4041a79c54bc7b3d090805d070bcdb9a9e4d1, introduces a sanity check. The developers decided to implement a blacklist approach (which is risky, but effective here against standard vectors) by explicitly checking for dangerous protocols.
// Patched Code
import {isValidURL} from '../../shared/utils';
export function repoUrl(url: string): string {
try {
// ... parsing logic ...
const parsedUrl = `${protocol(parsed.protocol)}://${parsed.resource}/${parsed.owner}/${parsed.name}`;
// The Fix: Validate the reconstructed URL before returning it
if (!isValidURL(parsedUrl)) {
return null;
}
return parsedUrl;
} catch {
return null;
}
}
// And the validation logic added to utils.ts
export function isValidURL(url: string): boolean {
try {
const parsedUrl = new URL(url);
// Explicitly blocking the "Big Three" of XSS vectors
return parsedUrl.protocol !== 'javascript:' &&
parsedUrl.protocol !== 'data:' &&
parsedUrl.protocol !== 'vbscript:';
} catch (TypeError) {
// Fallback for relative URLs
// ...
}
}By ensuring the final URL is parsed by the browser's native URL constructor and checking the protocol property, they effectively neutralize the injection.
The Exploit: Escalation to Admin
To exploit this, an attacker needs repository:update or repository:create permissions. In many organizations, developers are allowed to add their own application repositories. This is the entry point.
The Attack Chain
-
Injection: The attacker uses the Argo CD CLI or API to register a new repository. Instead of a valid Git URL, they supply the malicious payload.
argocd repo add "javascript:fetch('/api/v1/cluster', {method:'DELETE'})//" -
The Trap: The malicious entry is saved to the database. It sits there, waiting.
-
The Trigger: A cluster administrator logs into the Argo CD UI to check on the status of repositories or debug a connection issue. They navigate to
Settings -> Repositories. -
The Execution: The administrator sees the list. They might click the link thinking it goes to GitHub, or the script might execute if the attacker gets clever with how the DOM handles the link (though this specific vector requires a click). Upon clicking, the JavaScript executes in the administrator's session.
-
The Payload: Since Argo CD is a Single Page Application (SPA), the XSS payload shares the same origin. The attacker's script can make authenticated calls to the Argo CD API using the victim's cookies/tokens. They could create a new admin user, dump secrets, or delete entire clusters.
The Impact: Why You Should Panic
Often, XSS is dismissed as a "medium" severity issue. In the context of a GitOps tool like Argo CD, it is undeniably Critical. Argo CD is often configured with high privileges in the Kubernetes cluster (ClusterAdmin) to apply changes to any namespace.
If an attacker hijacks an administrator's session via this XSS:
- Lateral Movement: They can pivot from the UI to the underlying Kubernetes API.
- Secret Exfiltration: They can read K8s Secrets managed by Argo CD.
- Denial of Service: They can delete all Applications and unexpected resources.
- Supply Chain Poisoning: They could modify the sync settings of other applications to point to malicious container images.
The CVSS score of 9.1 reflects this reality. The scope is changed (S:C), meaning the vulnerability affects resources beyond the vulnerable component itself (i.e., the managed clusters).
The Fix: Remediation
The remediation is straightforward: patch your Argo CD instances. The vulnerability has been fixed in the following versions:
- 2.13.8
- 2.14.13
- 3.0.4
If you cannot upgrade immediately, you must restrict permissions. Ensure that only trusted administrators have the create or update permission on the repositories resource in your RBAC configuration. Additionally, you should audit your current repositories for any URLs starting with javascript:, data:, or vbscript:.
[!NOTE] This vulnerability demonstrates why "Input Validation" is not enough. You need "Output Encoding" and rigorous protocol validation, especially when handling URLs that turn into clickable elements.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Argo CD Argo Project | >= 1.2.0-rc1, <= 1.8.7 | N/A (End of Life) |
Argo CD Argo Project | >= 2.0.0-rc3, < 2.13.8 | 2.13.8 |
Argo CD Argo Project | >= 2.14.0-rc1, < 2.14.13 | 2.14.13 |
Argo CD Argo Project | >= 3.0.0-rc1, < 3.0.4 | 3.0.4 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2025-47933 |
| CVSS v3.1 | 9.1 (Critical) |
| Attack Vector | Network (Stored XSS) |
| CWE | CWE-79 (Improper Neutralization of Input) |
| Impact | RCE / Cluster Compromise |
| Authentication | Required (Low Privilege) |
| EPSS Score | 0.00011 (Low probability) |
MITRE ATT&CK Mapping
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.