attachment.zip
Description
Summary
JSONata is a JSON query and transformation language implementation for JavaScript (npm package jsonata, v2.2.2, MIT license). The built-in function $toMillis(timestamp, picture) converts a date string to milliseconds since epoch. When a picture string is provided, the function delegates to parseDateTime, which dynamically generates a regular expression from the picture string and matches the input against it. The f (fractional seconds) component in generateRegex unconditionally emits [0-9]+ regardless of the parseWidth defense mechanism, allowing an attacker to craft a picture string with multiple [f1] markers that produces a regex with multiple adjacent [0-9]+ capturing groups, causing polynomial backtracking (O(n^(k-1))) on non-matching inputs. This bypasses CVE-2026-52746, whose fix (PR #793) only patched the hardcoded iso8601regex used when no picture string is provided.
Details
Root cause: In src/src/datetime.js, the generateRegex function (line 954) processes each component of the picture string. The f component branch (line 997-998) is checked before the integerFormat branch (line 1002) and always sets res.regex = '[0-9]+', completely ignoring the parseWidth property. The parseWidth defense mechanism (line 628-632) sets previousPart.integerFormat.parseWidth = previousPart.integerFormat.mandatoryDigits when processing adjacent integer components, but since the f component does not go through the integerFormat branch, parseWidth has no effect on it.
Attack path: An attacker provides a JSONata expression $toMillis(malicious_input, '[f1][f1][f1][f1][f1][f1][f1]'). The analyseDateTimePicture function parses the picture string into 7 f component markers. The generateRegex function produces 7 parts each with regex = '[0-9]+'. The parseDateTime function constructs the full regex ^([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)$ and executes it via new RegExp(fullRegex, 'i').exec(timestamp) (line 1141-1142). For a non-matching input of 80 digits followed by 'x', the regex engine tries all possible ways to split 80 digits among 7 groups, resulting in O(n^6) polynomial backtracking. Harness testing confirms the polynomial growth: 30 digits = 22ms, 50 digits = 163ms, 60 digits = 713ms, 70 digits = 1.8s, 80 digits = ~25s, 100 digits = ~23s.
Additional exposure: The parseDateTime function uses new RegExp() directly (line 1141) instead of environment.base.RegexEngine, so the RegexEngine option (which allows callers to plug in a ReDoS-safe regex engine) does not protect this code path. The timeout guardrail also does not cover native RegExp execution (documented in guardrails.md). In the default configuration, there is no protection against this ReDoS.
PoC
# API call recipe (component library — no HTTP endpoint)
const jsonata = require('jsonata');
const expr = jsonata('$toMillis("<DIGIT_STRING>x", "[f1][f1][f1][f1][f1][f1][f1]")');
await expr.evaluate({});
# Where <DIGIT_STRING> is 80+ '0' characters
# Expected: evaluation hangs for ~25 seconds (80-digit input)
Impact
An attacker who can control the JSONata expression string (e.g., in an online JSONata playground, rule engine, or data transformation pipeline that accepts user-supplied expressions) can cause the host application to hang for tens of seconds or longer by evaluating $toMillis with a crafted picture string and malicious input. This results in denial of service. The vulnerability exists in the default configuration with no guardrails effective against it.
Affected products
- jsonata >= 2.2.0 (including the CVE-2026-52746 fix) — confirmed on 2.2.2
- jsonata >= 2.0.0, < 2.2.0 — original CVE path also vulnerable, this path additionally exploitable
- jsonata 1.x branch — requires verification
Severity / CWE
CWE: CWE-400 (Uncontrolled Resource Consumption)
CVSS 3.1: 7.5 (High)
- Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
attachment.zip
Description
Summary
JSONata is a JSON query and transformation language implementation for JavaScript (npm package
jsonata, v2.2.2, MIT license). The built-in function$toMillis(timestamp, picture)converts a date string to milliseconds since epoch. When a picture string is provided, the function delegates toparseDateTime, which dynamically generates a regular expression from the picture string and matches the input against it. Thef(fractional seconds) component ingenerateRegexunconditionally emits[0-9]+regardless of theparseWidthdefense mechanism, allowing an attacker to craft a picture string with multiple[f1]markers that produces a regex with multiple adjacent[0-9]+capturing groups, causing polynomial backtracking (O(n^(k-1))) on non-matching inputs. This bypasses CVE-2026-52746, whose fix (PR #793) only patched the hardcodediso8601regexused when no picture string is provided.Details
Root cause: In
src/src/datetime.js, thegenerateRegexfunction (line 954) processes each component of the picture string. Thefcomponent branch (line 997-998) is checked before theintegerFormatbranch (line 1002) and always setsres.regex = '[0-9]+', completely ignoring theparseWidthproperty. TheparseWidthdefense mechanism (line 628-632) setspreviousPart.integerFormat.parseWidth = previousPart.integerFormat.mandatoryDigitswhen processing adjacent integer components, but since thefcomponent does not go through theintegerFormatbranch,parseWidthhas no effect on it.Attack path: An attacker provides a JSONata expression
$toMillis(malicious_input, '[f1][f1][f1][f1][f1][f1][f1]'). TheanalyseDateTimePicturefunction parses the picture string into 7fcomponent markers. ThegenerateRegexfunction produces 7 parts each withregex = '[0-9]+'. TheparseDateTimefunction constructs the full regex^([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)([0-9]+)$and executes it vianew RegExp(fullRegex, 'i').exec(timestamp)(line 1141-1142). For a non-matching input of 80 digits followed by 'x', the regex engine tries all possible ways to split 80 digits among 7 groups, resulting in O(n^6) polynomial backtracking. Harness testing confirms the polynomial growth: 30 digits = 22ms, 50 digits = 163ms, 60 digits = 713ms, 70 digits = 1.8s, 80 digits = ~25s, 100 digits = ~23s.Additional exposure: The
parseDateTimefunction usesnew RegExp()directly (line 1141) instead ofenvironment.base.RegexEngine, so theRegexEngineoption (which allows callers to plug in a ReDoS-safe regex engine) does not protect this code path. Thetimeoutguardrail also does not cover native RegExp execution (documented in guardrails.md). In the default configuration, there is no protection against this ReDoS.PoC
Impact
An attacker who can control the JSONata expression string (e.g., in an online JSONata playground, rule engine, or data transformation pipeline that accepts user-supplied expressions) can cause the host application to hang for tens of seconds or longer by evaluating
$toMilliswith a crafted picture string and malicious input. This results in denial of service. The vulnerability exists in the default configuration with no guardrails effective against it.Affected products
Severity / CWE
CWE: CWE-400 (Uncontrolled Resource Consumption)
CVSS 3.1: 7.5 (High)
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H