diff --git a/__tests__/download/checksum/checksum.test.ts b/__tests__/download/checksum/checksum.test.ts index 8b56d0f..b3d13a6 100644 --- a/__tests__/download/checksum/checksum.test.ts +++ b/__tests__/download/checksum/checksum.test.ts @@ -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([ diff --git a/__tests__/download/download-version.test.ts b/__tests__/download/download-version.test.ts index e45d7e3..b2352dc 100644 --- a/__tests__/download/download-version.test.ts +++ b/__tests__/download/download-version.test.ts @@ -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", ); }); diff --git a/dist/setup/index.cjs b/dist/setup/index.cjs index 94b8954..afce4ce 100644 --- a/dist/setup/index.cjs +++ b/dist/setup/index.cjs @@ -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 { diff --git a/docs/customization.md b/docs/customization.md index fa3ad2d..cea511e 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -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 diff --git a/src/download/checksum/checksum.ts b/src/download/checksum/checksum.ts index ef8876c..11c2278 100644 --- a/src/download/checksum/checksum.ts +++ b/src/download/checksum/checksum.ts @@ -11,19 +11,30 @@ export async function validateChecksum( arch: Architecture, platform: Platform, version: string, + manifestChecksum?: string, ): Promise { 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); diff --git a/src/download/download-version.ts b/src/download/download-version.ts index 2c54c3a..dc808c2 100644 --- a/src/download/download-version.ts +++ b/src/download/download-version.ts @@ -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") {