VYPR
Low severityNVD Advisory· Published Jul 12, 2022· Updated Apr 23, 2025

Cross-site Scripting for Argo CD single sign on users

CVE-2022-31102

Description

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Argo CD starting with 2.3.0 and prior to 2.3.6 and 2.4.5 is vulnerable to a cross-site scripting (XSS) bug which could allow an attacker to inject arbitrary JavaScript in the /auth/callback page in a victim's browser. This vulnerability only affects Argo CD instances which have single sign on (SSO) enabled. The exploit also assumes the attacker has 1) access to the API server's encryption key, 2) a method to add a cookie to the victim's browser, and 3) the ability to convince the victim to visit a malicious /auth/callback link. The vulnerability is classified as low severity because access to the API server's encryption key already grants a high level of access. Exploiting the XSS would allow the attacker to impersonate the victim, but would not grant any privileges which the attacker could not otherwise gain using the encryption key. A patch for this vulnerability has been released in the following Argo CD versions 2.4.5 and 2.3.6. There is currently no known workaround.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cdGo
>= 2.3.0, < 2.3.62.3.6
github.com/argoproj/argo-cdGo
>= 2.4.0, < 2.4.52.4.5

Affected products

1

Patches

2
8d5119b1e303

Merge pull request from GHSA-pmjg-52h9-72qv

https://github.com/argoproj/argo-cdMichael CrenshawJul 12, 2022via ghsa
2 files changed · +64 4
  • util/oidc/oidc.go+13 3 modified
    @@ -28,6 +28,8 @@ import (
     	"github.com/argoproj/argo-cd/v2/util/settings"
     )
     
    +var InvalidRedirectURLError = fmt.Errorf("invalid return URL")
    +
     const (
     	GrantTypeAuthorizationCode = "authorization_code"
     	GrantTypeImplicit          = "implicit"
    @@ -185,10 +187,18 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
     		return "", err
     	}
     	cookieVal := string(val)
    -	returnURL := a.baseHRef
    +	redirectURL := a.baseHRef
     	parts := strings.SplitN(cookieVal, ":", 2)
     	if len(parts) == 2 && parts[1] != "" {
    -		returnURL = parts[1]
    +		if !isValidRedirectURL(parts[1], []string{a.settings.URL}) {
    +			sanitizedUrl := parts[1]
    +			if len(sanitizedUrl) > 100 {
    +				sanitizedUrl = sanitizedUrl[:100]
    +			}
    +			log.Warnf("Failed to verify app state - got invalid redirectURL %q", sanitizedUrl)
    +			return "", fmt.Errorf("failed to verify app state: %w", InvalidRedirectURLError)
    +		}
    +		redirectURL = parts[1]
     	}
     	if parts[0] != state {
     		return "", fmt.Errorf("invalid state in '%s' cookie", common.AuthCookieName)
    @@ -201,7 +211,7 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
     		SameSite: http.SameSiteLaxMode,
     		Secure:   a.secureCookie,
     	})
    -	return returnURL, nil
    +	return redirectURL, nil
     }
     
     // isValidRedirectURL checks whether the given redirectURL matches on of the
    
  • util/oidc/oidc_test.go+51 1 modified
    @@ -191,7 +191,7 @@ func TestGenerateAppState(t *testing.T) {
     	signature, err := util.MakeSignature(32)
     	require.NoError(t, err)
     	expectedReturnURL := "http://argocd.example.com/"
    -	app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature}, "", "")
    +	app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature, URL: expectedReturnURL}, "", "")
     	require.NoError(t, err)
     	generateResponse := httptest.NewRecorder()
     	state, err := app.generateAppState(expectedReturnURL, generateResponse)
    @@ -219,6 +219,56 @@ func TestGenerateAppState(t *testing.T) {
     	})
     }
     
    +func TestGenerateAppState_XSS(t *testing.T) {
    +	signature, err := util.MakeSignature(32)
    +	require.NoError(t, err)
    +	app, err := NewClientApp(
    +		&settings.ArgoCDSettings{
    +			// Only return URLs starting with this base should be allowed.
    +			URL: "https://argocd.example.com",
    +			ServerSignature: signature,
    +		},
    +		"", "",
    +	)
    +	require.NoError(t, err)
    +
    +	t.Run("XSS fails", func(t *testing.T) {
    +		// This attack assumes the attacker has compromised the server's secret key. We use `generateAppState` here for
    +		// convenience, but an attacker with access to the server secret could write their own code to generate the
    +		// malicious cookie.
    +
    +		expectedReturnURL := "javascript: alert('hi')"
    +		generateResponse := httptest.NewRecorder()
    +		state, err := app.generateAppState(expectedReturnURL, generateResponse)
    +		require.NoError(t, err)
    +
    +		req := httptest.NewRequest("GET", "/", nil)
    +		for _, cookie := range generateResponse.Result().Cookies() {
    +			req.AddCookie(cookie)
    +		}
    +
    +		returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
    +		assert.ErrorIs(t, err, InvalidRedirectURLError)
    +		assert.Empty(t, returnURL)
    +	})
    +
    +	t.Run("valid return URL succeeds", func(t *testing.T) {
    +		expectedReturnURL := "https://argocd.example.com/some/path"
    +		generateResponse := httptest.NewRecorder()
    +		state, err := app.generateAppState(expectedReturnURL, generateResponse)
    +		require.NoError(t, err)
    +
    +		req := httptest.NewRequest("GET", "/", nil)
    +		for _, cookie := range generateResponse.Result().Cookies() {
    +			req.AddCookie(cookie)
    +		}
    +
    +		returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
    +		assert.NoError(t, err, InvalidRedirectURLError)
    +		assert.Equal(t, expectedReturnURL, returnURL)
    +	})
    +}
    +
     func TestGenerateAppState_NoReturnURL(t *testing.T) {
     	signature, err := util.MakeSignature(32)
     	require.NoError(t, err)
    
3800a1e49d1d

Merge pull request from GHSA-pmjg-52h9-72qv

https://github.com/argoproj/argo-cdMichael CrenshawJul 12, 2022via ghsa
2 files changed · +64 4
  • util/oidc/oidc.go+13 3 modified
    @@ -28,6 +28,8 @@ import (
     	"github.com/argoproj/argo-cd/v2/util/settings"
     )
     
    +var InvalidRedirectURLError = fmt.Errorf("invalid return URL")
    +
     const (
     	GrantTypeAuthorizationCode = "authorization_code"
     	GrantTypeImplicit          = "implicit"
    @@ -185,10 +187,18 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
     		return "", err
     	}
     	cookieVal := string(val)
    -	returnURL := a.baseHRef
    +	redirectURL := a.baseHRef
     	parts := strings.SplitN(cookieVal, ":", 2)
     	if len(parts) == 2 && parts[1] != "" {
    -		returnURL = parts[1]
    +		if !isValidRedirectURL(parts[1], []string{a.settings.URL}) {
    +			sanitizedUrl := parts[1]
    +			if len(sanitizedUrl) > 100 {
    +				sanitizedUrl = sanitizedUrl[:100]
    +			}
    +			log.Warnf("Failed to verify app state - got invalid redirectURL %q", sanitizedUrl)
    +			return "", fmt.Errorf("failed to verify app state: %w", InvalidRedirectURLError)
    +		}
    +		redirectURL = parts[1]
     	}
     	if parts[0] != state {
     		return "", fmt.Errorf("invalid state in '%s' cookie", common.AuthCookieName)
    @@ -201,7 +211,7 @@ func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state
     		SameSite: http.SameSiteLaxMode,
     		Secure:   a.secureCookie,
     	})
    -	return returnURL, nil
    +	return redirectURL, nil
     }
     
     // isValidRedirectURL checks whether the given redirectURL matches on of the
    
  • util/oidc/oidc_test.go+51 1 modified
    @@ -191,7 +191,7 @@ func TestGenerateAppState(t *testing.T) {
     	signature, err := util.MakeSignature(32)
     	require.NoError(t, err)
     	expectedReturnURL := "http://argocd.example.com/"
    -	app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature}, "", "")
    +	app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature, URL: expectedReturnURL}, "", "")
     	require.NoError(t, err)
     	generateResponse := httptest.NewRecorder()
     	state, err := app.generateAppState(expectedReturnURL, generateResponse)
    @@ -219,6 +219,56 @@ func TestGenerateAppState(t *testing.T) {
     	})
     }
     
    +func TestGenerateAppState_XSS(t *testing.T) {
    +	signature, err := util.MakeSignature(32)
    +	require.NoError(t, err)
    +	app, err := NewClientApp(
    +		&settings.ArgoCDSettings{
    +			// Only return URLs starting with this base should be allowed.
    +			URL: "https://argocd.example.com",
    +			ServerSignature: signature,
    +		},
    +		"", "",
    +	)
    +	require.NoError(t, err)
    +
    +	t.Run("XSS fails", func(t *testing.T) {
    +		// This attack assumes the attacker has compromised the server's secret key. We use `generateAppState` here for
    +		// convenience, but an attacker with access to the server secret could write their own code to generate the
    +		// malicious cookie.
    +
    +		expectedReturnURL := "javascript: alert('hi')"
    +		generateResponse := httptest.NewRecorder()
    +		state, err := app.generateAppState(expectedReturnURL, generateResponse)
    +		require.NoError(t, err)
    +
    +		req := httptest.NewRequest("GET", "/", nil)
    +		for _, cookie := range generateResponse.Result().Cookies() {
    +			req.AddCookie(cookie)
    +		}
    +
    +		returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
    +		assert.ErrorIs(t, err, InvalidRedirectURLError)
    +		assert.Empty(t, returnURL)
    +	})
    +
    +	t.Run("valid return URL succeeds", func(t *testing.T) {
    +		expectedReturnURL := "https://argocd.example.com/some/path"
    +		generateResponse := httptest.NewRecorder()
    +		state, err := app.generateAppState(expectedReturnURL, generateResponse)
    +		require.NoError(t, err)
    +
    +		req := httptest.NewRequest("GET", "/", nil)
    +		for _, cookie := range generateResponse.Result().Cookies() {
    +			req.AddCookie(cookie)
    +		}
    +
    +		returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state)
    +		assert.NoError(t, err, InvalidRedirectURLError)
    +		assert.Equal(t, expectedReturnURL, returnURL)
    +	})
    +}
    +
     func TestGenerateAppState_NoReturnURL(t *testing.T) {
     	signature, err := util.MakeSignature(32)
     	require.NoError(t, err)
    

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

7

News mentions

0

No linked articles in our index yet.