VYPR
Critical severityNVD Advisory· Published Jun 27, 2022· Updated Apr 23, 2025

External URLs for Deployments can include javascript in argo-cd

CVE-2022-31035

Description

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. All versions of Argo CD starting with v1.0.0 are vulnerable to a cross-site scripting (XSS) bug allowing a malicious user to inject a javascript: link in the UI. When clicked by a victim user, the script will execute with the victim's permissions (up to and including admin). The script would be capable of doing anything which is possible in the UI or via the API, such as creating, modifying, and deleting Kubernetes resources. A patch for this vulnerability has been released in the following Argo CD versions: v2.4.1, v2.3.5, v2.2.10 and v2.1.16. There are no completely-safe workarounds besides upgrading.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cdGo
>= 1.0.0, < 2.1.162.1.16
github.com/argoproj/argo-cd/v2Go
< 2.1.162.1.16
github.com/argoproj/argo-cd/v2Go
>= 2.2.0, < 2.2.102.2.10
github.com/argoproj/argo-cd/v2Go
>= 2.3.0, < 2.3.52.3.5
github.com/argoproj/argo-cd/v2Go
>= 2.4.0, < 2.4.12.4.1

Affected products

1

Patches

1
8bc3ef690de2

Merge pull request from GHSA-h4w9-6x78-8vrj

https://github.com/argoproj/argo-cdMichael CrenshawJun 21, 2022via ghsa
4 files changed · +55 3
  • server/server.go+1 0 modified
    @@ -1010,6 +1010,7 @@ func (a *ArgoCDServer) Authenticate(ctx context.Context) (context.Context, error
     		if !argoCDSettings.AnonymousUserEnabled {
     			return ctx, claimsErr
     		} else {
    +			// nolint:staticcheck
     			ctx = context.WithValue(ctx, "claims", "")
     		}
     	}
    
  • server/server_test.go+1 1 modified
    @@ -508,7 +508,7 @@ func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool) (argoc
     		cm.Data["users.anonymous.enabled"] = "true"
     	}
     	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    -		return  // Start with a placeholder. We need the server URL before setting up the real handler.
    +		// Start with a placeholder. We need the server URL before setting up the real handler.
     	}))
     	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     		dexMockHandler(t, ts.URL)(w, r)
    
  • ui/src/app/applications/components/application-urls.test.ts+20 0 added
    @@ -0,0 +1,20 @@
    +import {ExternalLink, InvalidExternalLinkError} from './application-urls';
    +
    +test('rejects malicious URLs', () => {
    +    expect(() => {
    +        const _ = new ExternalLink('javascript:alert("hi")');
    +    }).toThrowError(InvalidExternalLinkError);
    +    expect(() => {
    +        const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
    +    }).toThrowError(InvalidExternalLinkError);
    +});
    +
    +test('allows absolute URLs', () => {
    +    expect(new ExternalLink('https://localhost:8080/applications').ref).toEqual('https://localhost:8080/applications');
    +});
    +
    +test('allows relative URLs', () => {
    +    // @ts-ignore
    +    window.location = new URL('https://localhost:8080/applications');
    +    expect(new ExternalLink('/applications').ref).toEqual('/applications');
    +});
    
  • ui/src/app/applications/components/application-urls.tsx+33 2 modified
    @@ -1,7 +1,15 @@
     import {DropDownMenu} from 'argo-ui';
     import * as React from 'react';
     
    -class ExternalLink {
    +export class InvalidExternalLinkError extends Error {
    +    constructor(message: string) {
    +        super(message);
    +        Object.setPrototypeOf(this, InvalidExternalLinkError.prototype);
    +        this.name = 'InvalidExternalLinkError';
    +    }
    +}
    +
    +export class ExternalLink {
         public title: string;
         public ref: string;
     
    @@ -14,13 +22,36 @@ class ExternalLink {
                 this.title = url;
                 this.ref = url;
             }
    +        if (!ExternalLink.isValidURL(this.ref)) {
    +            throw new InvalidExternalLinkError('Invalid URL');
    +        }
    +    }
    +
    +    private static isValidURL(url: string): boolean {
    +        try {
    +            const parsedUrl = new URL(url);
    +            return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
    +        } catch (TypeError) {
    +            try {
    +                // Try parsing as a relative URL.
    +                const parsedUrl = new URL(url, window.location.origin);
    +                return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
    +            } catch (TypeError) {
    +                return false;
    +            }
    +        }
         }
     }
     
     export const ApplicationURLs = ({urls}: {urls: string[]}) => {
         const externalLinks: ExternalLink[] = [];
         for (const url of urls || []) {
    -        externalLinks.push(new ExternalLink(url));
    +        try {
    +            const externalLink = new ExternalLink(url);
    +            externalLinks.push(externalLink);
    +        } catch (InvalidExternalLinkError) {
    +            continue;
    +        }
         }
     
         // sorted alphabetically & links with titles first
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.