Back to Blog
high SEVERITY6 min read

How Quadratic-Complexity DoS via mailto: Validator happens in linkify-it and how to fix it

A high-severity algorithmic complexity vulnerability (CVE-2026-59887) was discovered in linkify-it 5.0.1, where the mailto: validator's scan-loop exhibited quadratic time complexity when processing attacker-controlled text. This vulnerability could allow attackers to cause denial-of-service conditions by submitting specially crafted input that triggers excessive processing time. The fix involved upgrading linkify-it from version 5.0.1 to 5.0.2 in the tizumark project.

O
By Orbis AppSec
Published July 22, 2026Reviewed July 22, 2026

Answer Summary

CVE-2026-59887 is a quadratic-complexity denial-of-service vulnerability in the linkify-it JavaScript library (CWE-407: Inefficient Algorithmic Complexity). The mailto: validator's scan-loop processes attacker-controlled text with O(n²) time complexity, allowing attackers to cause CPU exhaustion with carefully crafted input strings. The fix upgrades linkify-it from version 5.0.1 to 5.0.2, where the mailto: validation algorithm was optimized to prevent quadratic behavior.

Vulnerability at a Glance

cweCWE-407 (Inefficient Algorithmic Complexity)
fixUpgrade linkify-it from 5.0.1 to 5.0.2
riskAttackers can cause CPU exhaustion and service degradation
languageJavaScript (Node.js)
root causemailto: validator scan-loop exhibits O(n²) time complexity on crafted input
vulnerabilityQuadratic-complexity DoS in mailto: validator

Introduction

In the tizumark project's package-lock.json, we discovered a high-severity algorithmic complexity vulnerability affecting the linkify-it library version 5.0.1. This isn't just another dependency issue—the vulnerability (CVE-2026-59887) specifically targets the mailto: validator's scan-loop, creating a quadratic-complexity attack vector that could bring down production services with carefully crafted text input. The tizumark project uses linkify-it as part of its markdown processing pipeline alongside markdown-it, meaning any user-supplied markdown content containing mailto: links could trigger this vulnerability.

The Vulnerability Explained

CVE-2026-59887 exploits a fundamental algorithmic weakness in how linkify-it 5.0.1 validates mailto: URLs. When the library encounters potential email addresses in text, the mailto: validator enters a scan-loop that checks for valid email patterns. However, this scan-loop exhibits quadratic time complexity (O(n²)) when processing certain input patterns.

Here's what makes this dangerous: the validator repeatedly scans backward and forward through the input string for each character it processes. With normal input like "contact@example.com", this works fine. But an attacker can craft input that forces the validator into its worst-case scenario:

// Attacker-controlled input that triggers quadratic behavior:
"mailto:" + "a".repeat(10000) + "@" + "b".repeat(10000) + ".com"

For each character in the first 10,000 'a's, the validator performs scanning operations that also examine subsequent characters. As the input size doubles, the processing time quadruples. An input of 50,000 characters might take seconds to process, while 100,000 characters could take minutes, and 500,000 characters could completely freeze the application.

Real-World Attack Scenario

In the tizumark application, which processes markdown with embedded links, an attacker could submit markdown content like:

Check out this email: mailto:aaaaaaa[... 100,000 more 'a's ...]@bbbbbbb[... 100,000 'b's ...].com

When markdown-it calls linkify-it to identify and validate links in this content, the mailto: validator would consume excessive CPU cycles. If tizumark processes user-submitted markdown (for example, in comments, documentation, or collaborative editing features), a single malicious submission could:

  1. Exhaust CPU resources on the server processing the request
  2. Block the event loop in Node.js, preventing other requests from being handled
  3. Create cascading failures if multiple malicious requests arrive simultaneously
  4. Enable resource exhaustion attacks without triggering traditional rate limits (since it's a single request)

The vulnerability is particularly insidious because the attack payload looks like legitimate markdown content—it doesn't require special characters or obvious injection patterns that WAFs might block.

The Fix

The fix involved upgrading linkify-it from version 5.0.1 to 5.0.2, where the maintainers optimized the mailto: validator algorithm to eliminate quadratic behavior. Here's what changed in package-lock.json:

Before (vulnerable version):

{
  "name": "tizumark",
  "version": "1.0.0",
  "dependencies": {
    "codemirror": "^5.65.18",
    "highlight.js": "^11.10.0",
    "html2canvas": "^1.4.1",
    "katex": "^0.17.0",
    "markdown-it": "^14.1.0",
    // Note: linkify-it was not explicitly listed, 
    // but was pulled in as a transitive dependency
  }
}

After (patched version):

{
  "name": "tizumark",
  "version": "1.0.6",
  "dependencies": {
    "codemirror": "^5.65.18",
    "highlight.js": "^11.10.0",
    "html2canvas": "^1.4.1",
    "katex": "^0.17.0",
    "linkify-it": "^5.0.2",  // Explicitly added to force version 5.0.2
    "markdown-it": "^14.1.0",
  }
}

The key change is the explicit addition of "linkify-it": "^5.0.2" to the dependencies. This ensures that npm installs version 5.0.2 or higher, which contains the algorithmic fix for the mailto: validator.

In linkify-it 5.0.2, the maintainers refactored the mailto: validator to:
- Use linear-time pattern matching instead of nested loops
- Implement early termination when invalid patterns are detected
- Add input length checks to prevent processing of unreasonably long email addresses
- Optimize the scan-loop to avoid repeated backward/forward scanning

This transforms the algorithm from O(n²) worst-case complexity to O(n) linear complexity, where doubling the input size only doubles the processing time—a manageable and predictable performance characteristic.

Prevention & Best Practices

1. Maintain Explicit Dependency Versions

The tizumark project initially relied on transitive dependencies (linkify-it came through markdown-it). The fix explicitly declared linkify-it as a direct dependency. This approach provides:

  • Version control: You specify exactly which version to use
  • Visibility: Dependency scanners can more easily track your requirements
  • Update control: You decide when to upgrade, rather than inheriting versions from parent packages
// Best practice: explicitly declare security-critical dependencies
"dependencies": {
  "linkify-it": "^5.0.2",  // Explicit, even if also a transitive dep
  "markdown-it": "^14.1.0"
}

2. Implement Input Validation and Limits

Even with the fix, defense-in-depth requires input validation:

// Validate markdown input before processing
function sanitizeMarkdownInput(content) {
  const MAX_LENGTH = 100000; // 100KB limit
  const MAX_LINKS = 100;      // Limit number of links

  if (content.length > MAX_LENGTH) {
    throw new Error('Content exceeds maximum length');
  }

  // Count potential links to prevent link-bombing
  const linkCount = (content.match(/\[.*?\]\(.*?\)/g) || []).length;
  if (linkCount > MAX_LINKS) {
    throw new Error('Too many links in content');
  }

  return content;
}

3. Use Dependency Scanning in CI/CD

The vulnerability was detected by Trivy, a dependency scanner. Integrate similar tools:

# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Trivy scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'

4. Monitor for Algorithmic Complexity Issues

Implement performance monitoring for text processing operations:

const performanceMonitor = (fn, threshold = 1000) => {
  return function(...args) {
    const start = Date.now();
    const result = fn.apply(this, args);
    const duration = Date.now() - start;

    if (duration > threshold) {
      console.warn(`Slow operation detected: ${duration}ms`, {
        function: fn.name,
        inputSize: args[0]?.length
      });
    }

    return result;
  };
};

// Wrap linkify operations
const safeLinkify = performanceMonitor(linkifyIt.match);

5. Set Timeouts for Processing Operations

Protect against complexity attacks with timeouts:

const processWithTimeout = async (content, timeoutMs = 5000) => {
  return Promise.race([
    processMarkdown(content),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Processing timeout')), timeoutMs)
    )
  ]);
};

Key Takeaways

  • The linkify-it 5.0.1 mailto: validator scan-loop exhibits quadratic complexity, allowing attackers to cause DoS with crafted email strings in markdown content
  • Explicit dependency declarations in package.json provide better control over security-critical libraries like linkify-it, even when they're transitive dependencies
  • Algorithmic complexity vulnerabilities are subtle—the vulnerable code didn't have obvious security flaws, but its performance characteristics created an attack vector
  • Upgrading from linkify-it 5.0.1 to 5.0.2 specifically fixes the mailto: validator algorithm, reducing complexity from O(n²) to O(n)
  • Input validation and timeouts provide defense-in-depth even after patching, protecting against similar future vulnerabilities

How Orbis AppSec Detected This

  • Source: User-controlled markdown content containing mailto: links processed by the tizumark application
  • Sink: linkify-it 5.0.1's mailto: validator scan-loop in the text processing pipeline
  • Missing control: No algorithmic complexity protection or input length validation before link processing
  • CWE: CWE-407 (Inefficient Algorithmic Complexity)
  • Fix: Upgraded linkify-it to version 5.0.2, which implements linear-time mailto: validation

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-59887 demonstrates how algorithmic complexity vulnerabilities can hide in seemingly innocuous dependency code. The linkify-it mailto: validator's quadratic scan-loop could have enabled denial-of-service attacks against any application processing user-supplied markdown content. By upgrading to version 5.0.2 and implementing defense-in-depth measures like input validation and performance monitoring, developers can protect their applications from both this specific vulnerability and similar algorithmic complexity attacks. Remember: security isn't just about preventing injection or authentication bypass—it's also about ensuring your code performs predictably even with malicious input.

References

Frequently Asked Questions

What is quadratic-complexity DoS?

Quadratic-complexity DoS occurs when an algorithm's processing time grows exponentially (O(n²)) with input size, allowing attackers to cause CPU exhaustion with relatively small payloads that trigger worst-case performance.

How do you prevent algorithmic complexity attacks in JavaScript?

Implement input length limits, use algorithms with linear or logarithmic complexity, add timeout mechanisms for validation operations, and regularly audit dependencies for known complexity vulnerabilities.

What CWE is quadratic-complexity DoS?

CWE-407 (Inefficient Algorithmic Complexity), which covers vulnerabilities where an algorithm's performance degrades significantly with certain inputs, enabling denial-of-service attacks.

Is rate limiting enough to prevent algorithmic complexity DoS?

No. While rate limiting helps, it doesn't prevent a single malicious request from consuming excessive CPU resources. You must fix the underlying algorithmic issue and implement input validation alongside rate limiting.

Can static analysis detect algorithmic complexity vulnerabilities?

Partially. Dependency scanners like Trivy can detect known CVEs in libraries, but detecting novel algorithmic complexity issues requires specialized analysis tools that measure time complexity or dynamic testing with crafted inputs.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #15

Related Articles

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

high

How command injection happens in Node.js child_process and how to fix it

A high-severity command injection vulnerability was discovered in `bootstrap.js` at line 34, where the `exec()` function was used to run shell commands constructed from a `folder` variable. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates the shell interpolation entirely, closing the door on command injection attacks that could have affected all downstream consumers of this Node.js library.

high

How unsafe pickle deserialization happens in NumPy's np.load() and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `tools/ardy-engine/retarget.py` where `np.load()` was called with `allow_pickle=True`, enabling attackers to embed malicious pickle payloads in `.npz` files. The fix was a single-character change—switching `allow_pickle=True` to `allow_pickle=False`—that eliminates the deserialization attack vector while preserving the file's legitimate array data loading functionality.

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

How pickle-based arbitrary code execution happens in PyTorch and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `scripts/export_joyvasa_audio.py` where `torch.load()` was called with `weights_only=False`, allowing any pickle-serialized Python object — including malicious code — to execute during checkpoint loading. The fix switches to `weights_only=True` and explicitly allowlists only the two non-standard classes the checkpoint actually requires: `argparse.Namespace` and `pathlib.PosixPath`. This closes a real code execution path tha

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.