From b07bad05a0dafc9c58facc183a6e9bede74d9d0f Mon Sep 17 00:00:00 2001 From: Taku Kodma <79110363+risu729@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:28:56 +1000 Subject: [PATCH 1/2] fix(hooks): reject nested hook arrays --- src/hooks.rs | 76 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index f8b5307aa56..ca6beb93e83 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -68,8 +68,16 @@ impl HookScripts { } #[derive(Debug, Clone, serde::Deserialize)] -#[serde(untagged, deny_unknown_fields)] +#[serde(untagged)] pub enum HookDef { + One(HookDefItem), + /// Array of hook definitions: `enter = ["echo hello", { task = "setup" }]` + Array(Vec), +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum HookDefItem { /// Simple run string: `enter = "echo hello"` RunString(String), /// Table with run: `enter = { run = "echo hello" }` @@ -86,8 +94,6 @@ pub enum HookDef { }, /// Task reference: `enter = { task = "setup" }` TaskRef { task: String }, - /// Array of hook definitions: `enter = ["echo hello", { task = "setup" }]` - Array(Vec), } #[derive(Debug, Clone)] @@ -128,7 +134,19 @@ impl HookDef { /// Convert to a list of Hook structs with the given hook type pub fn into_hooks(self, hook_type: Hooks) -> Vec { match self { - HookDef::RunString(script) => vec![Hook { + HookDef::One(def) => vec![def.into_hook(hook_type)], + HookDef::Array(arr) => arr + .into_iter() + .map(|def| def.into_hook(hook_type)) + .collect(), + } + } +} + +impl HookDefItem { + fn into_hook(self, hook_type: Hooks) -> Hook { + match self { + HookDefItem::RunString(script) => Hook { hook: hook_type, action: HookAction::Run { run: Some(script), @@ -138,8 +156,8 @@ impl HookDef { ignored_shell: None, }, global: false, - }], - HookDef::Run(table) => vec![Hook { + }, + HookDefItem::Run(table) => Hook { hook: hook_type, action: HookAction::Run { run: table.run, @@ -149,26 +167,22 @@ impl HookDef { ignored_shell: None, }, global: false, - }], - HookDef::ScriptTable { script, shell } => vec![Hook { + }, + HookDefItem::ScriptTable { script, shell } => Hook { hook: hook_type, action: script_hook_action(hook_type, script.into_script(), shell, true), global: false, - }], - HookDef::ScriptsTable { scripts, shell } => vec![Hook { + }, + HookDefItem::ScriptsTable { scripts, shell } => Hook { hook: hook_type, action: script_hook_action(hook_type, scripts.join("\n"), shell, true), global: false, - }], - HookDef::TaskRef { task } => vec![Hook { + }, + HookDefItem::TaskRef { task } => Hook { hook: hook_type, action: HookAction::Task { task_name: task }, global: false, - }], - HookDef::Array(arr) => arr - .into_iter() - .flat_map(|d| d.into_hooks(hook_type)) - .collect(), + }, } } } @@ -808,12 +822,32 @@ mod tests { } } + #[test] + fn hook_arrays_reject_nested_arrays() { + assert!( + toml::from_str::( + r#"hook = ["echo first", { task = "build" }, { run = "echo last" }]"# + ) + .is_ok() + ); + + for input in [ + r#"hook = [["echo nested"]]"#, + r#"hook = ["echo first", [{ task = "build" }]]"#, + ] { + assert!( + toml::from_str::(input).is_err(), + "unexpectedly accepted {input}" + ); + } + } + #[test] fn scripts_tables_are_legacy_only_when_spawned() { - let spawned = HookDef::ScriptsTable { + let spawned = HookDef::One(HookDefItem::ScriptsTable { scripts: vec!["echo one".into(), "echo two".into()], shell: None, - } + }) .into_hooks(Hooks::Postinstall); assert!(matches!( &spawned[0].action, @@ -823,10 +857,10 @@ mod tests { } )); - let current_shell = HookDef::ScriptsTable { + let current_shell = HookDef::One(HookDefItem::ScriptsTable { scripts: vec!["echo one".into(), "echo two".into()], shell: Some("bash".into()), - } + }) .into_hooks(Hooks::Enter); assert!(matches!( ¤t_shell[0].action, From 465b8276943ebfd45c8e3466f24f3e6473937c63 Mon Sep 17 00:00:00 2001 From: Taku Kodma <79110363+risu729@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:45:03 +1000 Subject: [PATCH 2/2] test(hooks): clarify flat array assertion --- src/hooks.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks.rs b/src/hooks.rs index ca6beb93e83..748a089315f 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -828,7 +828,8 @@ mod tests { toml::from_str::( r#"hook = ["echo first", { task = "build" }, { run = "echo last" }]"# ) - .is_ok() + .is_ok(), + "flat mixed array should be accepted" ); for input in [