Skip to content

Commit d3c7261

Browse files
authored
fix(scriptutils): prevent absolute paths from incorrectly triggering bare name rules
`PurePath.match()` performs right-aligned suffix matching, which caused absolute shebang paths (e.g., `C:\uv\python.exe`) to inadvertently evaluate true for bare names like `python.exe` or `python*.exe`. This resulted in two critical bugs: 1. Virtual environment paths were hijacked by the `is_default` trap, falling back to the global default Python runtime instead of the specified one. 2. Custom executables (like `python_uv_test.exe`) were intercepted by the fallback wildcard search, resulting in arbitrary slicing and lookup errors for phantom version tags (e.g., `_uv_test`). This commit introduces an `is_name_only` check (validating the absence of directory separators `/` and `\`) to safely distinguish bare commands from explicit paths. Gating the `is_default`, virtual alias, and wildcard extraction logic behind this check ensures explicit paths correctly fall through to `_find_on_path()`.
1 parent 016d5c9 commit d3c7261

1 file changed

Lines changed: 23 additions & 12 deletions

File tree

src/manage/scriptutils.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,19 @@ def _find_shebang_command(cmd, full_cmd, *, windowed=None):
4848
if not sh_cmd.match("*.exe"):
4949
sh_cmd = sh_cmd.with_name(sh_cmd.name + ".exe")
5050

51-
is_wdefault = (
51+
# Only apply virtual aliases and wildcard tag extraction to bare names.
52+
is_name_only = not ('/' in full_cmd or '\\' in full_cmd)
53+
54+
is_wdefault = is_name_only and (
5255
sh_cmd.match("pythonw.exe")
5356
or sh_cmd.match("pyw.exe")
5457
or sh_cmd.match("pythonw3.exe")
5558
)
56-
is_default = is_wdefault or (
59+
is_default = is_wdefault or (is_name_only and (
5760
sh_cmd.match("python.exe")
5861
or sh_cmd.match("py.exe")
5962
or sh_cmd.match("python3.exe")
60-
)
63+
))
6164

6265
# Internal logic error, but non-fatal, if it has no value
6366
assert windowed is not None
@@ -76,7 +79,8 @@ def _find_shebang_command(cmd, full_cmd, *, windowed=None):
7679

7780
for i in cmd.get_installs():
7881
for a in i.get("alias", ()):
79-
if sh_cmd.match(a["name"]):
82+
# Only match aliases against bare names
83+
if is_name_only and sh_cmd.match(a["name"]):
8084
exe = a["target"]
8185
LOGGER.debug("Matched alias %s in %s", a["name"], i["id"])
8286
if windowed and not a.get("windowed"):
@@ -85,20 +89,27 @@ def _find_shebang_command(cmd, full_cmd, *, windowed=None):
8589
exe = target[0]["target"]
8690
LOGGER.debug("Substituting target %s for windowed=1", exe)
8791
return {**i, "executable": i["prefix"] / exe}
88-
if sh_cmd.full_match(PurePath(i["executable"]).name):
92+
# Only match raw executable names against bare names
93+
if is_name_only and sh_cmd.full_match(PurePath(i["executable"]).name):
8994
LOGGER.debug("Matched executable name %s in %s", i["executable"], i["id"])
9095
return i
96+
# Ensure absolute shebang paths don't incorrectly match relative executable
97+
# paths
9198
if sh_cmd.match(i["executable"]):
99+
if not is_name_only and not PurePath(i["executable"]).is_absolute():
100+
continue
92101
LOGGER.debug("Matched executable %s in %s", i["executable"], i["id"])
93102
return i
94103

95-
# Fallback search for 'python[w]<TAG>.exe' shebangs
96-
if sh_cmd.match("pythonw*.exe"):
97-
tag = sh_cmd.name[7:-4]
98-
return cmd.get_install_to_run(f"PythonCore/{tag}", windowed=True)
99-
if sh_cmd.match("python*.exe"):
100-
tag = sh_cmd.name[6:-4]
101-
return cmd.get_install_to_run(f"PythonCore/{tag}", windowed=windowed)
104+
# Fallback search for 'python[w]<TAG>.exe' shebangs and prevent absolute paths
105+
# from being blindly sliced
106+
if is_name_only:
107+
if sh_cmd.match("pythonw*.exe"):
108+
tag = sh_cmd.name[7:-4]
109+
return cmd.get_install_to_run(f"PythonCore/{tag}", windowed=True)
110+
if sh_cmd.match("python*.exe"):
111+
tag = sh_cmd.name[6:-4]
112+
return cmd.get_install_to_run(f"PythonCore/{tag}", windowed=windowed)
102113

103114
raise LookupError
104115

0 commit comments

Comments
 (0)