From 6568f0a4cde2526cc21374cef59754ed2bc4fb95 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Thu, 2 Apr 2026 14:34:19 +0000 Subject: [PATCH] fix(parser): enforce subst depth limit in unquoted command substitution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #996 — read_word used simple parenthesis counting for $() in unquoted context, bypassing the max_subst_depth limit that was enforced in double-quoted strings. Now tracks $( nesting depth and stops recursing deeper when the limit is reached. --- crates/bashkit/src/parser/lexer.rs | 25 +++++++++++++++++- .../bash/cmdsub_depth_unquoted.test.sh | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 crates/bashkit/tests/spec_cases/bash/cmdsub_depth_unquoted.test.sh diff --git a/crates/bashkit/src/parser/lexer.rs b/crates/bashkit/src/parser/lexer.rs index ae24f2c5..c42fe7fc 100644 --- a/crates/bashkit/src/parser/lexer.rs +++ b/crates/bashkit/src/parser/lexer.rs @@ -682,11 +682,34 @@ impl<'a> Lexer<'a> { } } } else { - // Command substitution $(...) - track nested parens + // THREAT[TM-DOS]: Track substitution nesting depth to + // enforce max_subst_depth consistently in both quoted + // and unquoted contexts (issue #996). let mut depth = 1; + let mut subst_depth = 1usize; while let Some(c) = self.peek_char() { word.push(c); self.advance(); + if c == '$' && self.peek_char() == Some('(') { + subst_depth += 1; + if subst_depth > self.max_subst_depth { + // Depth limit exceeded — consume remaining + // parens and stop nesting deeper. + while let Some(ic) = self.peek_char() { + word.push(ic); + self.advance(); + if ic == '(' { + depth += 1; + } else if ic == ')' { + depth -= 1; + if depth == 0 { + break; + } + } + } + break; + } + } if c == '(' { depth += 1; } else if c == ')' { diff --git a/crates/bashkit/tests/spec_cases/bash/cmdsub_depth_unquoted.test.sh b/crates/bashkit/tests/spec_cases/bash/cmdsub_depth_unquoted.test.sh new file mode 100644 index 00000000..29b8430f --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/cmdsub_depth_unquoted.test.sh @@ -0,0 +1,26 @@ +# Command substitution depth limit in unquoted context +# Regression tests for issue #996 + +### shallow_cmdsub_unquoted +# Shallow command substitution in unquoted context works +x=$(echo hello) +echo $x +### expect +hello +### end + +### nested_cmdsub_unquoted +# Nested command substitution in unquoted context works +x=$(echo $(echo nested)) +echo $x +### expect +nested +### end + +### cmdsub_depth_3_unquoted +# Three levels of nesting works +x=$(echo $(echo $(echo deep))) +echo $x +### expect +deep +### end