From e6da0c6a14f2b5ab47804631fd9465d238f88b90 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 19 Jun 2026 15:43:40 +0200 Subject: [PATCH] Sanitize extension environment maps (#9884) Signed-off-by: Jasper Hugo --- crates/goose/src/agents/extension.rs | 22 +++++++++++++- crates/goose/src/agents/extension_manager.rs | 2 +- .../src/recipe/recipe_extension_adapter.rs | 29 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index 8ffa0f8c6..2d4a5377c 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -58,7 +58,7 @@ pub enum ExtensionError { pub type ExtensionResult = Result; -#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema, PartialEq)] +#[derive(Debug, Clone, Serialize, Default, ToSchema, PartialEq)] pub struct Envs { /// A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host #[serde(default)] @@ -66,6 +66,16 @@ pub struct Envs { map: HashMap, } +impl<'de> Deserialize<'de> for Envs { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let map = HashMap::::deserialize(deserializer)?; + Ok(Self::new(map)) + } +} + impl Envs { /// List of sensitive env vars that should not be overridden const DISALLOWED_KEYS: [&'static str; 31] = [ @@ -665,6 +675,16 @@ available_tools: [] } } + #[test] + fn envs_deserialization_filters_disallowed_keys() { + let envs: extension::Envs = + serde_yaml::from_str("LD_PRELOAD: /tmp/injected.so\nSAFE_VAR: ok\n").unwrap(); + let map = envs.get_env(); + + assert!(!map.contains_key("LD_PRELOAD")); + assert_eq!(map.get("SAFE_VAR"), Some(&"ok".to_string())); + } + #[test_case( ExtensionConfig::Builtin { name: "developer".into(), diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index a8ad77131..995a32488 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -498,7 +498,7 @@ pub(crate) async fn merge_environments( } } - Ok(all_envs) + Ok(Envs::new(all_envs).get_env()) } /// Substitute environment variables in a string. Supports both ${VAR} and $VAR syntax. diff --git a/crates/goose/src/recipe/recipe_extension_adapter.rs b/crates/goose/src/recipe/recipe_extension_adapter.rs index 2d88c7aa3..0372f4a5d 100644 --- a/crates/goose/src/recipe/recipe_extension_adapter.rs +++ b/crates/goose/src/recipe/recipe_extension_adapter.rs @@ -260,4 +260,33 @@ mod tests { other => panic!("unexpected extension variant: {:?}", other), } } + + #[test] + fn recipe_stdio_envs_deserialization_filters_disallowed_keys() { + let wrapper: Wrapper = serde_json::from_value(json!({ + "extensions": [{ + "type": "stdio", + "name": "test-stdio", + "cmd": "echo", + "args": [], + "envs": { + "LD_PRELOAD": "/tmp/injected.so", + "SAFE_VAR": "ok" + } + }] + })) + .expect("failed to deserialize extensions"); + + let extensions = wrapper.extensions.expect("expected extensions"); + assert_eq!(extensions.len(), 1); + + match &extensions[0] { + ExtensionConfig::Stdio { envs, .. } => { + let map = envs.get_env(); + assert!(!map.contains_key("LD_PRELOAD")); + assert_eq!(map.get("SAFE_VAR"), Some(&"ok".to_string())); + } + other => panic!("unexpected extension variant: {:?}", other), + } + } }