VYPR
High severityOSV Advisory· Published Aug 13, 2025· Updated Apr 15, 2026

CVE-2025-55196

CVE-2025-55196

Description

External Secrets Operator is a Kubernetes operator that integrates external secret management systems. From version 0.15.0 to before 0.19.2, a vulnerability was discovered where the List() calls for Kubernetes Secret and SecretStore resources performed by the PushSecret controller did not apply a namespace selector. This flaw allowed an attacker to use label selectors to list and read secrets/secret-stores across the cluster, bypassing intended namespace restrictions. An attacker with the ability to create or update PushSecret resources and control SecretStore configurations could exploit this vulnerability to exfiltrate sensitive data from arbitrary namespaces. This could lead to full disclosure of Kubernetes secrets, including credentials, tokens, and other sensitive information stored in the cluster. This vulnerability has been patched in version 0.19.2. A workaround for this issue includes auditing and restricting RBAC permissions so that only trusted service accounts can create or update PushSecret and SecretStore resources.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/external-secrets/external-secretsGo
>= 0.15.0, < 0.19.20.19.2

Affected products

1

Patches

3
f6b47d56d1a5

fix: bump support table & update roadmap (#5135)

2 files changed · +22 24
  • docs/contributing/roadmap.md+0 3 modified
    @@ -16,12 +16,9 @@ We have identified the following areas of work. This is subject to change while
         * ✓ end to end testing with ArgoCD and Flux
         * ✓ end to end testing for all project maintained providers
     * API enhancements
    -    * consolidate provider fields
         * ✓ dataFrom key rewrites
    -    * provider versioning strategy
         * ✓ pushing secrets to a provider
     * Documentation Improvements
    -    * Troubleshooting Guides
         * ✓ FAQ
         * ✓ review multi tenancy docs
         * ✓ security model for infosec teams
    
  • docs/introduction/stability-support.md+22 21 modified
    @@ -16,31 +16,32 @@ During a minor version support time, we cover:
     We do not do test coverage for any other kubernetes version than the ones running on our test suites.
     As of version 0.14.x , this is the only kubernetes version that we will guarantee support for.
     
    -| ESO Version | Kubernetes Version | Release Date | End of Life     |
    -| ----------- | ------------------ | ------------ | --------------- |
    -| 0.18.x      | 1.33               | July 17, 2025  | Release of 0.19 |
    -| 0.17.x      | 1.33               | May 14, 2025  | July 17, 2025    |
    -| 0.16.x      | 1.32               | Apr 14, 2025  | May 14, 2025    |
    -| 0.15.x      | 1.32               | Mar 19, 2025  | Apr 14, 2025    |
    -| 0.14.x      | 1.32               | Feb 4, 2025  | Mar 19, 2025    |
    -| 0.13.x      | 1.19 → 1.31        | Jan 21, 2025 | Feb 4, 2025     |
    -| 0.12.x      | 1.19 → 1.31        | Dec 24, 2024 | Jan 21, 2025    |
    -| 0.11.x      | 1.19 → 1.31        | Dec 2, 2024  | Dec 24, 2024    |
    -| 0.10.x      | 1.19 → 1.31        | Aug 3, 2024  | Dec 24, 2024    |
    -| 0.9.x       | 1.19 → 1.30        | Jun 22, 2023 | Dec 2, 2024     |
    -| 0.8.x       | 1.19 → 1.28        | Mar 16, 2023 | Aug 3, 2024     |
    -| 0.7.x       | 1.19 → 1.26        | Dec 11, 2022 | Jun 22, 2023    |
    -| 0.6.x       | 1.19 → 1.24        | Oct 9, 2022  | Mar 16, 2023    |
    -| 0.5.x       | 1.19 → 1.24        | Apr 6, 2022  | Dec 11, 2022    |
    -| 0.4.x       | 1.16 → 1.24        | Feb 2, 2022  | Oct 9, 2022     |
    -| 0.3.x       | 1.16 → 1.24        | Jul 25, 2021 | Apr 6, 2022     |
    +| ESO Version | Kubernetes Version | Release Date   | End of Life     |
    +| ----------- | ------------------ | -------------- | --------------- |
    +| 0.19.x      | 1.33               | August 2, 2025 | Release of 0.20 |
    +| 0.18.x      | 1.33               | July 17, 2025  | August 2, 2025  |
    +| 0.17.x      | 1.33               | May 14, 2025   | July 17, 2025   |
    +| 0.16.x      | 1.32               | Apr 14, 2025   | May 14, 2025    |
    +| 0.15.x      | 1.32               | Mar 19, 2025   | Apr 14, 2025    |
    +| 0.14.x      | 1.32               | Feb 4, 2025    | Mar 19, 2025    |
    +| 0.13.x      | 1.19 → 1.31        | Jan 21, 2025   | Feb 4, 2025     |
    +| 0.12.x      | 1.19 → 1.31        | Dec 24, 2024   | Jan 21, 2025    |
    +| 0.11.x      | 1.19 → 1.31        | Dec 2, 2024    | Dec 24, 2024    |
    +| 0.10.x      | 1.19 → 1.31        | Aug 3, 2024    | Dec 24, 2024    |
    +| 0.9.x       | 1.19 → 1.30        | Jun 22, 2023   | Dec 2, 2024     |
    +| 0.8.x       | 1.19 → 1.28        | Mar 16, 2023   | Aug 3, 2024     |
    +| 0.7.x       | 1.19 → 1.26        | Dec 11, 2022   | Jun 22, 2023    |
    +| 0.6.x       | 1.19 → 1.24        | Oct 9, 2022    | Mar 16, 2023    |
    +| 0.5.x       | 1.19 → 1.24        | Apr 6, 2022    | Dec 11, 2022    |
    +| 0.4.x       | 1.16 → 1.24        | Feb 2, 2022    | Oct 9, 2022     |
    +| 0.3.x       | 1.16 → 1.24        | Jul 25, 2021   | Apr 6, 2022     |
     
     ## Provider Stability and Support Level
     
     The following table describes the stability level of each provider and who's responsible.
     
    -| Provider                                                                                                   | Stability | Maintainer                                                                                          |
    -|------------------------------------------------------------------------------------------------------------|-:-:-------|--:--------------------------------------------------------------------------------------------------|
    +| Provider | Stability | Maintainer |
    +| -------- | --------: | ---------: |-------|--:--------------------------------------------------------------------------------------------------|
     | [AWS Secrets Manager](https://external-secrets.io/latest/provider/aws-secrets-manager/)                    | stable    | [external-secrets](https://github.com/external-secrets)                                             |
     | [AWS Parameter Store](https://external-secrets.io/latest/provider/aws-parameter-store/)                    | stable    | [external-secrets](https://github.com/external-secrets)                                             |
     | [Hashicorp Vault](https://external-secrets.io/latest/provider/hashicorp-vault/)                            | stable    | [external-secrets](https://github.com/external-secrets)                                             |
    @@ -78,7 +79,7 @@ The following table describes the stability level of each provider and who's res
     The following table show the support for features across different providers.
     
     | Provider                  | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret | DeletionPolicy Merge/Delete |
    -|---------------------------|:------------:|:------------:|:--------------------:|:-----------------------:|:----------------:|:-----------:|:---------------------------:|
    +| ------------------------- | :----------: | :----------: | :------------------: | :---------------------: | :--------------: | :---------: | :-------------------------: |
     | AWS Secrets Manager       |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
     | AWS Parameter Store       |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
     | Hashicorp Vault           |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
    
39cdba586353

fix: scope secret list call to the namespace the push secret was created (#5133)

3 files changed · +105 40
  • pkg/controllers/pushsecret/pushsecret_controller.go+1 1 modified
    @@ -429,7 +429,7 @@ func (r *Reconciler) resolveSecrets(ctx context.Context, ps *esapi.PushSecret) (
     		}
     
     		var secretList v1.SecretList
    -		err = r.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector})
    +		err = r.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
     		if err != nil {
     			return nil, err
     		}
    
  • pkg/controllers/pushsecret/pushsecret_controller_test.go+81 36 modified
    @@ -65,7 +65,6 @@ func init() {
     }
     
     func checkCondition(status v1alpha1.PushSecretStatus, cond v1alpha1.PushSecretStatusCondition) bool {
    -	fmt.Printf("status: %+v\ncond: %+v\n", status.Conditions, cond)
     	for _, condition := range status.Conditions {
     		if condition.Message == cond.Message &&
     			condition.Reason == cond.Reason &&
    @@ -232,7 +231,8 @@ var _ = Describe("PushSecret controller", func() {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
     				secretValue := secret.Data[defaultKey]
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    @@ -248,23 +248,26 @@ var _ = Describe("PushSecret controller", func() {
     			return nil
     		}
     		fakeProvider.SecretExistsFn = func(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
    -			_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			_, ok := setSecretArgs[ref.GetRemoteKey()]
     			return ok, nil
     		}
     		tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
    -		initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    -		tc.secret.Data[defaultKey] = []byte(newVal)
     
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
    -			Eventually(func() bool {
    -				By("checking if Provider value did not get updated")
    +			Consistently(func() bool {
    +				By("updating the secret value")
    +				tc.secret.Data[defaultKey] = []byte(newVal)
     				Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +
    +				By("checking if Provider value does not get updated")
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
     				got := providerValue.Value
    -				return bytes.Equal(got, initialValue)
    +				return bytes.Equal(got, []byte(defaultVal))
     			}, time.Second*10, time.Second).Should(BeTrue())
     			return true
     		}
    @@ -275,7 +278,8 @@ var _ = Describe("PushSecret controller", func() {
     			return nil
     		}
     		fakeProvider.SecretExistsFn = func(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
    -			_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			_, ok := setSecretArgs[ref.GetRemoteKey()]
     			return ok, nil
     		}
     		tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
    @@ -288,26 +292,25 @@ var _ = Describe("PushSecret controller", func() {
     			},
     		})
     
    -		initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    -		tc.secret.Data[defaultKey] = []byte(newVal) // change initial value in secret
    -		tc.secret.Data[otherKey] = []byte(otherVal)
    -
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			Eventually(func() bool {
    +				tc.secret.Data[defaultKey] = []byte(newVal) // change initial value in secret
    +				tc.secret.Data[otherKey] = []byte(otherVal)
    +
     				By("checking if only not existing Provider value got updated")
     				Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
     				got := providerValue.Value
    -				otherProviderValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[1].Match.RemoteRef.RemoteKey]
    +				otherProviderValue, ok := setSecretArgs[ps.Spec.Data[1].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
     				gotOther := otherProviderValue.Value
    -
    -				return bytes.Equal(gotOther, tc.secret.Data[otherKey]) && bytes.Equal(got, initialValue)
    +				return bytes.Equal(gotOther, tc.secret.Data[otherKey]) && bytes.Equal(got, []byte(defaultVal))
     			}, time.Second*10, time.Second).Should(BeTrue())
     			return true
     		}
    @@ -318,7 +321,8 @@ var _ = Describe("PushSecret controller", func() {
     			return nil
     		}
     		fakeProvider.SecretExistsFn = func(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
    -			_, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			_, ok := setSecretArgs[ref.GetRemoteKey()]
     			return ok, nil
     		}
     		tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
    @@ -371,24 +375,17 @@ var _ = Describe("PushSecret controller", func() {
     			return false, errors.New("don't know")
     		}
     		tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
    -		initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    -		tc.secret.Data[defaultKey] = []byte(newVal)
     
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			Eventually(func() bool {
     				By("checking if sync failed if secret existence cannot be verified in Provider")
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    -				if !ok {
    -					return false
    -				}
    -				got := providerValue.Value
     				expected := v1alpha1.PushSecretStatusCondition{
     					Type:    v1alpha1.PushSecretReady,
     					Status:  v1.ConditionFalse,
     					Reason:  v1alpha1.ReasonErrored,
     					Message: "set secret failed: could not verify if secret exists in store: don't know",
     				}
    -				return checkCondition(ps.Status, expected) && bytes.Equal(got, initialValue)
    +				return checkCondition(ps.Status, expected)
     			}, time.Second*10, time.Second).Should(BeTrue())
     			return true
     		}
    @@ -445,7 +442,8 @@ var _ = Describe("PushSecret controller", func() {
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    @@ -507,7 +505,8 @@ var _ = Describe("PushSecret controller", func() {
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    @@ -824,7 +823,8 @@ var _ = Describe("PushSecret controller", func() {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
     				secretValue := secret.Data["some-array_U005b_0_U005d_.entity"]
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    @@ -897,7 +897,8 @@ var _ = Describe("PushSecret controller", func() {
     		}
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			secretValue := secret.Data[defaultKey]
    -			providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			providerValue := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
     			expected := v1alpha1.PushSecretStatusCondition{
     				Type:    v1alpha1.PushSecretReady,
     				Status:  v1.ConditionTrue,
    @@ -929,7 +930,8 @@ var _ = Describe("PushSecret controller", func() {
     		tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			secretValue := secret.Data[defaultKey]
    -			providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			providerValue := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
     			expected := v1alpha1.PushSecretStatusCondition{
     				Type:    v1alpha1.PushSecretReady,
     				Status:  v1.ConditionTrue,
    @@ -951,7 +953,8 @@ var _ = Describe("PushSecret controller", func() {
     			Name:       "test",
     		}
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
    -			providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			providerValue := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
     			expected := v1alpha1.PushSecretStatusCondition{
     				Type:    v1alpha1.PushSecretReady,
     				Status:  v1.ConditionTrue,
    @@ -1017,7 +1020,8 @@ var _ = Describe("PushSecret controller", func() {
     		}
     		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
     			secretValue := secret.Data[defaultKey]
    -			providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
    +			setSecretArgs := fakeProvider.GetPushSecretData()
    +			providerValue := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
     			expected := v1alpha1.PushSecretStatusCondition{
     				Type:    v1alpha1.PushSecretReady,
     				Status:  v1.ConditionTrue,
    @@ -1174,6 +1178,44 @@ var _ = Describe("PushSecret controller", func() {
     		}
     	}
     
    +	// Secrets in different namespace than PushSecret should not be selected.
    +	secretDifferentNamespace := func(tc *testCase) {
    +		fakeProvider.SetSecretFn = func() error {
    +			return nil
    +		}
    +		// Create the Secret in a different namespace
    +		tc.secret = &v1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      SecretName,
    +				Namespace: OtherNamespace,
    +				Labels: map[string]string{
    +					"foo": "bar",
    +				},
    +			},
    +			Data: map[string][]byte{
    +				defaultKey: []byte(defaultVal),
    +			},
    +		}
    +		// Use label selector to select Secrets
    +		tc.pushsecret.Spec.Selector.Secret = &v1alpha1.PushSecretSecret{
    +			Selector: &metav1.LabelSelector{
    +				MatchLabels: map[string]string{
    +					"foo": "bar",
    +				},
    +			},
    +		}
    +
    +		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
    +			Eventually(func() bool {
    +				// We should not be able to reference a secret across namespaces,
    +				// the map should be empty.
    +				Expect(fakeProvider.GetPushSecretData()).To(BeEmpty())
    +				return true
    +			}, time.Second*10, time.Second).Should(BeTrue())
    +			return true
    +		}
    +	}
    +
     	DescribeTable("When reconciling a PushSecret",
     		func(tweaks ...testTweaks) {
     			tc := makeDefaultTestcase()
    @@ -1228,6 +1270,7 @@ var _ = Describe("PushSecret controller", func() {
     		Entry("should fail if no valid ClusterSecretStore", failNoClusterStore),
     		Entry("should fail if NewClient fails", newClientFail),
     		Entry("should not sync to SecretStore in different namespace", secretStoreDifferentNamespace),
    +		Entry("should not reference secret in different namespace", secretDifferentNamespace),
     	)
     })
     
    @@ -1425,7 +1468,8 @@ var _ = Describe("PushSecret Controller Un/Managed Stores", func() {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
     				secretValue := secret.Data[defaultKey]
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    @@ -1481,7 +1525,8 @@ var _ = Describe("PushSecret Controller Un/Managed Stores", func() {
     			Eventually(func() bool {
     				By("checking if Provider value got updated")
     				secretValue := secret.Data[defaultKey]
    -				providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
    +				setSecretArgs := fakeProvider.GetPushSecretData()
    +				providerValue, ok := setSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
     				if !ok {
     					return false
     				}
    
  • pkg/provider/testing/fake/fake.go+23 3 modified
    @@ -16,6 +16,7 @@ package fake
     
     import (
     	"context"
    +	"sync"
     
     	corev1 "k8s.io/api/core/v1"
     	"sigs.k8s.io/controller-runtime/pkg/client"
    @@ -33,7 +34,8 @@ type SetSecretCallArgs struct {
     
     // Client is a fake client for testing.
     type Client struct {
    -	SetSecretArgs   map[string]SetSecretCallArgs
    +	mu              *sync.RWMutex
    +	pushSecretData  map[string]SetSecretCallArgs
     	NewFn           func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error)
     	GetSecretFn     func(context.Context, esv1.ExternalSecretDataRemoteRef) ([]byte, error)
     	GetSecretMapFn  func(context.Context, esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
    @@ -46,6 +48,7 @@ type Client struct {
     // New returns a fake provider/client.
     func New() *Client {
     	v := &Client{
    +		mu: &sync.RWMutex{},
     		GetSecretFn: func(context.Context, esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
     			return nil, nil
     		},
    @@ -64,7 +67,7 @@ func New() *Client {
     		DeleteSecretFn: func() error {
     			return nil
     		},
    -		SetSecretArgs: map[string]SetSecretCallArgs{},
    +		pushSecretData: map[string]SetSecretCallArgs{},
     	}
     
     	v.NewFn = func(context.Context, esv1.GenericStore, client.Client, string) (esv1.SecretsClient, error) {
    @@ -85,13 +88,27 @@ func (v *Client) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind)
     }
     
     func (v *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
    -	v.SetSecretArgs[data.GetRemoteKey()] = SetSecretCallArgs{
    +	v.mu.Lock()
    +	defer v.mu.Unlock()
    +	v.pushSecretData[data.GetRemoteKey()] = SetSecretCallArgs{
     		Value:     secret.Data[data.GetSecretKey()],
     		RemoteRef: data,
     	}
     	return v.SetSecretFn()
     }
     
    +// GetPushSecretData safely retrieves the push secret data map for reading.
    +func (v *Client) GetPushSecretData() map[string]SetSecretCallArgs {
    +	v.mu.RLock()
    +	defer v.mu.RUnlock()
    +	// Create a copy to avoid race conditions
    +	result := make(map[string]SetSecretCallArgs, len(v.pushSecretData))
    +	for k, v := range v.pushSecretData {
    +		result[k] = v
    +	}
    +	return result
    +}
    +
     func (v *Client) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
     	return v.DeleteSecretFn()
     }
    @@ -180,4 +197,7 @@ func (v *Client) Reset() {
     		string) (esv1.SecretsClient, error) {
     		return v, nil
     	})
    +	v.mu.Lock()
    +	defer v.mu.Unlock()
    +	v.pushSecretData = map[string]SetSecretCallArgs{}
     }
    
de40e8f4fa95

fix: select secretstores in same ns as pushsecret (#5109)

2 files changed · +55 2
  • pkg/controllers/pushsecret/pushsecret_controller.go+1 1 modified
    @@ -494,7 +494,7 @@ func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (
     				}
     			} else {
     				secretStoreList := esv1.SecretStoreList{}
    -				err = r.List(ctx, &secretStoreList, &client.ListOptions{LabelSelector: labelSelector})
    +				err = r.List(ctx, &secretStoreList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
     				if err != nil {
     					return nil, fmt.Errorf("could not list Secret Stores: %w", err)
     				}
    
  • pkg/controllers/pushsecret/pushsecret_controller_test.go+54 1 modified
    @@ -86,7 +86,7 @@ var _ = Describe("PushSecret controller", func() {
     		SecretName      = "test-secret"
     	)
     
    -	var PushSecretNamespace string
    +	var PushSecretNamespace, OtherNamespace string
     
     	// if we are in debug and need to increase the timeout for testing, we can do so by using an env var
     	if customTimeout := os.Getenv("TEST_CUSTOM_TIMEOUT_SEC"); customTimeout != "" {
    @@ -99,6 +99,8 @@ var _ = Describe("PushSecret controller", func() {
     		var err error
     		PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
     		Expect(err).ToNot(HaveOccurred())
    +		OtherNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
    +		Expect(err).ToNot(HaveOccurred())
     		fakeProvider.Reset()
     
     		Expect(k8sClient.Create(context.Background(), &genv1alpha1.Fake{
    @@ -1121,6 +1123,56 @@ var _ = Describe("PushSecret controller", func() {
     			return checkCondition(ps.Status, expected)
     		}
     	}
    +	// SecretStores in different namespace than PushSecret should not be selected.
    +	secretStoreDifferentNamespace := func(tc *testCase) {
    +		fakeProvider.SetSecretFn = func() error {
    +			return nil
    +		}
    +		// Create the SecretStore in a different namespace
    +		tc.store = &esv1.SecretStore{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "other-ns-store",
    +				Namespace: OtherNamespace,
    +				Labels: map[string]string{
    +					"foo": "bar",
    +				},
    +			},
    +			TypeMeta: metav1.TypeMeta{
    +				Kind: "SecretStore",
    +			},
    +			Spec: esv1.SecretStoreSpec{
    +				Provider: &esv1.SecretStoreProvider{
    +					Fake: &esv1.FakeProvider{
    +						Data: []esv1.FakeProviderData{},
    +					},
    +				},
    +			},
    +		}
    +		// Use label selector to select SecretStores
    +		tc.pushsecret.Spec.SecretStoreRefs = []v1alpha1.PushSecretStoreRef{
    +			{
    +				Kind: "SecretStore",
    +				LabelSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						"foo": "bar",
    +					},
    +				},
    +			},
    +		}
    +		// Should not select the SecretStore in a different namespace
    +		// (if so, it would fail to find it in the same namespace and be reflected in the status)
    +		tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
    +			// Assert that the status is never updated (no SecretStores found)
    +			Consistently(func() bool {
    +				err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(ps), ps)
    +				if err != nil {
    +					return false
    +				}
    +				return len(ps.Status.Conditions) == 0
    +			}, timeout, interval).Should(BeTrue())
    +			return true
    +		}
    +	}
     
     	DescribeTable("When reconciling a PushSecret",
     		func(tweaks ...testTweaks) {
    @@ -1175,6 +1227,7 @@ var _ = Describe("PushSecret controller", func() {
     		Entry("should fail if no valid SecretStore", failNoSecretStore),
     		Entry("should fail if no valid ClusterSecretStore", failNoClusterStore),
     		Entry("should fail if NewClient fails", newClientFail),
    +		Entry("should not sync to SecretStore in different namespace", secretStoreDifferentNamespace),
     	)
     })
     
    

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.