CVE-2026-49823
Description
Fission is an open-source, Kubernetes-native serverless framework that simplifies the deployment of functions and applications on Kubernetes. Prior to version 1.24.0, a Fission Function spec carries three reference types — Secret, ConfigMap, and Package. The first two were namespace-validated by the admission webhook; PackageRef.Namespace was not. This issue has been patched in version 1.24.0.
Affected products
2Patches
180e7ba55228eReject cross-namespace Environment + Package references in Function (… (#3389)
4 files changed · +160 −0
pkg/executor/executortype/newdeploy/newdeploymgr.go+13 −0 modified@@ -270,6 +270,11 @@ func (deploy *NewDeploy) IsValid(ctx context.Context, fsvc *fscache.FuncSvc) boo // RefreshFuncPods deletes pods related to the function so that new pods are replenished func (deploy *NewDeploy) RefreshFuncPods(ctx context.Context, logger logr.Logger, f fv1.Function) error { + // Defence in depth for GHSA-cvw6-gfvv-953q — see fnCreate for context. + if envNs := f.Spec.Environment.Namespace; envNs != "" && envNs != f.Namespace { + return fmt.Errorf("cross-namespace environment reference is not allowed: fn.namespace=%s env.namespace=%s", + f.Namespace, envNs) + } env, err := deploy.fissionClient.CoreV1().Environments(f.Spec.Environment.Namespace).Get(ctx, f.Spec.Environment.Name, metav1.GetOptions{}) if err != nil { @@ -427,6 +432,14 @@ func (deploy *NewDeploy) deleteFunction(ctx context.Context, fn *fv1.Function) e } func (deploy *NewDeploy) fnCreate(ctx context.Context, fn *fv1.Function) (*fscache.FuncSvc, error) { + // Defence in depth for GHSA-cvw6-gfvv-953q — primary defence is the + // admission webhook in pkg/webhook/function.go, but a stale Function + // from a pre-webhook upgrade window (or failurePolicy=ignore) could + // still reach this path. + if envNs := fn.Spec.Environment.Namespace; envNs != "" && envNs != fn.Namespace { + return nil, fmt.Errorf("cross-namespace environment reference is not allowed: fn.namespace=%s env.namespace=%s", + fn.Namespace, envNs) + } cleanupFunc := func(ctx context.Context, ns string, name string) { err := deploy.cleanupNewdeploy(ctx, ns, name) if err != nil {
pkg/executor/executortype/poolmgr/gpm.go+9 −0 modified@@ -586,6 +586,15 @@ func (gpm *GenericPoolManager) getFunctionEnv(ctx context.Context, fn *fv1.Funct var env *fv1.Environment otelUtils.SpanTrackEvent(ctx, "getFunctionEnv", otelUtils.GetAttributesForFunction(fn)...) + // Defence in depth for GHSA-cvw6-gfvv-953q — the admission webhook + // already rejects this at submit time, but a stale Function object + // from an upgrade-before-webhook-restart window (or a cluster running + // with failurePolicy=ignore) could still reach this path. + if envNs := fn.Spec.Environment.Namespace; envNs != "" && envNs != fn.Namespace { + return nil, fmt.Errorf("cross-namespace environment reference is not allowed: fn.namespace=%s env.namespace=%s", + fn.Namespace, envNs) + } + // Cached ? // TODO: the cache should be able to search by <env name, fn namespace> instead of function metadata. result, err := gpm.functionEnv.Get(crd.CacheKeyURFromMeta(&fn.ObjectMeta))
pkg/webhook/function.go+19 −0 modified@@ -61,6 +61,25 @@ func (r *Function) Validate(new *v1.Function) error { return v1.AggregateValidationErrors("Function", err) } } + // Cross-namespace EnvironmentRef closes GHSA-cvw6-gfvv-953q. An empty + // namespace remains accepted — the Fission CLI populates it with the + // function's own namespace at creation time (pkg/fission-cli/cmd/function/ + // create.go), and downstream controllers tolerate empty via + // DefaultNSResolver. Rejecting only the explicit cross-namespace value is + // sufficient for this advisory; defaulting an empty namespace at admission + // is a separate hardening track tracked outside this fix. + if envRef := new.Spec.Environment; envRef.Namespace != "" && envRef.Namespace != new.Namespace { + err := fmt.Errorf("environment's namespace [%s] and function's namespace [%s] are different; cross-namespace Environment reference is not allowed", + envRef.Namespace, new.Namespace) + return v1.AggregateValidationErrors("Function", err) + } + // Cross-namespace PackageRef closes GHSA-3r8v-2xmj-5c39. Same shape as + // the EnvironmentRef check above, including the empty-is-accepted rule. + if pkgRef := new.Spec.Package.PackageRef; pkgRef.Namespace != "" && pkgRef.Namespace != new.Namespace { + err := fmt.Errorf("package's namespace [%s] and function's namespace [%s] are different; cross-namespace Package reference is not allowed", + pkgRef.Namespace, new.Namespace) + return v1.AggregateValidationErrors("Function", err) + } if err := new.Validate(); err != nil { return v1.AggregateValidationErrors("Function", err)
pkg/webhook/function_test.go+119 −0 added@@ -0,0 +1,119 @@ +/* +Copyright 2026. + +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 +*/ + +package webhook + +import ( + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "github.com/fission/fission/pkg/apis/core/v1" +) + +// makeValidFunction returns a Function object that satisfies v1.Function.Validate() +// so the cross-namespace branches are the only thing under test. The caller may +// override the Environment / PackageRef namespaces to exercise the rejects. +func makeValidFunction(fnNs, envNs, pkgNs string) *v1.Function { + return &v1.Function{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fn-1", + Namespace: fnNs, + }, + Spec: v1.FunctionSpec{ + Environment: v1.EnvironmentReference{ + Name: "env-1", + Namespace: envNs, + }, + Package: v1.FunctionPackageRef{ + PackageRef: v1.PackageRef{ + Name: "pkg-1", + Namespace: pkgNs, + }, + }, + InvokeStrategy: v1.InvokeStrategy{ + StrategyType: v1.StrategyTypeExecution, + ExecutionStrategy: v1.ExecutionStrategy{ + ExecutorType: v1.ExecutorTypePoolmgr, + }, + }, + }, + } +} + +func TestFunctionWebhook_Validate_CrossNamespaceEnvironment(t *testing.T) { + cases := []struct { + name string + fnNs string + envNs string + wantRejected bool + }{ + {name: "empty env.namespace is accepted", fnNs: "default", envNs: "", wantRejected: false}, + {name: "same namespace is accepted", fnNs: "default", envNs: "default", wantRejected: false}, + {name: "cross namespace is rejected", fnNs: "ns-attacker", envNs: "ns-victim", wantRejected: true}, + {name: "cross namespace rejected even when fn in kube-system", fnNs: "kube-system", envNs: "default", wantRejected: true}, + } + + r := &Function{} + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := r.Validate(makeValidFunction(tc.fnNs, tc.envNs, tc.fnNs)) + if tc.wantRejected { + if err == nil { + t.Fatalf("expected rejection, got nil") + } + if !strings.Contains(err.Error(), "Environment reference") { + t.Fatalf("error should reference cross-namespace Environment, got: %v", err) + } + if !strings.Contains(err.Error(), tc.envNs) || !strings.Contains(err.Error(), tc.fnNs) { + t.Fatalf("error should mention both namespaces (%q and %q), got: %v", tc.fnNs, tc.envNs, err) + } + } else if err != nil { + t.Fatalf("expected acceptance, got: %v", err) + } + }) + } +} + +func TestFunctionWebhook_Validate_CrossNamespacePackage(t *testing.T) { + cases := []struct { + name string + fnNs string + pkgNs string + wantRejected bool + }{ + {name: "empty pkg.namespace is accepted", fnNs: "default", pkgNs: "", wantRejected: false}, + {name: "same namespace is accepted", fnNs: "default", pkgNs: "default", wantRejected: false}, + {name: "cross namespace is rejected", fnNs: "ns-attacker", pkgNs: "ns-victim", wantRejected: true}, + } + + r := &Function{} + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Keep env.Namespace aligned with fn.Namespace so only the + // package-ref branch can trigger the cross-ns reject. + err := r.Validate(makeValidFunction(tc.fnNs, tc.fnNs, tc.pkgNs)) + if tc.wantRejected { + if err == nil { + t.Fatalf("expected rejection, got nil") + } + if !strings.Contains(err.Error(), "Package reference") { + t.Fatalf("error should reference cross-namespace Package, got: %v", err) + } + if !strings.Contains(err.Error(), tc.pkgNs) || !strings.Contains(err.Error(), tc.fnNs) { + t.Fatalf("error should mention both namespaces (%q and %q), got: %v", tc.fnNs, tc.pkgNs, err) + } + } else if err != nil { + t.Fatalf("expected acceptance, got: %v", err) + } + }) + } +}
Vulnerability mechanics
Root cause
"The Fission Function admission webhook did not validate the namespace of PackageRef, allowing cross-namespace access."
Attack vector
An attacker with `functions.fission.io/create` permission in their namespace can create a Fission Function. By setting the `spec.package.packageref.namespace` to a victim's namespace, the attacker can cause the function to read a Package from that namespace [ref_id=1]. This allows the attacker to access the victim's source code and embedded credentials, as the fission-fetcher service account has cluster-wide `get packages` permission [ref_id=2].
Affected code
The vulnerability lies in the Fission Function admission webhook, specifically in the `pkg/webhook/function.go::Validate` file. The fix was implemented in pull request #3389 [ref_id=1].
What the fix does
The patch modifies the admission webhook to reject Fission Functions where `spec.package.packageref.namespace` does not match the function's own namespace [ref_id=1]. This prevents the confused deputy primitive where a function in one namespace could reference a Package in another. The fix mirrors prior validation for Secret and ConfigMap references [ref_id=1].
Preconditions
- authAttacker must have `functions.fission.io/create` permission in their namespace.
- inputAttacker must be able to create a Fission Function with a `spec.package.packageref.namespace` set to a different namespace.
Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
1- Fission Kubernetes Serverless Framework: 17 Vulnerabilities Disclosed TogetherVypr Intelligence · Jun 10, 2026