Back to Blog
high SEVERITY8 min read

ReDoS in Nushell's TUI: When Search Input Freezes Your Terminal

A high-severity Regular Expression Denial of Service (ReDoS) vulnerability was discovered and patched in Nushell's interactive TUI explorer, where unvalidated user keystrokes could be passed directly into regex compilation, allowing adversarial inputs to consume 100% CPU and freeze the interface. This fix adds proper input validation and length limits to the search input handler, preventing catastrophic backtracking attacks. Understanding this vulnerability is essential for any developer buildin

O
By Orbis AppSec
Published May 20, 2026Reviewed June 3, 2026

Answer Summary

Regular Expression Denial of Service (ReDoS) is a vulnerability where specially crafted input patterns cause regex engines to enter catastrophic backtracking, consuming excessive CPU and freezing applications. In Nushell's TUI explorer, the search input handler compiled user keystrokes directly into regex patterns without validation. The fix adds input length limits and validates search patterns before compilation, preventing adversarial inputs from triggering exponential regex matching behavior. CWE-1333 covers this vulnerability class.

Vulnerability at a Glance

cweCWE-1333
fixAdd input validation, length limits, and safe regex compilation with timeout protection
riskApplication freeze, 100% CPU consumption, denial of service
languageRust (Nushell)
root causeUnvalidated user input passed directly to regex compilation without length limits
vulnerabilityRegular Expression Denial of Service (ReDoS)

ReDoS in Nushell's TUI: When Search Input Freezes Your Terminal

Introduction

Imagine typing a search query into your terminal explorer and watching your entire system grind to a halt — CPU pegged at 100%, the interface completely frozen, and no way out short of killing the process. That's the real-world consequence of a Regular Expression Denial of Service (ReDoS) vulnerability, and it's exactly what was lurking in Nushell's interactive TUI (Text User Interface) explorer.

This post breaks down the vulnerability discovered in crates/nu-explore/src/explore_config/tui.rs, explains how it could be exploited, and walks through the fix that was applied to protect users. Whether you're a Rust developer, a CLI tool maintainer, or simply someone who cares about writing robust software, this is a pattern worth understanding deeply.


The Vulnerability Explained

What Is ReDoS?

Regular Expression Denial of Service (ReDoS) is a class of vulnerability that exploits the way certain regex engines evaluate patterns. Most traditional regex engines use backtracking to find matches — when a path doesn't work, the engine backs up and tries another. For most inputs, this is fast. But certain combinations of patterns and inputs can cause the engine to explore an exponentially growing number of paths before concluding there's no match.

This is called catastrophic backtracking, and it can turn a single search query into an infinite loop.

The Vulnerable Code Path

The vulnerability existed in Nushell's TUI explorer at tui.rs:112. Here's the problematic flow:

User keystroke
     │
     ▼
handle_search_input()   ← tui.rs:112
     │
     ▼
apply_search_filter()   ← app.rs:194
     │
     ▼
Regex::new(user_input)  ← compiled without validation ⚠️

User keystrokes captured by handle_search_input() were passed directly to apply_search_filter() without:

  • ✗ Input sanitization or escaping
  • ✗ Length limits
  • ✗ Debouncing (rate limiting keystrokes)
  • ✗ Validation that the input forms a safe regex

How Could It Be Exploited?

An attacker (or even an unsuspecting user) could type a carefully crafted pattern into the TUI search box. Classic catastrophic backtracking patterns include:

# Catastrophic backtracking examples
(a+)+
(a|aa)+
([a-zA-Z]+)*
(a+)+$

When matched against a non-matching string like "aaaaaaaaaaaaaaaaaab", these patterns force the regex engine to explore an astronomical number of backtracking paths.

Example attack scenario:

# A user opens nu-explore and navigates to a large data table
# They type the following into the search box:
(a+)+zzzzzzzzzzzzzzzzzzzzz

# The regex engine begins evaluating this against every cell in the table
# CPU usage spikes to 100%
# The TUI freezes  no keyboard input is processed
# The user is forced to kill the terminal or reboot

Even without malicious intent, a user experimenting with regex syntax could accidentally trigger this. In a tool designed for exploring data interactively, a frozen interface is a critical failure.

Why Is This Rated High Severity?

  • Availability impact: The application becomes completely unresponsive
  • No authentication required: Any user of the tool can trigger it
  • Ease of exploitation: The payload is trivial to construct
  • No resource recovery: Without a timeout or kill mechanism, the process must be forcibly terminated
  • Data loss risk: Any unsaved state in the explorer session is lost when the process is killed

This maps to CWE-400: Uncontrolled Resource Consumption and is catalogued under the OWASP category of Denial of Service.


The Fix

The patch was applied in crates/nu-explore/src/explore_config/input.rs and addresses the vulnerability through a combination of defensive input handling strategies.

What Changed

Before: Unvalidated Input Flow

// tui.rs:112 (simplified representation of vulnerable pattern)
fn handle_search_input(&mut self, key: KeyEvent) {
    match key.code {
        KeyCode::Char(c) => {
            self.search_query.push(c);
            // Directly passed to filter — no validation!
            self.app.apply_search_filter(&self.search_query);
        }
        // ...
    }
}

// app.rs:194 (simplified)
fn apply_search_filter(&mut self, query: &str) {
    // Compiles user input directly as a regex ⚠️
    if let Ok(re) = Regex::new(query) {
        self.filter_data_with_regex(re);
    }
}

After: Validated, Length-Limited Input

// input.rs (patched)
const MAX_SEARCH_INPUT_LENGTH: usize = 128;

fn handle_search_input(&mut self, key: KeyEvent) {
    match key.code {
        KeyCode::Char(c) => {
            // Enforce length limit before appending
            if self.search_query.len() >= MAX_SEARCH_INPUT_LENGTH {
                return;
            }
            self.search_query.push(c);

            // Validate the regex before applying
            if let Some(safe_query) = validate_search_input(&self.search_query) {
                self.app.apply_search_filter(&safe_query);
            }
        }
        // ...
    }
}

fn validate_search_input(input: &str) -> Option<String> {
    // Reject inputs that are too long
    if input.len() > MAX_SEARCH_INPUT_LENGTH {
        return None;
    }

    // Attempt to compile — but with a complexity budget
    // or escape the input to treat it as a literal string
    // rather than a raw regex pattern
    Some(regex::escape(input))
}

Note: The exact implementation details may vary from the simplified representation above. The core principle — length limiting and input validation before regex compilation — is what matters.

How Does This Fix the Problem?

The patch applies defense in depth through multiple layers:

  1. Length limits: Capping input at 128 characters dramatically reduces the search space available to an attacker. Catastrophic backtracking patterns typically need room to grow — a hard length cap cuts them off at the knees.

  2. Input validation: By validating or escaping the input before passing it to the regex engine, the fix ensures that user input is treated as a literal search string rather than an arbitrary regex pattern. This is the safest default for a general-purpose search box.

  3. Separation of concerns: Moving validation logic into input.rs creates a clear boundary — input handling is now responsible for ensuring data is safe before it ever reaches the application logic layer.


Prevention & Best Practices

This vulnerability is far from unique to Nushell. Any application with a user-facing search or filter feature backed by regex is potentially at risk. Here's how to protect yourself and your users:

1. Never Compile Untrusted Input as Raw Regex

Unless your application is explicitly a regex tool, treat user search input as a literal string. Most regex libraries provide an escape function:

// Rust (regex crate)
use regex::escape;
let safe_pattern = escape(user_input);
let re = Regex::new(&safe_pattern)?;

// Python
import re
safe_pattern = re.escape(user_input)

// JavaScript
const safePattern = user_input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

2. Enforce Input Length Limits Early

Set a reasonable maximum length for search inputs and enforce it at the point of entry — not just at the point of use:

const MAX_QUERY_LEN: usize = 256;

if input.len() > MAX_QUERY_LEN {
    return Err(InputError::TooLong);
}

3. Use a ReDoS-Safe Regex Engine

The Rust regex crate is actually ReDoS-safe by design — it uses a finite automaton approach that guarantees linear time matching. However, this protection only applies when the pattern itself is controlled. User-supplied patterns can still construct inputs that are slow to compile or that exploit edge cases.

If you must allow user-supplied regex patterns, consider:

  • regex crate in Rust: Linear time matching, but validate patterns before compilation
  • RE2: Google's regex library, designed to avoid catastrophic backtracking
  • Timeout wrappers: Set a hard timeout on regex operations
// Using a timeout to bound regex execution time
use std::time::Duration;
use std::thread;

let handle = thread::spawn(move || {
    apply_search_filter(&query)
});

match handle.join_timeout(Duration::from_millis(100)) {
    Ok(result) => result,
    Err(_) => { /* timeout — abort the operation */ }
}

4. Implement Debouncing for Live Search

Even with safe regex handling, compiling a new regex on every keystroke is wasteful. Debouncing ensures the filter only fires after the user has paused typing:

// Conceptual debounce — wait 300ms after last keystroke
fn handle_search_input(&mut self, key: KeyEvent) {
    self.search_query.update(key);
    self.debounce_timer.reset(Duration::from_millis(300), || {
        self.apply_filter();
    });
}

5. Consider Literal Search as the Default

For most data exploration tools, users want to find literal strings — not write regex patterns. Offer regex as an opt-in mode (e.g., toggled with a button or prefix like /regex:), and default to safe literal matching.

Detection Tools

  • vuln-regex-detector: Detects potentially vulnerable regex patterns
  • safe-regex: JavaScript library to check regex safety
  • Clippy (Rust): While it doesn't catch ReDoS directly, linting can surface unsafe patterns
  • CodeQL: GitHub's static analysis tool has queries for ReDoS detection
  • regexploit: Tool for finding ReDoS vulnerabilities in regex patterns

Relevant Standards & References

Standard Reference
CWE CWE-400: Uncontrolled Resource Consumption
CWE CWE-1333: Inefficient Regular Expression Complexity
OWASP Denial of Service Cheat Sheet
OWASP Input Validation Cheat Sheet

Conclusion

The ReDoS vulnerability in Nushell's TUI explorer is a textbook example of how a seemingly innocuous feature — a search box — can become a denial-of-service vector when input isn't properly validated. The fix is elegant in its simplicity: enforce length limits, validate or escape input before it reaches the regex engine, and separate input handling from application logic.

Key Takeaways

  • 🔍 Treat all user input as untrusted, even in local, single-user tools
  • 📏 Enforce length limits early — at the point of input, not the point of use
  • 🛡️ Escape user input before using it as a regex pattern, unless regex mode is explicitly intended
  • ⏱️ Debounce live search to reduce unnecessary computation
  • 🧪 Test with adversarial inputs — include ReDoS patterns in your security test suite

Security vulnerabilities in developer tools deserve just as much attention as vulnerabilities in web applications. The developers who use these tools are often handling sensitive data, and a frozen or crashed tool at the wrong moment can have real consequences.

If you're maintaining a CLI tool or TUI application with search functionality, take 30 minutes today to audit your input handling. The fix is usually simple — the cost of not fixing it is not.


This vulnerability was identified and patched as part of automated security scanning by OrbisAI Security. Responsible disclosure and rapid patching are what keep the open-source ecosystem healthy.

Frequently Asked Questions

What is Regular Expression Denial of Service (ReDoS)?

ReDoS occurs when a regex engine processes specially crafted input that causes catastrophic backtracking, where the regex engine tries exponentially increasing combinations of pattern matches, consuming CPU and freezing the application.

How do you prevent ReDoS in Nushell and similar Rust applications?

Validate input length before regex compilation, implement regex timeouts, use non-backtracking regex engines when possible, and apply input sanitization to remove characters that could trigger backtracking patterns.

What CWE is ReDoS?

CWE-1333 (Inefficient Regular Expression Complexity) covers ReDoS vulnerabilities where regex patterns have excessive complexity.

Is input sanitization alone enough to prevent ReDoS?

No. While sanitization helps, the most effective prevention combines input length limits, regex compilation timeouts, and avoiding user-controlled regex patterns entirely when possible.

Can static analysis detect ReDoS vulnerabilities?

Yes. Tools like Semgrep can detect patterns where user input flows directly into regex compilation, though they cannot always identify vulnerable regex patterns themselves without specialized regex analysis.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18251

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.