Verify downloads with astral-sh/versions checksums (#1033)
test / test-default-version (ubuntu-latest) (push) Failing after 4s
test / test-uv-no-modify-path (push) Failing after 4s
test / test-specific-version (map[expected-version:0.1.0 resolution-strategy:lowest version-input:>=0.1.0,<0.2]) (push) Failing after 3s
test / test-specific-version (map[expected-version:0.1.45 resolution-strategy:highest version-input:>=0.1,<0.2]) (push) Failing after 3s
test / test-specific-version (map[expected-version:0.3.0 version-input:0.3.0]) (push) Failing after 4s
test / test-specific-version (map[expected-version:0.3.2 version-input:0.3.2]) (push) Failing after 4s
test / test-uvx (push) Failing after 1s
test / test-latest-version (>=0.8) (push) Failing after 2s
test / test-specific-version (map[expected-version:0.3.5 version-input:0.3]) (push) Failing after 22s
test / test-version-file-version (map[expected-version:0.6.17 version-file:__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt]) (push) Failing after 16s
test / test-from-working-directory-version (map[expected-version:0.5.15 working-directory:__tests__/fixtures/uv-toml-project]) (push) Failing after 17s
test / test-tool-versions-python-version (push) Failing after 16s
test / test-setup-cache-dependency-glob (push) Failing after 17s
test / test-restore-cache-requirements-txt (push) Skipped
test / test-restore-cache-dependency-glob (push) Skipped
test / test-restore-cache-save-cache-false (push) Skipped
test / test-no-python-version (push) Failing after 24s
test / test-custom-manifest-file (push) Failing after 25s
test / test-activate-environment-custom-path (ubuntu-latest) (push) Failing after 1s
test / test-checksum (map[checksum:4d9279ad5ca596b1e2d703901d508430eb07564dc4d8837de9e2fca9c90f8ecd os:ubuntu-latest]) (push) Failing after 18s
test / test-musl (push) Failing after 13s
test / test-specific-version (map[expected-version:0.4.25 resolution-strategy:lowest version-input:>=0.4.25,<0.5]) (push) Failing after 6s
test / test-with-explicit-token (push) Failing after 17s
test / test-malformed-pyproject-file-fallback (push) Failing after 17s
test / test-relative-path (push) Failing after 3s
test / test-setup-cache (true, ubuntu-latest) (push) Failing after 12s
test / test-cache-key-os-version (ubuntu-22.04, ubuntu-22.04) (push) Failing after 20s
test / test-tool-install (ubuntu-latest) (push) Failing after 2s
test / test-specific-version (map[expected-version:0.3.5 version-input:0.3.x]) (push) Failing after 9s
test / test-latest-version (latest) (push) Failing after 2s
test / test-activate-environment (ubuntu-latest) (push) Failing after 1s
test / test-activate-environment-no-project (push) Failing after 1s
test / test-specific-version (map[expected-version:0.4.25 resolution-strategy:lowest version-input:>=0.4.25]) (push) Failing after 12s
test / test-version-file-version (map[expected-version:0.5.15 version-file:__tests__/fixtures/.tool-versions]) (push) Failing after 16s
test / test-from-working-directory-version (map[expected-version:0.5.14 working-directory:__tests__/fixtures/pyproject-toml-project]) (push) Failing after 17s
test / test-specific-version (map[expected-version:0.4.30 version-input:>=0.4.25,<0.5]) (push) Failing after 20s
test / test-version-file-version (map[expected-version:0.8.3 version-file:__tests__/fixtures/uv-in-requirements-hash-txt-project/requirements.txt]) (push) Failing after 16s
test / test-python-version (ubuntu-latest) (push) Failing after 17s
test / test-debian-unstable (push) Failing after 15s
test / test-setup-cache (auto, ubuntu-latest) (push) Failing after 6s
test / test-setup-cache (false, ubuntu-latest) (push) Failing after 6s
test / test-cache-local (map[expected-cache-dir:/home/runner/work/_temp/setup-uv-cache os:ubuntu-latest]) (push) Failing after 5s
test / test-cache-local-cache-disabled (push) Failing after 4s
test / test-cache-local-cache-disabled-but-explicit-path (push) Failing after 4s
test / test-download-from-astral-mirror-false (push) Failing after 4s
test / test-absolute-path (push) Failing after 3s
test / test-setup-cache-requirements-txt (push) Failing after 17s
test / test-setup-cache-restore-cache-false (push) Failing after 18s
test / test-restore-cache-restore-cache-false (push) Skipped
test / test-cache-dir-from-file (push) Failing after 22s
test / test-cache-python-missing-managed-install-dir (push) Failing after 22s
test / test-restore-python-installs (push) Skipped
test / test-cache-prune-force (push) Failing after 22s
test / validate-typings (push) Successful in 44s
test / test-python-install-dir (map[expected-python-dir:/home/runner/work/_temp/uv-python-dir os:ubuntu-latest]) (push) Failing after 5s
test / test-setup-cache-save-cache-false (push) Failing after 17s
test / test-cache-python-installs (push) Failing after 21s
CodeQL / Analyze (TypeScript) (push) Failing after 1m40s
test / test-workflow-run (push) Failing after 35s
test / lint (push) Failing after 1m21s
test / test-default-version (macos-14) (push) Canceled after 0s
test / test-default-version (macos-latest) (push) Canceled after 0s
test / test-default-version (windows-latest) (push) Canceled after 0s
test / test-checksum (map[checksum:a70cbfbf3bb5c08b2f84963b4f12c94e08fbb2468ba418a3bfe1066fbe9e7218 os:macos-latest]) (push) Canceled after 0s
test / test-tool-install (macos-14) (push) Canceled after 0s
test / test-tool-install (macos-latest) (push) Canceled after 0s
test / test-tool-install (windows-latest) (push) Canceled after 0s
test / test-python-version (macos-latest) (push) Canceled after 0s
test / test-python-version (windows-latest) (push) Canceled after 0s
test / test-activate-environment (macos-latest) (push) Canceled after 0s
test / test-activate-environment (windows-latest) (push) Canceled after 0s
test / test-activate-environment-custom-path (macos-latest) (push) Canceled after 0s
test / test-activate-environment-custom-path (windows-latest) (push) Canceled after 0s
test / test-cache-key-os-version (macos-14, macos-14) (push) Canceled after 0s
test / test-cache-key-os-version (macos-15, macos-15) (push) Canceled after 0s
test / test-cache-key-os-version (windows-2025, windows-2025) (push) Canceled after 0s
test / test-setup-cache (true, windows-latest) (push) Canceled after 0s
test / test-cache-key-os-version (ubuntu-24.04, ubuntu-24.04) (push) Canceled after 0s
test / test-cache-key-os-version (windows-2022, windows-2022) (push) Canceled after 0s
test / test-setup-cache (auto, windows-latest) (push) Canceled after 0s
test / test-setup-cache (false, windows-latest) (push) Canceled after 0s
test / test-restore-cache (auto, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (auto, windows-latest) (push) Canceled after 0s
test / test-restore-cache (false, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (false, windows-latest) (push) Canceled after 0s
test / test-restore-cache (true, ubuntu-latest) (push) Canceled after 0s
test / test-restore-cache (true, windows-latest) (push) Canceled after 0s
test / test-cache-local (map[expected-cache-dir:D:\a\_temp\setup-uv-cache os:windows-latest]) (push) Canceled after 0s
test / test-python-install-dir (map[expected-python-dir:D:\a\_temp\uv-python-dir os:windows-latest]) (push) Canceled after 0s
test / all-tests-passed (push) Canceled after 0s
Release Drafter / ✏️ Draft release (push) Canceled after 0s

`setup-uv` currently ignores the `sha256` supplied by the default
`astral-sh/versions` manifest when a selected artifact is newer than its
bundled checksum table, allowing that download to proceed without
validation. Use the manifest checksum as a fallback after explicit and
bundled checksums, and reject manifest entries that do not provide one.
This preserves the stronger pinned hashes for known releases while
verifying newer releases without requiring an action update. Part of
#1032.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
Co-authored-by: William Woodruff <william@yossarian.net>
Co-authored-by: Kevin Stillhammer <kevin.stillhammer@gmail.com>
This commit is contained in:
zaniebot
2026-09-01 17:07:32 +02:00
committed by GitHub
co-authored by Zanie Blue William Woodruff Kevin Stillhammer
parent 3aef7b92c5
commit cd13f92170
6 changed files with 96 additions and 14 deletions
@@ -26,9 +26,49 @@ test("provided checksum beats known checksums", async () => {
"x86_64",
"unknown-linux-gnu",
"0.3.0",
"incorrect-manifest-checksum",
);
});
test("known checksums beat manifest checksums", async () => {
await expect(
validateChecksum(
undefined,
filePath,
"x86_64",
"unknown-linux-gnu",
"0.3.0",
validChecksum,
),
).rejects.toThrow("did not match");
});
test("manifest checksums are used when no known checksum exists", async () => {
await expect(
validateChecksum(
undefined,
filePath,
"aarch64",
"pc-windows-msvc",
"1.2.3",
"incorrect-manifest-checksum",
),
).rejects.toThrow("did not match");
});
test("empty manifest checksums are rejected", async () => {
await expect(
validateChecksum(
undefined,
filePath,
"aarch64",
"pc-windows-msvc",
"1.2.3",
"",
),
).rejects.toThrow("No checksum found");
});
type KnownVersionFixture = { version: string; known: boolean };
it.each<KnownVersionFixture>([
+5 -2
View File
@@ -227,10 +227,10 @@ describe("download-version", () => {
expect(mockValidateChecksum).not.toHaveBeenCalled();
});
it("uses built-in checksums for default manifest downloads", async () => {
it("uses the default manifest checksum as a fallback", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum-that-should-be-ignored",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/uv.tar.gz",
});
@@ -248,6 +248,7 @@ describe("download-version", () => {
"x86_64",
"unknown-linux-gnu",
"0.9.26",
"manifest-checksum",
);
});
@@ -400,6 +401,7 @@ describe("download-version", () => {
"x86_64",
"unknown-linux-gnu",
"0.9.26",
"manifest-checksum",
);
});
@@ -425,6 +427,7 @@ describe("download-version", () => {
"x86_64",
"unknown-linux-gnu",
"0.9.26",
"manifest-checksum",
);
});
Generated Vendored
+20 -5
View File
@@ -99730,15 +99730,20 @@ var known_checksums_default = {
var KNOWN_CHECKSUMS = known_checksums_default;
// src/download/checksum/checksum.ts
async function validateChecksum(checksum, downloadPath, arch3, platform2, version3) {
async function validateChecksum(checksum, downloadPath, arch3, platform2, version3, manifestChecksum) {
const key = `${arch3}-${platform2}-${version3}`;
const hasProvidedChecksum = checksum !== void 0 && checksum !== "";
const checksumToUse = hasProvidedChecksum ? checksum : KNOWN_CHECKSUMS[key];
const knownChecksum = KNOWN_CHECKSUMS[key];
const hasManifestChecksum = manifestChecksum !== void 0 && manifestChecksum !== "";
const checksumToUse = hasProvidedChecksum ? checksum : knownChecksum ?? (hasManifestChecksum ? manifestChecksum : void 0);
if (checksumToUse === void 0) {
if (manifestChecksum !== void 0) {
throw new Error(`No checksum found for ${key} in manifest.`);
}
debug(`No checksum found for ${key}.`);
return;
}
const checksumSource = hasProvidedChecksum ? "provided checksum" : `KNOWN_CHECKSUMS entry for ${key}`;
const checksumSource = hasProvidedChecksum ? "provided checksum" : knownChecksum !== void 0 ? `KNOWN_CHECKSUMS entry for ${key}` : "manifest checksum";
debug(`Validating checksum using ${checksumSource}.`);
const isValid = await validateFileCheckSum(downloadPath, checksumToUse);
if (!isValid) {
@@ -101656,6 +101661,7 @@ async function downloadVersion(platform2, arch3, version3, checksum, githubToken
);
}
const resolvedChecksum = manifestUrl === void 0 ? checksum : resolveChecksum(checksum, artifact.checksum);
const manifestChecksum = artifact.checksum;
const mirrorUrl = downloadFromAstralMirror ? rewriteToMirror(artifact.downloadUrl) : void 0;
const downloadUrl = mirrorUrl ?? artifact.downloadUrl;
try {
@@ -101666,6 +101672,7 @@ async function downloadVersion(platform2, arch3, version3, checksum, githubToken
arch3,
version3,
resolvedChecksum,
manifestChecksum,
githubTokenForUrl(downloadUrl, githubToken)
);
} catch (err) {
@@ -101682,6 +101689,7 @@ async function downloadVersion(platform2, arch3, version3, checksum, githubToken
arch3,
version3,
resolvedChecksum,
manifestChecksum,
githubTokenForUrl(artifact.downloadUrl, githubToken)
);
}
@@ -101699,14 +101707,21 @@ function githubTokenForUrl(downloadUrl, githubToken) {
return void 0;
}
}
async function downloadArtifact(downloadUrl, artifactName, platform2, arch3, version3, checksum, githubToken) {
async function downloadArtifact(downloadUrl, artifactName, platform2, arch3, version3, checksum, manifestChecksum, githubToken) {
info2(`Downloading uv from "${downloadUrl}" ...`);
const downloadPath = await downloadTool(
downloadUrl,
void 0,
githubToken
);
await validateChecksum(checksum, downloadPath, arch3, platform2, version3);
await validateChecksum(
checksum,
downloadPath,
arch3,
platform2,
version3,
manifestChecksum
);
let uvDir;
if (platform2 === "pc-windows-msvc") {
try {
+3 -2
View File
@@ -4,8 +4,9 @@ This document covers advanced customization options including checksum validatio
## Validate checksum
You can specify a checksum to validate the downloaded executable. Checksums up to the default version
are automatically verified by this action. The sha256 hashes can be found on the
Downloaded executables are automatically verified using checksums bundled with this action or,
for newer, not yet bundled versions, the checksum from [`astral-sh/versions`](https://github.com/astral-sh/versions).
You can specify a checksum to override those values. The sha256 hashes can also be found on the
[releases page](https://github.com/astral-sh/uv/releases) of the uv repo.
```yaml
+13 -2
View File
@@ -11,19 +11,30 @@ export async function validateChecksum(
arch: Architecture,
platform: Platform,
version: string,
manifestChecksum?: string,
): Promise<void> {
const key = `${arch}-${platform}-${version}`;
const hasProvidedChecksum = checksum !== undefined && checksum !== "";
const checksumToUse = hasProvidedChecksum ? checksum : KNOWN_CHECKSUMS[key];
const knownChecksum = KNOWN_CHECKSUMS[key];
const hasManifestChecksum =
manifestChecksum !== undefined && manifestChecksum !== "";
const checksumToUse = hasProvidedChecksum
? checksum
: (knownChecksum ?? (hasManifestChecksum ? manifestChecksum : undefined));
if (checksumToUse === undefined) {
if (manifestChecksum !== undefined) {
throw new Error(`No checksum found for ${key} in manifest.`);
}
core.debug(`No checksum found for ${key}.`);
return;
}
const checksumSource = hasProvidedChecksum
? "provided checksum"
: `KNOWN_CHECKSUMS entry for ${key}`;
: knownChecksum !== undefined
? `KNOWN_CHECKSUMS entry for ${key}`
: "manifest checksum";
core.debug(`Validating checksum using ${checksumSource}.`);
const isValid = await validateFileCheckSum(downloadPath, checksumToUse);
+15 -3
View File
@@ -47,12 +47,14 @@ export async function downloadVersion(
);
}
// For the default astral-sh/versions source, checksum validation relies on
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values.
// Custom manifests are explicitly selected by the user, so their checksum
// takes precedence over the built-in table. For the default manifest, pass
// its checksum as a fallback after user input and KNOWN_CHECKSUMS.
const resolvedChecksum =
manifestUrl === undefined
? checksum
: resolveChecksum(checksum, artifact.checksum);
const manifestChecksum = artifact.checksum;
const mirrorUrl = downloadFromAstralMirror
? rewriteToMirror(artifact.downloadUrl)
@@ -67,6 +69,7 @@ export async function downloadVersion(
arch,
version,
resolvedChecksum,
manifestChecksum,
githubTokenForUrl(downloadUrl, githubToken),
);
} catch (err) {
@@ -85,6 +88,7 @@ export async function downloadVersion(
arch,
version,
resolvedChecksum,
manifestChecksum,
githubTokenForUrl(artifact.downloadUrl, githubToken),
);
}
@@ -122,6 +126,7 @@ async function downloadArtifact(
arch: Architecture,
version: string,
checksum: string | undefined,
manifestChecksum: string | undefined,
githubToken: string | undefined,
): Promise<{ version: string; cachedToolDir: string }> {
log.info(`Downloading uv from "${downloadUrl}" ...`);
@@ -130,7 +135,14 @@ async function downloadArtifact(
undefined,
githubToken,
);
await validateChecksum(checksum, downloadPath, arch, platform, version);
await validateChecksum(
checksum,
downloadPath,
arch,
platform,
version,
manifestChecksum,
);
let uvDir: string;
if (platform === "pc-windows-msvc") {