On September 17, 2021, maintainers of the widely used ansi-regex npm package shipped a silent fix for a regular expression denial-of-service (ReDoS) flaw that had been sitting in the library's terminal-escape-code-stripping logic for years. That fix was later tracked as CVE-2021-3807. The vulnerability allows an attacker who can supply a crafted, attacker-controlled string to ansi-regex's matching function to trigger catastrophic backtracking, pinning a CPU core at 100% and effectively hanging the process. Because ansi-regex sits several layers deep in the dependency tree of an enormous number of CLI tools, test runners, and build pipelines — pulled in transitively through packages like strip-ansi, string-width, wrap-ansi, cliui, and ultimately yargs — the practical blast radius of this single regex bug touched a meaningful share of the JavaScript ecosystem, even though most consuming applications never call the vulnerable code path with untrusted input.
What ansi-regex does, and where the bug lives
ansi-regex exists to build a regular expression that matches ANSI escape codes — the invisible control sequences terminals use for color, cursor movement, and text styling. Tools use it to strip those codes out of strings before measuring string length, logging output, or rendering it somewhere that doesn't understand ANSI. It's a tiny, single-purpose utility, which is exactly why it ended up everywhere: chalk, boxen, inquirer, eslint, jest, and dozens of other foundational tools depend on it directly or indirectly.
The flaw is in the regex pattern itself. Certain constructions in the pattern allowed specially crafted input strings — long runs of specific characters — to force the regex engine into exponential-time backtracking rather than the linear-time matching the library was designed for. Feed the function a string built to trigger this worst case, and the single-threaded Node.js event loop that's evaluating the regex simply stops responding until the operation completes, which for a sufficiently long malicious input can mean minutes, hours, or effectively forever.
Affected versions
Per the GitHub Security Advisory (GHSA-93q8-gq69-wqmw) that accompanies CVE-2021-3807, the vulnerable regex existed across multiple major version lines of ansi-regex, not just the latest release at the time. Maintainers backported fixes to each active branch:
ansi-regex< 3.0.1— fixed in3.0.1ansi-regex>= 4.0.0, < 4.1.1— fixed in4.1.1ansi-regex>= 5.0.0, < 5.0.1— fixed in5.0.1ansi-regex>= 6.0.0, < 6.0.1— fixed in6.0.1
Because so few projects pin ansi-regex as a direct dependency, most exposure comes in transitively. If your package-lock.json or yarn.lock resolves ansi-regex to anything below these patched floors — commonly because a mid-tree dependency like wrap-ansi@6, string-width@4, or an older yargs/inquirer release pinned a range that predates the fix — your build, CLI, or server process is exposed whenever it processes attacker-influenced strings through that code path.
Severity, CVSS, EPSS, and KEV status
NVD scores CVE-2021-3807 at 7.5 (High) under CVSS 3.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) — network-exploitable, no privileges or user interaction required, with a pure availability impact and no confidentiality or integrity loss. That score reflects the theoretical ceiling of the bug's exploitability, not the practical risk of any given deployment. The base score assumes an attacker has a network-reachable path to feed input directly into the vulnerable regex; in reality, most consumers of ansi-regex only run it against strings they generated themselves (their own terminal output, their own CLI arguments), which sharply narrows real-world exploitability to services that pipe untrusted, attacker-controlled text through logging, formatting, or CLI-wrapping libraries built on top of it.
Consistent with that narrower practical exposure, EPSS (Exploit Prediction Scoring System) has kept this CVE in the low single-digit-percent range or below for most of its life — reflecting the absence of any known working exploit chain and the fact that meaningful exploitation requires a specific, less-common data flow. CVE-2021-3807 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, and there are no public reports of it being weaponized in the wild. It is best understood as a real, patchable defect with a legitimate CVSS rating rather than an active threat — which is exactly the kind of finding that clutters vulnerability backlogs when severity data isn't paired with exploitability and reachability context.
Timeline
- September 17, 2021 —
ansi-regexmaintainer Sindre Sorhus publishes fixed releases (3.0.1,4.1.1,5.0.1,6.0.1) alongside the GitHub Security Advisory GHSA-93q8-gq69-wqmw disclosing the ReDoS pattern. - Late September–October 2021 — The advisory is mirrored into the NVD, and the issue is assigned CVE-2021-3807, entering the standard vulnerability databases that SCA tools ingest.
- October 2021 onward — Automated dependency scanners (
npm audit, Dependabot, Snyk, etc.) begin flaggingansi-regexacross millions of repositories, largely as a transitive dependency ofwrap-ansi,string-width,cliui, and their downstream consumers. - 2022–present — Because
ansi-regexis buried so deep in the dependency graph of the JavaScript tooling ecosystem, the CVE continues to surface in scans of projects that haven't refreshed theiryarn.lock/package-lock.jsonin a while, even though upstream packages resolved the transitive version long ago. It remains one of the most frequently re-flagged "old" findings in npm dependency audits, precisely because lockfile staleness — not active neglect of the direct fix — keeps reintroducing it, alongside plenty of other unrelated 2021 disclosures (CVE-2021-3918 is another one that tends to linger in the same stale-lockfile scans) that warrant their own separate remediation, not a blanket fix.
Remediation steps
- Identify where the vulnerable version resolves. Run
npm ls ansi-regex(oryarn why ansi-regex/pnpm why ansi-regex) to see every path in your dependency graph that pulls in an affected version. Because it's almost always transitive, expect multiple resolution paths at different depths. - Update the direct dependency chain first. Bump the immediate parent package (
wrap-ansi,string-width,cliui,yargs,inquirer, etc.) to a version that itself depends on a patchedansi-regex. This is the cleanest fix because it removes the vulnerable resolution without fighting the natural dependency tree. - Force a resolution override if an upstream fix isn't available yet. If a maintaining package hasn't bumped its
ansi-regexrange, useoverrides(npm 8.3+),resolutions(Yarn), orpnpm.overridesin your rootpackage.jsonto pinansi-regexto^5.0.1,^6.0.1, or later across the entire tree, regardless of what individual packages request. - Regenerate and commit your lockfile. After updating or overriding, run a clean install and commit the updated
package-lock.json/yarn.lock/pnpm-lock.yamlso CI and every future clone resolve the patched version — this is the step most teams skip, which is why the CVE keeps reappearing in scans. - Re-run
npm audit/ your SCA tool to confirm the finding clears. Validate against the actual resolved version in the lockfile, not just the version ranges declared inpackage.json. - Prioritize based on reachability, not just presence. If your scanner flags
ansi-regexin a build-time-only tool (e.g., a linter or bundler that never touches attacker-controlled input at runtime), treat it as a lower-urgency lockfile hygiene item. If it sits in a runtime path that formats or logs user-supplied strings — a chat app, a log viewer, an API that echoes client input back through a CLI-styling library — treat it as a genuine availability risk and patch on an accelerated timeline. This same reachability-first approach applies if you're triaging CVE-2021-3807 alongside a broader batch of unrelated 2021-era CVEs (npm audit runs frequently surface names like CVE-2021-3331 or CVE-2021-41183 in the same report) — presence in a lockfile isn't evidence of exploitability for any of them, and each deserves its own reachability check rather than a blanket severity-based triage.
How Safeguard Helps
This is precisely the kind of CVE that overwhelms teams relying on presence-only scanning: it's genuinely old, genuinely transitive, and genuinely low-risk in most deployments, yet it re-triggers alerts every time a lockfile goes stale. Safeguard's reachability analysis traces whether the vulnerable ansi-regex matching function is actually invoked on a path that touches attacker-influenced data in your specific application, so you can immediately deprioritize the copies buried in build-only tooling and focus on the handful of services where it matters. Griffin AI correlates that reachability signal with the CVSS, EPSS, and KEV context above to produce a single, defensible risk score instead of a flat severity label, and it can explain in plain language why a given instance is or isn't urgent. Safeguard's SBOM generation and ingestion continuously map every transitive path ansi-regex enters your tree through — wrap-ansi, string-width, cliui, and beyond — so you see the full blast radius across every repo at once rather than chasing it service by service. When action is warranted, Safeguard opens an auto-fix pull request that bumps the resolved version or adds the correct overrides/resolutions entry and regenerates the lockfile, closing the loop from detection to merged fix without manual dependency archaeology.