#60695·istio

Gateway API: RegularExpression HTTPRoute route dropped when a "/" PathPrefix catch-all exists

Author: aditmenoCreated Jun 25, 2026Updated Sep 30, 2026
Labelslifecycle/stale

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

  1. sortHTTPRoutes ranks path match types via getURIRank: Exact(3) > Prefix(2) > Regex(1). A PathPrefix: / catch-all (rank 2) therefore sorts ahead of a more specific RegularExpression route (rank 1), regardless of how specific the regex is.
  2. BuildHTTPRoutesForVirtualService walks the sorted routes and, on the first IsCatchAllRoute match, sets catchall = true and breaks — "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)

yaml
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