A regular expression buried deep in one of the JavaScript ecosystem's most quietly ubiquitous packages turned out to be a denial-of-service trap. CVE-2021-23364 affects browserslist, the npm package that tells build tools like Babel, Autoprefixer, ESLint, and PostCSS which browsers to target based on market-share data and user-supplied query strings. Versions prior to 4.16.5 contain a Regular Expression Denial of Service (ReDoS) flaw: a specially crafted query string can drive the package's parsing regex into catastrophic backtracking, pinning a CPU core and stalling whatever build, CI job, or server process invoked it. Because browserslist sits several layers deep in almost every modern frontend toolchain, this wasn't a niche bug affecting a handful of projects — it was a transitive dependency of a meaningful share of the entire npm frontend ecosystem.
What Went Wrong
browserslist resolves human-written queries — things like "last 2 versions", ">1%", or version-range strings pulled from a project's .browserslistrc, package.json, or BROWSERSLIST environment variable — into a concrete list of browser/version pairs sourced from caniuse-lite data. To do that resolution, the library runs incoming query strings through regular expressions that match version ranges and comparison operators.
The vulnerable regex, like many ReDoS bugs, was written for the common case and never stress-tested against adversarial input. Certain crafted strings — long sequences of characters designed to create many overlapping ways for the regex engine to match, backtrack, fail, and retry — cause the matching time to blow up exponentially with input length. A payload that looks innocuous in size (a few hundred characters) can take seconds to minutes of pure CPU time to evaluate, and Node.js's single-threaded event loop means that one hung regex match can freeze an entire process: a build pipeline, a dev server, or in some deployments, a running application that resolves browser targets at request time.
This is the textbook ReDoS impact profile: no data is disclosed, no privilege is escalated, but availability takes a direct hit. If an attacker can influence any string that eventually flows into browserslist's query parser — a build config sourced from an untrusted repository, a CI job that reads environment variables from a pull request, or an application that exposes browser-targeting configuration to end users — they can degrade or halt processing with a single crafted string.
Affected Versions and Components
- Package:
browserslist(npm) - Affected versions: all releases prior to 4.16.5
- Fixed version: 4.16.5 and later
- Ecosystem exposure:
browserslistis a transitive dependency ofautoprefixer,postcss-preset-env,babel-preset-env/@babel/preset-env,eslint-plugin-compat, and the tooling underpinning Create React App, Vue CLI, Angular CLI, Next.js, and Gatsby, among many others. Most engineering teams never installedbrowserslistdirectly — it arrived silently through a build tool or framework CLI.
Because so few teams had a direct dependency edge to browserslist, standard "check my package.json" audits frequently missed it entirely. Detection required resolving the full dependency graph, not just the manifest.
CVSS, EPSS, and KEV Context
NVD scored CVE-2021-23364 in the medium severity range under CVSS v3.1, reflecting a network-exploitable, low-complexity issue with no privilege or user-interaction requirement, but with impact confined strictly to availability — confidentiality and integrity are untouched. That scoring pattern is typical of ReDoS findings: high reachability, low blast radius per exploitation event.
EPSS (Exploit Prediction Scoring System) tracks the real-world probability of exploitation attempts in the wild, and CVE-2021-23364 sits in the low percentile band, consistent with most build-time/dev-tooling ReDoS bugs — attackers have historically favored ReDoS payloads against internet-facing request parsers over frontend build tooling, where the attacker typically needs some existing foothold (a malicious PR, a compromised upstream config, or a service that echoes browser query strings) to trigger the flaw at all. It has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and there are no widely reported incidents of active exploitation. That does not mean it's safe to ignore — CVSS and EPSS describe likelihood and theoretical severity, not the disruption a stalled build pipeline causes when it happens to your team at 2 a.m. during a release freeze.
The practical risk calculus for most organizations is this: low likelihood of targeted exploitation, but non-trivial exposure given how deeply browserslist is embedded, and a real cost (CI/CD downtime, delayed releases) if it's triggered even accidentally by a malformed config rather than by an attacker.
Timeline
- Vulnerability introduced: present in
browserslistacross many historical releases, rooted in the version-range parsing regex used since early versions of the query resolver. - March 2021: the ReDoS weakness was identified and reported to the
browserslistmaintainer (Andrey Sitnik). - March 9, 2021: version 4.16.5 shipped, replacing the vulnerable regular expression with a safer, backtracking-resistant implementation.
- Late March 2021: the fix was published as a GitHub Security Advisory and formally assigned CVE-2021-23364, entering the NVD and downstream vulnerability databases (npm audit, GitHub Dependabot, Snyk's and other vendors' feeds).
- Following weeks: downstream frameworks and build tools (Create React App, Babel presets, Autoprefixer consumers) began bumping their pinned or ranged dependency on
browserslistto pull in the patched release, though — as with most deeply transitive fixes — full ecosystem propagation took considerably longer than the patch itself.
Remediation
- Upgrade
browserslistto 4.16.5 or later. For a direct dependency, runnpm install browserslist@latestoryarn upgrade browserslist. For most teams,browserslistis transitive — check withnpm ls browserslist(oryarn why browserslist) to find every path it enters your tree through. - Force resolution across the whole dependency graph. If a parent package (e.g., an old
autoprefixerorreact-scriptsversion) still pins a vulnerable range, useoverrides(npm ≥8.3) orresolutions(Yarn) inpackage.jsonto force every transitive copy up to a patched version without waiting on upstream maintainers to release a new major version. - Refresh the browser data alongside the package. Since
browserslistships alongsidecaniuse-litedata, runnpx browserslist@latest --update-dbafter upgrading to ensure the underlying dataset is current and consistent with the new parser logic. - Regenerate and commit your lockfile. After bumping versions or adding overrides, regenerate
package-lock.json/yarn.lock/pnpm-lock.yamland commit it so CI and every developer machine resolve to the identical, patched version — a stale lockfile silently reintroduces the vulnerable version on the next clean install. - Audit for untrusted input reaching browser-query parsing. Review any code path where
.browserslistrccontent,BROWSERSLIST/BROWSERSLIST_ENVenvironment variables, or build configuration can be influenced by external contributors (fork PRs, user-submitted config, third-party plugins) and treat those as an injection surface, not just a build-time convenience. - Verify with
npm audit/yarn auditand re-scan CI. Confirm the advisory clears in your dependency scanner of choice, and re-run the check in CI, not just locally, since many organizations run divergent lockfiles between developer machines and pipelines. - Extend the fix to CI-cached artifacts and Docker images. Rebuild and re-push any container images or CI caches that baked in the vulnerable
node_modulestree — a patchedpackage.jsonalone won't fix an already-built image with the old dependency layer intact.
How Safeguard Helps
Safeguard is built to catch exactly this class of finding before it becomes a 2 a.m. incident. Our SBOM generation and ingest pipeline continuously maps every transitive dependency edge — including deeply nested ones like browserslist hiding under autoprefixer or a framework CLI — so you get an accurate answer to "am I affected?" without manually walking npm ls across dozens of repos. Griffin AI correlates that inventory against CVE-2021-23364 and flags every affected path automatically, while our reachability analysis distinguishes packages that merely sit in your tree from those whose vulnerable query-parsing code is actually invoked with attacker-influenced input, so your team can triage ReDoS-class findings by real exploitability rather than chasing every medium-severity CVSS score with equal urgency. When a fix is available, Safeguard's auto-fix PRs open the version bump — with overrides/resolutions entries where needed for stubborn transitive pins — directly against your repository, tested and ready to merge. The result is less time spent manually auditing lockfiles for silently-inherited build tooling risk, and faster, more confident remediation across your entire software supply chain.