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 <cppcoffee@gmail.com>
This commit is contained in:
Xiaobo Liu 2026-02-14 16:08:38 +08:00 committed by GitHub
parent b90a370c86
commit 144dd9302b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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::<SettingsStore, _>(|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);