Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions desktop/scripts/verify-release-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function releaseMatrixTargets(workflowText: string): {

/**
* Every recorded checksum against the bytes on disk, in exactly the producers'
* format (64 hex, two spaces, bare name, one trailing newline). The recorded name
* format (64 hex, a space, text/binary marker, bare name, newline). The recorded name
* must equal the checksum file's own name minus the suffix: a foo.sha256 naming
* bar would leave foo's bytes unchecked while bar's are checked twice.
*/
Expand All @@ -104,7 +104,7 @@ export function verifyChecksums(dir: string): number {
if (checksumFiles.length === 0) throw new Error(`No .sha256 files found in ${dir}`);
for (const checksumFile of checksumFiles) {
const content = readFileSync(join(dir, checksumFile), "utf8");
const match = /^([0-9a-f]{64}) (\S+)\n$/.exec(content);
const match = /^([0-9a-f]{64}) [ *](\S+)\r?\n$/.exec(content);
if (!match) throw new Error(`Malformed checksum record in ${checksumFile}: ${JSON.stringify(content)}`);
const digest = match[1]!;
const recorded = match[2]!;
Expand Down
29 changes: 29 additions & 0 deletions tests/ci-workflows/release-desktop-scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,35 @@ describe("release asset verification", () => {
}
});

test("verifies Windows binary checksum records without weakening payload binding", () => {
const dir = temporaryDirectory();
const name = "ocx-1.0.0-bun-windows-x64.zip";
const asset = join(dir, name);
const checksum = `${asset}.sha256`;
// Standard sha256sum binary marker observed in release run 35728908862.
const digest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
try {
writeFileSync(asset, "");
for (const newline of ["\n", "\r\n"]) {
writeFileSync(checksum, `${digest} *${name}${newline}`);
expect(verifyChecksums(dir)).toBe(1);
}
writeFileSync(checksum, `${digest} *different.zip\n`);
expect(() => verifyChecksums(dir)).toThrow(/must record its own payload/);
for (const record of [`${digest} ?${name}\n`, `${digest}*${name}\n`, `${digest} *${name}\nextra\n`]) {
writeFileSync(checksum, record);
expect(() => verifyChecksums(dir)).toThrow(/Malformed checksum record/);
}
writeFileSync(checksum, `${digest} *${name}\n`);
writeFileSync(asset, "changed");
expect(() => verifyChecksums(dir)).toThrow(/Checksum mismatch/);
rmSync(asset);
expect(() => verifyChecksums(dir)).toThrow(/which is missing/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

test("rejects a tampered payload and a missing payload", () => {
const dir = temporaryDirectory();
try {
Expand Down
Loading