From 144dd9302b06bad6efbcaf0eebc5fbb2d6909c3b Mon Sep 17 00:00:00 2001 From: Xiaobo Liu Date: Sat, 14 Feb 2026 16:08:38 +0800 Subject: [PATCH] python: Prevent shell command injection in conda environment activation (#49160) Release Notes: - Fixed prevent shell command injection in conda environment activation The conda environment name was directly interpolated into the shell command without proper escaping, which could allow command injection if the environment name contained malicious shell metacharacters. Signed-off-by: Xiaobo Liu --- crates/languages/src/python.rs | 80 +++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 435dcba16c8a615f17d6a3d401fef6f3ada3b0ef..b1192464e9601183ac5d91196bfbe529feaa693f 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1393,7 +1393,15 @@ impl ToolchainLister for PythonToolchainProvider { } if let Some(name) = &toolchain.environment.name { - activation_script.push(format!("{manager} activate {name}")); + if let Some(quoted_name) = shell.try_quote(name) { + activation_script.push(format!("{manager} activate {quoted_name}")); + } else { + log::warn!( + "Could not safely quote environment name {:?}, falling back to base", + name + ); + activation_script.push(format!("{manager} activate base")); + } } else { activation_script.push(format!("{manager} activate base")); } @@ -2630,6 +2638,76 @@ mod tests { use crate::python::python_module_name_from_relative_path; + #[gpui::test] + async fn test_conda_activation_script_injection(cx: &mut TestAppContext) { + use language::{LanguageName, Toolchain, ToolchainLister}; + use settings::{CondaManager, VenvSettings}; + use task::ShellKind; + + use crate::python::PythonToolchainProvider; + + cx.executor().allow_parking(); + + cx.update(|cx| { + let test_settings = SettingsStore::test(cx); + cx.set_global(test_settings); + cx.update_global::(|store, cx| { + store.update_user_settings(cx, |s| { + s.terminal + .get_or_insert_with(Default::default) + .project + .detect_venv = Some(VenvSettings::On { + activate_script: None, + venv_name: None, + directories: None, + conda_manager: Some(CondaManager::Conda), + }); + }); + }); + }); + + let provider = PythonToolchainProvider; + let malicious_name = "foo; rm -rf /"; + + let manager_executable = std::env::current_exe().unwrap(); + + let data = serde_json::json!({ + "name": malicious_name, + "kind": "Conda", + "executable": "/tmp/conda/bin/python", + "version": serde_json::Value::Null, + "prefix": serde_json::Value::Null, + "arch": serde_json::Value::Null, + "displayName": serde_json::Value::Null, + "project": serde_json::Value::Null, + "symlinks": serde_json::Value::Null, + "manager": { + "executable": manager_executable, + "version": serde_json::Value::Null, + "tool": "Conda", + }, + }); + + let toolchain = Toolchain { + name: "test".into(), + path: "/tmp/conda".into(), + language_name: LanguageName::new_static("Python"), + as_json: data, + }; + + let script = cx + .update(|cx| provider.activation_script(&toolchain, ShellKind::Posix, cx)) + .await; + + assert!( + script + .iter() + .any(|s| s.contains("conda activate 'foo; rm -rf /'")), + "Script should contain quoted malicious name, actual: {:?}", + script + ); + } + #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);