Skip to content

Commit 6d6f2ea

Browse files
authored
fix(interpreter): re-validate budget after alias expansion (#1232)
Closes #1175 — After alias expansion, the budget validator now runs on the expanded AST before execution, preventing bypasses via aliases with expensive constructs.
1 parent 9de78ab commit 6d6f2ea

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

crates/bashkit/src/interpreter/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3578,7 +3578,18 @@ impl Interpreter {
35783578
self.limits.max_parser_operations,
35793579
);
35803580
let result = match parser.parse() {
3581-
Ok(s) => self.execute(&s).await,
3581+
Ok(s) => {
3582+
// THREAT[TM-DOS-031]: Validate budget on expanded alias AST
3583+
// to prevent bypassing static budget checks via alias expansion.
3584+
if let Err(e) = crate::parser::validate_budget(&s, &self.limits) {
3585+
Ok(ExecResult::err(
3586+
format!("bash: alias expansion: budget validation failed: {e}\n"),
3587+
1,
3588+
))
3589+
} else {
3590+
self.execute(&s).await
3591+
}
3592+
}
35823593
Err(e) => Ok(ExecResult::err(
35833594
format!("bash: alias expansion: parse error: {}\n", e),
35843595
1,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! Test for issue #1175: alias expansion should not bypass static budget validation.
2+
//!
3+
//! Aliases that expand to expensive constructs (huge brace ranges, deeply nested
4+
//! loops) must be caught by the budget validator after expansion.
5+
6+
use bashkit::Bash;
7+
8+
/// THREAT[TM-DOS-031]: Alias expanding to huge brace range is caught.
9+
/// The alias value is constructed from variables so the brace range literal
10+
/// doesn't appear in the original AST — only in the expanded alias.
11+
#[tokio::test]
12+
async fn alias_huge_brace_range_rejected() {
13+
let mut bash = Bash::new();
14+
// Define and use alias in one exec, constructing the alias value
15+
// from variables so the static budget check on the initial AST doesn't
16+
// see the brace range.
17+
let result = bash
18+
.exec(
19+
r#"
20+
shopt -s expand_aliases
21+
x='echo {1..9999'
22+
y='99999}'
23+
eval "alias boom='$x$y'"
24+
boom
25+
"#,
26+
)
27+
.await
28+
.unwrap();
29+
// Either exit_code != 0 (budget rejection) or the alias wasn't expanded
30+
// If the alias DID expand, the brace range would generate ~1B elements
31+
// and either be caught by budget validation or hit runtime limits
32+
assert!(
33+
result.exit_code != 0 || result.stdout.len() < 1000, // If it passes, output should be tiny (not expanded)
34+
"should reject huge brace range via alias, got exit={} stdout_len={}",
35+
result.exit_code,
36+
result.stdout.len()
37+
);
38+
}
39+
40+
/// Normal aliases should still work fine.
41+
#[tokio::test]
42+
async fn normal_alias_works() {
43+
let mut bash = Bash::new();
44+
let result = bash
45+
.exec(
46+
r#"
47+
shopt -s expand_aliases
48+
alias hi='echo hello'
49+
hi world
50+
"#,
51+
)
52+
.await
53+
.unwrap();
54+
assert_eq!(result.exit_code, 0);
55+
assert!(
56+
result.stdout.contains("hello world"),
57+
"expected 'hello world', got: {}",
58+
result.stdout
59+
);
60+
}

0 commit comments

Comments
 (0)