Skip to main content

Six Traefik Gateway API Advisories in Six Months: The Namespace-Boundary Probes Your Shared Ingress Owes Every Tenant

12 min readDora NodaDora Noda
Share
On this page

On August 3, 2026, Traefik disclosed that two HTTPRoutes named a-app in namespace team and app in namespace team-a collapse into the same internal route key: httproute-team-a-app-gw-gateway-shared-ep-web-0. The route identity is built by hyphen-joining namespace, name, gateway, entrypoint, and rule index — and since Kubernetes names may themselves contain hyphens, the construction is not injective. Two different routes, one key. The one that reconciles later silently overwrites the one that reconciled first, regardless of creation timestamp. GHSA-fgjj-px3w-67xx rates it CVSS v4 7.6 and lists no workaround — you patch or you carry it.

That advisory is the sixth of broadly the same shape in six months, and every one of them lands in the same place: the component you deployed to enforce the tenant boundary is the component that lost track of which tenant a request belonged to. Your RBAC was right. Your ReferenceGrants were right. The ingress resolved someone else's identity from a field an attacker controlled. If you run a self-hosted PaaS that terminates many customers' custom domains on one shared Traefik, none of the usual application-layer controls were ever in the blast radius.

So this post leads with the thing you can actually run today: seven probes against your own cluster, each with the manifest and the exact assertion. The advisory catalogue and the pattern behind it come after — earn them once you know where you stand.


Probe 0: Establish the Version Floor

Every probe below is meaningless if you are running a build that predates the fixes. Start here:

bash
kubectl -n traefik get deploy traefik \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
If you are onMinimum safe versionNotes
v3.7.xv3.7.10Fixes all six issues below
v3.6.xv3.6.25Last line receiving these patches
v3.0–v3.5No patches for the August advisory; upgrade to 3.6.25+
v2.11.xPatched for one issue (2.11.46) but outside the 3.6+ fix stream

Anything below v3.6.25 or v3.7.10 fails Probe 0. The remaining seven probes exist because passing Probe 0 is not sufficient — they test whether your Gateway topology would have contained each bug even unpatched, which is the only property that survives the next advisory.

Seven Namespace-Boundary Probes

Run these from a namespace that models an untrusted tenant — call it probe-tenant, with exactly the RBAC a customer gets (typically: create/update HTTPRoute in their own namespace, nothing else). If your platform never grants tenants HTTPRoute write access at all, that is a legitimate structural answer to most of this; skip to the last section. If it does — and any platform exposing custom domains, path rules, or header rewrites as a product feature effectively does — run all seven.

Probe 1: Can a tenant route reach an internal Traefik service?

The mechanism behind CVE-2026-54761. Create an HTTPRoute in probe-tenant with two weighted backendRefs, one of which points a TraefikService at an allow-listed namespace:

yaml
backendRefs:
  - group: traefik.io
    kind: TraefikService
    name: api@internal
    namespace: trusted      # a namespace in crossProviderNamespaces
    weight: 1000000
  - kind: Service
    name: whoami
    weight: 1

Assert: curl -H 'Host: probe.example.com' http://$GW_IP/api/rawdata returns 404 or a connection error — not a JSON dump of your entire dynamic configuration. Also check the route's status: ResolvedRefs should be False. A 200 with JSON means every tenant can read every other tenant's routers, services, and middleware names.

The single-backendRef path validated correctly here; only the multi-backendRef (weighted round-robin) path consulted backendRef.namespace instead of the route's own. Probe with two refs, not one.

Probe 2: Is the REST provider handler reachable?

CVE-2026-44774 carried a 9.9 CRITICAL from NVD, and the reason is worth stating plainly: providers.rest.insecure=false does not disable the REST handler. It only stops Traefik from creating its own built-in internal router to it. The handler stays resolvable, and the Gateway provider accepted any TraefikService name ending in @internal — so rest@internal was routable.

Assert two things. First, that the provider is off entirely:

bash
kubectl -n traefik get deploy traefik -o yaml | grep -i 'providers.rest'
# expect: no output

Second, that a tenant route to rest@internal cannot reach it:

bash
curl -s -o /dev/null -w '%{http_code}\n' -X PUT \
  -H 'Host: probe.example.com' -H 'Content-Type: application/json' \
  -d '{"http":{"routers":{}}}' \
  http://$GW_IP/api/providers/rest
# expect 404; a 200 means a tenant can rewrite your entire dynamic config

A 200 here is not a leak. It is dynamic-configuration write access from a namespace whose only real permission was "create an HTTPRoute."

Probe 3: Do two routes with ambiguous names collide?

The August advisory, reproduced deliberately. Create probe/a-app and probe-a/app, both attached to the same Gateway, both with hostname collide.example.com and path /, pointing at two visibly different backends (two whoami deployments with distinct WHOAMI_NAME values work fine).

bash
for i in $(seq 1 20); do
  curl -s -H 'Host: collide.example.com' http://$GW_IP/ | grep -i '^Name:'
done | sort -u

Assert: exactly one backend name appears, and it is the one you expect from the earlier route — or the Gateway reports a conflict condition on one of them. Two names in the output, or the wrong single name, means one namespace's traffic is already reachable by another's route author.

Probe 4: Do two routes sharing a backend share each other's filters?

CVE-2026-54765 (CVSS v3.1 8.5). Traefik generated the child-service key from namespace, service name, protocol, and port — omitting route, listener, rule, and filter identity. Two routes hitting default/whoami:80 with different backendRef filters produced the identical key default-whoami-http-80, and the second route's filter map overwrote the first via maps.Copy.

Create two HTTPRoutes in two namespaces, both targeting the same Service and port, each with a RequestHeaderModifier on the backendRef (not the rule) setting a different value:

yaml
filters:
  - type: RequestHeaderModifier
    requestHeaderModifier:
      set:
        - name: X-Tenant
          value: tenant-a     # tenant-b in the other route

Assert: requests through tenant A's hostname always arrive at the backend with X-Tenant: tenant-a, and never tenant-b. This is the probe most worth keeping in CI permanently, because X-Tenant in your test stands in for whatever your real backends trust — and if your backends trust a proxy-set identity header, a filter mix-up is an authentication bypass, not a cosmetic bug.

Probe 5: Can a tenant bind a middleware from a namespace it does not own?

CVE-2026-65601 resolved a backendRef filter's extensionRef against the backend Service's namespace rather than the route's. Given a ReferenceGrant permitting a cross-namespace Service reference — which is the normal, intended way to share a backend — the route author also got to bind any Middleware living in that backend's namespace, with no separate grant.

Set up the worst case: a Middleware in a privileged namespace that sets X-WEBAUTH-USER: admin, a ReferenceGrant allowing probe-tenant to reference a Service there, and a tenant HTTPRoute whose backendRef filter carries extensionRef pointing at that Middleware.

Assert: the header never reaches the backend. The advisory notes this header-auth class maps to real authenticated identities in Grafana, Gitea, Jenkins, SonarQube, and Nexus Repository — every one of which is plausibly running inside a platform team's own cluster. A ReferenceGrant for a Service must not be a grant for middleware.

Probe 6: Are header and query match values treated as data?

CVE-2026-29777. Traefik's router rule language uses backticks to delimit tokens, and header/query match values from an HTTPRoute were concatenated into that rule string without validation. Create a match whose value closes the token and opens a new predicate:

yaml
matches:
  - headers:
      - name: X-Probe
        value: "x`) || PathPrefix(`/"

Assert: the route is rejected, or the value matches literally. Then confirm the escape did not happen: curl -H 'Host: someone-elses-domain.example.com' http://$GW_IP/ must not be served by the probe route. Successfully appending || PathPrefix(/) to a rule means the listener hostname restriction — the thing separating tenant domains — stopped applying.

Probe 7: Can a tenant claim another tenant's hostname?

This one is not a Traefik bug, and that is the point: every advisory above assumes your Gateway already restricts which routes may attach and which hostnames they may claim. Verify the assumption. From probe-tenant, create an HTTPRoute with hostnames: ["a-real-customer-domain.example.com"] belonging to a different tenant.

Assert: the route is not accepted — via the listener's allowedRoutes.namespaces selector, a hostname-ownership admission policy, or both. If it attaches, you did not need a CVE; the topology hands out hostname hijacking as a feature.


The Cluster That Produced These Probes

AdvisoryMechanismAffectedPatchedSeverityDisclosed
CVE-2026-29777Backtick rule-token injection via unvalidated header/query match values< 3.6.103.6.106.5 v3.1Mar 11
CVE-2026-44774Any *@internal TraefikService accepted; rest@internal reachable despite insecure=false≤2.11.45, 3.0.0–3.6.16, 3.7.02.11.46 / 3.6.17 / 3.7.19.9 v3.1May 15
CVE-2026-54761crossProviderNamespaces checked against backendRef.namespace, not route namespace3.6.17–3.6.20, 3.7.1–3.7.43.6.21 / 3.7.57.1 v3.1Jun 23
CVE-2026-54765Child-service key omits filter identity; one route's filters overwrite another's3.7.0–3.7.53.7.68.5 v3.1Jul 6
CVE-2026-65601extensionRef middleware resolved in the backend's namespace, not the route's3.7.0–3.7.63.7.75.3Jul 9
GHSA-fgjj-px3w-67xxHyphen-joined route key is non-injective; team/a-app collides with team-a/app3.0.0–3.6.24, 3.7.0–3.7.93.6.25 / 3.7.107.6 v4Aug 3

The same week as the August advisory brought two more of the family — GHSA-62fc-8686-hfmq, an allowCrossNamespace=false bypass through a @kubernetescrd TraefikService backendRef, and a BasicAuth singleflight key collision — plus, in July, a cross-provider namespace bypass on IngressRouteTCP's ServersTransport. The Gateway API provider is where most of them landed, but the shape is not confined to it.

Two Failure Modes, Not Six Bugs

Sorted by mechanism rather than date, the cluster is two recurring mistakes:

The authorization check reads the wrong namespace. CVE-2026-54761 and CVE-2026-65601 both evaluate a permission against a namespace the attacker supplied in the reference, rather than the namespace the requesting object lives in. CVE-2026-44774 is the same error in string form: an @internal suffix was treated as sufficient identity. The fix in each case is one line of "check route.Namespace, not the field we just resolved from input" — which is exactly why it recurs. It reads correct at every call site until you trace where the variable was reassigned. In 54761, loadService() overwrote the namespace with backendRef.Namespace before handing it to the function that performed the check.

The internal identity key is not unique per tenant. CVE-2026-54765 and the August collision bug are the same bug at different layers: a key derived by concatenating attacker-influenceable strings, then used as a map key, where a collision means silent overwrite rather than an error. A non-injective key in a multi-tenant control plane is a cross-tenant write primitive, and maps.Copy will not tell you it happened.

Both are ordinary engineering mistakes. What makes them a distinct class is where they live. An application-layer authorization bug is contained by the ingress; an ingress-layer one is contained by nothing below it, and it is invisible to every audit that inspects your Kubernetes RBAC, your NetworkPolicies, or your ReferenceGrants — all of which were correctly configured in every scenario above.

What the Probes Cannot Catch

Be honest about what you have after running all seven: a regression suite for six known bugs and one topology assumption. Not a proof of isolation. The seventh advisory will use a mechanism none of these probes model, and the August one shipped with no workaround at all — patching was the only mitigation available.

That pushes the real decision up a level, to structure. Each option has a cost worth stating in full:

  • One Traefik per tenant. Genuinely removes the entire shared-key and cross-namespace-reference class, because there is no shared map to collide in. It also costs a load balancer per tenant — on the order of $5–$20/month each depending on provider, so 50 tenants is $250–$1,000/month in load balancers before a byte moves — plus roughly 100–200 MiB resident per idle Traefik pod. At 50 tenants that is 5–10 GiB of RAM doing nothing. Practical below ~20 high-value tenants; rarely practical above that.
  • One Traefik per trust tier. The middle path most platforms land on: shared ingress for free-tier and self-serve tenants, dedicated ingress for tenants whose blast radius you cannot accept. Costs the LB math only for the second group.
  • crossProviderNamespaces: []. Rejects every TraefikService backendRef, killing Probe 1 and Probe 2's route structurally. Watch the asymmetry: unset means all namespaces are allowed, while an empty list rejects everything. The safe value is the one that looks like a misconfiguration. The cost is real — you lose legitimate weighted and mirroring TraefikService backends.
  • providers.rest off, dashboard on its own entrypoint. Free. Do it regardless; CVE-2026-44774 required providers.rest=true to be exploitable at all.
  • An admission policy on tenant object names. Kyverno or a ValidatingAdmissionPolicy requiring tenant Route names to match ^[a-z0-9]+$ — no hyphens — makes the concatenation collision unconstructible in the namespaces you care about. This is the cheapest structural fix in the list and it defends against the next key-concatenation bug, not just this one.

None of this is a case against Traefik specifically. Every advisory here shipped with a patch, a clear write-up, and a reproduction; the same advisory feed also carries an ingress-nginx rewrite-target path traversal from July. Any proxy that multiplexes many tenants through one process and one config map is running this risk. The variable is not the vendor — it is how many tenants share one enforcement point.

The Question a Self-Hosted PaaS Actually Has to Answer

A platform that offers custom domains has decided, whether it wrote it down or not, how much of the tenant boundary rests on one ingress process getting namespace resolution right six times in a row. The advisory record for the first eight months of 2026 says that is a demanding thing to ask of any single component.

The tractable version of the question is smaller: do tenants have write access to routing objects at all? If your platform's control plane owns every Route and renders it from a validated, non-user-controlled template — tenant supplies a hostname, platform supplies the object name, namespace, filters, and backendRefs — then Probes 3, 4, 5, and 6 all become unreachable, because their precondition is an attacker who can author a Route. That is a control-plane design choice available on day one, and it costs far less than a load balancer per tenant.

Run the seven probes anyway. Then go read your own control plane and find out which of them a tenant could even attempt.


Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Routing and TLS are rendered by the control plane from validated App specs rather than hand-authored Route objects, on infrastructure you can probe yourself. Star the repo on GitHub or deploy your first app today.

Sources

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex