From 604dc23e242030f044c52d20e93b9c08741215aa Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 11 Jun 2026 00:18:58 -0500 Subject: [PATCH] Quote virtual environment activation paths with shell metacharacters Activation commands were only quoted when their paths contained spaces, leaving apostrophes and other shell metacharacters to produce invalid or unsafe shell commands. Match Python shlex.quote safe-character behavior so every unsafe path is single-quoted. --- crates/uv-shell/src/shlex.rs | 37 ++++++++++++++++++++++++++++------ crates/uv/tests/python/venv.rs | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/uv-shell/src/shlex.rs b/crates/uv-shell/src/shlex.rs index cdcbd478ffe77..b53b806939e60 100644 --- a/crates/uv-shell/src/shlex.rs +++ b/crates/uv-shell/src/shlex.rs @@ -6,13 +6,15 @@ pub fn shlex_posix(executable: impl AsRef) -> String { // Convert to a display path. let executable = executable.as_ref().portable_display().to_string(); - // Like Python's `shlex.quote`: - // > Use single quotes, and put single quotes into double quotes - // > The string $'b is then quoted as '$'"'"'b' - if executable.contains(' ') { - format!("'{}'", escape_posix_for_single_quotes(&executable)) - } else { + // Match Python's `shlex.quote` and leave only shell-safe ASCII characters unquoted. + if !executable.is_empty() + && executable + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || b"@%+=:,./-_".contains(&byte)) + { executable + } else { + format!("'{}'", escape_posix_for_single_quotes(&executable)) } } @@ -48,3 +50,26 @@ pub fn shlex_windows(executable: impl AsRef, shell: Shell) -> String { executable } } + +#[cfg(test)] +mod tests { + use super::shlex_posix; + + #[test] + fn posix_safe_path() { + assert_eq!(shlex_posix("/usr/bin/python3.12"), "/usr/bin/python3.12"); + } + + #[test] + fn posix_empty_path() { + assert_eq!(shlex_posix(""), "''"); + } + + #[test] + fn posix_path_with_metacharacters() { + assert_eq!( + shlex_posix("Testing's/$venv;activate"), + r#"'Testing'"'"'s/$venv;activate'"# + ); + } +} diff --git a/crates/uv/tests/python/venv.rs b/crates/uv/tests/python/venv.rs index 5dc1cd3d92302..4bbea40c17674 100644 --- a/crates/uv/tests/python/venv.rs +++ b/crates/uv/tests/python/venv.rs @@ -1610,7 +1610,7 @@ fn create_venv_apostrophe() { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: Testing's - Activate with: source Testing's/[BIN]/activate + Activate with: source 'Testing'\"'\"'s/[BIN]/activate' " );