Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/bashkit/src/parser/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,11 @@ impl<'a> Lexer<'a> {
/// Check if the content starting with { looks like a brace expansion
/// Brace expansion: {a,b,c} or {1..5} (contains , or ..)
/// Brace group: { cmd; } (contains spaces, semicolons, newlines)
/// THREAT[TM-DOS]: Caps lookahead to prevent O(n^2) scanning when input
/// contains many unmatched `{` characters (issue #997).
fn looks_like_brace_expansion(&self) -> bool {
const MAX_LOOKAHEAD: usize = 10_000;

// Clone the iterator to peek ahead without consuming
let mut chars = self.chars.clone();

Expand All @@ -1425,8 +1429,13 @@ impl<'a> Lexer<'a> {
let mut has_comma = false;
let mut has_dot_dot = false;
let mut prev_char = None;
let mut scanned = 0usize;

for ch in chars {
scanned += 1;
if scanned > MAX_LOOKAHEAD {
return false;
}
match ch {
'{' => depth += 1,
'}' => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Brace expansion lookahead limit
# Regression tests for issue #997

### normal_brace_expansion
# Normal brace expansion still works
echo {a,b,c}
### expect
a b c
### end

### range_brace_expansion
# Range brace expansion still works
echo {1..3}
### expect
1 2 3
### end

### unmatched_brace_literal
# Unmatched { is treated as literal
echo {abc
### expect
{abc
### end

### nested_brace_expansion
# Nested brace expansion works
echo {a,{b,c}}
### expect
a b c
### end
Loading