Cilium's Gateway API route matching order contradicts specification
Description
Cilium is a networking, observability, and security solution with an eBPF-based dataplane. In the 1.15 branch prior to 1.15.8 and the 1.16 branch prior to 1.16.1, Gateway API HTTPRoutes and GRPCRoutes do not follow the match precedence specified in the Gateway API specification. In particular, request headers are matched before request methods, when the specification describes that the request methods must be respected before headers are matched. This could result in unexpected behaviour with security This issue is fixed in Cilium v1.15.8 and v1.16.1. There is no workaround for this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/cilium/ciliumGo | >= 1.16.0, < 1.16.1 | 1.16.1 |
github.com/cilium/ciliumGo | >= 1.15.0, < 1.15.8 | 1.15.8 |
Affected products
1Patches
3d88772b9c29egateway-api: Add HTTP method condition in sortable routes
3 files changed · +116 −6
operator/pkg/model/translation/envoy_virtual_host.go+24 −0 modified@@ -42,6 +42,7 @@ type VirtualHostMutator func(*envoy_config_route_v3.VirtualHost) *envoy_config_r // - Exact Match length // - Regex Match length // - Prefix match length +// - Method match // - Number of header matches // - Number of query parameter matches // @@ -81,6 +82,20 @@ func (s SortableRoute) Less(i, j int) bool { return prefixMatch1 > prefixMatch2 } + // Next up, sort by method based on :method header + // Give higher priority for the route having method specified + method1 := getMethod(s[i].Match.GetHeaders()) + method2 := getMethod(s[j].Match.GetHeaders()) + if method1 == nil && method2 != nil { + return false + } + if method1 != nil && method2 == nil { + return true + } + if method1 != nil && *method1 != *method2 { + return *method1 < *method2 + } + // If that's the same, then sort by header length if headerMatch1 != headerMatch2 { return headerMatch1 > headerMatch2 @@ -90,6 +105,15 @@ func (s SortableRoute) Less(i, j int) bool { return queryMatch1 > queryMatch2 } +func getMethod(headers []*envoy_config_route_v3.HeaderMatcher) *string { + for _, h := range headers { + if h.Name == ":method" { + return model.AddressOf(h.GetStringMatch().GetExact()) + } + } + return nil +} + func (s SortableRoute) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
operator/pkg/model/translation/envoy_virtual_host_test.go+88 −2 modified@@ -91,7 +91,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "regex match with two headers", Match: &envoy_config_route_v3.RouteMatch{ @@ -124,7 +123,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "exact match short", Match: &envoy_config_route_v3.RouteMatch{ @@ -141,6 +139,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "exact match long with POST method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "POST", + }, + }, + }, + }, + }, + }, + }, + { + Name: "exact match long with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "exact match with one header", Match: &envoy_config_route_v3.RouteMatch{ @@ -227,6 +265,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "prefix match short with HEAD method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "HEAD", + }, + }, + }, + }, + }, + }, + }, + { + Name: "prefix match short with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "prefix match long", Match: &envoy_config_route_v3.RouteMatch{ @@ -328,10 +406,14 @@ func TestSortableRoute(t *testing.T) { "regex match with two headers", "exact match short", "exact match long", + "exact match long with POST method", + "exact match long with GET method", "exact match with one header", "exact match with one header and one query", "exact match with two headers", "prefix match short", + "prefix match short with HEAD method", + "prefix match short with GET method", "prefix match long", "prefix match with one header", "prefix match with one header and one query", @@ -342,6 +424,8 @@ func TestSortableRoute(t *testing.T) { namesAfterSort := buildNameSlice(arr) assert.Equal(t, []string{ + "exact match long with GET method", + "exact match long with POST method", "exact match long", "exact match with two headers", "exact match with one header and one query", @@ -353,6 +437,8 @@ func TestSortableRoute(t *testing.T) { "regex match with one header", "regex match short", "prefix match long", + "prefix match short with GET method", + "prefix match short with HEAD method", "prefix match short", "prefix match with two headers", "prefix match with one header and one query",
operator/pkg/model/translation/gateway-api/translator_fixture_test.go+4 −4 modified@@ -2066,14 +2066,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "POST", + Exact: "GET", }, }, }, }, }, }, - Action: routeActionBackendV1, + Action: routeActionBackendV2, }, { Match: &envoy_config_route_v3.RouteMatch{ @@ -2086,14 +2086,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "GET", + Exact: "POST", }, }, }, }, }, }, - Action: routeActionBackendV2, + Action: routeActionBackendV1, }, }, },
fe42273566a9gateway-api: Add HTTP method condition in sortable routes
3 files changed · +116 −6
operator/pkg/model/translation/envoy_virtual_host.go+24 −0 modified@@ -42,6 +42,7 @@ type VirtualHostMutator func(*envoy_config_route_v3.VirtualHost) *envoy_config_r // - Exact Match length // - Regex Match length // - Prefix match length +// - Method match // - Number of header matches // - Number of query parameter matches // @@ -81,6 +82,20 @@ func (s SortableRoute) Less(i, j int) bool { return prefixMatch1 > prefixMatch2 } + // Next up, sort by method based on :method header + // Give higher priority for the route having method specified + method1 := getMethod(s[i].Match.GetHeaders()) + method2 := getMethod(s[j].Match.GetHeaders()) + if method1 == nil && method2 != nil { + return false + } + if method1 != nil && method2 == nil { + return true + } + if method1 != nil && *method1 != *method2 { + return *method1 < *method2 + } + // If that's the same, then sort by header length if headerMatch1 != headerMatch2 { return headerMatch1 > headerMatch2 @@ -90,6 +105,15 @@ func (s SortableRoute) Less(i, j int) bool { return queryMatch1 > queryMatch2 } +func getMethod(headers []*envoy_config_route_v3.HeaderMatcher) *string { + for _, h := range headers { + if h.Name == ":method" { + return model.AddressOf(h.GetStringMatch().GetExact()) + } + } + return nil +} + func (s SortableRoute) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
operator/pkg/model/translation/envoy_virtual_host_test.go+88 −2 modified@@ -91,7 +91,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "regex match with two headers", Match: &envoy_config_route_v3.RouteMatch{ @@ -124,7 +123,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "exact match short", Match: &envoy_config_route_v3.RouteMatch{ @@ -141,6 +139,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "exact match long with POST method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "POST", + }, + }, + }, + }, + }, + }, + }, + { + Name: "exact match long with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "exact match with one header", Match: &envoy_config_route_v3.RouteMatch{ @@ -227,6 +265,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "prefix match short with HEAD method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "HEAD", + }, + }, + }, + }, + }, + }, + }, + { + Name: "prefix match short with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "prefix match long", Match: &envoy_config_route_v3.RouteMatch{ @@ -328,10 +406,14 @@ func TestSortableRoute(t *testing.T) { "regex match with two headers", "exact match short", "exact match long", + "exact match long with POST method", + "exact match long with GET method", "exact match with one header", "exact match with one header and one query", "exact match with two headers", "prefix match short", + "prefix match short with HEAD method", + "prefix match short with GET method", "prefix match long", "prefix match with one header", "prefix match with one header and one query", @@ -342,6 +424,8 @@ func TestSortableRoute(t *testing.T) { namesAfterSort := buildNameSlice(arr) assert.Equal(t, []string{ + "exact match long with GET method", + "exact match long with POST method", "exact match long", "exact match with two headers", "exact match with one header and one query", @@ -353,6 +437,8 @@ func TestSortableRoute(t *testing.T) { "regex match with one header", "regex match short", "prefix match long", + "prefix match short with GET method", + "prefix match short with HEAD method", "prefix match short", "prefix match with two headers", "prefix match with one header and one query",
operator/pkg/model/translation/gateway-api/translator_fixture_test.go+4 −4 modified@@ -2498,14 +2498,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "POST", + Exact: "GET", }, }, }, }, }, }, - Action: routeActionBackendV1, + Action: routeActionBackendV2, }, { Match: &envoy_config_route_v3.RouteMatch{ @@ -2518,14 +2518,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "GET", + Exact: "POST", }, }, }, }, }, }, - Action: routeActionBackendV2, + Action: routeActionBackendV1, }, }, },
a3510fe4a923gateway-api: Add HTTP method condition in sortable routes
3 files changed · +116 −6
operator/pkg/model/translation/envoy_virtual_host.go+24 −0 modified@@ -42,6 +42,7 @@ type VirtualHostMutator func(*envoy_config_route_v3.VirtualHost) *envoy_config_r // - Exact Match length // - Regex Match length // - Prefix match length +// - Method match // - Number of header matches // - Number of query parameter matches // @@ -81,6 +82,20 @@ func (s SortableRoute) Less(i, j int) bool { return prefixMatch1 > prefixMatch2 } + // Next up, sort by method based on :method header + // Give higher priority for the route having method specified + method1 := getMethod(s[i].Match.GetHeaders()) + method2 := getMethod(s[j].Match.GetHeaders()) + if method1 == nil && method2 != nil { + return false + } + if method1 != nil && method2 == nil { + return true + } + if method1 != nil && *method1 != *method2 { + return *method1 < *method2 + } + // If that's the same, then sort by header length if headerMatch1 != headerMatch2 { return headerMatch1 > headerMatch2 @@ -90,6 +105,15 @@ func (s SortableRoute) Less(i, j int) bool { return queryMatch1 > queryMatch2 } +func getMethod(headers []*envoy_config_route_v3.HeaderMatcher) *string { + for _, h := range headers { + if h.Name == ":method" { + return model.AddressOf(h.GetStringMatch().GetExact()) + } + } + return nil +} + func (s SortableRoute) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
operator/pkg/model/translation/envoy_virtual_host_test.go+88 −2 modified@@ -91,7 +91,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "regex match with two headers", Match: &envoy_config_route_v3.RouteMatch{ @@ -124,7 +123,6 @@ func TestSortableRoute(t *testing.T) { }, }, }, - { Name: "exact match short", Match: &envoy_config_route_v3.RouteMatch{ @@ -141,6 +139,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "exact match long with POST method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "POST", + }, + }, + }, + }, + }, + }, + }, + { + Name: "exact match long with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{ + Path: "/exact/match/longest", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "exact match with one header", Match: &envoy_config_route_v3.RouteMatch{ @@ -227,6 +265,46 @@ func TestSortableRoute(t *testing.T) { }, }, }, + { + Name: "prefix match short with HEAD method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "HEAD", + }, + }, + }, + }, + }, + }, + }, + { + Name: "prefix match short with GET method", + Match: &envoy_config_route_v3.RouteMatch{ + PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{ + PathSeparatedPrefix: "/prefix/match", + }, + Headers: []*envoy_config_route_v3.HeaderMatcher{ + { + Name: ":method", + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: "GET", + }, + }, + }, + }, + }, + }, + }, { Name: "prefix match long", Match: &envoy_config_route_v3.RouteMatch{ @@ -328,10 +406,14 @@ func TestSortableRoute(t *testing.T) { "regex match with two headers", "exact match short", "exact match long", + "exact match long with POST method", + "exact match long with GET method", "exact match with one header", "exact match with one header and one query", "exact match with two headers", "prefix match short", + "prefix match short with HEAD method", + "prefix match short with GET method", "prefix match long", "prefix match with one header", "prefix match with one header and one query", @@ -342,6 +424,8 @@ func TestSortableRoute(t *testing.T) { namesAfterSort := buildNameSlice(arr) assert.Equal(t, []string{ + "exact match long with GET method", + "exact match long with POST method", "exact match long", "exact match with two headers", "exact match with one header and one query", @@ -353,6 +437,8 @@ func TestSortableRoute(t *testing.T) { "regex match with one header", "regex match short", "prefix match long", + "prefix match short with GET method", + "prefix match short with HEAD method", "prefix match short", "prefix match with two headers", "prefix match with one header and one query",
operator/pkg/model/translation/gateway-api/translator_fixture_test.go+4 −4 modified@@ -2498,14 +2498,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "POST", + Exact: "GET", }, }, }, }, }, }, - Action: routeActionBackendV1, + Action: routeActionBackendV2, }, { Match: &envoy_config_route_v3.RouteMatch{ @@ -2518,14 +2518,14 @@ var methodMatchingHTTPListenersHTTPListenersCiliumEnvoyConfig = &ciliumv2.Cilium HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &envoy_type_matcher_v3.StringMatcher{ MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ - Exact: "GET", + Exact: "POST", }, }, }, }, }, }, - Action: routeActionBackendV2, + Action: routeActionBackendV1, }, }, },
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- github.com/advisories/GHSA-qcm3-7879-xcwwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-42487ghsaADVISORY
- github.com/cilium/cilium/commit/a3510fe4a92305822aa1a5e08cb6d6c873c8699aghsax_refsource_MISCWEB
- github.com/cilium/cilium/commit/d88772b9c29e370becbc4547cada6711d51edcdeghsaWEB
- github.com/cilium/cilium/commit/fe42273566a943a0f3174c87b23a195c856b51d6ghsaWEB
- github.com/cilium/cilium/pull/34109ghsax_refsource_MISCWEB
- github.com/cilium/cilium/security/advisories/GHSA-qcm3-7879-xcwwghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.