Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package argocd-cli for openSUSE:Factory checked in at 2026-05-13 17:21:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/argocd-cli (Old) and /work/SRC/openSUSE:Factory/.argocd-cli.new.1966 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "argocd-cli" Wed May 13 17:21:40 2026 rev:69 rq:1352920 version:3.4.2 Changes: -------- --- /work/SRC/openSUSE:Factory/argocd-cli/argocd-cli.changes 2026-05-06 19:24:29.373398932 +0200 +++ /work/SRC/openSUSE:Factory/.argocd-cli.new.1966/argocd-cli.changes 2026-05-13 17:23:28.525587578 +0200 @@ -1,0 +2,10 @@ +Wed May 13 04:43:35 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 3.4.2: + No CLI-related changes + Dependency updates: + * chore: bump otel/sdk to 1.43.0 (release-3.4) (#27806) + * chore(deps): bump github.com/moby/spdystream from 0.5.0 to + 0.5.1 (cherry-pick #27401 for 3.4) (#27734) + +------------------------------------------------------------------- Old: ---- argocd-cli-3.4.1.obscpio New: ---- argocd-cli-3.4.2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ argocd-cli.spec ++++++ --- /var/tmp/diff_new_pack.q1pZOj/_old 2026-05-13 17:23:29.829641706 +0200 +++ /var/tmp/diff_new_pack.q1pZOj/_new 2026-05-13 17:23:29.833641872 +0200 @@ -19,7 +19,7 @@ %define executable_name argocd Name: argocd-cli -Version: 3.4.1 +Version: 3.4.2 Release: 0 Summary: CLI for the ArgoCD declarative continuous deployment tool License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.q1pZOj/_old 2026-05-13 17:23:29.881643864 +0200 +++ /var/tmp/diff_new_pack.q1pZOj/_new 2026-05-13 17:23:29.885644030 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/argoproj/argo-cd</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v3.4.1</param> + <param name="revision">v3.4.2</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.q1pZOj/_old 2026-05-13 17:23:29.917645358 +0200 +++ /var/tmp/diff_new_pack.q1pZOj/_new 2026-05-13 17:23:29.925645691 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/argoproj/argo-cd</param> - <param name="changesrevision">bafd59bd37138ed731d3c3aad8e18731d72aed46</param></service></servicedata> + <param name="changesrevision">0dc6b1b57dd5bb925d5b03c3d09419ab9fb4225e</param></service></servicedata> (No newline at EOF) ++++++ argocd-cli-3.4.1.obscpio -> argocd-cli-3.4.2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/VERSION new/argocd-cli-3.4.2/VERSION --- old/argocd-cli-3.4.1/VERSION 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/VERSION 2026-05-12 22:31:52.000000000 +0200 @@ -1 +1 @@ -3.4.1 +3.4.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/controller/cache/info.go new/argocd-cli-3.4.2/controller/cache/info.go --- old/argocd-cli-3.4.1/controller/cache/info.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/controller/cache/info.go 2026-05-12 22:31:52.000000000 +0200 @@ -20,6 +20,7 @@ "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v3/util/argo/normalizers" "github.com/argoproj/argo-cd/v3/util/resource" + "github.com/argoproj/argo-cd/v3/util/settings" ) func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo, customLabels []string) { @@ -40,6 +41,16 @@ for k, v := range un.GetAnnotations() { if strings.HasPrefix(k, common.AnnotationKeyLinkPrefix) { + // Annotation values may be either a bare URL or "title|url"; validate + // the URL portion to prevent XSS via javascript:/data:/vbscript: URIs + // when the value is rendered as an href in the UI. + urlPart := v + if idx := strings.Index(v, "|"); idx >= 0 { + urlPart = v[idx+1:] + } + if err := settings.ValidateExternalURL(urlPart); err != nil { + continue + } if res.NetworkingInfo == nil { res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{} } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/controller/cache/info_test.go new/argocd-cli-3.4.2/controller/cache/info_test.go --- old/argocd-cli-3.4.1/controller/cache/info_test.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/controller/cache/info_test.go 2026-05-12 22:31:52.000000000 +0200 @@ -62,6 +62,30 @@ ingress: - hostname: localhost`) + testMaliciousLinkAnnotatedService = strToUnstructured(` + apiVersion: v1 + kind: Service + metadata: + name: helm-guestbook + namespace: default + resourceVersion: "123" + uid: "4" + annotations: + link.argocd.argoproj.io/javascript: 'javascript:alert(1)' + link.argocd.argoproj.io/data: 'data:text/html,<script>alert(1)</script>' + link.argocd.argoproj.io/vbscript: 'vbscript:msgbox(1)' + link.argocd.argoproj.io/titled-javascript: 'click me|javascript:alert(1)' + link.argocd.argoproj.io/no-scheme: 'example.com/foo' + link.argocd.argoproj.io/safe: 'http://my-grafana.example.com/pre-generated-link' + spec: + selector: + app: guestbook + type: LoadBalancer + status: + loadBalancer: + ingress: + - hostname: localhost`) + testIngress = strToUnstructured(` apiVersion: extensions/v1beta1 kind: Ingress @@ -1140,6 +1164,15 @@ }, info.NetworkingInfo) } +func TestMaliciousLinkAnnotatedServiceInfoFiltered(t *testing.T) { + info := &ResourceInfo{} + populateNodeInfo(testMaliciousLinkAnnotatedService, info, []string{}) + require.NotNil(t, info.NetworkingInfo) + // Only the http URL should make it through; javascript:, data:, vbscript:, + // "title|javascript:..." and scheme-less values must be dropped. + assert.Equal(t, []string{"http://my-grafana.example.com/pre-generated-link"}, info.NetworkingInfo.ExternalURLs) +} + func TestGetIstioVirtualServiceInfo(t *testing.T) { info := &ResourceInfo{} populateNodeInfo(testIstioVirtualService, info, []string{}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/controller/state.go new/argocd-cli-3.4.2/controller/state.go --- old/argocd-cli-3.4.1/controller/state.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/controller/state.go 2026-05-12 22:31:52.000000000 +0200 @@ -41,18 +41,13 @@ "github.com/argoproj/argo-cd/v3/util/argo/normalizers" appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate" "github.com/argoproj/argo-cd/v3/util/db" - "github.com/argoproj/argo-cd/v3/util/env" "github.com/argoproj/argo-cd/v3/util/gpg" utilio "github.com/argoproj/argo-cd/v3/util/io" "github.com/argoproj/argo-cd/v3/util/settings" "github.com/argoproj/argo-cd/v3/util/stats" ) -var ( - ErrCompareStateRepo = errors.New("failed to get repo objects") - - processManifestGeneratePathsEnabled = env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_PROCESS_MANIFEST_GENERATE_PATHS", true) -) +var ErrCompareStateRepo = errors.New("failed to get repo objects") type resourceInfoProviderStub struct{} @@ -75,7 +70,7 @@ // AppStateManager defines methods which allow to compare application spec and actual application state. type AppStateManager interface { - CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache, noRevisionCache bool, localObjects []string, hasMultipleSources bool) (*comparisonResult, error) + CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool) (*comparisonResult, error) SyncAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, state *v1alpha1.OperationState) GetRepoObjs(ctx context.Context, app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, sendRuntimeState bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, bool, error) } @@ -261,14 +256,7 @@ appNamespace := app.Spec.Destination.Namespace apiVersions := argo.APIResourcesToStrings(apiResources, true) - updateRevisions := processManifestGeneratePathsEnabled && - // updating revisions result is not required if automated sync is not enabled - app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil && - // using updating revisions gains performance only if manifest generation is required. - // just reading pre-generated manifests is comparable to updating revisions time-wise - app.Status.SourceType != v1alpha1.ApplicationSourceTypeDirectory - - if updateRevisions && repo.Depth == 0 && syncedRevision != "" && !source.IsRef() && keyManifestGenerateAnnotationExists && keyManifestGenerateAnnotationVal != "" && (syncedRevision != revision || app.Spec.HasMultipleSources()) { + if repo.Depth == 0 && syncedRevision != "" && !source.IsRef() && keyManifestGenerateAnnotationExists && keyManifestGenerateAnnotationVal != "" && (syncedRevision != revision || app.Spec.HasMultipleSources()) { // Validate the manifest-generate-path annotation to avoid generating manifests if it has not changed. updateRevisionResult, err := repoClient.UpdateRevisionForPaths(ctx, &apiclient.UpdateRevisionForPathsRequest{ Repo: repo, @@ -367,7 +355,7 @@ } // ResolveGitRevision will resolve the given revision to a full commit SHA. Only works for git. -func (m *appStateManager) ResolveGitRevision(repoURL, revision string) (string, error) { +func (m *appStateManager) ResolveGitRevision(repoURL string, revision string) (string, error) { conn, repoClient, err := m.repoClientset.NewRepoServerClient() if err != nil { return "", fmt.Errorf("failed to connect to repo server: %w", err) @@ -568,7 +556,7 @@ // CompareAppState compares application git state to the live app state, using the specified // revision and supplied source. If revision or overrides are empty, then compares against // revision and overrides in the app spec. -func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache, noRevisionCache bool, localManifests []string, hasMultipleSources bool) (*comparisonResult, error) { +func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool) (*comparisonResult, error) { ts := stats.NewTimingStats() logCtx := log.WithFields(applog.GetAppLogFields(app)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/controller/sync.go new/argocd-cli-3.4.2/controller/sync.go --- old/argocd-cli-3.4.1/controller/sync.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/controller/sync.go 2026-05-12 22:31:52.000000000 +0200 @@ -308,22 +308,9 @@ sync.WithLogr(logutils.NewLogrusLogger(logEntry)), sync.WithHealthOverride(lua.ResourceHealthOverrides(resourceOverrides)), sync.WithPermissionValidator(func(un *unstructured.Unstructured, res *metav1.APIResource) error { - if !project.IsGroupKindNamePermitted(un.GroupVersionKind().GroupKind(), un.GetName(), res.Namespaced) { - return fmt.Errorf("resource %s:%s is not permitted in project %s", un.GroupVersionKind().Group, un.GroupVersionKind().Kind, project.Name) - } - if res.Namespaced { - permitted, err := project.IsDestinationPermitted(destCluster, un.GetNamespace(), func(project string) ([]*v1alpha1.Cluster, error) { - return m.db.GetProjectClusters(context.TODO(), project) - }) - if err != nil { - return err - } - - if !permitted { - return fmt.Errorf("namespace %v is not permitted in project '%s'", un.GetNamespace(), project.Name) - } - } - return nil + return validateSyncPermissions(project, destCluster, func(proj string) ([]*v1alpha1.Cluster, error) { + return m.db.GetProjectClusters(context.TODO(), proj) + }, un, res) }), sync.WithOperationSettings(syncOp.DryRun, syncOp.Prune, syncOp.SyncStrategy.Force(), syncOp.IsApplyStrategy() || len(syncOp.Resources) > 0), sync.WithInitialState(state.Phase, state.Message, initialResourcesRes, state.StartedAt), @@ -605,3 +592,33 @@ // if there is no match found in the AppProject.Spec.DestinationServiceAccounts, use the default service account of the destination namespace. return "", fmt.Errorf("no matching service account found for destination server %s and namespace %s", application.Spec.Destination.Server, serviceAccountNamespace) } + +// validateSyncPermissions checks whether the given resource is permitted by the project's +// allow/deny lists and destination rules. It returns an error if the API resource info is nil +// (preventing a nil-pointer panic), if the resource's group/kind is not permitted, or if +// the resource's namespace is not an allowed destination. +func validateSyncPermissions( + project *v1alpha1.AppProject, + destCluster *v1alpha1.Cluster, + getProjectClusters func(string) ([]*v1alpha1.Cluster, error), + un *unstructured.Unstructured, + res *metav1.APIResource, +) error { + if res == nil { + return fmt.Errorf("failed to get API resource info for %s/%s: unable to verify permissions", un.GroupVersionKind().Group, un.GroupVersionKind().Kind) + } + if !project.IsGroupKindNamePermitted(un.GroupVersionKind().GroupKind(), un.GetName(), res.Namespaced) { + return fmt.Errorf("resource %s:%s is not permitted in project %s", un.GroupVersionKind().Group, un.GroupVersionKind().Kind, project.Name) + } + if res.Namespaced { + permitted, err := project.IsDestinationPermitted(destCluster, un.GetNamespace(), getProjectClusters) + if err != nil { + return err + } + + if !permitted { + return fmt.Errorf("namespace %v is not permitted in project '%s'", un.GetNamespace(), project.Name) + } + } + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/controller/sync_test.go new/argocd-cli-3.4.2/controller/sync_test.go --- old/argocd-cli-3.4.1/controller/sync_test.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/controller/sync_test.go 2026-05-12 22:31:52.000000000 +0200 @@ -13,6 +13,7 @@ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v3/controller/testdata" @@ -1653,3 +1654,116 @@ return i } + +func TestValidateSyncPermissions(t *testing.T) { + t.Parallel() + + newResource := func(group, kind, name, namespace string) *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: "v1", Kind: kind}) + obj.SetName(name) + obj.SetNamespace(namespace) + return obj + } + + project := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-project", + Namespace: "argocd", + }, + Spec: v1alpha1.AppProjectSpec{ + Destinations: []v1alpha1.ApplicationDestination{ + {Namespace: "default", Server: "*"}, + }, + }, + } + + destCluster := &v1alpha1.Cluster{ + Server: "https://kubernetes.default.svc", + } + + noopGetClusters := func(_ string) ([]*v1alpha1.Cluster, error) { + return nil, nil + } + + t.Run("nil APIResource returns error", func(t *testing.T) { + t.Parallel() + un := newResource("apps", "Deployment", "my-deploy", "default") + + err := validateSyncPermissions(project, destCluster, noopGetClusters, un, nil) + + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to get API resource info for apps/Deployment") + assert.Contains(t, err.Error(), "unable to verify permissions") + }) + + t.Run("permitted namespaced resource returns no error", func(t *testing.T) { + t.Parallel() + un := newResource("", "ConfigMap", "my-cm", "default") + res := &metav1.APIResource{Name: "configmaps", Namespaced: true} + + err := validateSyncPermissions(project, destCluster, noopGetClusters, un, res) + + assert.NoError(t, err) + }) + + t.Run("group kind not permitted returns error", func(t *testing.T) { + t.Parallel() + projectWithDenyList := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "restricted-project", + Namespace: "argocd", + }, + Spec: v1alpha1.AppProjectSpec{ + Destinations: []v1alpha1.ApplicationDestination{ + {Namespace: "*", Server: "*"}, + }, + ClusterResourceBlacklist: []v1alpha1.ClusterResourceRestrictionItem{ + {Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"}, + }, + }, + } + un := newResource("rbac.authorization.k8s.io", "ClusterRole", "my-role", "") + res := &metav1.APIResource{Name: "clusterroles", Namespaced: false} + + err := validateSyncPermissions(projectWithDenyList, destCluster, noopGetClusters, un, res) + + require.Error(t, err) + assert.Contains(t, err.Error(), "is not permitted in project") + }) + + t.Run("namespace not permitted returns error", func(t *testing.T) { + t.Parallel() + un := newResource("", "ConfigMap", "my-cm", "kube-system") + res := &metav1.APIResource{Name: "configmaps", Namespaced: true} + + err := validateSyncPermissions(project, destCluster, noopGetClusters, un, res) + + require.Error(t, err) + assert.Contains(t, err.Error(), "namespace kube-system is not permitted in project") + }) + + t.Run("cluster-scoped resource skips namespace check", func(t *testing.T) { + t.Parallel() + projectWithClusterResources := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-project", + Namespace: "argocd", + }, + Spec: v1alpha1.AppProjectSpec{ + Destinations: []v1alpha1.ApplicationDestination{ + {Namespace: "default", Server: "*"}, + }, + ClusterResourceWhitelist: []v1alpha1.ClusterResourceRestrictionItem{ + {Group: "*", Kind: "*"}, + }, + }, + } + un := newResource("", "Namespace", "my-ns", "") + res := &metav1.APIResource{Name: "namespaces", Namespaced: false} + + err := validateSyncPermissions(projectWithClusterResources, destCluster, noopGetClusters, un, res) + + assert.NoError(t, err) + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/gitops-engine/pkg/diff/diff.go new/argocd-cli-3.4.2/gitops-engine/pkg/diff/diff.go --- old/argocd-cli-3.4.1/gitops-engine/pkg/diff/diff.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/gitops-engine/pkg/diff/diff.go 2026-05-12 22:31:52.000000000 +0200 @@ -189,15 +189,27 @@ Normalize(predictedLive, opts...) unstructured.RemoveNestedField(predictedLive.Object, "metadata", "managedFields") unstructured.RemoveNestedField(predictedLive.Object, "metadata", "resourceVersion") + unstructured.RemoveNestedField(predictedLive.Object, "metadata", "annotations", AnnotationLastAppliedConfig) + + Normalize(live, opts...) + unstructured.RemoveNestedField(live.Object, "metadata", "managedFields") + unstructured.RemoveNestedField(live.Object, "metadata", "resourceVersion") + unstructured.RemoveNestedField(live.Object, "metadata", "annotations", AnnotationLastAppliedConfig) + + if isCoreSecret(config) { + // Mask Secret data symmetrically before comparison. + // Equal values get equal placeholders, different values get different placeholders. + predictedLive, live, err = HideSecretData(predictedLive, live, nil) + if err != nil { + return nil, fmt.Errorf("error hiding secret data for resource %s/%s: %w", config.GetKind(), config.GetName(), err) + } + } predictedLiveBytes, err := json.Marshal(predictedLive) if err != nil { return nil, fmt.Errorf("error marshaling predicted live for resource %s/%s: %w", config.GetKind(), config.GetName(), err) } - Normalize(live, opts...) - unstructured.RemoveNestedField(live.Object, "metadata", "managedFields") - unstructured.RemoveNestedField(live.Object, "metadata", "resourceVersion") liveBytes, err := json.Marshal(live) if err != nil { return nil, fmt.Errorf("error marshaling live resource %s/%s: %w", config.GetKind(), config.GetName(), err) @@ -357,6 +369,15 @@ return &unstructured.Unstructured{Object: res}, nil } +// isCoreSecret reports whether obj is a core/v1 Secret (Group="" and Kind="Secret"). +func isCoreSecret(obj *unstructured.Unstructured) bool { + if obj == nil { + return false + } + gvk := obj.GroupVersionKind() + return gvk.Group == "" && gvk.Kind == "Secret" +} + // StructuredMergeDiff will calculate the diff using the structured-merge-diff // k8s library (https://github.com/kubernetes-sigs/structured-merge-diff). func StructuredMergeDiff(config, live *unstructured.Unstructured, gvkParser *managedfields.GvkParser, manager string) (*DiffResult, error) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/gitops-engine/pkg/diff/diff_test.go new/argocd-cli-3.4.2/gitops-engine/pkg/diff/diff_test.go --- old/argocd-cli-3.4.1/gitops-engine/pkg/diff/diff_test.go 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/gitops-engine/pkg/diff/diff_test.go 2026-05-12 22:31:52.000000000 +0200 @@ -7,6 +7,7 @@ "os" "os/exec" "path/filepath" + "strconv" "strings" "testing" @@ -1254,6 +1255,193 @@ assert.Contains(t, liveData, "key3", "key3 should still be in live state") }) + t.Run("will strip kubectl.kubernetes.io/last-applied-configuration from both sides", func(t *testing.T) { + t.Parallel() + + const lastAppliedRaw = `{"apiVersion":"v1","kind":"Secret","metadata":{"name":"secret","namespace":"default","annotations":{"app":"test"}},"data":{"password":"U0VDUkVUVkFM"},"stringData":{"username":"SECRETVAL"}}` + + liveState := StrToUnstructured(`{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "secret", + "namespace": "default", + "annotations": { + "app": "test", + "kubectl.kubernetes.io/last-applied-configuration": ` + strconv.Quote(lastAppliedRaw) + ` + } + }, + "type": "Opaque", + "data": { + "password": "U0VDUkVUVkFM" + } + }`) + desiredState := StrToUnstructured(`{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "secret", + "namespace": "default", + "annotations": { + "app": "test" + } + }, + "type": "Opaque", + "data": { + "password": "U0VDUkVUVkFM" + } + }`) + predictedLiveJSON := `{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "secret", + "namespace": "default", + "annotations": { + "app": "test", + "kubectl.kubernetes.io/last-applied-configuration": ` + strconv.Quote(lastAppliedRaw) + ` + } + }, + "type": "Opaque", + "data": { + "password": "U0VDUkVUVkFM" + } + }` + opts := buildOpts(predictedLiveJSON) + opts = append(opts, WithIgnoreMutationWebhook(false)) + + // when + result, err := serverSideDiff(desiredState, liveState, opts...) + + // then + require.NoError(t, err) + require.NotNil(t, result) + assert.NotContains(t, string(result.PredictedLive), "kubectl.kubernetes.io/last-applied-configuration", + "PredictedLive must not contain the last-applied-configuration annotation") + assert.NotContains(t, string(result.NormalizedLive), "kubectl.kubernetes.io/last-applied-configuration", + "NormalizedLive must not contain the last-applied-configuration annotation") + assert.NotContains(t, string(result.PredictedLive), "SECRETVAL", + "PredictedLive must not contain raw secret values from last-applied-configuration") + }) + t.Run("will mask Secret data symmetrically so identical values do not produce a spurious diff", func(t *testing.T) { + t.Parallel() + + desired := buildSecret("test-secret", "default", map[string]string{"password": "vault:secret/foo"}, nil) + live := buildSecret("test-secret", "default", map[string]string{"password": "injected-by-webhook"}, nil) + predictedLiveJSON := mustMarshalJSON(t, buildSecret("test-secret", "default", map[string]string{"password": "injected-by-webhook"}, nil)) + + opts := append(buildOpts(predictedLiveJSON), WithIgnoreMutationWebhook(false)) + result, err := serverSideDiff(desired, live, opts...) + require.NoError(t, err) + require.NotNil(t, result) + + assert.False(t, result.Modified, "identical secret values on both sides must not be flagged as modified after masking") + + predictedData := mustGetSecretData(t, result.PredictedLive) + liveData := mustGetSecretData(t, result.NormalizedLive) + assert.Equal(t, "++++++++", predictedData["password"], "predicted data must be masked, not raw") + assert.Equal(t, "++++++++", liveData["password"], "live data must be masked, not raw") + }) + + t.Run("will keep Secret data masked but still detect genuine value differences", func(t *testing.T) { + t.Parallel() + + desired := buildSecret("test-secret", "default", map[string]string{"password": "vault:secret/foo"}, nil) + live := buildSecret("test-secret", "default", map[string]string{"password": "old-value"}, nil) + predictedLiveJSON := mustMarshalJSON(t, buildSecret("test-secret", "default", map[string]string{"password": "new-value"}, nil)) + + opts := append(buildOpts(predictedLiveJSON), WithIgnoreMutationWebhook(false)) + result, err := serverSideDiff(desired, live, opts...) + require.NoError(t, err) + require.NotNil(t, result) + + assert.True(t, result.Modified, "different secret values must still be flagged as modified") + + predictedData := mustGetSecretData(t, result.PredictedLive) + liveData := mustGetSecretData(t, result.NormalizedLive) + // HideSecretData yields different placeholder lengths for different values, so the + // data field is masked on both sides and the two placeholders differ. + assert.NotEqual(t, "new-value", predictedData["password"], "raw new value must not leak into PredictedLive") + assert.NotEqual(t, "old-value", liveData["password"], "raw old value must not leak into NormalizedLive") + assert.NotEqual(t, predictedData["password"], liveData["password"], "differing values must yield differing placeholders") + }) + + t.Run("will detect Secret key additions and removals", func(t *testing.T) { + t.Parallel() + + desired := buildSecret("test-secret", "default", map[string]string{"password": "x", "token": "y"}, nil) + live := buildSecret("test-secret", "default", map[string]string{"password": "x"}, nil) + predictedLiveJSON := mustMarshalJSON(t, buildSecret("test-secret", "default", map[string]string{"password": "x", "token": "y"}, nil)) + + opts := append(buildOpts(predictedLiveJSON), WithIgnoreMutationWebhook(false)) + result, err := serverSideDiff(desired, live, opts...) + require.NoError(t, err) + require.NotNil(t, result) + + assert.True(t, result.Modified, "added Secret keys must still be flagged as modified after masking") + }) + + t.Run("will not mask non-core Secret resources", func(t *testing.T) { + // Resources whose Kind is "Secret" but whose Group is non-empty (e.g. CRDs) + // must not be touched by the core/v1 Secret masking path. + t.Parallel() + + desired := buildSecret("test-secret", "default", map[string]string{"password": "raw-value"}, nil) + desired.SetAPIVersion("custom.io/v1") + live := buildSecret("test-secret", "default", map[string]string{"password": "raw-value"}, nil) + live.SetAPIVersion("custom.io/v1") + predictedLiveJSON := mustMarshalJSON(t, desired) + + opts := append(buildOpts(predictedLiveJSON), WithIgnoreMutationWebhook(false)) + result, err := serverSideDiff(desired, live, opts...) + require.NoError(t, err) + require.NotNil(t, result) + + predictedData := mustGetSecretData(t, result.PredictedLive) + assert.Equal(t, "raw-value", predictedData["password"], "non-core Secret data must be left untouched") + }) +} + +// buildSecret returns a core/v1 Secret as an *unstructured.Unstructured. +func buildSecret(name, namespace string, data map[string]string, annotations map[string]string) *unstructured.Unstructured { + dataField := make(map[string]any, len(data)) + for k, v := range data { + dataField[k] = v + } + metadata := map[string]any{ + "name": name, + "namespace": namespace, + } + if len(annotations) > 0 { + annField := make(map[string]any, len(annotations)) + for k, v := range annotations { + annField[k] = v + } + metadata["annotations"] = annField + } + return &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": metadata, + "type": "Opaque", + "data": dataField, + }} +} + +func mustMarshalJSON(t *testing.T, obj *unstructured.Unstructured) string { + t.Helper() + bytes, err := json.Marshal(obj) + require.NoError(t, err) + return string(bytes) +} + +func mustGetSecretData(t *testing.T, secretBytes []byte) map[string]any { + t.Helper() + var obj map[string]any + require.NoError(t, json.Unmarshal(secretBytes, &obj)) + data, ok := obj["data"].(map[string]any) + require.True(t, ok, "expected data field to be a map") + return data } // testIgnoreDifferencesNormalizer implements a simple normalizer that removes specified fields diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/go.mod new/argocd-cli-3.4.2/go.mod --- old/argocd-cli-3.4.1/go.mod 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/go.mod 2026-05-12 22:31:52.000000000 +0200 @@ -91,10 +91,10 @@ gitlab.com/gitlab-org/api/client-go v1.46.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 - go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 - go.opentelemetry.io/otel/sdk v1.42.0 - go.opentelemetry.io/otel/trace v1.42.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 golang.org/x/crypto v0.49.0 golang.org/x/net v0.52.0 golang.org/x/oauth2 v0.36.0 @@ -238,7 +238,7 @@ github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -276,7 +276,7 @@ github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/go.sum new/argocd-cli-3.4.2/go.sum --- old/argocd-cli-3.4.1/go.sum 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/go.sum 2026-05-12 22:31:52.000000000 +0200 @@ -685,8 +685,9 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -964,20 +965,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/base/commit-server/kustomization.yaml new/argocd-cli-3.4.2/manifests/base/commit-server/kustomization.yaml --- old/argocd-cli-3.4.1/manifests/base/commit-server/kustomization.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/base/commit-server/kustomization.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -12,4 +12,4 @@ images: - name: quay.io/argoproj/argocd newName: quay.io/argoproj/argocd - newTag: v3.4.1 + newTag: v3.4.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/base/kustomization.yaml new/argocd-cli-3.4.2/manifests/base/kustomization.yaml --- old/argocd-cli-3.4.1/manifests/base/kustomization.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/base/kustomization.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -5,7 +5,7 @@ images: - name: quay.io/argoproj/argocd newName: quay.io/argoproj/argocd - newTag: v3.4.1 + newTag: v3.4.2 resources: - ./application-controller - ./dex diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/core-install/kustomization.yaml new/argocd-cli-3.4.2/manifests/core-install/kustomization.yaml --- old/argocd-cli-3.4.1/manifests/core-install/kustomization.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/core-install/kustomization.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -12,4 +12,4 @@ images: - name: quay.io/argoproj/argocd newName: quay.io/argoproj/argocd - newTag: v3.4.1 + newTag: v3.4.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/core-install-with-hydrator.yaml new/argocd-cli-3.4.2/manifests/core-install-with-hydrator.yaml --- old/argocd-cli-3.4.1/manifests/core-install-with-hydrator.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/core-install-with-hydrator.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -31332,7 +31332,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -31473,7 +31473,7 @@ key: log.format.timestamp name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -31601,7 +31601,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -31910,7 +31910,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -31963,7 +31963,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -32366,7 +32366,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/core-install.yaml new/argocd-cli-3.4.2/manifests/core-install.yaml --- old/argocd-cli-3.4.1/manifests/core-install.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/core-install.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -31300,7 +31300,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -31429,7 +31429,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -31738,7 +31738,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -31791,7 +31791,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -32194,7 +32194,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/ha/base/kustomization.yaml new/argocd-cli-3.4.2/manifests/ha/base/kustomization.yaml --- old/argocd-cli-3.4.1/manifests/ha/base/kustomization.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/ha/base/kustomization.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -12,7 +12,7 @@ images: - name: quay.io/argoproj/argocd newName: quay.io/argoproj/argocd - newTag: v3.4.1 + newTag: v3.4.2 resources: - ../../base/application-controller - ../../base/applicationset-controller diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/ha/install-with-hydrator.yaml new/argocd-cli-3.4.2/manifests/ha/install-with-hydrator.yaml --- old/argocd-cli-3.4.1/manifests/ha/install-with-hydrator.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/ha/install-with-hydrator.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -32758,7 +32758,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -32899,7 +32899,7 @@ key: log.format.timestamp name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -33057,7 +33057,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -33159,7 +33159,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -33283,7 +33283,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -33618,7 +33618,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -33671,7 +33671,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -34100,7 +34100,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -34532,7 +34532,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/ha/install.yaml new/argocd-cli-3.4.2/manifests/ha/install.yaml --- old/argocd-cli-3.4.1/manifests/ha/install.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/ha/install.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -32728,7 +32728,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -32887,7 +32887,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -32989,7 +32989,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -33113,7 +33113,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -33448,7 +33448,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -33501,7 +33501,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -33930,7 +33930,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -34362,7 +34362,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/ha/namespace-install-with-hydrator.yaml new/argocd-cli-3.4.2/manifests/ha/namespace-install-with-hydrator.yaml --- old/argocd-cli-3.4.1/manifests/ha/namespace-install-with-hydrator.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/ha/namespace-install-with-hydrator.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -2005,7 +2005,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -2146,7 +2146,7 @@ key: log.format.timestamp name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -2304,7 +2304,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -2406,7 +2406,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -2530,7 +2530,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -2865,7 +2865,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -2918,7 +2918,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -3347,7 +3347,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -3779,7 +3779,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/ha/namespace-install.yaml new/argocd-cli-3.4.2/manifests/ha/namespace-install.yaml --- old/argocd-cli-3.4.1/manifests/ha/namespace-install.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/ha/namespace-install.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -1975,7 +1975,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -2134,7 +2134,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -2236,7 +2236,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -2360,7 +2360,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -2695,7 +2695,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -2748,7 +2748,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -3177,7 +3177,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -3609,7 +3609,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/install-with-hydrator.yaml new/argocd-cli-3.4.2/manifests/install-with-hydrator.yaml --- old/argocd-cli-3.4.1/manifests/install-with-hydrator.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/install-with-hydrator.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -31776,7 +31776,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -31917,7 +31917,7 @@ key: log.format.timestamp name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -32075,7 +32075,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -32177,7 +32177,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -32279,7 +32279,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -32588,7 +32588,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -32641,7 +32641,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -33068,7 +33068,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -33500,7 +33500,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/install.yaml new/argocd-cli-3.4.2/manifests/install.yaml --- old/argocd-cli-3.4.1/manifests/install.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/install.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -31744,7 +31744,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -31903,7 +31903,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -32005,7 +32005,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -32107,7 +32107,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -32416,7 +32416,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -32469,7 +32469,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -32896,7 +32896,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -33328,7 +33328,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/namespace-install-with-hydrator.yaml new/argocd-cli-3.4.2/manifests/namespace-install-with-hydrator.yaml --- old/argocd-cli-3.4.1/manifests/namespace-install-with-hydrator.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/namespace-install-with-hydrator.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -1023,7 +1023,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -1164,7 +1164,7 @@ key: log.format.timestamp name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -1322,7 +1322,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -1424,7 +1424,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -1526,7 +1526,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -1835,7 +1835,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -1888,7 +1888,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -2315,7 +2315,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -2747,7 +2747,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/manifests/namespace-install.yaml new/argocd-cli-3.4.2/manifests/namespace-install.yaml --- old/argocd-cli-3.4.1/manifests/namespace-install.yaml 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/manifests/namespace-install.yaml 2026-05-12 22:31:52.000000000 +0200 @@ -991,7 +991,7 @@ key: applicationsetcontroller.status.max.resources.count name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-applicationset-controller ports: @@ -1150,7 +1150,7 @@ - -n - /usr/local/bin/argocd - /shared/argocd-dex - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: copyutil securityContext: @@ -1252,7 +1252,7 @@ key: notificationscontroller.repo.server.plaintext name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: tcpSocket: @@ -1354,7 +1354,7 @@ - argocd - admin - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: IfNotPresent name: secret-init securityContext: @@ -1663,7 +1663,7 @@ value: /helm-working-dir - name: HELM_DATA_HOME value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: failureThreshold: 3 @@ -1716,7 +1716,7 @@ command: - sh - -c - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 name: copyutil securityContext: allowPrivilegeEscalation: false @@ -2143,7 +2143,7 @@ key: server.sync.replace.allowed name: argocd-cmd-params-cm optional: true - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always livenessProbe: httpGet: @@ -2575,7 +2575,7 @@ optional: true - name: KUBECACHEDIR value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.1 + image: quay.io/argoproj/argocd:v3.4.2 imagePullPolicy: Always name: argocd-application-controller ports: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/ui/src/app/applications/components/application-summary/application-summary.tsx new/argocd-cli-3.4.2/ui/src/app/applications/components/application-summary/application-summary.tsx --- old/argocd-cli-3.4.1/ui/src/app/applications/components/application-summary/application-summary.tsx 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/ui/src/app/applications/components/application-summary/application-summary.tsx 2026-05-12 22:31:52.000000000 +0200 @@ -20,6 +20,7 @@ import {AuthSettingsCtx, Consumer, ContextApis} from '../../../shared/context'; import * as models from '../../../shared/models'; import {services} from '../../../shared/services'; +import {isValidURL} from '../../../shared/utils'; import {ApplicationSyncOptionsField} from '../application-sync-options/application-sync-options'; import {RevisionFormField} from '../revision-form-field/revision-form-field'; @@ -272,6 +273,10 @@ <div className='application-summary__links-rows'> {urls .map(item => item.split('|')) + // Drop entries whose URL uses an unsafe protocol (e.g. javascript:, data:, + // vbscript:) to prevent XSS via attacker-controlled + // link.argocd.argoproj.io/* annotations on managed resources. + .filter(parts => isValidURL(parts.length > 1 ? parts[1] : parts[0])) .map((parts, i) => ( <div className='application-summary__links-row'> <a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='_blank'> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/argocd-cli-3.4.1/ui/src/app/shared/utils.test.ts new/argocd-cli-3.4.2/ui/src/app/shared/utils.test.ts --- old/argocd-cli-3.4.1/ui/src/app/shared/utils.test.ts 2026-05-06 09:46:04.000000000 +0200 +++ new/argocd-cli-3.4.2/ui/src/app/shared/utils.test.ts 2026-05-12 22:31:52.000000000 +0200 @@ -1,4 +1,9 @@ +/* eslint-env jest */ +declare const test: any; +declare const expect: any; +declare const describe: any; import {concatMaps} from './utils'; +import {isValidURL} from './utils'; test('map concatenation', () => { const map1 = { @@ -12,3 +17,23 @@ const map3 = concatMaps(map1, map2); expect(map3).toEqual(new Map(Object.entries({a: '9', b: '2', c: '8'}))); }); + +describe('isValidURL', () => { + test('accepts http/https URLs', () => { + expect(isValidURL('http://example.com')).toBe(true); + expect(isValidURL('https://example.com/path?q=1')).toBe(true); + }); + + test('accepts relative URLs', () => { + // @ts-ignore + window.location = new URL('https://localhost:8080/applications'); + expect(isValidURL('/applications')).toBe(true); + }); + + test('rejects unsafe protocols', () => { + expect(isValidURL('javascript:alert(1)')).toBe(false); + expect(isValidURL('JaVaScRiPt:alert(1)')).toBe(false); + expect(isValidURL('data:text/html,<script>alert(1)</script>')).toBe(false); + expect(isValidURL('vbscript:msgbox(1)')).toBe(false); + }); +}); ++++++ argocd-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.q1pZOj/_old 2026-05-13 17:23:43.466207727 +0200 +++ /var/tmp/diff_new_pack.q1pZOj/_new 2026-05-13 17:23:43.470207893 +0200 @@ -1,5 +1,5 @@ name: argocd-cli -version: 3.4.1 -mtime: 1778053564 -commit: bafd59bd37138ed731d3c3aad8e18731d72aed46 +version: 3.4.2 +mtime: 1778617912 +commit: 0dc6b1b57dd5bb925d5b03c3d09419ab9fb4225e ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/argocd-cli/vendor.tar.gz /work/SRC/openSUSE:Factory/.argocd-cli.new.1966/vendor.tar.gz differ: char 13, line 1
