OpenTelemetry Python: Quadratic tracestate Parsing Lets One Request Stall a Server
SecDim Security Research reported CVE-2026-114603, a ReDoS vulnerability in the OpenTelemetry Python API, on 2026-10-03 and the vulnerability was subsequently fixed in opentelemetry-api 1.45.1. An unauthenticated request carrying an oversized tracestate header can keep a worker busy for seconds and, under an asyncio server, stall every other connection while it is parsed.
The vulnerability
OpenTelemetry passes trace information between services in HTTP headers. One of them, tracestate, is a list of key=value entries separated by commas. The Python library reads this header from every incoming request.
To break the list into entries, the library splits the header with a regular expression that allows any amount of spaces or tabs around each comma:
_delimiter_pattern = re.compile(r"[ \t]*,[ \t]*")
members = re.split(_delimiter_pattern, header)
The regular expression checks for the comma from every position in the header. At each position inside a long run of spaces, it first reads all the remaining spaces, only to find that no comma follows, and then gives up and starts again one character later. A run of 100,000 spaces therefore takes up to about 100,000 × 100,000 steps instead of 100,000. This means doubling the header makes the work four times larger.
The library does limit a tracestate to 32 entries, but only after the split has finished, and no limit is placed on the length of the header itself. So the check never gets a chance to help.
Proof of concept
The script below times how long the library takes to parse a tracestate header made of one valid entry, followed by a long run of spaces and a final character. The header contains no comma. It repeats the test with a longer run each time, doubling it. A parser that scales linearly would take twice as long on each run.
import logging
import time
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
logging.disable(logging.CRITICAL)
TRACEPARENT = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
propagator = TraceContextTextMapPropagator()
for n in (10_000, 20_000, 40_000, 80_000):
carrier = {"traceparent": TRACEPARENT, "tracestate": "a=b" + " " * n + "x"}
start = time.perf_counter()
propagator.extract(carrier)
print(f"{n:>6} characters {time.perf_counter() - start:5.2f} s")
10000 characters 0.06 s
20000 characters 0.26 s
40000 characters 1.04 s
80000 characters 4.13 s
Every time the header doubles in size, the parsing time quadruples, which confirms the quadratic cost. The exact timings depend on the CPU, but the factor of four does not. Using tabs instead of spaces makes the same header about 1.6 times slower.
We also tested this weakness on a minimal web application using OpenTelemetry’s ASGI middleware, running on uvicorn with the httptools parser. One request with a 100,000-space tracestate took 6.4 seconds to complete. A separate, harmless /ping request sent half a second later had to wait 5.9 seconds for it to finish. With 100,000 tabs, the wait was 11.9 seconds.
Impact
Exploitation requires a server that accepts a tracestate header larger than roughly 16 to 30 KB. No authentication is needed. In our testing, uvicorn with the httptools parser (the default with uvicorn[standard]) accepted tracestate headers up to 20 MB, and waitress accepted up to 256 KB. gunicorn rejects headers of 8190 bytes or more, and uvicorn --http h11 rejects headers above 16 KiB, before they reach the application.
Where the header is accepted, a request of about 100 KB costs 6 to 12 seconds of CPU on one core. Under an asyncio server the event loop is blocked for that time, so every other connection waits, and repeating the request keeps the service unavailable.
Advisory timeline
- 2026-10-03: SecDim Security Research reports the vulnerability to the OpenTelemetry Python maintainers. Day 0 of a 90-day responsible disclosure window.
- 2026-10-05: The maintainer reviews the report and finds the same flaw in
opentelemetry-java, which is tracked as a separate advisory. - 2026-10-06: The fix lands on
main(pull request #5746) and is cherry-picked to therelease/v1.45.x-0.66bxbranch (pull request #5749). - 2026-10-06:
opentelemetry-api1.45.1 is released with the fix. - 2026-10-07: The
opentelemetry-javafix lands onmain(pull request #8914). - 2026-10-09:
opentelemetry-java1.67.0 is released with its fix. - 2026-10-12: The advisory is published.
Fix status
The fix shipped in opentelemetry-api 1.45.1. TraceState.from_header now splits the header on commas and strips optional whitespace from each member, which is linear in the input size. In addition, TraceContextTextMapPropagator.extract discards a tracestate whose combined length exceeds 8192 characters. Users running an affected version should upgrade to 1.45.1 or later. If you cannot upgrade yet, limit request header size at the server or gateway.
The same flaw existed in opentelemetry-java and is tracked separately in GHSA-ccrj-gw8r-854m. It is fixed in io.opentelemetry:opentelemetry-api 1.67.0.
References
- CVE-2026-114603: Unbounded tracestate parsing in opentelemetry-api · Advisory · open-telemetry/opentelemetry-python · GitHub
- Java advisory: Quadratic CPU consumption in W3C tracestate parsing · Advisory · open-telemetry/opentelemetry-java · GitHub
- CVSS 3.1: 5.3, medium severity (
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) - CWE-400, Uncontrolled Resource Consumption, and CWE-1333, Inefficient Regular Expression Complexity
- Affected:
opentelemetry-api<= 1.45.0(confirmed on 1.44.0 and 1.45.0; the pattern is present back to 1.0.0, intermediate releases not checked)
Find, hack and fix this class of vulnerability in: ReDoS sandbox
