DEV Community

Lav Kumar Vishwakarma
Lav Kumar Vishwakarma

Posted on Originally published at lavkumar.dev on

Two Traefik Bugs, One Broken Boundary

Traefik bugs

How a router-name collision made another tenant’s application disappear, and a cached TLS option bypassed namespace restrictions.

A few weeks ago, while digging through Traefik’s Kubernetes Ingress NGINX provider, I came across something that didn’t look quite right.

The code was generating router names by joining different Kubernetes resource identifiers with hyphens.

Nothing unusual there. I’ve written plenty of similar code myself.

But then I had a thought: What happens if those identifiers already contain hyphens?

That question eventually led me to a cross-namespace routing issue that could make another tenant’s application disappear from Traefik’s configuration.

While investigating the provider further, I found another issue involving cached TLS options and cross-namespace authorization.

Two different bugs, both involving Kubernetes namespace isolation, both reported to Traefik.

Here’s what I found, how I reproduced the problems, and what happened after reporting them.

Discovery #1: How one Kubernetes tenant could make another tenant’s route disappear

GHSA-vm53-8m79-wq7m

Researcher-assessed severity: CVSS 8.5 (High)

Let’s say you’re running a Kubernetes cluster shared by multiple teams.

Each team has its own namespace, services, and Ingress resources.

Naturally, you’d expect a team to be unable to interfere with another team’s routing just by creating an Ingress in its own namespace.

But I discovered a way this could happen.

It started with a simple naming collision

Traefik’s Kubernetes Ingress NGINX provider converts Ingress resources into internal routers.

In the version I examined, it generated router identifiers using the namespace, Ingress name, and rule/path indices.

The relevant code was essentially:

routerKey = provider.Normalize(
    fmt.Sprintf(
        "%s-%s-rule-%d-path-%d",
        loc.Namespace,
        loc.IngressName,
        loc.RuleIndex,
        loc.LocationIndex,
    ),
)
Enter fullscreen mode Exit fullscreen mode

Look at these two resources:

Namespace Ingress name
team dev-web
team-dev web

Although they belong to completely different namespaces, both produce:

team-dev-web-rule-0-path-0
Enter fullscreen mode Exit fullscreen mode

The separator doesn’t preserve where the namespace ends and the Ingress name begins.

That alone might seem like an ordinary naming bug.

But Traefik stores its generated routers in a shared map, and the generated name is the key.

When two routers have the same key, one replaces the other.

No collision check. No warning that another namespace’s configuration has been overwritten.

And that’s where the security implications begin.

Reproducing it with the official Traefik image

I didn’t want to stop at identifying a theoretical collision.

I wanted to see what happened to actual traffic.

So I created a local Kubernetes environment with two namespaces, two independent backend services, and two Ingress resources.

I used the official traefik:v3.7.13 image for testing.

Both Ingress resources were valid. Each had its own hostname and pointed to its own healthy backend.

I expected both hostnames to work.

Instead, I got:

attacker.local -> HTTP 200
victim.local -> HTTP 404
Enter fullscreen mode Exit fullscreen mode

The victim’s Ingress hadn’t been deleted.

The backend wasn’t down.

The route had simply disappeared from Traefik’s generated configuration.

Even more interesting, the outcome could change across configuration rebuilds because of Go map iteration order.

One of the two routes was always lost, but which one survived wasn’t necessarily deterministic.

Imagine debugging that in a production cluster. Everything in Kubernetes looks correct, yet an application’s route mysteriously disappears.

But could this go beyond denial of service?

This was the more important question.

An Ingress router isn’t responsible only for sending requests to a backend.

It can also have authentication, IP allow-listing, and rate-limiting middlewares attached to it.

When that router is overwritten, its middleware configuration disappears with it.

I also tested a scenario where the attacker-controlled Ingress declared the victim’s hostname.

When the attacker’s router was the surviving one, requests intended for the victim were directed to the attacker’s backend.

The result:

Host: victim.local

Name: ATTACKER-BACKEND-team-dev
HTTP 200
Enter fullscreen mode Exit fullscreen mode

That demonstrated a potential cross-namespace route takeover, not merely a missing page.

The attack still required suitable resource-name combinations and the attacker-controlled router to survive the collision. It wasn’t a universal takeover of arbitrary Kubernetes namespaces.

But a tenant with permission to create an Ingress only in its own namespace could interfere with routing belonging to another tenant.

To me, that was the most important part of the discovery.

What happened after I reported it?

I submitted the finding to Traefik through its private security reporting process, including the reproduction steps and demonstrated impact.

My report assessed the issue as High severity, with a CVSS score of 8.5.

The maintainers ultimately closed the security report and handled the underlying issue as a regular bug rather than publishing it as a security vulnerability.

The fix was released on October 28, 2026.

Although the report wasn’t accepted as a security advisory, I’m glad the underlying problem was addressed.

The technical lesson remains valuable: an internal identifier shouldn’t allow two resources from different tenants to overwrite each other.

And a Kubernetes namespace boundary is only useful if the software translating Kubernetes resources into runtime configuration preserves that boundary.

Discovery #2: A TLS cache that skipped the security check

GHSA-28hp-c82p-8vqj

Official severity: Low (CVSS 4.0 score 2.1)

The second finding was a different kind of issue.

This time, the problem wasn’t how Traefik named an object.

It was how Traefik reused one.

The feature was working as intended… initially

Traefik supports the nginx.ingress.kubernetes.io/auth-tls-secret annotation, which allows an Ingress to reference a Kubernetes Secret containing a certificate authority used for mutual TLS client authentication.

Normally, Traefik restricts cross-namespace access.

The relevant setting is:

allowCrossNamespaceResources = false
Enter fullscreen mode Exit fullscreen mode

That’s the default.

An Ingress in one namespace shouldn’t be allowed to use another namespace’s CA Secret unless cross-namespace access is explicitly enabled.

The provider did have a check for this.

But the interesting part was when that check ran.

What happens when the TLS option already exists?

Traefik caches and reuses generated TLS options.

That’s a sensible optimization. If multiple resources need equivalent configuration, there’s no reason to rebuild everything unnecessarily.

However, I found that the cross-namespace authorization check happened when Traefik initially built a TLS option.

When the option was already present in the shared cache, a subsequent Ingress could reuse it without going through the same namespace authorization check.

Essentially:

First Ingress
    |
    v
Check namespace permissions
    |
    v
Build TLS option
    |
    v
Store in shared cache

Later Ingress
    |
    v
TLS option already cached?
    |
    v
Reuse existing option
    |
    v
Namespace check skipped
Enter fullscreen mode Exit fullscreen mode

The issue was subtle because the original authorization logic existed.

It just wasn’t applied on every relevant path through the code.

Why that matters

Suppose an Ingress in Namespace A legitimately references a CA Secret.

Traefik builds the TLS option and stores it.

Later, someone with permission to create an Ingress in Namespace B references the same CA Secret.

Even with cross-namespace resources disabled, the existing cached option could be reused.

That meant the second namespace could use a CA configuration it wasn’t supposed to reference.

The issue also allowed limited information disclosure about whether the referenced CA Secret existed and was already in use.

It did not disclose the Secret’s contents or private keys.

This is why the official advisory classifies the issue as Low severity rather than High.

Still, I find this kind of bug particularly interesting because it points to a broader engineering problem.

Authorization is not a property of a cached object. It’s a decision about who is allowed to access that object.

Just because something was authorized for one requester doesn’t mean it’s authorized for everyone who references it later.

I’ve worked extensively with backend systems, caching, and distributed architectures, and this is exactly the sort of edge case that can be overlooked when optimizing code.

A cache hit feels harmless.

Until it skips a security decision.

Disclosure and official acknowledgement

I reported this issue to Traefik, and the maintainers investigated and addressed it.

The advisory was officially published on October 16, 2026 , under:

GHSA-28hp-c82p-8vqj

The affected versions were v3.7.11 through v3.7.14, and the fix was released in Traefik v3.7.15.

The published advisory credits me as the reporter.

A CVE identifier has been requested and is currently pending assignment. I’ll update this article once it’s assigned.

The interesting connection between these two findings

After investigating both issues, something stood out to me.

The vulnerabilities were technically different, but they shared a common theme.

In the first finding, Traefik treated two different resources as if they had the same identity.

In the second, Traefik reused an already-authorized configuration without checking whether the next resource was authorized to use it.

One involved generated names.

The other involved caching.

Neither required some exotic exploitation technique.

They came down to fairly ordinary backend implementation decisions.

And that’s exactly why I think they’re worth studying.

In a single-tenant application, these choices might not cause much trouble.

But in a multi-tenant system, a small mistake in how objects are identified, stored, or reused can cross a boundary between customers or teams.

That boundary needs to survive every layer of the implementation, not just the Kubernetes RBAC configuration.

What I’d look for in similar systems

These discoveries reinforced a few things I now pay particular attention to when reviewing shared infrastructure.

Generated identifiers: If a system builds unique keys by concatenating user-controlled values, can two different inputs produce the same key?

Shared configuration maps: Can one tenant’s resource silently overwrite another tenant’s entry?

Caching and authorization: Does the authorization check still happen when the requested object comes from a cache?

Security controls attached to generated resources: What happens to authentication and rate limiting when a router or configuration object gets replaced?

Non-deterministic processing: Could the order in which configuration is generated change the effective routing or security behavior?

None of these questions is specific to Traefik.

They’re equally relevant to API gateways, Kubernetes operators, service meshes, multi-tenant platforms, and ordinary backend applications.

Disclosure timeline and current status

Discovery Outcome
Router-name collision Reported privately, addressed as a regular bug, fix released October 28, 2026
TLS-option authorization bypass Published as GHSA-28hp-c82p-8vqj on October 16, 2026; fixed in v3.7.15

For clarity, my original 8.5 severity assessment belongs to the first report and does not represent an official Traefik security rating.

The first security report was closed without a public advisory or CVE assignment.

For the second, publicly acknowledged vulnerability, a CVE has been requested and is awaiting assignment.

I appreciate the Traefik maintainers for reviewing the reports and addressing the underlying issues, even though the two findings went through different disclosure outcomes.

Final thoughts

One thing I like about security research is that you don’t always need to find a complicated vulnerability to uncover an important problem.

Sometimes it’s a string concatenation.

Sometimes it’s a cache lookup.

Sometimes it’s a security check that exists in the code but doesn’t execute in the path you expected.

The difficult part is often figuring out whether that small implementation detail actually changes the security boundary.

For me, these two Traefik findings were a good reminder of something I’ve learned from building distributed systems:

Isolation isn’t guaranteed just because you’ve separated resources at the infrastructure level. Every component that processes those resources has to maintain the separation.

And sometimes, the best place to start looking is the most ordinary-looking code.


References

About the author

I’m Lav Kumar Vishwakarma, a backend engineer and independent security researcher with over 10 years of experience building distributed systems. My research focuses on cloud infrastructure, application security, AI agents, and MCP security.

You can find more of my engineering work and security research at lavkumar.dev.

Top comments (0)