VYPR
Medium severity4.9NVD Advisory· Published Jun 10, 2026· Updated Jun 10, 2026

CVE-2026-50565

CVE-2026-50565

Description

Fission builder pods improperly auto-mounted the fission-builder ServiceAccount token, allowing access to secrets and configmaps.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Fission builder pods improperly auto-mounted the fission-builder ServiceAccount token, allowing access to secrets and configmaps.

Vulnerability

Prior to version 1.24.0, Fission builder pods were configured with ServiceAccountName: fission-builder and AutomountServiceAccountToken: true. This allowed the kubelet to automatically mount the fission-builder service account token into every container within the pod, including user-supplied builder images. This vulnerability affects Fission versions prior to 1.24.0 [3].

Exploitation

An attacker with create/update permissions on Environment CRDs in a namespace observed by the buildermgr could exploit this by supplying a custom podspec. The auto-mounted service account token, accessible at /var/run/secrets/kubernetes.io/serviceaccount/token within the builder container, grants the identity of fission-builder. This identity has namespace-wide get permissions on secrets and configmaps, allowing the attacker to read sensitive information [3].

Impact

Successful exploitation allows an attacker to read all Secrets and ConfigMaps within the builder namespace. This could lead to the disclosure of sensitive credentials, API keys, or configuration data, compromising the security of the Kubernetes cluster [3].

Mitigation

This issue has been fixed in Fission version 1.24.0, released on 2026-06-10 [2]. The fix involves setting AutomountServiceAccountToken=false at the pod level and mounting the token via a projected volume only to the fetcher sidecar, preventing it from being available to the user-supplied builder container [3].

AI Insight generated on Jun 10, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Fission/Fissionreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <1.24.0

Patches

1
8fa799417c77

Drop fission-builder SA token from user builder container (GHSA-8wcj) (#3390)

https://github.com/fission/fissionSanket SudakeMay 23, 2026via body-scan-shorthand
2 files changed · +275 4
  • pkg/buildermgr/envwatcher.go+47 4 modified
    @@ -439,25 +439,47 @@ func (envw *environmentWatcher) createBuilderDeployment(ctx context.Context, env
     		return nil, err
     	}
     
    +	// AutomountServiceAccountToken=false stops Kubernetes from injecting
    +	// the fission-builder ServiceAccount token into every container in
    +	// the pod. The fetcher sidecar re-mounts the token via the projected
    +	// volume defined below — the user-supplied builder container does
    +	// not. See GHSA-8wcj-mfrc-jx5q (the buildermgr sibling of
    +	// GHSA-85g2-pmrx-r49q).
    +	automountSAToken := false
     	pod := apiv1.PodTemplateSpec{
     		ObjectMeta: metav1.ObjectMeta{
     			Labels:      sel,
     			Annotations: podAnnotations,
     		},
     		Spec: apiv1.PodSpec{
    -			Containers:         []apiv1.Container{*container},
    -			ServiceAccountName: fv1.FissionBuilderSA,
    +			Containers:                   []apiv1.Container{*container},
    +			ServiceAccountName:           fv1.FissionBuilderSA,
    +			AutomountServiceAccountToken: &automountSAToken,
    +			Volumes: []apiv1.Volume{
    +				util.FetcherSATokenProjectedVolume(),
    +			},
     		},
     	}
     
     	if envw.podSpecPatch != nil {
    -
    -		updatedPodSpec, err := util.MergePodSpec(&pod.Spec, envw.podSpecPatch)
    +		// Merge into a deep copy: MergePodSpec mutates its first argument
    +		// in place even on error (it joins partial errors and keeps the
    +		// fields that did merge cleanly). Passing pod.Spec directly would
    +		// leave the deployment with partial patch mutations applied on a
    +		// log-and-continue error path. The copy is discarded on failure
    +		// so pod.Spec retains its pre-merge state.
    +		srcCopy := pod.Spec.DeepCopy()
    +		updatedPodSpec, err := util.MergePodSpec(srcCopy, envw.podSpecPatch)
     		if err == nil {
     			pod.Spec = *updatedPodSpec
     		} else {
     			envw.logger.Error(err, "Failed to merge the specs")
     		}
    +		// Re-clamp after the merge: MergePodSpec propagates a non-nil
    +		// AutomountServiceAccountToken from the patch, which would
    +		// otherwise re-enable the kubelet auto-mount on the user-supplied
    +		// builder container. See GHSA-8wcj-mfrc-jx5q.
    +		pod.Spec.AutomountServiceAccountToken = new(false)
     	}
     
     	pod.Spec = *(util.ApplyImagePullSecret(env.Spec.ImagePullSecret, pod.Spec))
    @@ -500,6 +522,27 @@ func (envw *environmentWatcher) createBuilderDeployment(ctx context.Context, env
     			return nil, err
     		}
     		deployment.Spec.Template.Spec = *newPodSpec
    +		// Re-clamp after the merge: MergePodSpec propagates a non-nil
    +		// AutomountServiceAccountToken from env.Spec.Builder.PodSpec,
    +		// which would otherwise re-enable the kubelet auto-mount on the
    +		// user-supplied builder container. See GHSA-8wcj-mfrc-jx5q.
    +		deployment.Spec.Template.Spec.AutomountServiceAccountToken = new(false)
    +	}
    +
    +	// Re-mount the fission-builder SA token at the canonical Kubernetes
    +	// path on the fetcher container only. The pod-level
    +	// AutomountServiceAccountToken=false flag set above suppresses the
    +	// implicit mount on every container, including fetcher, so we add it
    +	// back explicitly here. This must run AFTER the
    +	// env.Spec.Builder.PodSpec merge — MergePodSpec can append additional
    +	// volumeMounts to the fetcher container, including one at this same
    +	// path, and kubelet would reject the pod with a duplicate-mount-path
    +	// error. The helper strips any pre-existing mount at the path before
    +	// adding its own, so running it last guarantees a single mount on
    +	// the fetcher container backed by the projected SA token volume.
    +	// See GHSA-8wcj-mfrc-jx5q.
    +	if err := util.MountFetcherSATokenOnFetcher(&deployment.Spec.Template.Spec); err != nil {
    +		return nil, err
     	}
     
     	_, err = envw.kubernetesClient.AppsV1().Deployments(ns).Create(ctx, deployment, metav1.CreateOptions{})
    
  • pkg/buildermgr/envwatcher_test.go+228 0 added
    @@ -0,0 +1,228 @@
    +/*
    +Copyright 2026 The Fission Authors.
    +
    +Licensed under the Apache License, Version 2.0 (the "License");
    +you may not use this file except in compliance with the License.
    +You may obtain a copy of the License at
    +
    +    http://www.apache.org/licenses/LICENSE-2.0
    +
    +Unless required by applicable law or agreed to in writing, software
    +distributed under the License is distributed on an "AS IS" BASIS,
    +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +See the License for the specific language governing permissions and
    +limitations under the License.
    +*/
    +
    +package buildermgr
    +
    +import (
    +	"context"
    +	"testing"
    +
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +	apiv1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/types"
    +	k8sfake "k8s.io/client-go/kubernetes/fake"
    +
    +	fv1 "github.com/fission/fission/pkg/apis/core/v1"
    +	"github.com/fission/fission/pkg/executor/util"
    +	fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
    +	"github.com/fission/fission/pkg/utils"
    +	"github.com/fission/fission/pkg/utils/loggerfactory"
    +)
    +
    +// envBuilderContainerName is the name AddFetcherToPodSpec is called with in
    +// envwatcher.createBuilderDeployment for the user-supplied builder container.
    +const envBuilderContainerName = "builder"
    +
    +// newTestEnvironmentWatcher returns an environmentWatcher wired up just enough
    +// to exercise createBuilderDeployment in unit tests. The k8s client is a
    +// fake so Create() is a no-op against in-memory state; createBuilderDeployment
    +// still returns the constructed *appsv1.Deployment which is what the assertions
    +// inspect.
    +func newTestEnvironmentWatcher(t *testing.T) *environmentWatcher {
    +	t.Helper()
    +	cfg, err := fetcherConfig.MakeFetcherConfig("/packages")
    +	require.NoError(t, err)
    +	return &environmentWatcher{
    +		logger:           loggerfactory.GetLogger(),
    +		kubernetesClient: k8sfake.NewSimpleClientset(),
    +		nsResolver:       utils.DefaultNSResolver(),
    +		fetcherConfig:    cfg,
    +		cache:            map[types.UID]*builderInfo{},
    +	}
    +}
    +
    +func newTestBuilderEnv() *fv1.Environment {
    +	return &fv1.Environment{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name:      "py",
    +			Namespace: "default",
    +		},
    +		Spec: fv1.EnvironmentSpec{
    +			Version: 2,
    +			Runtime: fv1.Runtime{
    +				Image: "fission/python-env:latest",
    +			},
    +			Builder: fv1.Builder{
    +				Image: "fission/python-builder:latest",
    +			},
    +		},
    +	}
    +}
    +
    +// TestBuilderPodSpecDoesNotAutomountTokenInBuilderContainer pins the
    +// security-advisory-8wcj invariant: the fission-builder SA token is only
    +// mounted inside the fetcher sidecar, never in the user-supplied builder
    +// container. See GHSA-8wcj-mfrc-jx5q.
    +func TestBuilderPodSpecDoesNotAutomountTokenInBuilderContainer(t *testing.T) {
    +	envw := newTestEnvironmentWatcher(t)
    +	env := newTestBuilderEnv()
    +
    +	deployment, err := envw.createBuilderDeployment(context.Background(), env, "default")
    +	require.NoError(t, err)
    +	pod := deployment.Spec.Template
    +
    +	// Pod-level AutomountServiceAccountToken must be explicitly false.
    +	require.NotNil(t, pod.Spec.AutomountServiceAccountToken,
    +		"pod-level AutomountServiceAccountToken must be set, not nil")
    +	assert.False(t, *pod.Spec.AutomountServiceAccountToken,
    +		"pod-level AutomountServiceAccountToken must be false")
    +
    +	// Pod still runs as fission-builder so the fetcher container can use
    +	// its projected token to talk to the API server.
    +	assert.Equal(t, fv1.FissionBuilderSA, pod.Spec.ServiceAccountName)
    +
    +	// Projected SA-token volume must exist.
    +	var projected *apiv1.Volume
    +	for i := range pod.Spec.Volumes {
    +		if pod.Spec.Volumes[i].Name == util.FetcherSATokenVolumeName {
    +			projected = &pod.Spec.Volumes[i]
    +			break
    +		}
    +	}
    +	require.NotNil(t, projected, "projected SA token volume %q must exist", util.FetcherSATokenVolumeName)
    +	require.NotNil(t, projected.Projected, "%q must be a projected volume", util.FetcherSATokenVolumeName)
    +
    +	// Locate fetcher + builder containers.
    +	var fetcher, builder *apiv1.Container
    +	for i := range pod.Spec.Containers {
    +		switch pod.Spec.Containers[i].Name {
    +		case util.FetcherContainerName:
    +			fetcher = &pod.Spec.Containers[i]
    +		case envBuilderContainerName:
    +			builder = &pod.Spec.Containers[i]
    +		}
    +	}
    +	require.NotNil(t, fetcher, "fetcher container must be present")
    +	require.NotNil(t, builder, "builder container must be present")
    +
    +	// Fetcher must mount the projected SA token at the canonical k8s path.
    +	fetcherHasMount := false
    +	for _, vm := range fetcher.VolumeMounts {
    +		if vm.MountPath == util.FetcherSATokenMountPath {
    +			fetcherHasMount = true
    +			assert.Equal(t, util.FetcherSATokenVolumeName, vm.Name,
    +				"fetcher SA-token mount must be backed by the projected volume")
    +			assert.True(t, vm.ReadOnly, "fetcher SA-token mount must be read-only")
    +		}
    +	}
    +	assert.True(t, fetcherHasMount, "fetcher must mount its own SA token")
    +
    +	// Builder (user) container must have NO mount at the SA-token path.
    +	for _, vm := range builder.VolumeMounts {
    +		assert.NotEqual(t, util.FetcherSATokenMountPath, vm.MountPath,
    +			"builder container must not have any mount at the SA token path")
    +	}
    +}
    +
    +// TestBuilderPodSpecPatchCannotReEnableAutomount asserts that an envw with a
    +// podSpecPatch that sets AutomountServiceAccountToken=true cannot override the
    +// invariant. The re-clamp after MergePodSpec is what blocks this. See
    +// GHSA-8wcj-mfrc-jx5q.
    +func TestBuilderPodSpecPatchCannotReEnableAutomount(t *testing.T) {
    +	envw := newTestEnvironmentWatcher(t)
    +	envw.podSpecPatch = &apiv1.PodSpec{
    +		AutomountServiceAccountToken: new(true),
    +	}
    +	env := newTestBuilderEnv()
    +
    +	deployment, err := envw.createBuilderDeployment(context.Background(), env, "default")
    +	require.NoError(t, err)
    +	pod := deployment.Spec.Template
    +
    +	require.NotNil(t, pod.Spec.AutomountServiceAccountToken)
    +	assert.False(t, *pod.Spec.AutomountServiceAccountToken,
    +		"envw.podSpecPatch must not be able to re-enable auto-mount")
    +}
    +
    +// TestBuilderEnvBuilderPodSpecCannotReEnableAutomount asserts that an
    +// env.Spec.Builder.PodSpec with AutomountServiceAccountToken=true cannot
    +// override the invariant.
    +func TestBuilderEnvBuilderPodSpecCannotReEnableAutomount(t *testing.T) {
    +	envw := newTestEnvironmentWatcher(t)
    +	env := newTestBuilderEnv()
    +	env.Spec.Builder.PodSpec = &apiv1.PodSpec{
    +		AutomountServiceAccountToken: new(true),
    +	}
    +
    +	deployment, err := envw.createBuilderDeployment(context.Background(), env, "default")
    +	require.NoError(t, err)
    +	pod := deployment.Spec.Template
    +
    +	require.NotNil(t, pod.Spec.AutomountServiceAccountToken)
    +	assert.False(t, *pod.Spec.AutomountServiceAccountToken,
    +		"env.Spec.Builder.PodSpec must not be able to re-enable auto-mount")
    +}
    +
    +// TestBuilderEnvBuilderPodSpecCannotIntroduceDuplicateSATokenMount pins the
    +// invariant from PR #3366 (Copilot Round-3) for the buildermgr path: an env
    +// author who supplies env.Spec.Builder.PodSpec.Containers = [{name: "fetcher",
    +// volumeMounts: [{mountPath: <SA token path>}]}] must not cause the final
    +// fetcher container to end up with two mounts at the SA-token path.
    +func TestBuilderEnvBuilderPodSpecCannotIntroduceDuplicateSATokenMount(t *testing.T) {
    +	envw := newTestEnvironmentWatcher(t)
    +	env := newTestBuilderEnv()
    +	env.Spec.Builder.PodSpec = &apiv1.PodSpec{
    +		Containers: []apiv1.Container{
    +			{
    +				Name: util.FetcherContainerName,
    +				VolumeMounts: []apiv1.VolumeMount{
    +					{
    +						Name:      "evil-sa-mount",
    +						MountPath: util.FetcherSATokenMountPath,
    +					},
    +				},
    +			},
    +		},
    +	}
    +
    +	deployment, err := envw.createBuilderDeployment(context.Background(), env, "default")
    +	require.NoError(t, err)
    +	pod := deployment.Spec.Template
    +
    +	var fetcher *apiv1.Container
    +	for i := range pod.Spec.Containers {
    +		if pod.Spec.Containers[i].Name == util.FetcherContainerName {
    +			fetcher = &pod.Spec.Containers[i]
    +			break
    +		}
    +	}
    +	require.NotNil(t, fetcher, "fetcher container must be present")
    +
    +	mountsAtSAPath := 0
    +	var mountVolumeName string
    +	for _, vm := range fetcher.VolumeMounts {
    +		if vm.MountPath == util.FetcherSATokenMountPath {
    +			mountsAtSAPath++
    +			mountVolumeName = vm.Name
    +		}
    +	}
    +	assert.Equal(t, 1, mountsAtSAPath,
    +		"fetcher must have exactly one mount at the SA-token path, not duplicates")
    +	assert.Equal(t, util.FetcherSATokenVolumeName, mountVolumeName,
    +		"the sole mount at the SA-token path must be the projected volume, not the user-supplied one")
    +}
    

Vulnerability mechanics

Root cause

"Fission builder pods were created with an auto-mounted service account token, allowing user-supplied builder images to access secrets and configmaps."

Attack vector

An attacker with permission to create or update Environment CRDs in a namespace observed by the buildermgr could exploit this vulnerability. By supplying a custom builder image and podspec, the attacker could leverage the auto-mounted service account token. This token grants the builder container the identity of the `fission-builder` service account, enabling it to read all secrets and configmaps within the builder namespace [ref_id=2].

Affected code

The vulnerability existed in the creation of builder pods within Fission. Specifically, the `fission-builder` service account was auto-mounted without `AutomountServiceAccountToken: false` in the pod specification. This allowed the user-supplied builder image to access the token, which was located at `/var/run/secrets/kubernetes.io/serviceaccount/token` within the container [ref_id=2]. The fix was implemented in `pkg/buildermgr/envwatcher.go` and related components.

What the fix does

The fix involves setting `AutomountServiceAccountToken=false` at the pod level for builder pods and ensuring this setting is maintained after any merge operations [patch_id=5504367]. Additionally, the service account token is now mounted via a projected volume specifically for the fetcher sidecar, which is necessary for legitimate build operations. This prevents the user-supplied builder container from inheriting the `fission-builder` identity and accessing sensitive information [ref_id=2].

Preconditions

  • authAttacker must have create/update permissions on Environment CRDs.
  • configThe Fission framework must be deployed and operational.

Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

1