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
This commit is contained in:
konsti 2025-04-25 09:10:10 +02:00 committed by GitHub
parent 9fb19cd43c
commit cd7621043e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 97 additions and 0 deletions

View file

@ -82,4 +82,6 @@ pub enum Error {
InvalidEggLink(PathBuf),
#[error(transparent)]
LauncherError(#[from] uv_trampoline_builder::Error),
#[error("Scripts must not use the reserved name {0}")]
ReservedScriptName(String),
}

View file

@ -18,6 +18,7 @@ use uv_normalize::PackageName;
use uv_pypi_types::DirectUrl;
use uv_shell::escape_posix_for_single_quotes;
use uv_trampoline_builder::windows_script_launcher;
use uv_warnings::warn_user_once;
use crate::record::RecordEntry;
use crate::script::{scripts_from_ini, Script};
@ -186,6 +187,25 @@ pub(crate) fn write_script_entrypoints(
is_gui: bool,
) -> Result<(), Error> {
for entrypoint in entrypoints {
let warn_names = ["activate", "activate_this.py"];
if warn_names.contains(&entrypoint.name.as_str())
|| entrypoint.name.starts_with("activate.")
{
warn_user_once!(
"The script name `{}` is reserved for virtual environment activation scripts.",
entrypoint.name
);
}
let reserved_names = ["python", "pythonw", "python3"];
if reserved_names.contains(&entrypoint.name.as_str())
|| entrypoint
.name
.strip_prefix("python3.")
.is_some_and(|suffix| suffix.parse::<u8>().is_ok())
{
return Err(Error::ReservedScriptName(entrypoint.name.clone()));
}
let entrypoint_absolute = entrypoint_path(entrypoint, layout);
let entrypoint_relative = pathdiff::diff_paths(&entrypoint_absolute, site_packages)

View file

@ -11161,3 +11161,78 @@ async fn bogus_redirect() -> Result<()> {
Ok(())
}
#[test]
fn reserved_script_name() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
[project.scripts]
"activate.bash" = "project:activate"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
)?;
context
.temp_dir
.child("src")
.child("project")
.child("__init__.py")
.touch()?;
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
warning: The script name `activate.bash` is reserved for virtual environment activation scripts.
Installed 1 package in [TIME]
+ project==0.1.0 (from file://[TEMP_DIR]/)
"
);
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
[project.scripts]
"python" = "project:python"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
)?;
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
error: Failed to install: project-0.1.0-py3-none-any.whl (project==0.1.0 (from file://[TEMP_DIR]/))
Caused by: Scripts must not use the reserved name python
"
);
Ok(())
}