Lede
CVE-2021-3803 is a Regular Expression Denial of Service (ReDoS) vulnerability in nth-check, a small but extraordinarily widely-used npm package that parses the "nth" argument of CSS3 pseudo-selectors such as :nth-child(an+b). A crafted, attacker-controlled selector string can trigger catastrophic regex backtracking in nth-check's parsing logic, causing CPU consumption to spike and effectively hanging the process that invokes it. On its own, that sounds like a niche edge case. In practice, nth-check sits several layers deep in the dependency tree of css-select, which in turn is a dependency of svgo (the SVG optimizer), which in turn was bundled inside react-scripts — the build tooling behind Create React App. That chain turned a small parsing bug into one of the most-reported npm audit findings of 2021, showing up in thousands of frontend repositories that had no direct relationship to nth-check at all.
The vulnerability is a textbook illustration of how supply chain risk compounds: a single inefficient regular expression, four dependency layers removed from the application code a developer actually wrote, became a finding that security teams everywhere had to triage, explain, and — in many cases — wait months to remediate because the fix had to propagate up through several maintainers' release cycles before it reached the tools developers actually installed.
Affected Versions and Components
- Vulnerable component:
nth-checknpm package, versions prior to 2.0.1. - Patched version:
nth-check2.0.1 and later, which replaced the vulnerable regular expression with a safer parsing approach. - Root cause: an inefficient regular expression used to parse the
an+bmicro-syntax inside CSS:nth-child()/:nth-of-type()style selectors. Certain crafted input strings cause exponential backtracking in the regex engine. - Primary transitive path:
nth-checkis a direct dependency ofcss-select, which is a dependency ofsvgo(versions in the 1.x line), which was in turn pulled in byreact-scripts(Create React App's build tooling) via its Webpack/SVG-handling toolchain. - Downstream blast radius: because
react-scriptswas one of the most widely installed packages in the JavaScript ecosystem, any project scaffolded with Create React App — and never having pinned or overridden its transitive dependencies — inherited the vulnerable nth-check version without the application developer ever importing it directly.
This is the detail that made CVE-2021-3803 memorable: most of the people fixing it were not calling nth-check, css-select, or even svgo from their own code. They were fixing a finding that surfaced purely because of how deep npm's dependency resolution goes, and because react-scripts did not always resolve to a patched svgo/css-select/nth-check chain on its own.
CVSS, EPSS, and KEV Context
The vulnerability was assigned a CVSS v3.1 base score of 7.5 (High), reflecting a network-exploitable, low-complexity issue that requires no privileges or user interaction but whose impact is confined to availability (a denial-of-service condition) — it does not expose confidentiality or allow integrity tampering. That scoring profile is typical of ReDoS bugs: the "worst case" is a hung process or exhausted worker thread, not data exposure or remote code execution.
CVE-2021-3803 has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and there is no widely reported evidence of active exploitation in the wild. That's consistent with the nature of the flaw: exploiting it requires getting attacker-controlled input into a code path that calls nth-check's selector parser — most commonly in build-time SVG optimization or server-side HTML/SVG scraping and sanitization workflows, rather than in a typical end-user-facing request path. This is a meaningful nuance for risk prioritization: the severity score is high because of the mechanics of the flaw, but real-world exploitability depends heavily on whether an application actually routes untrusted, attacker-influenced strings into a CSS selector parser at runtime. For most Create React App consumers, the exposure was almost entirely a build-time dependency hygiene issue rather than a live production attack surface — which is exactly why it generated so much audit noise without a matching wave of real incidents.
Timeline
- Disclosure: The nth-check ReDoS was reported and published as a GitHub Security Advisory (GHSA-rp65-9cf3-cjxr) and subsequently assigned CVE-2021-3803, entering public vulnerability databases in 2021.
- Fix released upstream: The nth-check maintainers shipped version 2.0.1, replacing the vulnerable regex with a non-backtracking parser implementation.
- Propagation lag: Because nth-check was buried under
css-selectandsvgo, the fix did not reach downstream consumers immediately.svgohad to bump itscss-select/nth-checkdependency, andreact-scriptshad to bump itssvgodependency (a change that eventually landed as part of the react-scripts 5.x line). In the interim, developers runningnpm auditon Create React App projects saw persistent high-severity warnings they could not resolve with a simplenpm update, because the top-level package (react-scripts) had not yet released a version pointing at the patched chain. - Community workarounds: During the gap, the common mitigation was to force resolution of the patched nth-check version using
resolutions(Yarn) oroverrides(npm) inpackage.json, without waiting on react-scripts to catch up — a pattern that became one of the more widely copy-pasted supply chain fixes of that year.
Remediation Steps
- Identify exposure. Run
npm ls nth-checkoryarn why nth-checkto determine whether a vulnerable version (<2.0.1) is present anywhere in your dependency tree, direct or transitive. Most exposure will trace back throughcss-selectandsvgo. - Upgrade the direct dependency where possible. If your project depends on
css-select,svgo, ornth-checkdirectly, upgrade to versions that pull in nth-check 2.0.1 or later. - Force resolution for transitive cases. If the vulnerable version is nested under a package like
react-scriptsthat hasn't yet released a patched chain, useoverrides(npm 8.3+) orresolutions(Yarn) to pinnth-checkto^2.0.1directly inpackage.json, then reinstall and re-audit. - Upgrade
react-scriptswhen feasible. Moving to a react-scripts release built on a patched svgo/css-select chain removes the need for manual overrides and reduces future drift. - Re-run
npm audit/yarn auditand your SCA tooling after remediation to confirm the finding clears, and check that the override didn't silently fail to apply (a common gotcha when lockfiles aren't regenerated). - Assess actual runtime exposure, not just the presence of the package. If nth-check's parsing path is only exercised at build time (e.g., SVG optimization during
npm run build), your production risk profile differs from an application that parses attacker-supplied CSS selectors at request time — prioritize accordingly, but don't skip the fix, since build pipelines are themselves a supply chain target. - Regenerate lockfiles and rebuild your SBOM after the change so your software bill of materials accurately reflects the patched dependency graph, not a stale snapshot from before remediation.
How Safeguard Helps
CVE-2021-3803 is a case study in why dependency-tree depth, not just direct imports, has to be part of vulnerability management. Safeguard is built around that reality:
- Full transitive dependency visibility. Safeguard maps your entire dependency graph — not just
package.jsontop-level entries — so a finding buried four layers deep underreact-scripts → svgo → css-select → nth-checksurfaces automatically instead of requiring a manualnpm lsspelunking session. - Reachability-aware risk scoring. Rather than flagging every project that merely has a vulnerable package present, Safeguard helps distinguish build-time-only exposure from code paths where untrusted input actually reaches the vulnerable parser, so teams can triage by real risk instead of raw CVSS score alone.
- Continuous SBOM and drift detection. Safeguard keeps a live software bill of materials for every repository, so when an upstream maintainer finally ships the patched chain (as happened here with react-scripts catching up to nth-check 2.0.1), you get notified that you can safely remove a manual override — closing the loop instead of leaving pinned overrides to rot indefinitely.
- Policy-driven remediation workflows. Safeguard can enforce
overrides/resolutionspolicies across an organization's repositories, ensuring that when a fix for a transitive ReDoS or similar issue is available, it's applied consistently rather than depending on individual developers remembering to patch each project by hand. - Alerting tied to your actual exposure window. Because CVE-2021-3803 had a real propagation lag between the upstream fix and downstream availability, Safeguard tracks the gap between "patch exists" and "patch is reachable through your dependency resolution," so security teams know exactly when a temporary override is needed versus when it's safe to standardize on the upstream fix.
Small, deeply nested packages like nth-check rarely get security attention until an advisory forces the issue — and by then, the fix has to travel through several maintainers before it reaches the applications that depend on it. Safeguard's job is to shorten that gap: surfacing transitive risk early, scoring it against real reachability, and keeping remediation moving until the entire dependency chain — not just the top-level package — is actually patched.