{"items":[{"id":"NVI-00009059","kind":"vulnerability","title":"joi: quadratic-time split in `string().email({ multiple: true })` allows denial of service","revision":4,"unknowns":["Customer inventory, deployment, reachability and exploitation have not been inspected.","NVD lastModified does not establish freshness of every associated CPE record."],"lifecycle":"active","identifiers":["GHSA-4528-x5fx-h6hg","CVE-2026-118772"],"plain_summary":"GitHub lists this reviewed advisory for joi, @hapi/joi, joi. Published version ranges and source prerequisites are shown below; this is not an inspection of a visitor’s application.","review_status":"source_listed","control_topics":[],"public_actions":[{"summary":"Identify the exact component and deployed version, examine the linked publisher’s conditions, and retain evidence of any mitigation.","source_ids":["sr-0215f344b3b970a8e8b19d64b7dbd585"],"action_type":"investigate","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 17.13.9 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 18.3.1 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null}],"schema_version":"1.1.0","source_metrics":[{"kind":"CVSS","as_of":"2026-10-30T23:10:26.000Z","value":"7.5 high","meaning":"GitHub source severity; exploitability and local consequence require separate evidence.","source_id":"sr-959f8591b8f9d531015111636ca7b784"},{"kind":"CVSS","as_of":"2026-11-04T02:17:15.813Z","value":"7.5 HIGH (3.1)","meaning":"security-advisories@github.com severity; no combined Null risk score.","source_id":"sr-205291ce96c7c4acf4b3874c846a1442"},{"kind":"EPSS","as_of":"2026-11-05T12:03:04.000Z","value":"0.467%","meaning":"FIRST EPSS model v2026.06.15; exploitation probability over 30 days, percentile 0.40595. Missing scores are unknown.","source_id":"sr-0215f344b3b970a8e8b19d64b7dbd585"}],"why_it_matters":"Published affected context helps an owner decide what evidence to gather. This report has not examined your application.","related_sources":[{"id":"sr-959f8591b8f9d531015111636ca7b784","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub Advisory Database; CC-BY-4.0. Each advisory links its original attribution. Null normalizes fields.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database","observed_at":"2026-10-31T21:55:49.621Z"},{"id":"sr-603f273913926ada8a3e8ec26b2dec83","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub-origin advisory, CC-BY-4.0; OSV is an aggregate copy, not independent corroboration.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database via OSV","observed_at":"2026-10-31T22:07:25.165Z"},{"id":"sr-205291ce96c7c4acf4b3874c846a1442","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-118772","basis":"official_catalog","notes":"This product uses data from the NVD API but is not endorsed or certified by the NVD.","title":"CVE-2026-118772","publisher":"NIST National Vulnerability Database","observed_at":"2026-11-07T07:00:25.983Z"},{"id":"sr-0215f344b3b970a8e8b19d64b7dbd585","url":"https://www.first.org/epss/data.html","basis":"official_catalog","notes":"FIRST EPSS; probability of exploitation in the next 30 days, not local compromise. Dataset date/model retained; FIRST services terms apply.","title":"CVE-2026-118772","publisher":"FIRST EPSS","observed_at":"2026-11-06T00:24:57.640Z"}],"affected_context":[{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 17.1.1, < 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 16.0.0, <= 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 18.0.0, < 18.3.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 17.1.1; fixed 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 16.0.0; last_affected 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 18.0.0; fixed 18.3.1"]}],"null_published_at":"2026-11-07T18:43:12.073Z","source_updated_at":"2026-10-30T23:10:26.000Z","source_disclosed_at":"2026-10-30T23:10:23.000Z","is_synthetic_fixture":false,"null_last_reviewed_at":null,"public_change_summary":"Source facts changed; compare the preserved prior revision.","null_first_observed_at":"2026-10-31T21:55:49.621Z"},{"id":"NVI-00009059","kind":"vulnerability","title":"joi: quadratic-time split in `string().email({ multiple: true })` allows denial of service","revision":3,"unknowns":["Customer inventory, deployment, reachability and exploitation have not been inspected."],"lifecycle":"active","identifiers":["GHSA-4528-x5fx-h6hg","CVE-2026-118772"],"plain_summary":"GitHub lists this reviewed advisory for joi, @hapi/joi, joi. Published version ranges and source prerequisites are shown below; this is not an inspection of a visitor’s application.","review_status":"source_listed","control_topics":[],"public_actions":[{"summary":"Identify the exact component and deployed version, examine the linked publisher’s conditions, and retain evidence of any mitigation.","source_ids":["sr-0215f344b3b970a8e8b19d64b7dbd585"],"action_type":"investigate","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 17.13.9 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 18.3.1 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null}],"schema_version":"1.1.0","source_metrics":[{"kind":"CVSS","as_of":"2026-10-30T23:10:26.000Z","value":"7.5 high","meaning":"GitHub source severity; exploitability and local consequence require separate evidence.","source_id":"sr-959f8591b8f9d531015111636ca7b784"},{"kind":"EPSS","as_of":"2026-11-05T12:03:04.000Z","value":"0.467%","meaning":"FIRST EPSS model v2026.06.15; exploitation probability over 30 days, percentile 0.40595. Missing scores are unknown.","source_id":"sr-0215f344b3b970a8e8b19d64b7dbd585"}],"why_it_matters":"Published affected context helps an owner decide what evidence to gather. This report has not examined your application.","related_sources":[{"id":"sr-959f8591b8f9d531015111636ca7b784","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub Advisory Database; CC-BY-4.0. Each advisory links its original attribution. Null normalizes fields.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database","observed_at":"2026-10-31T21:55:49.621Z"},{"id":"sr-603f273913926ada8a3e8ec26b2dec83","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub-origin advisory, CC-BY-4.0; OSV is an aggregate copy, not independent corroboration.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database via OSV","observed_at":"2026-10-31T22:07:25.165Z"},{"id":"sr-0215f344b3b970a8e8b19d64b7dbd585","url":"https://www.first.org/epss/data.html","basis":"official_catalog","notes":"FIRST EPSS; probability of exploitation in the next 30 days, not local compromise. Dataset date/model retained; FIRST services terms apply.","title":"CVE-2026-118772","publisher":"FIRST EPSS","observed_at":"2026-11-06T00:24:57.640Z"}],"affected_context":[{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 17.1.1, < 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 16.0.0, <= 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 18.0.0, < 18.3.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 17.1.1; fixed 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 16.0.0; last_affected 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 18.0.0; fixed 18.3.1"]}],"null_published_at":"2026-11-07T07:07:42.523Z","source_updated_at":"2026-10-30T23:10:26.000Z","source_disclosed_at":"2026-10-30T23:10:23.000Z","is_synthetic_fixture":false,"null_last_reviewed_at":null,"public_change_summary":"Dated EPSS estimate updated; this is not a new disclosure or proof of exploitation.","null_first_observed_at":"2026-10-31T21:55:49.621Z"},{"id":"NVI-00009059","kind":"vulnerability","title":"joi: quadratic-time split in `string().email({ multiple: true })` allows denial of service","revision":2,"unknowns":["Customer inventory, deployment, reachability and exploitation have not been inspected."],"lifecycle":"active","identifiers":["GHSA-4528-x5fx-h6hg","CVE-2026-118772"],"plain_summary":"GitHub lists this reviewed advisory for joi, @hapi/joi, joi. Published version ranges and source prerequisites are shown below; this is not an inspection of a visitor’s application.","review_status":"source_listed","control_topics":[],"public_actions":[{"summary":"Identify the exact component and deployed version, examine the linked publisher’s conditions, and retain evidence of any mitigation.","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"action_type":"investigate","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 17.13.9 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null},{"summary":"The original advisory lists fixed boundaries 18.3.1 for joi. Confirm the publisher’s latest guidance.","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"action_type":"vendor_patch","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null}],"schema_version":"1.1.0","source_metrics":[{"kind":"CVSS","as_of":"2026-10-30T23:10:26.000Z","value":"7.5 high","meaning":"GitHub source severity; exploitability and local consequence require separate evidence.","source_id":"sr-959f8591b8f9d531015111636ca7b784"}],"why_it_matters":"Published affected context helps an owner decide what evidence to gather. This report has not examined your application.","related_sources":[{"id":"sr-959f8591b8f9d531015111636ca7b784","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub Advisory Database; CC-BY-4.0. Each advisory links its original attribution. Null normalizes fields.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database","observed_at":"2026-10-31T21:55:49.621Z"},{"id":"sr-603f273913926ada8a3e8ec26b2dec83","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub-origin advisory, CC-BY-4.0; OSV is an aggregate copy, not independent corroboration.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database via OSV","observed_at":"2026-10-31T22:07:25.165Z"}],"affected_context":[{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 17.1.1, < 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 16.0.0, <= 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 18.0.0, < 18.3.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 17.1.1; fixed 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 16.0.0; last_affected 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-603f273913926ada8a3e8ec26b2dec83"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":["SEMVER: introduced 18.0.0; fixed 18.3.1"]}],"null_published_at":"2026-10-31T22:27:03.527Z","source_updated_at":"2026-10-30T23:10:26.000Z","source_disclosed_at":"2026-10-30T23:10:23.000Z","is_synthetic_fixture":false,"null_last_reviewed_at":null,"public_change_summary":"Source facts changed; compare the preserved prior revision.","null_first_observed_at":"2026-10-31T21:55:49.621Z"},{"id":"NVI-00009059","kind":"vulnerability","title":"joi: quadratic-time split in `string().email({ multiple: true })` allows denial of service","revision":1,"unknowns":["Customer inventory, deployment, reachability and exploitation have not been inspected."],"lifecycle":"active","identifiers":["GHSA-4528-x5fx-h6hg","CVE-2026-118772"],"plain_summary":"GitHub lists this reviewed advisory for joi, @hapi/joi, joi. Published version ranges and source prerequisites are shown below; this is not an inspection of a visitor’s application.","review_status":"source_listed","control_topics":[],"public_actions":[{"summary":"Identify the exact component and deployed version, examine the linked publisher’s conditions, and retain evidence of any mitigation.","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"action_type":"investigate","limitations":"Confirm current vendor support, compatibility and deployment evidence. This report has not tested a fix in your system.","current_supported_target":null}],"schema_version":"1.1.0","source_metrics":[{"kind":"CVSS","as_of":"2026-10-30T23:10:26.000Z","value":"7.5 high","meaning":"GitHub source severity; exploitability and local consequence require separate evidence.","source_id":"sr-959f8591b8f9d531015111636ca7b784"}],"why_it_matters":"Published affected context helps an owner decide what evidence to gather. This report has not examined your application.","related_sources":[{"id":"sr-959f8591b8f9d531015111636ca7b784","url":"https://github.com/advisories/GHSA-4528-x5fx-h6hg","basis":"vendor_advisory","notes":"GitHub Advisory Database; CC-BY-4.0. Each advisory links its original attribution. Null normalizes fields.","title":"GHSA-4528-x5fx-h6hg","publisher":"GitHub Advisory Database","observed_at":"2026-10-31T21:55:49.621Z"}],"affected_context":[{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 17.1.1, < 17.13.9"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"@hapi/joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 16.0.0, <= 17.1.1"]},{"certainty":"source_listed","source_ids":["sr-959f8591b8f9d531015111636ca7b784"],"technology":"joi","known_exclusions":[],"conditions_summary":"### Summary\n\n`Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.\n\n### Details\n\nWhen the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:\n\n```js\nconst regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n// ...\nconst emails = options.multiple ? value.split(regex) : [value];\n```\n\n`String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.\n\nThe fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:\n\n```js\nconst regex = new RegExp(`(?:(?<!\\\\s)\\\\s+)?[${options.separator ? escapeRegex(options.se","public_affected_ranges":[">= 18.0.0, < 18.3.1"]}],"null_published_at":"2026-10-31T22:13:31.272Z","source_updated_at":"2026-10-30T23:10:26.000Z","source_disclosed_at":"2026-10-30T23:10:23.000Z","is_synthetic_fixture":false,"null_last_reviewed_at":null,"public_change_summary":"First indexed by Null; source disclosure date is shown separately.","null_first_observed_at":"2026-10-31T21:55:49.621Z"}],"next_cursor":null,"snapshot_revision":"39771"}