Back to Blog
high SEVERITY7 min read

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a

O
By Orbis AppSec
Published August 23, 2026Reviewed August 23, 2026

Answer Summary

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability (CWE-1333) in the picomatch JavaScript glob-matching library. Attackers can supply crafted extglob patterns — such as deeply nested `+(...)` constructs — that trigger catastrophic backtracking in picomatch's internally compiled regular expressions, freezing the Node.js event loop. The fix upgrades picomatch from 4.0.3 to 4.0.4 in `client/package-lock.json` and adds a `"picomatch": "4.0.4"` override in `client/package.json` to ensure all transitive dependents resolve to the patched version.

Vulnerability at a Glance

cweCWE-1333
fixUpgrade picomatch to 4.0.4 and pin the version via package.json overrides so all transitive copies are replaced
riskAttacker-supplied glob patterns can freeze the Node.js event loop, causing application-wide denial of service
languageJavaScript / Node.js
root causepicomatch's extglob-to-regex compiler produced patterns with exponential backtracking complexity for certain nested extglob inputs
vulnerabilityRegular Expression Denial of Service (ReDoS)

How Regular Expression Denial of Service Happens in JavaScript and How to Fix It

The Incident: A Glob Library Hiding a Denial-of-Service Trap

The client/package-lock.json in this project pinned picomatch at version 4.0.3 — a version that contains a confirmed Regular Expression Denial of Service (ReDoS) vulnerability tracked as CVE-2026-33671. Trivy's dependency scanner flagged the package during a routine security scan, and an automated fix was generated to upgrade the library and prevent any user-influenced glob pattern from being able to freeze the Node.js event loop.

This post walks through exactly what went wrong, how the attack works at the regex level, and what the fix does to close the door.


The Vulnerability Explained

What Is picomatch?

picomatch is one of the most widely used glob-matching libraries in the JavaScript ecosystem. It converts glob patterns like **/*.js or +(foo|bar) into compiled JavaScript regular expressions and then tests strings against them. It is a transitive dependency of tools like Vite, Rollup, chokidar, and many others — meaning it quietly lives inside almost every modern frontend build toolchain.

Extglob Patterns and the ReDoS Root Cause

The vulnerability lives in picomatch's extglob handling — the +(...), *(...), ?(...), @(...), and !(...) pattern syntax inherited from ksh/bash. When picomatch compiles an extglob pattern into a JavaScript regex, certain nested or repeated extglob structures produce a compiled regex with exponential backtracking complexity.

Consider a pattern like:

+(a+)+b

When compiled naively, the resulting regex contains nested quantifiers over overlapping character classes. If the input string is a long sequence of a characters that does not end in b, the regex engine must explore an exponentially growing number of possible ways to partition the as across the outer and inner + quantifiers before concluding there is no match. This is the classic catastrophic backtracking scenario.

In picomatch 4.0.3, the vulnerable code path is inside the extglob compiler — the function that translates +(...) and friends into raw regex syntax. The generated pattern strings were not checked or rewritten to eliminate ambiguous quantifier nesting.

The Vulnerable Dependency Entry

Before the fix, client/package-lock.json resolved picomatch to:

"node_modules/picomatch": {
  "version": "4.0.3",
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
  "license": "MIT",
  "engines": {
    "node": ">=12"
  }
}

Any code that eventually calls picomatch(pattern) with a user-influenced pattern string — or passes user data through a library that internally uses picomatch for file filtering — is potentially reachable by this attack.

Real-World Attack Scenario

Imagine a build-tool API endpoint or a file-watching feature that accepts a glob pattern from the user (e.g., a "watch files matching this pattern" configuration field). An attacker submits:

+(a+(b+(c+(d+(e+(f+)))))+)

picomatch 4.0.3 compiles this into a deeply nested regex. When the application then tests even a moderately long string against the compiled pattern, the JavaScript regex engine enters catastrophic backtracking. Because Node.js runs JavaScript on a single-threaded event loop, the entire server — including all concurrent requests — is blocked for the duration of the backtrack storm. A single HTTP request containing a crafted pattern is enough to cause application-wide denial of service.


The Fix

Two-File Change, One Clear Goal

The fix touches exactly two files: client/package-lock.json and client/package.json. Here is what each change does.

1. client/package-lock.json — Upgrade the Resolved Version

Before:

"node_modules/picomatch": {
  "version": "4.0.3",
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
}

After:

"node_modules/picomatch": {
  "version": "4.0.4",
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
}

The new integrity hash (sha512-QP88...) cryptographically binds the installed package to the exact patched release, preventing supply-chain substitution.

2. client/package.json — Pin via overrides

Before:

"overrides": {
  "nanoid": "3.3.18"
}

After:

"overrides": {
  "nanoid": "3.3.18",
  "picomatch": "4.0.4"
}

This is the critical second step. Without the overrides entry, any transitive dependency that declares a loose picomatch range (e.g., "picomatch": "^2.0.0") could still resolve to a vulnerable version deeper in the dependency tree. The overrides field in npm forces every node in the dependency graph that requires picomatch to receive version 4.0.4, regardless of what semver range they declare.

Why the Fix Works

Picomatch 4.0.4 rewrites the extglob-to-regex compiler to produce patterns that avoid ambiguous quantifier nesting. The fix applies atomic groups or possessive quantifiers where available, and restructures the generated regex alternations so that the engine can fail fast without exploring exponential match paths. Valid glob patterns continue to match exactly as before — the change only affects how the regex is internally structured, not what it accepts.


Prevention & Best Practices

1. Use overrides / resolutions for Security-Critical Transitive Dependencies

When a vulnerability is in a deeply transitive dependency, simply upgrading your direct dependencies may not be enough. npm's overrides field (and Yarn's resolutions) let you force a specific version across the entire tree:

"overrides": {
  "picomatch": "4.0.4"
}

Always verify the override took effect by running npm ls picomatch and confirming no stale copies remain.

2. Never Pass Unsanitized User Input to Glob Matchers

Treat glob patterns from user input the same way you treat SQL queries: validate them before use. Consider:

  • Allowlisting a set of safe pattern characters (e.g., [a-zA-Z0-9_\-\/\*\?\.])
  • Rejecting patterns that contain extglob syntax (+(, *(, !(, etc.) unless your application explicitly requires it
  • Enforcing a maximum pattern length

3. Integrate Dependency Scanning into CI

Tools like Trivy, npm audit, Snyk, and OWASP Dependency-Check can catch known-vulnerable versions before they reach production. Add a step to your CI pipeline:

# Example: fail the build on high-severity findings
trivy fs --exit-code 1 --severity HIGH,CRITICAL .

4. Monitor for ReDoS-Prone Regex Patterns

For custom regex in your own code, use tools like:
- vuln-regex-detector — static analysis for catastrophic backtracking
- safe-regex — npm package that flags dangerous patterns
- Semgrep — rules for detecting ReDoS-prone patterns in source code

5. Relevant Standards


Key Takeaways

  • Extglob patterns in picomatch 4.0.3 compile to regexes with exponential backtracking — a single crafted pattern string is enough to block the entire Node.js event loop.
  • Upgrading package-lock.json alone is not sufficient — without the "picomatch": "4.0.4" entry in overrides, transitive dependents can still resolve to a vulnerable copy.
  • The integrity hash change from sha512-5gTmg... to sha512-QP88... is your cryptographic proof that the patched binary is installed, not just a version number bump.
  • ReDoS is a single-threaded event loop killer — unlike memory exhaustion, a ReDoS attack requires no persistence, no authentication, and produces no noisy error logs until the server is already unresponsive.
  • Glob patterns from user input are an under-appreciated attack surface — any feature that lets users specify file patterns, watch paths, or filter expressions should validate or sandbox those patterns before passing them to libraries like picomatch.

How Orbis AppSec Detected This

  • Source: User-influenced glob pattern strings passed into picomatch's pattern compiler (e.g., via build-tool configuration endpoints or file-watcher APIs that accept patterns from external input).
  • Sink: picomatch's internal extglob-to-regex compilation function, invoked whenever picomatch(pattern) is called with an extglob-containing string — resolved to node_modules/picomatch version 4.0.3 in client/package-lock.json.
  • Missing control: No version constraint or overrides pin prevented the vulnerable 4.0.3 release from being installed; no input validation blocked extglob patterns from reaching the compiler.
  • CWE: CWE-1333 — Inefficient Regular Expression Complexity.
  • Fix: picomatch was upgraded to 4.0.4 in client/package-lock.json and pinned via "picomatch": "4.0.4" in the overrides section of client/package.json to ensure all transitive copies resolve to the patched version.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-33671 is a reminder that denial-of-service vulnerabilities don't require memory corruption or authentication bypass — a carefully constructed string fed to an unsuspecting regex engine is enough. picomatch's extglob compiler in versions before 4.0.4 / 3.0.2 / 2.3.2 produced internally unsafe regex patterns that could be triggered by any code path accepting user-influenced glob strings.

The fix is clean and minimal: two files changed, one version number bumped, one overrides entry added. But the lesson is broader — transitive dependencies deserve the same security scrutiny as your own code, and dependency overrides are a powerful tool for enforcing patched versions across an entire dependency graph.

Keep your lock files up to date, integrate scanner tooling into CI, and treat user-supplied pattern strings with the same suspicion you'd give to SQL input.


References

Frequently Asked Questions

What is a Regular Expression Denial of Service (ReDoS)?

ReDoS is an attack where a specially crafted input causes a regex engine to explore an exponentially large number of possible match paths (catastrophic backtracking), consuming 100% CPU and blocking other work for seconds, minutes, or indefinitely.

How do you prevent ReDoS in JavaScript?

Use well-maintained libraries that have been audited for catastrophic backtracking, pin dependency versions with overrides/resolutions, validate and sanitize user-supplied glob patterns before passing them to matching functions, and consider using linear-time regex engines or safe glob libraries.

What CWE is ReDoS?

ReDoS is classified under CWE-1333 (Inefficient Regular Expression Complexity), though it is also related to CWE-400 (Uncontrolled Resource Consumption).

Is input validation alone enough to prevent ReDoS in picomatch?

Input validation helps but is not sufficient on its own, because the vulnerable regex complexity lives inside picomatch's compiled patterns — the only reliable fix is upgrading to a patched version (4.0.4 / 3.0.2 / 2.3.2) that rewrites those patterns.

Can static analysis detect ReDoS vulnerabilities?

Yes. Tools like Trivy (which flagged this exact issue), Semgrep, and dedicated ReDoS analyzers such as vuln-regex-detector can identify known-vulnerable package versions and regex patterns with polynomial or exponential backtracking complexity.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2123

Related Articles

high

How Denial of Service via Regular Expression Happens in Node.js Dependencies and How to Fix It

A high-severity denial of service vulnerability in the `path-to-regexp` package (CVE-2026-4926) could allow attackers to craft malicious regular expressions that consume excessive CPU resources. The fix upgrades from version 8.2.0 to 8.4.0, which hardens regex handling and prevents ReDoS (Regular Expression Denial of Service) attacks in Express applications.

critical

How ReDoS Vulnerabilities Happen in Node.js Express Applications and How to Fix Them

A critical Regular Expression Denial of Service (ReDoS) vulnerability in the path-to-regexp package (CVE-2024-45296) was discovered in the lacartoons-addon project's dependency tree. The vulnerable versions used backtracking regular expressions that could cause catastrophic performance degradation when processing malicious route patterns. Upgrading to patched versions (0.1.10 for Express's internal router) eliminates this attack vector.

high

How API key exposure and ReDoS happens in Node.js and how to fix it

A critical vulnerability in `roll/openai.js` could expose OpenAI API keys to client-side JavaScript bundles, allowing attackers to extract secrets from browser developer tools. Additionally, a Regular Expression Denial of Service (ReDoS) pattern in the `generateErrorMessage()` method could crash the process. Both issues were fixed with targeted, minimal code changes.

high

How ReDoS happens in Node.js MCP SDK and how to fix it

A Regular Expression Denial of Service (ReDoS) vulnerability was discovered in Anthropic's Model Context Protocol (MCP) TypeScript SDK version 1.24.0. This high-severity flaw (CVE-2026-0621) could allow attackers to craft malicious input that causes catastrophic regex backtracking, freezing the Node.js event loop. The fix involves upgrading to @modelcontextprotocol/sdk version 1.25.2, which patches the vulnerable regex patterns.

high

How Regular Expression Denial of Service happens in Node.js picomatch extglob patterns and how to fix it

A Regular Expression Denial of Service (ReDoS) vulnerability in picomatch versions prior to 2.3.2, 3.0.2, and 4.0.4 allowed attackers to craft malicious extglob patterns that triggered catastrophic backtracking in the regex engine, potentially freezing Node.js applications. The fix, tracked as CVE-2026-33671, involved upgrading picomatch to patched versions and pinning the dependency explicitly in `package.json` to ensure the safe version is resolved across the dependency tree.

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.