Feb 21, 2026·6 min read·4 visits
Traefik versions < 2.6.1 fail to normalize Fully Qualified Domain Names (FQDNs) during TLS configuration lookups. Sending a Host header like 'example.com.' causes Traefik to miss the custom security policy (like mTLS) and revert to the default policy, potentially allowing unauthenticated access.
A logic error in Traefik's TCP router allows attackers to bypass specific TLS configurations—including Mutual TLS (mTLS) requirements—simply by appending a trailing dot to the Host header. This forces the proxy to fall back to the default, often less secure, TLS configuration.
In the world of DNS, we often forget that google.com is actually google.com.. That trailing dot represents the root of the DNS hierarchy. Browsers hide it, marketing teams hate it, but to a resolver, it is the absolute truth. It is the Fully Qualified Domain Name (FQDN). Most web servers normalize this automatically, stripping the dot before processing the request to ensure example.com and example.com. are treated as the same entity.
But what happens when a modern, "cloud-native" reverse proxy like Traefik decides to take things literally? You get CVE-2022-23632. This isn't a buffer overflow or a complex heap grooming exercise. It is a fundamental disagreement between how a user types a domain and how Go's map lookups handle strings.
Imagine you have a fortress. The front door is guarded by a bouncer who checks IDs (mTLS). The window, however, just has a sign that says "Default Entry." If you walk up to the bouncer and say "I am here for the Fortress," he checks your ID. If you say "I am here for the Fortress..." (with a dramatic pause/dot), the bouncer gets confused, checks his list, doesn't see an exact string match for the name with the pause, and shrugs: "I guess you go to the Default Entry then." You just walked past the security because of a punctuation mark.
The vulnerability resides in pkg/server/router/tcp/router.go. Traefik uses a map to store TLS configurations, keyed by the hostname. When a request comes in, Traefik extracts the Host header (or SNI) and looks it up in this map to determine which security settings to apply—cipher suites, min/max TLS versions, and crucially, ClientAuth (mTLS) settings.
The logic was painfully simple: exact string matching. In Go, map["example.com"] is a completely different memory address than map["example.com."]. There was no normalization step. If your configuration defined rules for secure.internal, the map key was secure.internal.
When an attacker sends a request with Host: secure.internal., the router successfully routes the traffic (because the routing logic often handles FQDNs or wildcards differently), but the TLS configuration lookup fails. Instead of rejecting the connection, Traefik's fail-safe mechanism kicks in: it applies the DefaultTLSConfigName. If your default configuration is permissive (which it usually is to support public traffic), the attacker just downgraded the security of that specific connection to the baseline.
Let's look at the vulnerable function findTLSOptionName. It takes a map of options and the host string. Notice the lack of preprocessing on the host variable.
// Vulnerable Code (simplified)
func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string {
// Direct map lookup. If host is "example.com.", this returns nil
tlsOptions, ok := tlsOptionsForHost[host]
if ok {
return tlsOptions
}
// ... (logic to check wildcards omitted) ...
// Fallback to default if no match found
return traefiktls.DefaultTLSConfigName
}The fix introduced in version 2.6.1 is a classic "try it both ways" approach. It acknowledges that the input might be an FQDN and tries to normalize it by stripping or adding the dot to find a match.
// Patched Code in v2.6.1
func findTLSOptName(tlsOptionsForHost map[string]string, host string, fqdn bool) string {
// 1. Try exact match
tlsOptions, ok := tlsOptionsForHost[host]
if ok { return tlsOptions }
if !fqdn { return "" }
// 2. Try STRIPPING the trailing dot
if last := len(host) - 1; last >= 0 && host[last] == '.' {
tlsOptions, ok = tlsOptionsForHost[host[:last]]
if ok { return tlsOptions }
return ""
}
// 3. Try ADDING a trailing dot
tlsOptions, ok = tlsOptionsForHost[host+"."]
if ok { return tlsOptions }
return ""
}This simple change closes the gap. Now, example.com. will successfully resolve to the config for example.com.
The most dangerous scenario for this bug is an mTLS bypass. Mutual TLS is often used for internal services, where the server requires the client to present a trusted certificate. This is common in Zero Trust architectures.
The Setup:
api.secret.corp with strict mTLS required.The Attack: A standard request gets blocked:
$ curl https://api.secret.corp/admin
> 400 Bad Request (No Client Certificate)The exploit request adds the dot:
$ curl -k --header "Host: api.secret.corp." https://api.secret.corp/adminWhat happens internally:
api.secret.corp in the ClientHello (standard TLS).api.secret.corp. (from the header/SNI logic depending on setup).DefaultTLSConfig (no mTLS).api.secret.corp. to the correct backend service because the routing rules often treat FQDNs leniently or use regex.This vulnerability turns robust security configurations into security theater. The impact is technically rated as High (7.4), but functionally, it can be Critical depending on your architecture.
If you rely on Traefik as an Ingress Controller for Kubernetes, and you use it to terminate mTLS for sensitive pods, this bug renders that protection null and void against a trivial manipulation. It also allows attackers to bypass specific cipher suite restrictions. For example, if secure.bank.com requires TLS 1.3, but the default config allows TLS 1.0, an attacker could force a protocol downgrade by using the FQDN trick, potentially opening the door to other cryptographic attacks.
The real kicker? This leaves almost no trace in standard logs other than a slightly weird Host header, which most analysts would ignore as a typo or browser quirk.
The remediation is straightforward: Upgrade to Traefik v2.6.1. The patch handles the FQDN normalization logic internally, ensuring that host and host. share the same security context.
Emergency Workaround:
If you cannot upgrade immediately (perhaps you are stuck on an old vendor fork), you must explicitly define the FQDN in your router rules. If your rule is Host(\example.com`)`, you need to change it to:
rule = "Host(`example.com`) || Host(`example.com.`)"However, this is tedious and error-prone. The upgrade is the only scalable fix. This serves as a reminder to developers: inputs are messy, humans are creative, and DNS is a ancient beast that will bite you if you don't respect the dot.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Traefik Traefik Labs | < 2.6.1 | 2.6.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-295 |
| Attack Vector | Network |
| CVSS v3.1 | 7.4 (High) |
| EPSS Score | 0.00557 (Low) |
| Impact | Security Bypass (mTLS/TLS Options) |
| Exploit Status | PoC Available |
Improper Certificate Validation
A property shadowing vulnerability exists in protobufjs where schema-derived names can collide with and overwrite runtime-critical internal helper properties. This issue leads to uncaught runtime exceptions and crash-based Denial of Service.
An integer truncation vulnerability (CWE-197) exists in SQLite before version 3.50.2 during the processing of aggregate queries with more than 32,767 distinct column references. This causes an internal 32-bit counter to truncate to a signed 16-bit integer, producing negative values that cause out-of-bounds heap operations in release builds.
An integer overflow vulnerability in the Windows kernel-mode HTTP driver (HTTP.sys) allows an unauthenticated remote attacker to execute arbitrary code with kernel privileges or cause a Denial of Service via a specially crafted sequence of HTTP request headers.
A memory corruption vulnerability exists in the FTS5 (Full-Text Search 5) extension of SQLite prior to version 3.53.2. An attacker can construct a malicious database file containing corrupt FTS5 page data. Querying this database triggers out-of-bounds reads and heap-based buffer overflows, potentially causing a crash or arbitrary code execution.
A mass assignment vulnerability (CWE-915) in n8n's self-service settings API endpoint (PATCH /me/settings) allows authenticated Single Sign-On (SSO) users to disable SSO enforcement for their accounts by injecting administrative parameters. This bypasses organizational identity provider controls and multi-factor authentication (MFA).
CVE-2026-55699 (also identified as GHSA-4gxm-v5v7-fqc4) is a critical path traversal and arbitrary directory deletion vulnerability in the pnpm package manager. The issue exists because the manifest validation process fails to prevent relative path segments within the package 'bin' keys. When a malicious package containing structured path traversal markers is globally installed and later manipulated, pnpm resolves the target paths through path.join() and passes the resolved paths to a recursive deletion function, resulting in arbitrary directory removal.