Oct 28, 2026·10 min read·3 visits
An inefficient default regular expression used by the Datadog Node.js APM tracer (dd-trace) to redact sensitive parameters from URL query strings allows unauthenticated attackers to trigger catastrophic backtracking via crafted HTTP requests, blocking the Node.js single-threaded event loop and causing a full application denial of service.
CVE-2026-116693 is a high-severity Regular Expression Denial of Service (ReDoS) vulnerability affecting the official Datadog Application Performance Monitoring (APM) client library for Node.js, dd-trace. The defect resides in the default regular expression defined by the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP configuration parameter, which is used to sanitize incoming and outgoing HTTP URL query strings prior to recording trace span metadata. Due to overlapping sub-patterns and unanchored repetition blocks in the JSON Web Token (JWT) and URL-encoded string matching branches, the V8 JavaScript engine's regular expression subsystem (Irregexp) can be forced into catastrophic or high-degree polynomial backtracking. Because regular expression replacement occurs synchronously on the primary Node.js event loop, an unauthenticated remote attacker can send crafted HTTP requests with malicious query strings to consume 100% of the process CPU core, stalling concurrent request processing and rendering the application unavailable.
CVE-2026-116693 (also tracked as GHSA-x94g-w73q-hpcw) is an algorithmic complexity vulnerability classified under CWE-1333 (Inefficient Regular Expression Complexity) in dd-trace, the Datadog Application Performance Monitoring (APM) library for Node.js. The dd-trace package provides automatic and manual instrumentation for Node.js runtime environments, hooking into standard web frameworks (including Express, Fastify, Koa, Hapi, and Connect) as well as core Node.js networking modules (http, https, and http2).
During the lifecycle of an instrumented HTTP transaction, dd-trace intercepts the request or response to populate APM span tags, specifically the http.url tag. Because raw URLs frequently contain sensitive secrets, session tokens, or personally identifiable information inside query parameters, the tracer automatically runs a sanitization routine over the query string before queuing the span for transmission to the Datadog Agent. This redaction step is enabled by default whenever HTTP tracing and query string capture are active.
The sanitization logic relies on a global, case-insensitive regular expression configured via the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP option. In versions >= 2.0.0, < 5.131.1 and >= 6.0.0, < 6.20.1, this default regular expression contains multiple structural ambiguities and unanchored repetition groups. Because Node.js executes JavaScript and regular expression evaluations synchronously on a single-threaded event loop, any input that induces non-linear evaluation time directly blocks the execution thread.
An unauthenticated remote adversary with network access to any HTTP endpoint instrumented by dd-trace can supply a crafted query string in a standard GET or POST request. The tracer evaluates this query string prior to or alongside application routing logic, causing the V8 engine to enter catastrophic backtracking. This results in immediate CPU exhaustion and complete starvation of the Node.js event loop, denying service to all legitimate clients connected to the affected process.
The root cause of CVE-2026-116693 lies in the construction of the default 626-character regular expression assigned to DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP, combined with the backtracking behavior of V8's non-backtracking-unaware NFA engine (Irregexp). The pattern is designed to detect a broad range of credential formats in URL query strings, including key-value pairs (password=, secret=, api_key=), Bearer tokens, GitHub personal access tokens, JSON Web Tokens (JWTs), PEM-encoded private keys, and SSH public keys.
(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\s|%20)*(?::|%3A)(?:\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|bearer(?:\s|%20)+[a-z0-9\._\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\w=-]|%3D)+\.ey[I-L](?:[\w=-]|%3D)+(?:\.(?:[\w.+\/=-]|%3D|%2F|%2B)+)?|[\-]{5}BEGIN(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY[\-]{5}[^\-]+[\-]{5}END(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY|ssh-rsa(?:\s|%20)*(?:[a-z0-9\/\.+]|%2F|%5C|%2B){100,}The primary backtracking hotspot occurs in the JWT detection branch: ey[I-L](?:[\w=-]|%3D)+\.ey[I-L](?:[\w=-]|%3D)+(?:\.(?:[\w.+\/=-]|%3D|%2F|%2B)+)?. First, this sub-pattern lacks a leading boundary anchor or lookbehind assertion (such as ^ or a non-word delimiter), allowing the engine to initiate a match attempt at any arbitrary offset within a long string where the substring ey followed by I, J, K, or L appears. Second, the repeated alternation group (?:[\w=-]|%3D)+ introduces ambiguity when processing URL-encoded sequences or strings containing 3 and D characters, alongside repeated dot (.) delimiters across the three JWT segments.
When a query string contains multiple segments starting with eyJ followed by base64url-compatible characters and dots, but fails to satisfy the trailing segment requirements or triggers repeated sub-match attempts across the string, the NFA engine must explore an exponential or high-degree polynomial number of paths before abandoning the match at each starting offset. Because the g (global) flag is set on the compiled RegExp object, String.prototype.replace() advances character by character and re-evaluates the pattern across the remaining substring.
A secondary hotspot exists in the JSON-encoded value matching branch: (?:%2[^2]|%[^2]|[^"%])+. Here, three alternatives are wrapped inside a greedy + quantifier to match characters up to a closing quote (" or %22). When an attacker supplies a sequence of percent-encoded characters starting with % or %2 that is never terminated by a closing quote (" or %22), the engine backtracks through the overlapping character classes across the length of the input.
The vulnerable code path resides in packages/dd-trace/src/plugins/util/url.js and packages/dd-trace/src/config/supported-configurations.json. When dd-trace initializes its HTTP instrumentation plugins, getQsObfuscator(config) compiles the regular expression string from config.queryStringObfuscation (populated by default from DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP) into a cached RegExp instance with the gi flags.
During request processing, obfuscateQs(config, url) slices the query string after the first ? delimiter and executes qs.replace(queryStringObfuscation, '<redacted>'). Prior to the patch in commits 5eeee790c93036a50549056fc890028cb4c86b1b (v6 branch) and cf4e68a45fa1442c9850f2086c81901258e821b8 (v5 branch), url.js performed a static string replacement without accounting for boundary capture groups:
// Vulnerable implementation in packages/dd-trace/src/plugins/util/url.js
function obfuscateQs (config, url) {
// ... validation and index lookup for '?' ...
let qs = url.slice(i + 1)
// Synchronous execution of vulnerable regex on main thread
qs = qs.replace(queryStringObfuscation, '<redacted>')
return `${path}?${qs}`
}To remediate the vulnerability in Pull Request #10675, Datadog modified both the regular expression definition and the replacement logic in url.js. In the updated regular expression, the JWT branch was anchored to a preceding delimiter using a capturing group ^|[^\w%-]|%[0-9a-f]{2}, and the character classes for the JWT header and payload were tightened from (?:[\w=-]|%3D)+ to [\w-]+(?:=|%3D)*. Separating the base64url body characters ([\w-]+) from the optional padding characters ((?:=|%3D)*) eliminates the quantifier nesting ambiguity and enforces strict token boundaries.
// Patched implementation in packages/dd-trace/src/plugins/util/url.js
const { defaults } = require('../../config/defaults')
// ...
function obfuscateQs (config, url) {
// ...
let qs = url.slice(i + 1)
// Group 1 of the default regex captures the delimiter preceding a JWT.
// If the default obfuscator is active, preserve Group 1 ($1) during replacement.
// If a custom user-provided regex is active, redact the entire match to avoid leaking Group 1.
const replacement = queryStringObfuscation === defaultQsObfuscator ? '$1<redacted>' : '<redacted>'
qs = qs.replace(queryStringObfuscation, replacement)
return `${path}?${qs}`
}
const qsObfuscatorCache = new Map()
let defaultQsObfuscator
function getQsObfuscator (config) {
// ...
} else if (obfuscator !== '.*') {
try {
compiled = new RegExp(obfuscator, 'gi')
// Track reference to the compiled default regex
if (obfuscator === defaults.DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP) defaultQsObfuscator = compiled
} catch (err) {
log.error('Error getting qs obfuscator', err)
}
}
}> [!NOTE]
> Assessment of Fix Completeness: The patch effectively neutralizes the JWT backtracking vector by anchoring the match start and decoupling the padding quantifier (=|%3D)* from the body [\w-]+. It also hardens the SSH key matching branch ((?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)...). However, the JSON value matching sub-branch (?:%2[^2]|%[^2]|[^"%])+ remains present in the default pattern. While its alternatives (%2[^2], %[^2], and [^"%]) are mutually exclusive at the first character or second character lookahead level—preventing exponential $O(2^n)$ backtracking within that specific group—the overarching regular expression still relies on V8's backtracking engine rather than a linear-time automaton.
Exploiting CVE-2026-116693 requires no authentication, valid session state, or knowledge of the target application's internal route structure. Because dd-trace hooks into the lowest-level Node.js HTTP server events (specifically the request event emitted by http.Server or http2.Http2Server), the tracer processes the request URL even if the target path returns a 404 Not Found or 401 Unauthorized response at the application layer.
To trigger the denial of service, an attacker constructs an HTTP request containing a query string specifically structured to maximize backtracking transitions in the default DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP pattern. The attack targets either the unanchored JWT sub-pattern or the compound key-value alternations.
dd-trace (versions 2.0.0 through 5.131.0 or 6.0.0 through 6.20.0).eyJ followed by word characters, hyphens, and ambiguous = or %3D sequences separated by dots (.), terminated by a character that invalidates the third JWT segment (such as & or %).maxHeaderSize defaults to 16 KB in modern releases), providing ample payload space within a single request.obfuscateQs() invokes qs.replace() on the malicious query string. The main Node.js execution thread stalls inside C++ V8 Irregexp execution. By sending a low-rate stream of requests (a few requests per second), a single attacker can keep a multi-worker or clustered deployment continuously saturated.The primary impact of CVE-2026-116693 is a complete denial of service against the affected Node.js process, reflected in the CVSS v4.0 base score of 8.7 (High) with vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N. Because Confidentiality (VC:N) and Integrity (VI:N) are unaffected, the risk is strictly confined to system Availability (VA:H).
In the Node.js runtime architecture, all I/O operations, timers, promise resolutions, and incoming HTTP connections are multiplexed onto a single main thread via libuv. When CPU-bound synchronous work such as catastrophic regular expression backtracking executes on this thread, the event loop cannot advance to the poll phase. Consequently, active sockets stop receiving data, database connection pools time out, and health check probes (such as Kubernetes liveness and readiness checks) fail to receive responses.
In containerized orchestration environments (such as Kubernetes or Amazon ECS), sustained failure of liveness probes caused by event loop starvation will prompt the orchestrator to terminate and restart the container. If the attacker maintains a minimal request rate across load-balanced replicas, the entire service tier can be forced into a continuous crash-loop state (Application Exhaustion Flood, MITRE ATT&CK T1499.004).
According to current threat intelligence, CVE-2026-116693 is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no public standalone Proof-of-Concept (PoC) scripts have been published on ExploitDB or GitHub. However, because the exact diff between the vulnerable and fixed regular expressions is publicly visible in the repository commit history, deriving a triggering string requires minimal effort for a skilled adversary.
Organizations utilizing dd-trace in Node.js applications should upgrade to a patched release immediately. Datadog has resolved this issue in versions 5.131.1 (for the v5.x release line and earlier v2.x-v4.x users upgrading to v5) and 6.20.1 (for the v6.x release line).
# Upgrade to the latest patched v6 release
npm install dd-trace@^6.20.1
# Or upgrade to the latest patched v5 release
npm install dd-trace@^5.131.1For environments where upgrading the dd-trace package dependency and redeploying the build artifact cannot be performed immediately, administrators can apply an immediate hotfix via environment configuration. By overriding the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP environment variable with a hardened regular expression that uses lookbehind assertions (?:(?<![\w-])|(?<=%[0-9a-f]{2})) instead of capturing groups, the ReDoS vector is mitigated without altering the underlying url.js replacement code:
export DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP='(?:(?:"|%22)?)(?:(?:old[-_]?|new[-_]?)?p(?:ass)?w(?:or)?d(?:1|2)?|pass(?:[-_]?phrase)?|secret|(?:api[-_]?|private[-_]?|public[-_]?|access[-_]?|secret[-_]?|app(?:lication)?[-_]?)key(?:[-_]?id)?|token|consumer[-_]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\s|%20)*(?::|%3A)(?:\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|(?:bearer(?:\s|%20)+[a-z0-9._\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|(?:(?<![\w-])|(?<=%[0-9a-f]{2}))ey[I-L][\w-]+(?:=|%3D)*\.ey[I-L][\w-]+(?:=|%3D)*(?:\.(?:[\w.+/=-]|%3D|%2F|%2B)+)?|-{5}BEGIN(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY-{5}[^\-]+-{5}END(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY(?:-{5})?(?:\n|%0A)?|(?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)(?:\s|%20|%09)+(?:[a-z0-9/.+]|%2F|%5C|%2B){100,}(?:=|%3D)*(?:(?:\s|%20|%09)+[a-z0-9._-]+)?)'> [!NOTE]
> When applying the environment variable workaround on unpatched versions of dd-trace, you must use the non-capturing lookbehind syntax (?:(?<![\w-])|(?<=%[0-9a-f]{2})) shown above rather than the capturing group (^|[^\w%-]|%[0-9a-f]{2}) used in the v5.131.1/v6.20.1 source code. Unpatched versions replace the entire match with <redacted> rather than $1<redacted>, which would otherwise strip the delimiter character preceding a JWT.
Alternatively, if query string parameters are not needed in APM trace metadata, teams can bypass the regular expression engine entirely either by setting DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP='.*' (which triggers a fast-path boolean redaction of the entire query string in getQsObfuscator) or by disabling query string collection in the tracer initialization options.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
dd-trace Datadog | >= 2.0.0, < 5.131.1 | 5.131.1 |
dd-trace Datadog | >= 6.0.0, < 6.20.1 | 6.20.1 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-116693 (GHSA-x94g-w73q-hpcw) |
| Vulnerability Class | CWE-1333: Inefficient Regular Expression Complexity (ReDoS) |
| Affected Component | dd-trace (npm) URL Query String Obfuscator (obfuscateQs) |
| Attack Vector | Network (Unauthenticated HTTP/HTTPS Requests) |
| CVSS v4.0 Score | 8.7 High (AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N) |
| Security Impact | Node.js Event Loop Starvation / Application Denial of Service |
| Patched Versions | 5.131.1, 6.20.1 |
| CISA KEV Status | Not Listed |
The product uses a regular expression that can take an exponential or polynomial amount of time to evaluate relative to the size of the input, enabling Regular Expression Denial of Service (ReDoS).
CVE-2026-116692 (GHSA-26p8-2jq9-3vq9) is a remote code execution vulnerability in MLflow affecting versions 3.12.0rc0 prior to 3.17.0. The flaw stems from an overly permissive module prefix check during the deserialization of third-party GenAI evaluation scorers in mlflow/genai/scorers/base.py. By setting an experiment's artifact root to a local package directory within the Python module search path and uploading a crafted Python file as a run artifact, an attacker can invoke the /ajax-api/3.0/mlflow/scorer/invoke endpoint to dynamically import and execute arbitrary code on the MLflow tracking server.
CVE-2026-116691 (GHSA-j7qq-vr8c-xvw3) is a sensitive credential disclosure vulnerability in the MLflow platform affecting versions 2.19.0 through 3.16.x. When CrewAI autologging is enabled via mlflow.crewai.autolog(), the tracing instrumentation fails to sanitize nested CrewAI LLM objects before serializing them into OpenTelemetry trace spans. Consequently, plaintext LLM provider API keys are extracted from object dictionaries and string representations, then transmitted and stored in MLflow tracking servers or external OpenTelemetry Protocol (OTLP) telemetry backends.
MLflow versions 3.10.0 through 3.16.1 contain a path traversal vulnerability (CWE-22) in the model version creation handler. Specifically, the prompt-specific execution branch in `_create_model_version()` fails to validate schemeless relative paths and evaluates the `mlflow.prompt.is_prompt` tag based on key presence rather than value. An unauthenticated remote attacker on default deployments, or an authenticated low-privilege user on basic-auth deployments, can register a model version with a relative local path as its artifact source and subsequently retrieve arbitrary files from the server filesystem via the `/api/2.0/mlflow/model-versions/get-artifact` endpoint.
CVE-2026-109383 (GHSA-2m5v-pch6-74hx) is an improper certificate validation vulnerability (CWE-295) in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js. In affected versions, the Agent constructor clones configuration options using a JSON-based deep clone function (util.deepClone), which executes JSON.parse(JSON.stringify(options)). Because JSON serialization omits function properties, custom certificate validation callbacks passed via the tls configuration option—such as tls.checkServerIdentity—are silently stripped before being passed to origin connection pools. Consequently, connections fall back to standard Node.js TLS validation, bypassing custom certificate pinning or hostname verification rules and allowing an Adversary-in-the-Middle (AitM) with a CA-trusted certificate to intercept and modify traffic.
A cache key origin desynchronization and intermediate Cache-Control suppression vulnerability exists in the Node.js Undici HTTP client library. When an originless Agent composes the redirect and cache interceptors in the order compose(redirect(), cache()) with a shared cache store, cross-origin responses and sensitive redirected resources are stored under the initial request's cache key.
CVE-2026-109855 is a high-severity Denial of Service (DoS) vulnerability in undici, the primary HTTP/1.1 and HTTP/2 client library for Node.js maintained under the OpenJS Foundation. In versions 8.11.0 and 8.11.1, the WebSocket client fails to properly terminate underlying ClientHttp2Stream instances when an HTTP/2 WebSocket handshake fails validation. When the remote server subsequently closes or resets the stream, Node.js emits an unhandled error event on the orphaned stream, crashing the client process.