Reject paths in .tool-versions (#1007)

## Summary
- reject path-like uv versions from `.tool-versions`
- reject path-like Python versions from `.tool-versions`
- document the restriction and cover Unix and Windows paths in tests

## Testing
- `npm ci --ignore-scripts`
- `npm run all`

Refs: pi-session 019ff4bb-8b7c-7c4b-8bdf-7c188dfa2e3f
This commit is contained in:
Kevin Stillhammer
2026-08-12 13:18:20 +02:00
committed by GitHub
parent 4f6036f71c
commit f9cdb47d48
5 changed files with 58 additions and 16 deletions
+3 -2
View File
@@ -161,8 +161,9 @@ python 3.13
version-file: ".tool-versions"
```
Only a single Python version is supported. Multiple fallback versions and the asdf `ref:`, `path:`,
and `system` forms are ignored with a warning.
Only a single Python version is supported. Filesystem paths are not supported for uv or Python.
Multiple Python fallback versions and the asdf `ref:`, `path:`, and `system` forms are ignored with
a warning.
```yaml
- name: Install the latest version of uv and set the python version to 3.13t
+29 -11
View File
@@ -113,6 +113,20 @@ describe("getUvVersionFromToolVersions", () => {
);
});
it.each(["/my/python/exploit", "my/exploited/uv", "C:\\exploited\\uv"])(
"should warn and return undefined for path %s",
async (version) => {
mockReadFileSync.mockReturnValue(`uv ${version}`);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
`The uv version ${version} in .tool-versions is not supported. Paths are not allowed.`,
);
},
);
it("should handle file path with .tool-versions extension", async () => {
const fileContent = "uv 0.1.0";
mockReadFileSync.mockReturnValue(fileContent);
@@ -170,19 +184,23 @@ describe("getPythonVersionFromToolVersions", () => {
);
});
it.each(["ref:main", "path:~/src/python", "system"])(
"should warn and return undefined for %s",
async (version) => {
mockReadFileSync.mockReturnValue(`python ${version}`);
it.each([
"ref:main",
"path:~/src/python",
"system",
"/my/python/exploit",
"my/exploited/python",
"C:\\exploited\\python",
])("should warn and return undefined for %s", async (version) => {
mockReadFileSync.mockReturnValue(`python ${version}`);
const result = await getPythonVersionFromToolVersions(".tool-versions");
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
`The Python version ${version} in .tool-versions is not supported. The Python entry will be ignored.`,
);
},
);
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
`The Python version ${version} in .tool-versions is not supported. The Python entry will be ignored.`,
);
});
it("should return undefined for non-.tool-versions files", async () => {
const result = await getPythonVersionFromToolVersions(".python-version");
Generated Vendored
+4 -1
View File
@@ -61862,7 +61862,7 @@ function getPythonVersionFromToolVersions(filePath) {
return void 0;
}
const version3 = stripVersionPrefix(versions[0]);
if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:")) {
if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:") || isPath(version3)) {
warning(
`The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.`
);
@@ -61890,6 +61890,9 @@ function getToolVersions(filePath, toolName) {
function stripVersionPrefix(version3) {
return version3.startsWith("v") ? version3.slice(1) : version3;
}
function isPath(version3) {
return version3.includes("/") || version3.includes("\\");
}
// src/utils/config-file.ts
var import_node_fs3 = __toESM(require("node:fs"), 1);
Generated Vendored
+10 -1
View File
@@ -99245,6 +99245,12 @@ function getUvVersionFromToolVersions(filePath) {
return void 0;
}
const version3 = stripVersionPrefix(versions[0]);
if (isPath(version3)) {
warning(
`The uv version ${versions[0]} in .tool-versions is not supported. Paths are not allowed.`
);
return void 0;
}
if (version3.startsWith("ref")) {
warning(
"The ref syntax of .tool-versions is not supported. Please use a released version instead."
@@ -99265,7 +99271,7 @@ function getPythonVersionFromToolVersions(filePath) {
return void 0;
}
const version3 = stripVersionPrefix(versions[0]);
if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:")) {
if (version3 === "system" || version3.startsWith("ref:") || version3.startsWith("path:") || isPath(version3)) {
warning(
`The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.`
);
@@ -99293,6 +99299,9 @@ function getToolVersions(filePath, toolName) {
function stripVersionPrefix(version3) {
return version3.startsWith("v") ? version3.slice(1) : version3;
}
function isPath(version3) {
return version3.includes("/") || version3.includes("\\");
}
// src/version/uv-lock-file.ts
var import_node_fs5 = __toESM(require("node:fs"), 1);
+12 -1
View File
@@ -10,6 +10,12 @@ export function getUvVersionFromToolVersions(
}
const version = stripVersionPrefix(versions[0]);
if (isPath(version)) {
core.warning(
`The uv version ${versions[0]} in .tool-versions is not supported. Paths are not allowed.`,
);
return undefined;
}
if (version.startsWith("ref")) {
core.warning(
"The ref syntax of .tool-versions is not supported. Please use a released version instead.",
@@ -37,7 +43,8 @@ export function getPythonVersionFromToolVersions(
if (
version === "system" ||
version.startsWith("ref:") ||
version.startsWith("path:")
version.startsWith("path:") ||
isPath(version)
) {
core.warning(
`The Python version ${versions[0]} in .tool-versions is not supported. The Python entry will be ignored.`,
@@ -73,3 +80,7 @@ function getToolVersions(
function stripVersionPrefix(version: string): string {
return version.startsWith("v") ? version.slice(1) : version;
}
function isPath(version: string): boolean {
return version.includes("/") || version.includes("\\");
}