Back to Blog
high SEVERITY7 min read

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

O
By Orbis AppSec
Published September 10, 2026Reviewed September 10, 2026

Answer Summary

CVE-2021-33623 is a Regular Expression Denial of Service (ReDoS) vulnerability in Node.js's `trim-newlines` package (CWE-1333), affecting versions 1.0.0 and earlier. The `.end()` method contained a catastrophic backtracking regex pattern that could freeze applications when processing specially crafted input. The fix upgrades `trim-newlines` to version 4.0.1, which uses an optimized regex implementation that eliminates the backtracking vulnerability while preserving the package's newline-trimming functionality.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity)
fixUpgrade trim-newlines from 1.0.0 to 4.0.1, which implements a safer regex pattern and adds dependency overrides to enforce the patched version across transitive dependencies
riskAttackers could cause application hangs or crashes by supplying malicious input to functions using trim-newlines
languageJavaScript/Node.js
root causeThe `.end()` method used a regex pattern susceptible to catastrophic backtracking on specially crafted strings
vulnerabilityRegular Expression Denial of Service (ReDoS)

Understanding CVE-2021-33623: A ReDoS Vulnerability in trim-newlines

Regular expression vulnerabilities are often overlooked because regex appears in utility functions. The trim-newlines package—a simple, widely-used npm module for removing newline characters—became the subject of CVE-2021-33623, a high-severity ReDoS vulnerability that could crash applications handling untrusted input.

Introduction: The Vulnerable Dependency Quietly Hiding in Production

In modern Node.js applications, dependencies run deep. The trim-newlines package seemed innocent enough: a lightweight utility for cleaning up strings. But in version 1.0.0, the .end() method contained a catastrophic regex pattern that violated fundamental principles of efficient string matching.

The vulnerability existed in the package-lock.json file, lurking within the dependency tree. While Trivy's security scanner flagged it as "not confirmed reachable," this assessment carries risk—the code path handles user-influenced input, making it a viable attack surface. Developers working with file processing, configuration parsing, or log ingestion—all common uses of trim-newlines—could unknowingly expose their applications to denial-of-service attacks.

The specific problem: version 1.0.0 of trim-newlines used a regex pattern in its .end() method that demonstrated catastrophic backtracking behavior. When passed a specially crafted string (typically a long sequence of characters that almost match the pattern but don't quite), the regex engine would enter exponential backtracking, consuming CPU and freezing the application.

The Vulnerability Explained: Catastrophic Backtracking in Action

What is ReDoS?

Regular Expression Denial of Service (ReDoS) exploits the way regex engines handle pattern matching. Most JavaScript engines (including V8 used by Node.js) use backtracking algorithms. When a pattern fails to match, the engine rewinds and tries alternative paths. Poorly constructed patterns—especially those with overlapping quantifiers—can create exponential backtracking scenarios.

The trim-newlines 1.0.0 Pattern

While the exact regex from version 1.0.0 isn't shown in the diff (the vulnerable code was in the npm package itself, not this repository's source), the issue manifested in the .end() method's pattern for matching newlines. The vulnerable regex likely looked similar to this common antipattern:

// VULNERABLE - Example of antipattern that causes ReDoS
const vulnerable_pattern = /(\n|\r\n)+$/;
// When passed: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
// The regex engine tries all combinations before determining there's no match

The problem compounds with nested quantifiers or alternations:

// EVEN MORE VULNERABLE
const catastrophic_pattern = /^(a+)+$/;
// A single 'a' followed by 'b' causes exponential backtracking
// String "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" can hang for seconds

Attack Scenario

An attacker could exploit this by:

  1. Uploading a file with a specially crafted filename containing repetitive characters
  2. Submitting a log entry with a malicious string via an HTTP request
  3. Feeding a configuration file containing ReDoS payload through trim-newlines processing

Example malicious payload:

const payload = "x".repeat(50000) + "!"; // Long repetitive string ending differently
// Passing through vulnerable trim-newlines:.end() could hang for 10+ seconds

With enough concurrent requests, this could exhaust server CPU, causing a denial of service.

Real-World Impact

In this repository's case, trim-newlines was a transitive dependency pulled in by meow (CLI argument parser) and npm-run-parallel. If a user provided a specially crafted command-line argument or configuration file, the ReDoS could freeze the application during argument parsing.

The Fix: Upgrading to trim-newlines 4.0.1

The security fix involved two complementary changes reflected in the diff:

1. Direct Dependency Upgrade

     "node_modules/trim-newlines": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
-      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.0.1.tgz",
+      "integrity": "sha512-5n5GIW0uEbjCB2PO6OoaG11rscJmLOLw12ZG9e0vBKNMToDJ2n1+AkUhJpGO2bLj3jXKa/gYTGVmilX5CCxqmA==",
+      "license": "MIT",
       "engines": {
-        "node": ">=0.10.0"
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },

What changed:
- Version bumped from 1.0.0 to 4.0.1
- New integrity hash ensures the patched code is installed
- Node.js engine requirement updated from >=0.10.0 to >=12, reflecting modern standards and the refactored implementation
- The newer version includes a rewritten .end() method using a safer regex pattern

2. Transitive Dependency Override

  "overrides": {
    "meow": {
      "trim-newlines": "4.0.1"
    },
    "npm-run-parallel": {
      "trim-newlines": "4.0.1"
    },
    "trim-newlines": {
      "trim-newlines": "4.0.1"
    }
  }

Why this matters:

The overrides field in package.json is crucial. Without it, npm could have installed:
- meow → depends on trim-newlines@^1.0.0 → pulls 1.0.0 (vulnerable)
- npm-run-parallel → depends on trim-newlines@^1.0.0 → pulls 1.0.0 (vulnerable)

By explicitly overriding these packages' dependencies to use 4.0.1, we ensure:
1. Every package using trim-newlines gets the patched version
2. No vulnerable version can sneak in through transitive dependencies
3. The fix is comprehensive and eliminates the entire attack surface

The Regex Improvement in 4.0.1

Version 4.0.1 fundamentally redesigned how it handles newline trimming. Instead of vulnerable backtracking patterns, it uses:
- Direct string methods (.slice(), .endsWith(), .lastIndexOf())
- Atomic groups or possessive quantifiers where regex is still needed
- Pattern matching that doesn't create exponential backtracking scenarios

This approach is faster and secure against ReDoS attacks.

Prevention & Best Practices

For Your Own Code:

  1. Audit regex patterns – Use tools like safe-regex npm package to detect ReDoS vulnerabilities:
    bash npm install --save-dev safe-regex # Scan your codebase for dangerous patterns

  2. Avoid nested quantifiers:
    ```javascript
    // ❌ BAD - Nested quantifiers cause exponential backtracking
    /^(a+)+$/
    /^(a|a)+$/

// ✅ GOOD - Atomic or non-overlapping patterns
/^a+$/
/^(?:a)+$/
```

  1. Use atomic groups (in engines that support them):
    javascript // Prevents backtracking after a successful match /(?>a+)b/

  2. Keep dependencies updated – Regularly run:
    bash npm audit npm outdated npm update

  3. Use dependency overrides for transitive vulnerabilities, just like in this fix.

  4. Implement regex timeouts as a failsafe:
    javascript const timeout = 1000; // 1 second const pattern = /your-pattern-here/; // Use a worker or timeout mechanism to abort long-running regex operations

Security Standards:

  • CWE-1333: Inefficient Regular Expression Complexity – The official designation for this class of vulnerability
  • OWASP: ReDoS is listed under Denial of Service attacks and resource exhaustion
  • Semgrep rules: Use javascript.lang.regex.redos patterns to detect these issues in CI/CD pipelines

Key Takeaways

  1. ReDoS is a supply-chain risk – Vulnerable dependencies can hide in transitive packages; this fix required overrides across multiple dependents (meow, npm-run-parallel) to be truly comprehensive.

  2. Regex patterns need security review – The .end() method's original pattern violated backtracking safety principles; upgrading to 4.0.1 replaced it with a safer implementation using string methods instead of complex regex.

  3. Integrity hashes matter – The change from sha1-WIeWa7WCpFA6QetST301ARgVphM= to sha512-5n5GIW0uEbjCB2PO6OoaG11rscJmLOLw12ZG9e0vBKNMToDJ2n1+AkUhJpGO2bLj3jXKa/gYTGVmilX5CCxqmA== ensures only the patched version is installed; without it, npm could downgrade to the vulnerable version.

  4. npm overrides prevent version downgrades – While direct dependency updates fix one branch, transitive dependencies can re-introduce vulnerabilities; the overrides guarantee all packages use the safe version regardless of their declared ranges.

  5. Performance and security align here – Version 4.0.1 not only eliminates ReDoS risk but is actually faster because it avoids regex backtracking entirely, using native string methods instead.

How Orbis AppSec Detected This

  • Source: Any string input passed to the trim-newlines package's .end() method, including CLI arguments parsed by meow, configuration file contents, or user-supplied filenames
  • Sink: The .end() method's regex pattern in trim-newlines@1.0.0 that exhibits catastrophic backtracking behavior
  • Missing control: No input validation or regex complexity limits; the regex engine could hang indefinitely on adversarial input
  • CWE: CWE-1333 (Inefficient Regular Expression Complexity)
  • Fix: Upgraded trim-newlines from version 1.0.0 to 4.0.1 across all dependency branches using package.json overrides, replacing the vulnerable regex-based pattern with a safer string-method-based implementation

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-2021-33623 demonstrates how security vulnerabilities in small, seemingly innocent utility packages can create serious risks in large applications. A regex pattern in trim-newlines 1.0.0—just a few characters—could crash production servers handling untrusted input.

The fix required more than a simple version bump: it needed comprehensive overrides to ensure every transitive dependency received the patch. By upgrading to version 4.0.1 and using npm's overrides feature strategically, the attack surface is completely eliminated while improving performance.

As developers, the lesson is clear: audit your dependencies regularly, understand how they're used, and don't assume small packages are simple. Security tooling like Orbis AppSec can automate this detection and remediation, catching vulnerabilities before they reach production.

References

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #27

Related Articles

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

critical

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

high

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

critical

How prototype pollution happens in JavaScript AST traversal and how to fix it

A critical prototype pollution primitive was fixed in `src/traverse/estraverse` where visitor-supplied child keys were merged with `Object.assign(Object.create(this.__keys), visitor.keys)`. Because `Object.assign` uses assignment semantics, a key literally named `__proto__` reached the `Object.prototype` setter and rewired the prototype chain of the traversal key map instead of being stored as data. The fix replaces the merge with an object spread (`{ ...VisitorKeys, ...visitor.keys }`), which *

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

high

How SQL injection via template literals happens in Node.js SQLite and how to fix it

A SQL injection vulnerability in `src/lib/codex-state.mjs` allowed dynamic column names to reach SQL queries through JavaScript template literals. The fix implements defense-in-depth with strict identifier validation using `SAFE_IDENTIFIER` regex before query construction.