A regular expression denial-of-service (ReDoS) flaw in ms, the tiny time-string parsing library baked into thousands of Node.js dependency trees, shows how a four-line utility package can become an outsized availability risk. Tracked as CVE-2015-8315, the bug lives in the regex ms uses to convert strings like "2 days" or "10h" into milliseconds. Because that regex can be forced into catastrophic backtracking, an attacker who controls any input that eventually reaches ms() — a timeout value, a cache TTL, a header, a config field — can pin a Node.js event loop at 100% CPU with a single crafted string, taking down an entire single-threaded process. The bug is old, but ms is one of the most heavily-depended-upon packages in the npm ecosystem (it ships inside debug, which itself sits underneath Express, Mocha, and a huge share of the registry), so the practical blast radius of this ms package ReDoS vulnerability is still worth understanding today.
What is ms and why does it matter
ms converts human-readable time strings into milliseconds and back again. It's small, has no dependencies of its own, and is exactly the kind of "leaf" utility that gets pulled in transitively without anyone auditing it directly. That ubiquity is the core reason this vulnerability keeps resurfacing in dependency scans years later: teams that have never heard of ms are still shipping vulnerable copies of it because debug, or some other mid-tier dependency, pinned an old range.
The vulnerable code path is the parsing regular expression used when ms() is called with a string argument rather than a number. In versions prior to the fix, that regex used nested quantifiers and optional groups in a way that is vulnerable to catastrophic backtracking: for certain crafted inputs (long runs of whitespace combined with a trailing unit token, or similarly ambiguous character sequences), the regex engine's matching time grows exponentially with input length instead of linearly. A payload that is only a few hundred characters long can take seconds to minutes to evaluate, and because Node.js runs JavaScript on a single thread, that one slow regex match blocks every other request the process is trying to handle.
Affected versions and components
- Package:
ms(npm) - Vulnerable versions:
msbefore0.7.1 - Fixed version:
0.7.1and later (the regex was rewritten and hardened again in subsequent 1.x/2.x releases) - Primary exposure path: direct use of
ms(userControlledString), and — more commonly — indirect exposure throughdebug, which callsmsinternally when formatting log timers and durations, and through the many frameworks and middleware packages that depend ondebug
The practical risk profile here is almost entirely about transitive exposure. Very few applications call ms directly with attacker-controlled input. What actually matters is whether any code path in your dependency graph passes untrusted, attacker-influenced strings into a vulnerable ms() call — for example, a logging middleware that echoes a request header into a debug timer label, or a rate-limiter/cache library that accepts a duration string from a config value that is itself derived from user input. Because ms can appear at multiple depths and multiple versions simultaneously in a single node_modules tree (thanks to npm's nested resolution), a single npm audit or lockfile diff can miss instances that are several levels removed from the top-level package.json.
CVSS, EPSS, and KEV context
CVE-2015-8315 is scored as a CVSS v3.1 base score of 7.5 (High), reflecting a network-exploitable, low-complexity, no-privileges-required denial-of-service vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). The score is availability-only: there is no confidentiality or integrity impact from the ReDoS itself, but a stalled event loop can be just as damaging as a crash if it takes down a shared API process, a payment webhook handler, or a health-check endpoint that triggers cascading failures upstream.
From an exploitation-likelihood standpoint, this CVE's EPSS score has historically sat in the low range rather than the high percentiles associated with actively-exploited, high-value bugs — unsurprising for a decade-old, narrowly-scoped ReDoS in a utility library rather than, say, a remote code execution flaw in an internet-facing product. It is not currently listed in CISA's Known Exploited Vulnerabilities (KEV) catalog. That combination — old CVE, low EPSS, no KEV listing — is exactly the profile that causes teams to deprioritize it, and exactly the profile Safeguard's customers most often ask us to help them triage correctly: is this "just noise," or is it reachable in our application? For ms, the honest answer is that severity is highly dependent on whether user input actually reaches the vulnerable parse function, which is precisely the kind of question a CVSS base score cannot answer on its own.
Timeline
- ~October 2015 — The ReDoS pattern in the
msparsing regex is identified and reported to the maintainer, describing crafted time strings that trigger catastrophic backtracking. - November 2015 —
ms 0.7.1is published, replacing the vulnerable regular expression with a version resistant to exponential backtracking on the identified input classes. - Shortly after — CVE-2015-8315 is assigned and published in the NVD, formally documenting the flaw and affected version range.
- 2016–present — The advisory continues to surface in software composition analysis (SCA) and
npm auditoutput for projects with old lockfiles or dependency ranges that never re-resolved pastms@0.7.1, largely due to how deeplymsis nested underdebugand other long-lived transitive dependencies. - Ongoing — Later
msreleases (1.x and 2.x) continued to refine the parsing logic, but the 0.7.1 fix remains the relevant remediation boundary for this specific CVE.
Remediation steps
- Identify every copy of
msin your dependency tree, not just the top-level one. Runnpm ls ms(oryarn why ms/pnpm why ms) across each application and service repo — expect to find multiple versions at different nesting depths, often introduced independently bydebug,express, build tooling, and test frameworks. - Upgrade any instance below
0.7.1. For most projects, this means bumping the version range that a parent package (commonlydebug) resolves to, either by upgrading that parent package or by adding an explicit override/resolution entry inpackage.json(overridesfor npm,resolutionsfor Yarn,pnpm.overridesfor pnpm) to force a safemsversion across the whole tree. - Regenerate and commit your lockfile after the override so the fix is durable and doesn't silently regress on the next clean install.
- Audit call sites that pass dynamic strings into
ms()ordebug-style timer labels. Even after patching, it's worth confirming that no code path feeds unbounded, attacker-influenced strings into duration-parsing functions — the same defensive posture that protects you against this specific CVE also protects you against the next ReDoS discovered in a similar utility. - Add regex complexity checks to CI where practical. Static analysis rules or linters that flag nested quantifiers and ambiguous alternation in newly introduced regular expressions catch this entire vulnerability class before it ships, rather than after an advisory is published.
- Re-run SCA scans post-remediation to confirm no shadow copies of the vulnerable
msversion remain in build artifacts, container base images, or vendored bundles that weren't covered by the lockfile update.
How Safeguard Helps
CVEs like this one are exactly why blanket "upgrade everything with a CVE" policies burn out engineering teams: ms's ReDoS is nine years old, low-EPSS, and absent from KEV, yet it still shows up in nearly every dependency audit because of how deeply it nests under debug. Safeguard's reachability analysis traces whether attacker-controlled input can actually reach a vulnerable ms() call path in your specific codebase, so you can distinguish the instances that genuinely need an immediate fix from the ones that are dormant, unreachable code. Griffin, Safeguard's AI-powered analysis engine, correlates that reachability signal with dependency depth and call-graph context to explain why a given ms instance matters (or doesn't) in plain language your team can act on without reading the regex yourself. Safeguard's SBOM generation and ingest capabilities surface every nested copy of ms across your services — including versions introduced by transitive dependencies like debug that a top-level package.json scan would miss — and auto-fix pull requests apply the version bump or override automatically, with the lockfile regenerated and tests re-run, so remediation ships in minutes instead of tickets.