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:
- Uploading a file with a specially crafted filename containing repetitive characters
- Submitting a log entry with a malicious string via an HTTP request
- Feeding a configuration file containing ReDoS payload through
trim-newlinesprocessing
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:
-
Audit regex patterns – Use tools like
safe-regexnpm package to detect ReDoS vulnerabilities:
bash npm install --save-dev safe-regex # Scan your codebase for dangerous patterns -
Avoid nested quantifiers:
```javascript
// ❌ BAD - Nested quantifiers cause exponential backtracking
/^(a+)+$/
/^(a|a)+$/
// ✅ GOOD - Atomic or non-overlapping patterns
/^a+$/
/^(?:a)+$/
```
-
Use atomic groups (in engines that support them):
javascript // Prevents backtracking after a successful match /(?>a+)b/ -
Keep dependencies updated – Regularly run:
bash npm audit npm outdated npm update -
Use dependency overrides for transitive vulnerabilities, just like in this fix.
-
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.redospatterns to detect these issues in CI/CD pipelines
Key Takeaways
-
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. -
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. -
Integrity hashes matter – The change from
sha1-WIeWa7WCpFA6QetST301ARgVphM=tosha512-5n5GIW0uEbjCB2PO6OoaG11rscJmLOLw12ZG9e0vBKNMToDJ2n1+AkUhJpGO2bLj3jXKa/gYTGVmilX5CCxqmA==ensures only the patched version is installed; without it, npm could downgrade to the vulnerable version. -
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.
-
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-newlinespackage's.end()method, including CLI arguments parsed bymeow, configuration file contents, or user-supplied filenames - Sink: The
.end()method's regex pattern intrim-newlines@1.0.0that 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-newlinesfrom version 1.0.0 to 4.0.1 across all dependency branches usingpackage.jsonoverrides, 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.