From c39f7e6da506eb93e90514132dcde379704f03ac Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 22:49:24 +0000 Subject: [PATCH 1/5] fix(interpreter): expand assoc array keys with command substitutions Add async expand_assoc_key() that uses full word expansion (parse_word_string + expand_word) for associative array keys containing $() or backtick substitutions. Replaces the sync-only expand_variable_or_literal() call in process_command_assignments(). Fixes #872 --- crates/bashkit/src/interpreter/mod.rs | 14 +++++- crates/bashkit/tests/issue_872_test.rs | 66 ++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 crates/bashkit/tests/issue_872_test.rs diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index aca72bdb..362b915e 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -3129,7 +3129,7 @@ impl Interpreter { if let Some(index_str) = &assignment.index { let resolved_name = self.resolve_nameref(&assignment.name).to_string(); if self.assoc_arrays.contains_key(&resolved_name) { - let key = self.expand_variable_or_literal(index_str); + let key = self.expand_assoc_key(index_str).await?; let is_new_entry = self .assoc_arrays .get(&resolved_name) @@ -7791,6 +7791,18 @@ impl Interpreter { s.to_string() } + /// Expand an associative array key with full word expansion. + /// Unlike `expand_variable_or_literal`, this handles command substitutions + /// (`$(...)`, backticks) and all other expansion types. (Issue #872) + async fn expand_assoc_key(&mut self, s: &str) -> Result { + if s.contains('$') || s.contains('`') { + let word = crate::parser::Parser::parse_word_string(s); + self.expand_word(&word).await + } else { + Ok(s.to_string()) + } + } + /// THREAT[TM-INJ-009]: Check if a variable name is an internal marker. fn is_internal_variable(name: &str) -> bool { is_internal_variable(name) diff --git a/crates/bashkit/tests/issue_872_test.rs b/crates/bashkit/tests/issue_872_test.rs new file mode 100644 index 00000000..76a55203 --- /dev/null +++ b/crates/bashkit/tests/issue_872_test.rs @@ -0,0 +1,66 @@ +//! Test for issue #872: Associative array keys with command substitutions +//! expand to empty string. + +use bashkit::Bash; + +#[tokio::test] +async fn assoc_key_command_substitution() { + let mut bash = Bash::new(); + let result = bash + .exec( + r#" +declare -A m=() +m["$(echo hello)"]="world" +echo "count: ${#m[@]}" +for k in "${!m[@]}"; do echo "key=[$k] val=[${m[$k]}]"; done +"#, + ) + .await + .unwrap(); + assert!( + result.stdout.contains("key=[hello] val=[world]"), + "expected key=[hello], got: {}", + result.stdout + ); +} + +#[tokio::test] +async fn assoc_key_variable_expansion() { + let mut bash = Bash::new(); + let result = bash + .exec( + r#" +declare -A m=() +key="mykey" +m[$key]="myval" +echo "${m[mykey]}" +"#, + ) + .await + .unwrap(); + assert!( + result.stdout.contains("myval"), + "expected myval, got: {}", + result.stdout + ); +} + +#[tokio::test] +async fn assoc_key_literal_unchanged() { + let mut bash = Bash::new(); + let result = bash + .exec( + r#" +declare -A m=() +m[literal]="val" +echo "${m[literal]}" +"#, + ) + .await + .unwrap(); + assert!( + result.stdout.contains("val"), + "expected val, got: {}", + result.stdout + ); +} From e0134bba7cafe071d3c9c8067d34ce3d52e94873 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 23:03:44 +0000 Subject: [PATCH 2/5] chore: retrigger CI From fc5886e3284a1bd4df425ee413ce7f05d6ff68fe Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 23:09:36 +0000 Subject: [PATCH 3/5] chore: prune stale cargo-vet exemptions --- supply-chain/config.toml | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index a7594112..df0c62cd 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -154,10 +154,6 @@ criteria = "safe-to-run" version = "0.2.4" criteria = "safe-to-deploy" -[[exemptions.cc]] -version = "1.2.57" -criteria = "safe-to-deploy" - [[exemptions.cc]] version = "1.2.58" criteria = "safe-to-deploy" @@ -206,10 +202,6 @@ criteria = "safe-to-deploy" version = "1.1.0" criteria = "safe-to-deploy" -[[exemptions.cmake]] -version = "0.1.57" -criteria = "safe-to-deploy" - [[exemptions.cmake]] version = "0.1.58" criteria = "safe-to-deploy" @@ -238,10 +230,6 @@ criteria = "safe-to-deploy" version = "0.9.0" criteria = "safe-to-deploy" -[[exemptions.console]] -version = "0.15.11" -criteria = "safe-to-run" - [[exemptions.console]] version = "0.16.3" criteria = "safe-to-run" @@ -578,10 +566,6 @@ criteria = "safe-to-deploy" version = "2.13.0" criteria = "safe-to-deploy" -[[exemptions.insta]] -version = "1.46.3" -criteria = "safe-to-run" - [[exemptions.insta]] version = "1.47.0" criteria = "safe-to-run" @@ -598,10 +582,6 @@ criteria = "safe-to-deploy" version = "2.12.0" criteria = "safe-to-deploy" -[[exemptions.iri-string]] -version = "0.7.10" -criteria = "safe-to-deploy" - [[exemptions.iri-string]] version = "0.7.11" criteria = "safe-to-deploy" @@ -642,10 +622,6 @@ criteria = "safe-to-deploy" version = "0.21.1" criteria = "safe-to-deploy" -[[exemptions.jni-sys]] -version = "0.3.0" -criteria = "safe-to-deploy" - [[exemptions.jni-sys]] version = "0.3.1" criteria = "safe-to-deploy" @@ -726,10 +702,6 @@ criteria = "safe-to-deploy" version = "0.8.9" criteria = "safe-to-deploy" -[[exemptions.mio]] -version = "1.1.1" -criteria = "safe-to-deploy" - [[exemptions.mio]] version = "1.2.0" criteria = "safe-to-deploy" @@ -1190,10 +1162,6 @@ criteria = "safe-to-deploy" version = "0.9.1" criteria = "safe-to-deploy" -[[exemptions.simd-adler32]] -version = "0.3.8" -criteria = "safe-to-deploy" - [[exemptions.simd-adler32]] version = "0.3.9" criteria = "safe-to-deploy" @@ -1390,10 +1358,6 @@ criteria = "safe-to-deploy" version = "0.1.25" criteria = "safe-to-deploy" -[[exemptions.unicode-segmentation]] -version = "1.13.1" -criteria = "safe-to-deploy" - [[exemptions.unicode-segmentation]] version = "1.13.2" criteria = "safe-to-deploy" @@ -1562,10 +1526,6 @@ criteria = "safe-to-deploy" version = "0.52.0" criteria = "safe-to-deploy" -[[exemptions.windows-sys]] -version = "0.59.0" -criteria = "safe-to-run" - [[exemptions.windows-sys]] version = "0.60.2" criteria = "safe-to-deploy" From 63ac3a37e7340fdbe870e30b494fc956ed70ef71 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 23:17:07 +0000 Subject: [PATCH 4/5] chore: retrigger CI (transient audit failure) From 033b5fa76df7a6f5028d9da2939a36c6a5754240 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 23:22:55 +0000 Subject: [PATCH 5/5] chore: update cargo-vet exemptions for wasm-bindgen 0.2.115 --- supply-chain/config.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index df0c62cd..93052b55 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -639,7 +639,7 @@ version = "0.1.34" criteria = "safe-to-deploy" [[exemptions.js-sys]] -version = "0.3.91" +version = "0.3.92" criteria = "safe-to-deploy" [[exemptions.leb128fmt]] @@ -1427,23 +1427,23 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" criteria = "safe-to-run" [[exemptions.wasm-bindgen]] -version = "0.2.114" +version = "0.2.115" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-futures]] -version = "0.4.64" +version = "0.4.65" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro]] -version = "0.2.114" +version = "0.2.115" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro-support]] -version = "0.2.114" +version = "0.2.115" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-shared]] -version = "0.2.114" +version = "0.2.115" criteria = "safe-to-deploy" [[exemptions.wasm-encoder]] @@ -1463,7 +1463,7 @@ version = "0.244.0" criteria = "safe-to-deploy" [[exemptions.web-sys]] -version = "0.3.91" +version = "0.3.92" criteria = "safe-to-deploy" [[exemptions.web-time]]