Skip to content

Commit cd76210

Browse files
authored
Block scripts from overwriting python (#13051)
uv adds some binaries and scripts to a venv, and installed packages should not be allowed to overwrite them. Fixes #12983
1 parent 9fb19cd commit cd76210

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

crates/uv-install-wheel/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,6 @@ pub enum Error {
8282
InvalidEggLink(PathBuf),
8383
#[error(transparent)]
8484
LauncherError(#[from] uv_trampoline_builder::Error),
85+
#[error("Scripts must not use the reserved name {0}")]
86+
ReservedScriptName(String),
8587
}

crates/uv-install-wheel/src/wheel.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use uv_normalize::PackageName;
1818
use uv_pypi_types::DirectUrl;
1919
use uv_shell::escape_posix_for_single_quotes;
2020
use uv_trampoline_builder::windows_script_launcher;
21+
use uv_warnings::warn_user_once;
2122

2223
use crate::record::RecordEntry;
2324
use crate::script::{scripts_from_ini, Script};
@@ -186,6 +187,25 @@ pub(crate) fn write_script_entrypoints(
186187
is_gui: bool,
187188
) -> Result<(), Error> {
188189
for entrypoint in entrypoints {
190+
let warn_names = ["activate", "activate_this.py"];
191+
if warn_names.contains(&entrypoint.name.as_str())
192+
|| entrypoint.name.starts_with("activate.")
193+
{
194+
warn_user_once!(
195+
"The script name `{}` is reserved for virtual environment activation scripts.",
196+
entrypoint.name
197+
);
198+
}
199+
let reserved_names = ["python", "pythonw", "python3"];
200+
if reserved_names.contains(&entrypoint.name.as_str())
201+
|| entrypoint
202+
.name
203+
.strip_prefix("python3.")
204+
.is_some_and(|suffix| suffix.parse::<u8>().is_ok())
205+
{
206+
return Err(Error::ReservedScriptName(entrypoint.name.clone()));
207+
}
208+
189209
let entrypoint_absolute = entrypoint_path(entrypoint, layout);
190210

191211
let entrypoint_relative = pathdiff::diff_paths(&entrypoint_absolute, site_packages)

crates/uv/tests/it/pip_install.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11161,3 +11161,78 @@ async fn bogus_redirect() -> Result<()> {
1116111161

1116211162
Ok(())
1116311163
}
11164+
11165+
#[test]
11166+
fn reserved_script_name() -> Result<()> {
11167+
let context = TestContext::new("3.12");
11168+
11169+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
11170+
pyproject_toml.write_str(
11171+
r#"
11172+
[project]
11173+
name = "project"
11174+
version = "0.1.0"
11175+
requires-python = ">=3.12"
11176+
11177+
[project.scripts]
11178+
"activate.bash" = "project:activate"
11179+
11180+
[build-system]
11181+
requires = ["hatchling"]
11182+
build-backend = "hatchling.build"
11183+
"#,
11184+
)?;
11185+
11186+
context
11187+
.temp_dir
11188+
.child("src")
11189+
.child("project")
11190+
.child("__init__.py")
11191+
.touch()?;
11192+
11193+
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r"
11194+
success: true
11195+
exit_code: 0
11196+
----- stdout -----
11197+
11198+
----- stderr -----
11199+
Resolved 1 package in [TIME]
11200+
Prepared 1 package in [TIME]
11201+
warning: The script name `activate.bash` is reserved for virtual environment activation scripts.
11202+
Installed 1 package in [TIME]
11203+
+ project==0.1.0 (from file://[TEMP_DIR]/)
11204+
"
11205+
);
11206+
11207+
pyproject_toml.write_str(
11208+
r#"
11209+
[project]
11210+
name = "project"
11211+
version = "0.1.0"
11212+
requires-python = ">=3.12"
11213+
11214+
[project.scripts]
11215+
"python" = "project:python"
11216+
11217+
[build-system]
11218+
requires = ["hatchling"]
11219+
build-backend = "hatchling.build"
11220+
"#,
11221+
)?;
11222+
11223+
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r"
11224+
success: false
11225+
exit_code: 2
11226+
----- stdout -----
11227+
11228+
----- stderr -----
11229+
Resolved 1 package in [TIME]
11230+
Prepared 1 package in [TIME]
11231+
Uninstalled 1 package in [TIME]
11232+
error: Failed to install: project-0.1.0-py3-none-any.whl (project==0.1.0 (from file://[TEMP_DIR]/))
11233+
Caused by: Scripts must not use the reserved name python
11234+
"
11235+
);
11236+
11237+
Ok(())
11238+
}

0 commit comments

Comments
 (0)