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);