Skip to content

Bump ddtrace from 2.19.0 to 4.15.6 - #153

Open
superbot-helper wants to merge 1 commit into
calpaterson:mainfrom
superbot-helper:security/bump-ddtrace-4.15.6
Open

superbot-helper wants to merge 1 commit into
calpaterson:mainfrom
superbot-helper:security/bump-ddtrace-4.15.6

Conversation

@superbot-helper

Copy link
Copy Markdown

Updates ddtrace from 2.19.0 to 4.15.6.

Fixes GHSA-3633-r77q-h3v3 and CVE-2026-116588 — one query string holds the request worker for 12.817 seconds: a 60,000-byte query string costs 12.817 s (median of three) on the pinned 2.19.0 and 0.0264 s on the patched 4.15.6 through the same served route, while a same-size benign query string costs 0.0240 s and 0.0259 s respectively; the attacker controls both the length and the number of eyI tokens, so a handful of concurrent requests stall every worker.

This bump also closes GHSA-p3m5-h5v9-vqj6 (dd-trace-py IAST can terminate the application process, range >=3.13.0, <4.15.5), which the ReDoS advisory's minimal 3.x patched release 3.19.9 still carries, so the pin moves to 4.15.6 on the 4.x line, outside both ranges.

What this changes:

  • requirements.txt — ddtrace moves from 2.19.0 to 4.15.6 on line 7; no other package or line changes

How to verify:

pip install --dry-run -r requirements.txt | grep ddtrace # resolves ddtrace==4.15.6
pip install -r requirements.txt && python -c "import ddtrace; print(ddtrace.__version__)" # 4.15.6
python -c "print('eyI'*20000)" | xargs -I{} curl -sS -o /dev/null -w '%{time_total}\n' 'http://127.0.0.1:6001/nonexistent?{}' # 12.817 s on 2.19.0, 0.0264 s on 4.15.6

Why the query string is exposed

csvbase/web/app.py:112 calls datadog.configure_datadog(); csvbase/datadog.py:11 imports ddtrace.auto when enable_datadog is true, which installs ddtrace's Flask/WSGI instrumentation (ddtrace/contrib/internal/flask/patch.py:368).
Every request then goes through ddtrace/_trace/trace_handlers.py:158 _on_request_prepare -> ddtrace/contrib/trace_utils.py:428 set_http_meta -> :412 _set_url_tag -> ddtrace/internal/utils/http.py:83 redact_url -> :77 redact_query_string, which runs the obfuscation regex from ddtrace/settings/config.py:56-88 over the raw QUERY_STRING before the app routes the request.
In 2.19.0 the JWT branch of that regex, ey[I-L](?:[\w=-]|%3D)+\.ey[I-L](?:[\w=-]|%3D)+, retries its trailing (?:[\w=-]|%3D)+ from every one of the many eyI starts in the query string, so the total work is quadratic in the query-string length. The fixed releases replace it with (^|[^\w%-]|%[0-9a-f]{2})ey[I-L][\w-]+(?:=|%3D)*\.ey[I-L][\w-]+..., which consumes each token once.

The same 60,000 bytes through the same route

query string ddtrace 2.19.0 (pinned at HEAD) ddtrace 4.15.6 (this PR)
20,000 eyI tokens (60,000 B), median of 3 12.817 s 0.0264 s
benign a= + 59,998 a (60,000 B), median of 3 0.0240 s 0.0259 s
8,000 eyI tokens (24,000 B) 2.0849 s 0.0220 s
2,000 eyI tokens (6,000 B) 0.1396 s 0.0126 s
The patched side was installed from the patched requirements.txt with the repository's own pip install -r requirements.txt in a clean virtualenv and served the same way; the served command is the repository's own flask run target with enable_datadog=true.

Tracing only runs when enable_datadog is set (csvbase/datadog.py:7-13), and gunicorn's default --limit-request-line of 4094 bytes caps a single request at roughly 0.05 s unless it is raised, so the 12.817 s figure is the repository's own flask run target with no request-line cap, and the patched requirements.txt was installed with pip install -r requirements.txt in a clean virtualenv before submission (exit 0, ddtrace 4.15.6).

Automated security patch from Superbot.

GHSA-3633-r77q-h3v3 (CVE-2026-116588): ddtrace's default query-string
obfuscation pattern backtracks quadratically over a query string built from
repeated eyI tokens. With the Flask instrumentation installed, every request
runs that pattern over the raw query string before the app routes it: a
60,000-byte query string takes 12.817 s in redact_query_string as opposed to
0.0240 s for a same-size benign one, while a 6,000-byte one takes 0.1396 s and
a 24,000-byte one 2.0849 s.

4.15.6 is the pin rather than 3.19.9 because 3.19.9 still carries
GHSA-p3m5-h5v9-vqj6 (>=3.13.0, <4.15.5); 4.15.6 is outside both ranges.

requirements.txt only; no source changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant