Gateway API: RegularExpression HTTPRoute route dropped when a "/" PathPrefix catch-all exists
Is this the right place to submit this?
- This is not a security vulnerability or a crashing bug.
- This is not a question about how to use Istio.
Bug Description
When an HTTPRoute has a rule whose path match is RegularExpression and another rule (or another HTTPRoute on the same parent) uses a PathPrefix: / catch-all, the RegularExpression route is silently dropped from the generated Envoy route config. There is no error and the HTTPRoute status is Accepted: True, but the regex route never reaches the data plane, so requests that should match the specific regex fall through to the catch-all.
This is a recurrence of #50108 (auto-closed stale, never fixed) and the never-merged fix attempt #50109 (closed by the lifecycle bot on needs-rebase). It still reproduces on 1.29.2, 1.30, and master.
Root cause
sortHTTPRoutesranks path match types viagetURIRank:Exact(3) > Prefix(2) > Regex(1). APathPrefix: /catch-all (rank 2) therefore sorts ahead of a more specificRegularExpressionroute (rank 1), regardless of how specific the regex is.BuildHTTPRoutesForVirtualServicewalks the sorted routes and, on the firstIsCatchAllRoutematch, setscatchall = trueandbreaks — "Routes are matched in order, so we will never go beyond this match." Every route after the catch-all (including the regex route) is truncated.
So the catch-all is emitted, the regex route is not.
Note: this is independent of multi-match-rule handling. Istio already splits each rule into single-match routes before sorting (
route_collections.go:// split the rule to make sure each rule has up to one match), so every route already carries one match. The bug is purely the catch-all rank inversion + truncation and reproduces with plain single-match rules.
Reproduce (adapted from #50108)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: match-order
spec:
parentRefs:
- {group: gateway.networking.k8s.io, kind: Gateway, name: gw}
rules:
- matches:
- path: {type: RegularExpression, value: "^/api/[0-9]+/charge$"}
backendRefs: [{name: svc-a, port: 80}]
- matches:
- path: {type: PathPrefix, value: /}
backendRefs: [{name: svc-b, port: 80}]istioctl proxy-config route <gw-pod> (or pilot-agent request GET config_dump) shows only the / route → svc-b; the ^/api/[0-9]+/charge$ route is absent. POST /api/5/charge is served by svc-b, not svc-a.
Impact
A route-scoped policy attached only to the specific path (e.g. retry: {attempts: 0} to make a non-idempotent endpoint non-retryable) never takes effect, because the route carrying it is dropped. In our case this caused a non-idempotent payment POST to be retried and posted to the backend PMS multiple times.
Expected
A specific RegularExpression route must not be shadowed/dropped by a less specific PathPrefix: / catch-all. The catch-all should sort last.
Version
Reproduced on:
- Istio 1.29.2 (prod), 1.30 (Envoy 1.38.x), and current master
- Gateway API: gateway.networking.k8s.io/v1 HTTPRoute
- Kubernetes: 1.30 (EKS)
Code: pilot/pkg/config/kube/gateway/conversion.go (getURIRank/sortHTTPRoutes)
pilot/pkg/networking/core/route/route.go (BuildHTTPRoutesForVirtualService, IsCatchAllRoute)Additional Information
Evidence gathered from the live gateway proxy via pilot-agent request GET config_dump (route name for the regex rule is absent from the virtual host; :method/regex path matchers do not appear). A unit-level repro exists in TestSortHTTPRoutes and an end-to-end repro in the route-precedence golden in pilot/pkg/config/kube/gateway. Fix proposed in #60696.
Affected product area
- Networking
Source: istio/istio