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

CVE-2026-50569

CVE-2026-50569

Description

Fission versions prior to 1.25.0 allowed HTTPTriggers to bypass URL validation via kubectl or direct API calls, enabling path traversal and route collisions.

AI Insight

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

Fission versions prior to 1.25.0 allowed HTTPTriggers to bypass URL validation via kubectl or direct API calls, enabling path traversal and route collisions.

Vulnerability

Fission versions prior to 1.25.0 contained a vulnerability where the HTTPTriggerSpec.Validate() function did not validate the RelativeURL and Prefix fields. These fields were only validated at the CLI level. Following a change to use API-server CEL for validation, and since CEL also lacked rules for these fields, HTTPTriggers created via kubectl apply or direct Kubernetes REST API calls bypassed all URL-level checks [2].

Exploitation

An authenticated Kubernetes user with HTTPTrigger create permissions could create a malicious HTTPTrigger. This could involve setting an empty RelativeURL or Prefix, using a RelativeURL that does not start with /, setting RelativeURL to /, including .. traversal segments, or colliding with router-owned routes like /router-healthz or the internal /fission-function// prefix [2].

Impact

Successful exploitation allows an attacker to create triggers with unexpected or malicious URLs. This could lead to path traversal, allowing access to unintended resources, or route collisions, potentially disrupting legitimate services or gaining unauthorized access to internal Fission routes [2].

Mitigation

This vulnerability was fixed in Fission version 1.25.0, released on 2024-04-09 [1]. The fix enforces path-safety invariants at both the API server's CEL admission gate and within the HTTPTriggerSpec.Validate() function to ensure consistency between the CLI and the API server [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.25.0

Patches

1
0deed6bf3f26

fix(httptrigger): enforce path-safety at admission (GHSA-vchh) (#3464)

https://github.com/fission/fissionSanket SudakeJun 4, 2026via body-scan-shorthand
4 files changed · +164 0
  • crds/v1/fission.io_httptriggers.yaml+20 0 modified
    @@ -237,6 +237,26 @@ spec:
                 required:
                 - functionref
                 type: object
    +            x-kubernetes-validations:
    +            - message: 'HTTPTriggerSpec: at least one of relativeurl or prefix must
    +                be set'
    +              rule: self.relativeurl != '' || (has(self.prefix) && self.prefix !=
    +                '')
    +            - message: HTTPTriggerSpec.relativeurl must start with '/', not be '/',
    +                not contain '..' path segments, not collide with a router-owned path
    +                (/router-healthz, /readyz, /_version, /auth/login), and not start
    +                with /fission-function/
    +              rule: self.relativeurl == '' || (self.relativeurl.startsWith('/') &&
    +                self.relativeurl != '/' && !self.relativeurl.matches('(^|/)[.][.](/|$)')
    +                && !(self.relativeurl in ['/router-healthz','/readyz','/_version','/auth/login'])
    +                && !self.relativeurl.startsWith('/fission-function/'))
    +            - message: HTTPTriggerSpec.prefix must start with '/', not be '/', not
    +                contain '..' path segments, not collide with a router-owned path (/router-healthz,
    +                /readyz, /_version, /auth/login), and not start with /fission-function/
    +              rule: '!has(self.prefix) || self.prefix == '''' || (self.prefix.startsWith(''/'')
    +                && self.prefix != ''/'' && !self.prefix.matches(''(^|/)[.][.](/|$)'')
    +                && !(self.prefix in [''/router-healthz'',''/readyz'',''/_version'',''/auth/login''])
    +                && !self.prefix.startsWith(''/fission-function/''))'
               status:
                 description: HTTPTriggerStatus describes the observed state of an HTTPTrigger.
                 properties:
    
  • pkg/apis/core/v1/types.go+3 0 modified
    @@ -708,6 +708,9 @@ type (
     	//
     
     	// HTTPTriggerSpec is for router to expose user functions at the given URL path.
    +	// +kubebuilder:validation:XValidation:rule="self.relativeurl != '' || (has(self.prefix) && self.prefix != '')",message="HTTPTriggerSpec: at least one of relativeurl or prefix must be set"
    +	// +kubebuilder:validation:XValidation:rule="self.relativeurl == '' || (self.relativeurl.startsWith('/') && self.relativeurl != '/' && !self.relativeurl.matches('(^|/)[.][.](/|$)') && !(self.relativeurl in ['/router-healthz','/readyz','/_version','/auth/login']) && !self.relativeurl.startsWith('/fission-function/'))",message="HTTPTriggerSpec.relativeurl must start with '/', not be '/', not contain '..' path segments, not collide with a router-owned path (/router-healthz, /readyz, /_version, /auth/login), and not start with /fission-function/"
    +	// +kubebuilder:validation:XValidation:rule="!has(self.prefix) || self.prefix == '' || (self.prefix.startsWith('/') && self.prefix != '/' && !self.prefix.matches('(^|/)[.][.](/|$)') && !(self.prefix in ['/router-healthz','/readyz','/_version','/auth/login']) && !self.prefix.startsWith('/fission-function/'))",message="HTTPTriggerSpec.prefix must start with '/', not be '/', not contain '..' path segments, not collide with a router-owned path (/router-healthz, /readyz, /_version, /auth/login), and not start with /fission-function/"
     	HTTPTriggerSpec struct {
     		// TODO: remove this field since we have IngressConfig already
     		// Deprecated: the original idea of this field is not for setting Ingress.
    
  • pkg/apis/core/v1/validation.go+64 0 modified
    @@ -459,9 +459,73 @@ func (spec HTTPTriggerSpec) Validate() error {
     	if spec.CorsConfig != nil {
     		errs = errors.Join(errs, spec.CorsConfig.Validate())
     	}
    +
    +	// Path validation. HTTPTrigger has no admission webhook on current main
    +	// (the API server's CEL evaluation is the admission gate); these checks
    +	// mirror the CEL rules on HTTPTriggerSpec so the CLI and the router
    +	// reconciler's status-Condition path agree with what the API server
    +	// admits. Closes GHSA-vchh-r53j-8mpw.
    +	prefix := ""
    +	if spec.Prefix != nil {
    +		prefix = *spec.Prefix
    +	}
    +	if spec.RelativeURL == "" && prefix == "" {
    +		errs = errors.Join(errs, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec", "",
    +			"at least one of relativeurl or prefix must be set"))
    +	}
    +	if spec.RelativeURL != "" {
    +		errs = errors.Join(errs, validateTriggerPath("HTTPTriggerSpec.RelativeURL", spec.RelativeURL))
    +	}
    +	if prefix != "" {
    +		errs = errors.Join(errs, validateTriggerPath("HTTPTriggerSpec.Prefix", prefix))
    +	}
     	return errs
     }
     
    +// routerReservedExactPaths are URL paths the router serves itself: liveness
    +// (/router-healthz), readiness (/readyz), version (/_version), and the
    +// chart-default auth login (/auth/login). Installations that change the auth
    +// path must still ensure their custom path does not collide with another
    +// HTTPTrigger; this list covers the defaults the router ships with.
    +var routerReservedExactPaths = map[string]struct{}{
    +	"/router-healthz": {},
    +	"/readyz":         {},
    +	"/_version":       {},
    +	"/auth/login":     {},
    +}
    +
    +// routerInternalFunctionPrefix is the URL prefix the router serves on its
    +// internal listener (post-GHSA-3g33-6vg6-27m8) for direct function invocation.
    +// Triggers under this prefix would shadow internal routes if the public/
    +// internal listener split is misconfigured.
    +const routerInternalFunctionPrefix = "/fission-function/"
    +
    +// validateTriggerPath enforces the URL-path safety invariants for RelativeURL
    +// and Prefix in HTTPTriggerSpec. Keep these checks aligned with the CEL rules
    +// on HTTPTriggerSpec in types.go.
    +func validateTriggerPath(field, path string) error {
    +	if !strings.HasPrefix(path, "/") {
    +		return MakeValidationErr(ErrorInvalidValue, field, path, "must start with '/'")
    +	}
    +	if path == "/" {
    +		return MakeValidationErr(ErrorInvalidValue, field, path, "root-only path '/' is not allowed")
    +	}
    +	// Reject any ".." path segment. Splitting on '/' (rather than substring
    +	// match) permits literal names like "..foo" or "foo..bar" while catching
    +	// the traversal form ".." that the router would otherwise resolve away.
    +	if slices.Contains(strings.Split(path, "/"), "..") {
    +		return MakeValidationErr(ErrorInvalidValue, field, path, "must not contain '..' path segments")
    +	}
    +	if _, reserved := routerReservedExactPaths[path]; reserved {
    +		return MakeValidationErr(ErrorInvalidValue, field, path, "collides with a router-owned path")
    +	}
    +	if strings.HasPrefix(path, routerInternalFunctionPrefix) {
    +		return MakeValidationErr(ErrorInvalidValue, field, path,
    +			"collides with the router-internal "+routerInternalFunctionPrefix+" prefix")
    +	}
    +	return nil
    +}
    +
     // Validate enforces the CORS spec invariants that browsers will reject
     // at runtime, plus the URL-shape and duration-format invariants that
     // surface as router-side configuration errors. Validation runs at
    
  • pkg/apis/core/v1/validation_validators_test.go+77 0 modified
    @@ -137,17 +137,93 @@ func TestHTTPTriggerSpecValidate(t *testing.T) {
     	t.Parallel()
     	valid := HTTPTriggerSpec{
     		Methods:           []string{"GET", "POST"},
    +		RelativeURL:       "/api/hello",
     		FunctionReference: FunctionReference{Type: FunctionReferenceTypeFunctionName, Name: "hello"},
     	}
     	require.NoError(t, valid.Validate())
     
     	bad := HTTPTriggerSpec{
     		Method:            "FETCH",
    +		RelativeURL:       "/api/hello",
     		FunctionReference: FunctionReference{Type: FunctionReferenceTypeFunctionName, Name: "hello"},
     	}
     	require.Error(t, bad.Validate())
     }
     
    +// TestHTTPTriggerSpecValidate_Path covers the URL-path safety rules added for
    +// GHSA-vchh-r53j-8mpw. Keep these cases aligned with the CEL rules on
    +// HTTPTriggerSpec in types.go so the API server's admission decision and the
    +// Go-side Validate() decision (used by the CLI and the router reconciler's
    +// status-Condition path) stay in lockstep.
    +func TestHTTPTriggerSpecValidate_Path(t *testing.T) {
    +	t.Parallel()
    +	str := func(s string) *string { return &s }
    +	fnRef := FunctionReference{Type: FunctionReferenceTypeFunctionName, Name: "hello"}
    +
    +	for _, tc := range []struct {
    +		name        string
    +		relativeURL string
    +		prefix      *string
    +		wantErr     bool
    +		errSub      string
    +	}{
    +		// happy paths
    +		{name: "valid relativeurl", relativeURL: "/api/hello"},
    +		{name: "valid prefix", prefix: str("/api/")},
    +		{name: "valid both set", relativeURL: "/api/hello", prefix: str("/api/")},
    +		{name: "literal dot-dot inside segment is allowed", relativeURL: "/api/..foo"},
    +		{name: "double-dot suffix in segment is allowed", relativeURL: "/api/foo..bar"},
    +
    +		// at-least-one-set
    +		{name: "neither set", wantErr: true, errSub: "at least one"},
    +		{name: "empty relativeurl and empty prefix", prefix: str(""), wantErr: true, errSub: "at least one"},
    +
    +		// leading slash
    +		{name: "no leading slash relativeurl", relativeURL: "hello", wantErr: true, errSub: "must start with '/'"},
    +		{name: "no leading slash prefix", prefix: str("hello"), wantErr: true, errSub: "must start with '/'"},
    +
    +		// root-only
    +		{name: "root-only relativeurl", relativeURL: "/", wantErr: true, errSub: "root-only"},
    +		{name: "root-only prefix", prefix: str("/"), wantErr: true, errSub: "root-only"},
    +
    +		// `..` traversal
    +		{name: "traversal relativeurl", relativeURL: "/api/../admin", wantErr: true, errSub: "'..'"},
    +		{name: "traversal prefix", prefix: str("/api/../admin"), wantErr: true, errSub: "'..'"},
    +		{name: "leading traversal", relativeURL: "/..", wantErr: true, errSub: "'..'"},
    +		{name: "trailing traversal", relativeURL: "/api/..", wantErr: true, errSub: "'..'"},
    +
    +		// router-owned exact paths
    +		{name: "reserved /router-healthz", relativeURL: "/router-healthz", wantErr: true, errSub: "router-owned"},
    +		{name: "reserved /readyz", relativeURL: "/readyz", wantErr: true, errSub: "router-owned"},
    +		{name: "reserved /_version", relativeURL: "/_version", wantErr: true, errSub: "router-owned"},
    +		{name: "reserved /auth/login", relativeURL: "/auth/login", wantErr: true, errSub: "router-owned"},
    +		{name: "reserved path as prefix", prefix: str("/readyz"), wantErr: true, errSub: "router-owned"},
    +
    +		// router-internal /fission-function/ prefix
    +		{name: "internal-prefix relativeurl", relativeURL: "/fission-function/ns/fn", wantErr: true, errSub: "/fission-function/"},
    +		{name: "internal-prefix as Prefix field", prefix: str("/fission-function/"), wantErr: true, errSub: "/fission-function/"},
    +	} {
    +		t.Run(tc.name, func(t *testing.T) {
    +			t.Parallel()
    +			spec := HTTPTriggerSpec{
    +				RelativeURL:       tc.relativeURL,
    +				Prefix:            tc.prefix,
    +				FunctionReference: fnRef,
    +				Methods:           []string{"GET"},
    +			}
    +			err := spec.Validate()
    +			if tc.wantErr {
    +				require.Error(t, err, "expected error containing %q", tc.errSub)
    +				if tc.errSub != "" {
    +					require.Contains(t, err.Error(), tc.errSub)
    +				}
    +				return
    +			}
    +			require.NoError(t, err)
    +		})
    +	}
    +}
    +
     func TestIngressConfigValidate(t *testing.T) {
     	t.Parallel()
     	require.NoError(t, IngressConfig{Path: "/foo", Host: "*"}.Validate())
    @@ -209,6 +285,7 @@ func TestCRDValidate(t *testing.T) {
     	t.Run("httptrigger", func(t *testing.T) {
     		h := &HTTPTrigger{ObjectMeta: meta}
     		h.Spec.FunctionReference = FunctionReference{Type: FunctionReferenceTypeFunctionName, Name: "hello"}
    +		h.Spec.RelativeURL = "/api/hello"
     		require.NoError(t, h.Validate())
     	})
     }
    

Vulnerability mechanics

Root cause

"The HTTPTrigger admission validation logic failed to check RelativeURL and Prefix fields, allowing them to be set to unsafe values."

Attack vector

An authenticated Kubernetes user with HTTPTrigger create permission can create an HTTPTrigger resource via `kubectl apply` or a direct Kubernetes REST API call. This bypasses the CLI-level validation for `RelativeURL` and `Prefix`. The attacker can then set these fields to values such as an empty string, a path not starting with '/', exactly '/', containing `..` traversal segments, or colliding with reserved router paths or the internal `/fission-function/` prefix [ref_id=1].

Affected code

The vulnerability lies in the `HTTPTriggerSpec.Validate()` function within `pkg/apis/core/v1/validation.go`, which silently skipped validation for `RelativeURL` and `Prefix`. These fields were only validated at the CLI level. Following the retirement of the HTTPTrigger webhook in favor of API-server CEL, these fields lacked validation rules in the CEL configuration as well [ref_id=1][ref_id=2].

What the fix does

The patch enforces path-safety invariants at both the API server's CEL admission gate and within the Go-side `HTTPTriggerSpec.Validate()` function. This ensures that the validation rules for `RelativeURL` and `Prefix` are consistent across all admission layers, including those bypassed by direct API calls. The `validateTriggerPath` helper function was updated to mirror the CEL rules, aligning CLI rejection and router reconciler status conditions with API server admission [ref_id=2][patch_id=5504356].

Preconditions

  • authAuthenticated Kubernetes user with HTTPTrigger create permission.

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