GHSA-62FC-8686-HFMQ
Vulnerability from github – Published: 2026-08-06 16:38 – Updated: 2026-08-06 16:38Summary
There is a medium severity vulnerability in Traefik's Kubernetes CRD provider. When providers.kubernetesCRD.allowCrossNamespace is disabled — the default — cross-namespace @kubernetescrd references are rejected for middlewares, TLS options and HTTP/TCP ServersTransports, but the same restriction was not applied to TraefikService backend references resolved by the service resolver. A tenant confined by RBAC to a single namespace can therefore bind its own router to a TraefikService owned by another namespace and expose or reroute that namespace's backend, defeating the namespace isolation allowCrossNamespace=false is meant to enforce. Traefik v2 releases and the unmaintained v3 minor lines below v3.6 are affected and will not receive a patch on their own line; the remedy for those users is upgrading to a maintained, patched release.
Patches
- https://github.com/traefik/traefik/releases/tag/v2.11.54
- https://github.com/traefik/traefik/releases/tag/v3.6.25
- https://github.com/traefik/traefik/releases/tag/v3.7.10
For more information
If you have any questions or comments about this advisory, please open an issue.
Original Description ### Summary When `providers.kubernetesCRD.allowCrossNamespace=false` (the default), Traefik correctly rejects cross-namespace `@kubernetescrd` references for middlewares, TLS options, and HTTP/TCP `ServersTransport`, but it does not apply the same restriction to service (`TraefikService`) backendRefs. As a result, a Kubernetes tenant who is confined by RBAC to their own namespace can bind their own router to a `TraefikService` owned by another namespace simply by referencing it as `-@kubernetescrd`, defeating the namespace-isolation boundary that `allowCrossNamespace=false` is meant to enforce. This is the service-resolver sibling of the cross-namespace isolation family that Traefik has been fixing one resolver at a time (`df00d82f` / CVE-2026-41174 for Chain middlewares, and `67501cbe` for TCP `ServersTransport`, which shipped in v3.7.7 only four days before the analyzed commit). The `TraefikService` resolver in `configBuilder.nameAndService` was never given the guard its sibling resolvers received. ### Details ### Root cause `nameAndService` only performs the same-namespace check (`isNamespaceAllowed`) inside the branch that handles names without an `@` separator. For names that contain an `@` separator (that is, `@kubernetescrd` cross-namespace references) it applies only the `crossProviderNamespaces` allowlist check, and that check returns `true` by default because a `nil` allowlist means "unrestricted". It never applies the `!allowCrossNamespace && strings.HasSuffix(name, "@kubernetescrd")` rejection that the sibling resolvers all apply, so `allowCrossNamespace=false` is effectively never consulted for `@kubernetescrd` service references. ### Vulnerable code// pkg/provider/kubernetes/crd/kubernetes_http.go:662-695 — nameAndService (VULNERABLE)
func (c configBuilder) nameAndService(ctx context.Context, parentNamespace string, service traefikv1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
svcCtx := log.Ctx(ctx).With().Str(logs.ServiceName, service.Name).Logger().WithContext(ctx)
if !strings.Contains(service.Name, providerNamespaceSeparator) { // 665: only names WITHOUT "@"
service = *service.DeepCopy()
service.Namespace = namespaceOrParentNamespace(service.Namespace, parentNamespace)
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, service.Namespace) { // 669
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", ...)
}
}
// 674: for "@"-names, the ONLY gate is crossProviderNamespaces, which defaults to allow-all (nil).
if !isCrossProviderNamespaceAllowed(c.crossProviderNamespaces, parentNamespace) && strings.Contains(service.Name, providerNamespaceSeparator) {
return "", nil, fmt.Errorf("service %q reference is not allowed: ...", service.Name)
}
// ^-- MISSING: no `!c.allowCrossNamespace && strings.HasSuffix(service.Name, "@"+ProviderName)` rejection.
switch service.Kind {
case "TraefikService":
return fullServiceName(svcCtx, service, intstr.FromInt(0)), nil, nil // 690: returns the cross-namespace reference
...
}
}
For comparison, the sibling resolver used for middleware and TLS references does carry the guard:
// pkg/provider/kubernetes/crd/kubernetes.go:1653-1668 — resolveReference (CORRECT)
func resolveReference(ctx context.Context, parentNs, ns, name string, crossProviderNamespaces []string, allowCrossNamespace bool) (string, error) {
if strings.Contains(name, providerNamespaceSeparator) {
if !allowCrossNamespace && strings.HasSuffix(name, providerNamespaceSeparator+ProviderName) {
return "", errors.New("when allowCrossNamespace is disabled, @kubernetescrd references are disallowed") // 1656 — THE GUARD
}
...
}
...
}
The same guard is also present at `pkg/provider/kubernetes/crd/kubernetes_http.go:500` (`makeServersTransportKey`, HTTP) and `pkg/provider/kubernetes/crd/kubernetes_tcp.go:316` (`makeTCPServersTransportKey`, TCP, added by commit `67501cbe`). Only the service resolver `nameAndService` lacks it.
### Data flow
An `IngressRoute` created by a tenant in namespace `attacker` declares a route service `{ name: "victim-backend@kubernetescrd", kind: TraefikService }`; the tenant controls this reference string. In `nameAndService`, because the name contains `@`, the same-namespace check at line 669 is skipped, and `isCrossProviderNamespaceAllowed(nil, "attacker")` returns `true` under the default `nil` allowlist, so no rejection fires. `fullServiceName` then resolves the reference to the victim namespace's `TraefikService`, and the attacker's HTTP router is generated and bound to the victim's backend. At runtime the attacker's `Host(...)` route forwards to namespace `victim`'s backend pods.
### Default reachability
`AllowCrossNamespace` defaults to `false` (`pkg/provider/kubernetes/crd/kubernetes.go:57`, never set to `true` by any `SetDefaults`), so the isolation this bug bypasses is on by default. `CrossProviderNamespaces` defaults to `nil`, and `isCrossProviderNamespaceAllowed` returns `true` for a `nil` allowlist (`pkg/provider/kubernetes/crd/kubernetes.go:1645-1651`), so the only check `nameAndService` applies to an `@`-name is inert by default. The attacker needs only namespace-scoped RBAC to create an `IngressRoute` or `TraefikService` in their own namespace (the standard hard-multi-tenant Traefik setup) and knowledge of the target `TraefikService`'s namespace and name.
### PoC
A table-style harness was added to the CRD provider package. It defines a victim `TraefikService` (`backend` in namespace `victim`, backed by a real endpoint) and an attacker `IngressRoute` (namespace `attacker`) that references it via `victim-backend@kubernetescrd`, plus a control route that references a victim `Middleware` via `victim-mw@kubernetescrd`. The control route is given a valid local service so that the only reason it could be dropped is the cross-namespace middleware guard. The provider is run with `AllowCrossNamespace: false` and `CrossProviderNamespaces: nil` (both defaults).
$ go test -run TestPoC_CrossNamespaceServiceBypass ./pkg/provider/kubernetes/crd/ -v
HTTP routers: [attacker-attacker-svc-route-7df4381938699bd21215]
HTTP services: [victim-whoami-victim-80 victim-backend]
CONTROL OK: cross-ns MIDDLEWARE ref (victim-mw@kubernetescrd) rejected -> router dropped
BYPASS CONFIRMED: attacker router bound to cross-ns service "victim-backend" despite AllowCrossNamespace=false
The control route (middleware reference) is dropped even though it has a valid local service, confirming that the isolation control is active for middlewares; the service route survives and its `Service` field resolves to `victim-backend`, with the victim's services pulled into the generated configuration and reachable through the attacker's router.
The harness also runs two corroborating cases. With `AllowCrossNamespace=false` and `CrossProviderNamespaces=["someotherns"]` (an allowlist that excludes the attacker), the service reference is blocked, which proves that the only gate ever applied to an `@kubernetescrd` service name is `crossProviderNamespaces` (inert by default) and that `allowCrossNamespace=false` is never consulted. With `AllowCrossNamespace=true`, both the service and middleware references are accepted, as expected when isolation is intentionally disabled.
### Impact
In a multi-tenant cluster relying on `allowCrossNamespace=false` for namespace isolation, a tenant confined to their own namespace can attach their own router (their own `Host` rule and entrypoint) to another tenant's `TraefikService` backend, exposing an otherwise internal-only service on the data plane under the attacker's hostname, and can route or mirror traffic to another namespace's backend that they should not be able to reference.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.11.53"
},
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.11.54"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.6.24"
},
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik/v3"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "3.6.25"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.7.9"
},
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik/v3"
},
"ranges": [
{
"events": [
{
"introduced": "3.7.0"
},
{
"fixed": "3.7.10"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.7.34"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-71325"
],
"database_specific": {
"cwe_ids": [
"CWE-653",
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-06T16:38:37Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nThere is a medium severity vulnerability in Traefik\u0027s Kubernetes CRD provider. When `providers.kubernetesCRD.allowCrossNamespace` is disabled \u2014 the default \u2014 cross-namespace `@kubernetescrd` references are rejected for middlewares, TLS options and HTTP/TCP ServersTransports, but the same restriction was not applied to `TraefikService` backend references resolved by the service resolver. A tenant confined by RBAC to a single namespace can therefore bind its own router to a `TraefikService` owned by another namespace and expose or reroute that namespace\u0027s backend, defeating the namespace isolation `allowCrossNamespace=false` is meant to enforce. Traefik v2 releases and the unmaintained v3 minor lines below v3.6 are affected and will not receive a patch on their own line; the remedy for those users is upgrading to a maintained, patched release.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v2.11.54\n- https://github.com/traefik/traefik/releases/tag/v3.6.25\n- https://github.com/traefik/traefik/releases/tag/v3.7.10\n\n## For more information\n\nIf you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).\n\n\u003cdetails\u003e\n\u003csummary\u003eOriginal Description\u003c/summary\u003e\n\n### Summary\nWhen `providers.kubernetesCRD.allowCrossNamespace=false` (the default), Traefik correctly rejects cross-namespace `@kubernetescrd` references for middlewares, TLS options, and HTTP/TCP `ServersTransport`, but it does not apply the same restriction to service (`TraefikService`) backendRefs. As a result, a Kubernetes tenant who is confined by RBAC to their own namespace can bind their own router to a `TraefikService` owned by another namespace simply by referencing it as `\u003cvictim-namespace\u003e-\u003cname\u003e@kubernetescrd`, defeating the namespace-isolation boundary that `allowCrossNamespace=false` is meant to enforce.\n\nThis is the service-resolver sibling of the cross-namespace isolation family that Traefik has been fixing one resolver at a time (`df00d82f` / CVE-2026-41174 for Chain middlewares, and `67501cbe` for TCP `ServersTransport`, which shipped in v3.7.7 only four days before the analyzed commit). The `TraefikService` resolver in `configBuilder.nameAndService` was never given the guard its sibling resolvers received.\n\n### Details\n### Root cause\n\n`nameAndService` only performs the same-namespace check (`isNamespaceAllowed`) inside the branch that handles names without an `@` separator. For names that contain an `@` separator (that is, `@kubernetescrd` cross-namespace references) it applies only the `crossProviderNamespaces` allowlist check, and that check returns `true` by default because a `nil` allowlist means \"unrestricted\". It never applies the `!allowCrossNamespace \u0026\u0026 strings.HasSuffix(name, \"@kubernetescrd\")` rejection that the sibling resolvers all apply, so `allowCrossNamespace=false` is effectively never consulted for `@kubernetescrd` service references.\n\n### Vulnerable code\n\n```go\n// pkg/provider/kubernetes/crd/kubernetes_http.go:662-695 \u2014 nameAndService (VULNERABLE)\nfunc (c configBuilder) nameAndService(ctx context.Context, parentNamespace string, service traefikv1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {\n\tsvcCtx := log.Ctx(ctx).With().Str(logs.ServiceName, service.Name).Logger().WithContext(ctx)\n\n\tif !strings.Contains(service.Name, providerNamespaceSeparator) { // 665: only names WITHOUT \"@\"\n\t\tservice = *service.DeepCopy()\n\t\tservice.Namespace = namespaceOrParentNamespace(service.Namespace, parentNamespace)\n\t\tif !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, service.Namespace) { // 669\n\t\t\treturn \"\", nil, fmt.Errorf(\"service %s/%s not in the parent resource namespace %s\", ...)\n\t\t}\n\t}\n\n\t// 674: for \"@\"-names, the ONLY gate is crossProviderNamespaces, which defaults to allow-all (nil).\n\tif !isCrossProviderNamespaceAllowed(c.crossProviderNamespaces, parentNamespace) \u0026\u0026 strings.Contains(service.Name, providerNamespaceSeparator) {\n\t\treturn \"\", nil, fmt.Errorf(\"service %q reference is not allowed: ...\", service.Name)\n\t}\n\t// ^-- MISSING: no `!c.allowCrossNamespace \u0026\u0026 strings.HasSuffix(service.Name, \"@\"+ProviderName)` rejection.\n\n\tswitch service.Kind {\n\tcase \"TraefikService\":\n\t\treturn fullServiceName(svcCtx, service, intstr.FromInt(0)), nil, nil // 690: returns the cross-namespace reference\n\t...\n\t}\n}\n```\n\nFor comparison, the sibling resolver used for middleware and TLS references does carry the guard:\n\n```go\n// pkg/provider/kubernetes/crd/kubernetes.go:1653-1668 \u2014 resolveReference (CORRECT)\nfunc resolveReference(ctx context.Context, parentNs, ns, name string, crossProviderNamespaces []string, allowCrossNamespace bool) (string, error) {\n\tif strings.Contains(name, providerNamespaceSeparator) {\n\t\tif !allowCrossNamespace \u0026\u0026 strings.HasSuffix(name, providerNamespaceSeparator+ProviderName) {\n\t\t\treturn \"\", errors.New(\"when allowCrossNamespace is disabled, @kubernetescrd references are disallowed\") // 1656 \u2014 THE GUARD\n\t\t}\n\t\t...\n\t}\n\t...\n}\n```\n\nThe same guard is also present at `pkg/provider/kubernetes/crd/kubernetes_http.go:500` (`makeServersTransportKey`, HTTP) and `pkg/provider/kubernetes/crd/kubernetes_tcp.go:316` (`makeTCPServersTransportKey`, TCP, added by commit `67501cbe`). Only the service resolver `nameAndService` lacks it.\n\n### Data flow\n\nAn `IngressRoute` created by a tenant in namespace `attacker` declares a route service `{ name: \"victim-backend@kubernetescrd\", kind: TraefikService }`; the tenant controls this reference string. In `nameAndService`, because the name contains `@`, the same-namespace check at line 669 is skipped, and `isCrossProviderNamespaceAllowed(nil, \"attacker\")` returns `true` under the default `nil` allowlist, so no rejection fires. `fullServiceName` then resolves the reference to the victim namespace\u0027s `TraefikService`, and the attacker\u0027s HTTP router is generated and bound to the victim\u0027s backend. At runtime the attacker\u0027s `Host(...)` route forwards to namespace `victim`\u0027s backend pods.\n\n### Default reachability\n\n`AllowCrossNamespace` defaults to `false` (`pkg/provider/kubernetes/crd/kubernetes.go:57`, never set to `true` by any `SetDefaults`), so the isolation this bug bypasses is on by default. `CrossProviderNamespaces` defaults to `nil`, and `isCrossProviderNamespaceAllowed` returns `true` for a `nil` allowlist (`pkg/provider/kubernetes/crd/kubernetes.go:1645-1651`), so the only check `nameAndService` applies to an `@`-name is inert by default. The attacker needs only namespace-scoped RBAC to create an `IngressRoute` or `TraefikService` in their own namespace (the standard hard-multi-tenant Traefik setup) and knowledge of the target `TraefikService`\u0027s namespace and name.\n\n### PoC\nA table-style harness was added to the CRD provider package. It defines a victim `TraefikService` (`backend` in namespace `victim`, backed by a real endpoint) and an attacker `IngressRoute` (namespace `attacker`) that references it via `victim-backend@kubernetescrd`, plus a control route that references a victim `Middleware` via `victim-mw@kubernetescrd`. The control route is given a valid local service so that the only reason it could be dropped is the cross-namespace middleware guard. The provider is run with `AllowCrossNamespace: false` and `CrossProviderNamespaces: nil` (both defaults).\n\n```\n$ go test -run TestPoC_CrossNamespaceServiceBypass ./pkg/provider/kubernetes/crd/ -v\n\nHTTP routers: [attacker-attacker-svc-route-7df4381938699bd21215]\nHTTP services: [victim-whoami-victim-80 victim-backend]\nCONTROL OK: cross-ns MIDDLEWARE ref (victim-mw@kubernetescrd) rejected -\u003e router dropped\nBYPASS CONFIRMED: attacker router bound to cross-ns service \"victim-backend\" despite AllowCrossNamespace=false\n```\n\nThe control route (middleware reference) is dropped even though it has a valid local service, confirming that the isolation control is active for middlewares; the service route survives and its `Service` field resolves to `victim-backend`, with the victim\u0027s services pulled into the generated configuration and reachable through the attacker\u0027s router.\n\nThe harness also runs two corroborating cases. With `AllowCrossNamespace=false` and `CrossProviderNamespaces=[\"someotherns\"]` (an allowlist that excludes the attacker), the service reference is blocked, which proves that the only gate ever applied to an `@kubernetescrd` service name is `crossProviderNamespaces` (inert by default) and that `allowCrossNamespace=false` is never consulted. With `AllowCrossNamespace=true`, both the service and middleware references are accepted, as expected when isolation is intentionally disabled.\n\n### Impact\nIn a multi-tenant cluster relying on `allowCrossNamespace=false` for namespace isolation, a tenant confined to their own namespace can attach their own router (their own `Host` rule and entrypoint) to another tenant\u0027s `TraefikService` backend, exposing an otherwise internal-only service on the data plane under the attacker\u0027s hostname, and can route or mirror traffic to another namespace\u0027s backend that they should not be able to reference.\n\n\n\u003c/details\u003e\n---",
"id": "GHSA-62fc-8686-hfmq",
"modified": "2026-08-06T16:38:37Z",
"published": "2026-08-06T16:38:37Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/security/advisories/GHSA-62fc-8686-hfmq"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/commit/65ebf4b47fbdc33e3856803a5844a404e094d52d"
},
{
"type": "PACKAGE",
"url": "https://github.com/traefik/traefik"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/releases/tag/v2.11.54"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/releases/tag/v3.6.25"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/releases/tag/v3.7.10"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Traefik: `allowCrossNamespace=false` bypass via `@kubernetescrd` TraefikService backendRef"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.