VYPR
Critical severityNVD Advisory· Published Sep 4, 2025· Updated Sep 5, 2025

Argo CD: Project API Token Exposes Repository Credentials

CVE-2025-55190

Description

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. In versions 2.13.0 through 2.13.8, 2.14.0 through 2.14.15, 3.0.0 through 3.0.12 and 3.1.0-rc1 through 3.1.1, API tokens with project-level permissions are able to retrieve sensitive repository credentials (usernames, passwords) through the project details API endpoint, even when the token only has standard application management permissions and no explicit access to secrets. This vulnerability does not only affect project-level permissions. Any token with project get permissions is also vulnerable, including global permissions such as: p, role/user, projects, get, *, allow. This issue is fixed in versions 2.13.9, 2.14.16, 3.0.14 and 3.1.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/argoproj/argo-cd/v2Go
>= 2.13.0, < 2.13.92.13.9
github.com/argoproj/argo-cd/v2Go
>= 2.14.0, < 2.14.162.14.16
github.com/argoproj/argo-cd/v3Go
< 3.0.143.0.14
github.com/argoproj/argo-cd/v3Go
>= 3.1.0-rc1, < 3.1.23.1.2

Affected products

1

Patches

1
e8f86101f537

fix(security): repository.GetDetailedProject exposes repo secrets (#24387)

https://github.com/argoproj/argo-cdMichael CrenshawSep 4, 2025via ghsa
10 files changed · +124 17
  • docs/operator-manual/upgrading/2.13-2.14.md+9 1 modified
    @@ -11,4 +11,12 @@ Eg, `https://github.com/argoproj/argo-cd/manifests/ha/cluster-install?ref=v2.14.
     ## Upgraded Helm Version
     
     Helm was upgraded to 3.16.2 and the skipSchemaValidation Flag was added to
    -the [CLI and Application CR](https://argo-cd.readthedocs.io/en/latest/user-guide/helm/#helm-skip-schema-validation). 
    \ No newline at end of file
    +the [CLI and Application CR](https://argo-cd.readthedocs.io/en/latest/user-guide/helm/#helm-skip-schema-validation). 
    +
    +## Breaking Changes
    +
    +## Sanitized project API response
    +
    +Due to security reasons ([GHSA-786q-9hcg-v9ff](https://github.com/argoproj/argo-cd/security/advisories/GHSA-786q-9hcg-v9ff)),
    +the project API response was sanitized to remove sensitive information. This includes
    +credentials of project-scoped repositories and clusters.
    
  • docs/operator-manual/upgrading/2.14-3.0.md+6 0 modified
    @@ -492,3 +492,9 @@ resource.customizations.ignoreDifferences.apiextensions.k8s.io_CustomResourceDef
     ```
     
     More details for ignored resource updates in the [Diffing customization](../../user-guide/diffing.md) documentation.
    +
    +### Sanitized project API response
    +
    +Due to security reasons ([GHSA-786q-9hcg-v9ff](https://github.com/argoproj/argo-cd/security/advisories/GHSA-786q-9hcg-v9ff)),
    +the project API response was sanitized to remove sensitive information. This includes
    +credentials of project-scoped repositories and clusters.
    \ No newline at end of file
    
  • docs/operator-manual/upgrading/3.0-3.1.md+8 0 modified
    @@ -55,3 +55,11 @@ Argo CD v3.1 upgrades the bundled Helm version to 3.18.4. There are no breaking
     
     Argo CD v3.1 upgrades the bundled Kustomize version to 5.7.0. There are no breaking changes in Kustomize 5.7 according
     to the [release notes](https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv5.7.0).
    +
    +## Breaking Changes
    +
    +## Sanitized project API response
    +
    +Due to security reasons ([GHSA-786q-9hcg-v9ff](https://github.com/argoproj/argo-cd/security/advisories/GHSA-786q-9hcg-v9ff)),
    +the project API response was sanitized to remove sensitive information. This includes
    +credentials of project-scoped repositories and clusters.
    
  • docs/operator-manual/upgrading/3.1-3.2.md+8 0 modified
    @@ -52,3 +52,11 @@ If you do not want your CronJob to affect the Application's aggregated Health, y
     `argocd.argoproj.io/ignore-healthcheck: "true"` on the CronJob resource.
     
     The health can also be configured globally using the `resource.customizations.health.batch_CronJob` configuration to change the default behaviour.
    +
    +## Breaking Changes
    +
    +## Sanitized project API response
    +
    +Due to security reasons ([GHSA-786q-9hcg-v9ff](https://github.com/argoproj/argo-cd/security/advisories/GHSA-786q-9hcg-v9ff)),
    +the project API response was sanitized to remove sensitive information. This includes
    +credentials of project-scoped repositories and clusters.
    
  • pkg/apis/application/v1alpha1/repository_types.go+0 1 modified
    @@ -347,7 +347,6 @@ func (repo *Repository) Sanitized() *Repository {
     		Repo:                       repo.Repo,
     		Type:                       repo.Type,
     		Name:                       repo.Name,
    -		Username:                   repo.Username,
     		Insecure:                   repo.IsInsecure(),
     		EnableLFS:                  repo.EnableLFS,
     		EnableOCI:                  repo.EnableOCI,
    
  • pkg/apis/application/v1alpha1/types.go+26 0 modified
    @@ -2237,6 +2237,32 @@ type Cluster struct {
     	Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,13,opt,name=annotations"`
     }
     
    +func (c *Cluster) Sanitized() *Cluster {
    +	return &Cluster{
    +		ID:                 c.ID,
    +		Server:             c.Server,
    +		Name:               c.Name,
    +		Project:            c.Project,
    +		Namespaces:         c.Namespaces,
    +		Shard:              c.Shard,
    +		Labels:             c.Labels,
    +		Annotations:        c.Annotations,
    +		ClusterResources:   c.ClusterResources,
    +		ConnectionState:    c.ConnectionState,
    +		ServerVersion:      c.ServerVersion,
    +		Info:               c.Info,
    +		RefreshRequestedAt: c.RefreshRequestedAt,
    +		Config: ClusterConfig{
    +			AWSAuthConfig:      c.Config.AWSAuthConfig,
    +			ProxyUrl:           c.Config.ProxyUrl,
    +			DisableCompression: c.Config.DisableCompression,
    +			TLSClientConfig: TLSClientConfig{
    +				Insecure: c.Config.Insecure,
    +			},
    +		},
    +	}
    +}
    +
     // Equals returns true if two cluster objects are considered to be equal
     func (c *Cluster) Equals(other *Cluster) bool {
     	if c.Server != other.Server {
    
  • pkg/apis/application/v1alpha1/types_test.go+55 0 modified
    @@ -4705,3 +4705,58 @@ func TestSyncWindow_Hash(t *testing.T) {
     		require.Equal(t, hash1, hash2, "windows with same core identity but different metadata should produce same hash")
     	})
     }
    +
    +func TestSanitized(t *testing.T) {
    +	now := metav1.Now()
    +	cluster := &Cluster{
    +		ID:            "123",
    +		Server:        "https://example.com",
    +		Name:          "example",
    +		ServerVersion: "v1.0.0",
    +		Namespaces:    []string{"default", "kube-system"},
    +		Project:       "default",
    +		Labels: map[string]string{
    +			"env": "production",
    +		},
    +		Annotations: map[string]string{
    +			"annotation-key": "annotation-value",
    +		},
    +		ConnectionState: ConnectionState{
    +			Status:     ConnectionStatusSuccessful,
    +			Message:    "Connection successful",
    +			ModifiedAt: &now,
    +		},
    +		Config: ClusterConfig{
    +			Username:    "admin",
    +			Password:    "password123",
    +			BearerToken: "abc",
    +			TLSClientConfig: TLSClientConfig{
    +				Insecure: true,
    +			},
    +			ExecProviderConfig: &ExecProviderConfig{
    +				Command: "test",
    +			},
    +		},
    +	}
    +
    +	assert.Equal(t, &Cluster{
    +		ID:            "123",
    +		Server:        "https://example.com",
    +		Name:          "example",
    +		ServerVersion: "v1.0.0",
    +		Namespaces:    []string{"default", "kube-system"},
    +		Project:       "default",
    +		Labels:        map[string]string{"env": "production"},
    +		Annotations:   map[string]string{"annotation-key": "annotation-value"},
    +		ConnectionState: ConnectionState{
    +			Status:     ConnectionStatusSuccessful,
    +			Message:    "Connection successful",
    +			ModifiedAt: &now,
    +		},
    +		Config: ClusterConfig{
    +			TLSClientConfig: TLSClientConfig{
    +				Insecure: true,
    +			},
    +		},
    +	}, cluster.Sanitized())
    +}
    
  • server/cluster/cluster.go+1 12 modified
    @@ -471,19 +471,8 @@ func (s *Server) RotateAuth(ctx context.Context, q *cluster.ClusterQuery) (*clus
     }
     
     func (s *Server) toAPIResponse(clust *appv1.Cluster) *appv1.Cluster {
    +	clust = clust.Sanitized()
     	_ = s.cache.GetClusterInfo(clust.Server, &clust.Info)
    -
    -	clust.Config.Password = ""
    -	clust.Config.BearerToken = ""
    -	clust.Config.KeyData = nil
    -	if clust.Config.ExecProviderConfig != nil {
    -		// We can't know what the user has put into args or
    -		// env vars on the exec provider that might be sensitive
    -		// (e.g. --private-key=XXX, PASSWORD=XXX)
    -		// Implicitly assumes the command executable name is non-sensitive
    -		clust.Config.ExecProviderConfig.Env = make(map[string]string)
    -		clust.Config.ExecProviderConfig.Args = nil
    -	}
     	// populate deprecated fields for backward compatibility
     	//nolint:staticcheck
     	clust.ServerVersion = clust.Info.ServerVersion
    
  • server/project/project.go+10 2 modified
    @@ -310,12 +310,20 @@ func (s *Server) GetDetailedProject(ctx context.Context, q *project.ProjectQuery
     	}
     	proj.NormalizeJWTTokens()
     	globalProjects := argo.GetGlobalProjects(proj, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.settingsMgr)
    +	var apiRepos []*v1alpha1.Repository
    +	for _, repo := range repositories {
    +		apiRepos = append(apiRepos, repo.Normalize().Sanitized())
    +	}
    +	var apiClusters []*v1alpha1.Cluster
    +	for _, cluster := range clusters {
    +		apiClusters = append(apiClusters, cluster.Sanitized())
    +	}
     
     	return &project.DetailedProjectsResponse{
     		GlobalProjects: globalProjects,
     		Project:        proj,
    -		Repositories:   repositories,
    -		Clusters:       clusters,
    +		Repositories:   apiRepos,
    +		Clusters:       apiClusters,
     	}, err
     }
     
    
  • server/repository/repository_test.go+1 1 modified
    @@ -313,7 +313,7 @@ func TestRepositoryServer(t *testing.T) {
     		testRepo := &appsv1.Repository{
     			Repo:           url,
     			Type:           "git",
    -			Username:       "foo",
    +			Username:       "",
     			InheritedCreds: true,
     		}
     		db.On("ListRepositories", t.Context()).Return([]*appsv1.Repository{testRepo}, nil)
    

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

4

News mentions

0

No linked articles in our index yet.