From 64b5c919a950a394f4ca40242cb5d0b2845880ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 00:05:40 -0700 Subject: [PATCH 01/15] test: bind release verifier to matrix artifact namespaces --- ...eArtifactVerifierDirectoryContract.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/lib/releaseArtifactVerifierDirectoryContract.test.ts diff --git a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts new file mode 100644 index 000000000..1fedc5a41 --- /dev/null +++ b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts @@ -0,0 +1,69 @@ +import { createHash } from 'node:crypto'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const runAttempt = '1'; +const platformDirectories = { + linux: `release-disksage-ubuntu-22.04-${runAttempt}`, + windows: `release-disksage-windows-2022-${runAttempt}`, + macos: `release-disksage-macos-latest-${runAttempt}`, +} as const; + +function write(path: string, bytes: Buffer | string) { + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, bytes); +} + +function addCli(artifactRoot: string, directory: string, name: string) { + const bytes = Buffer.from(`cli:${name}`); + const assetPath = join(artifactRoot, directory, name); + write(assetPath, bytes); + write( + `${assetPath}.sha256`, + `${createHash('sha256').update(bytes).digest('hex')} ${name}\n`, + ); +} + +describe('release artifact verifier directory contract', () => { + it.runIf(process.platform !== 'win32')( + 'accepts the exact platform namespaces uploaded by the release matrix', + () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), 'disksage-release-artifact-verifier-')); + const artifactRoot = join(fixtureRoot, 'release-artifacts'); + try { + write(join(artifactRoot, platformDirectories.linux, 'bundle/deb/disksage.deb'), 'deb'); + write(join(artifactRoot, platformDirectories.linux, 'bundle/appimage/disksage.AppImage'), 'appimage'); + write(join(artifactRoot, platformDirectories.windows, 'bundle/msi/disksage.msi'), 'msi'); + write(join(artifactRoot, platformDirectories.windows, 'bundle/nsis/disksage-setup.exe'), 'nsis'); + write(join(artifactRoot, platformDirectories.macos, 'bundle/dmg/disksage.dmg'), 'dmg'); + + addCli(artifactRoot, platformDirectories.linux, 'disksage-cloud-plan-linux-x86_64'); + addCli(artifactRoot, platformDirectories.linux, 'disksage-duplicate-audit-linux-x86_64'); + addCli(artifactRoot, platformDirectories.windows, 'disksage-cloud-plan-windows-x86_64.exe'); + addCli(artifactRoot, platformDirectories.windows, 'disksage-duplicate-audit-windows-x86_64.exe'); + addCli(artifactRoot, platformDirectories.macos, 'disksage-cloud-plan-macos-arm64'); + addCli(artifactRoot, platformDirectories.macos, 'disksage-duplicate-audit-macos-arm64'); + + const result = spawnSync( + 'bash', + [ + resolve(repositoryRoot, '.github/scripts/verify-release-artifacts.sh'), + artifactRoot, + runAttempt, + ], + { cwd: repositoryRoot, encoding: 'utf8' }, + ); + + expect(result.status, result.stderr).toBe(0); + expect(result.stderr).toBe(''); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } + }, + ); +}); From 46d219898e3335b914eca8d05bafb193f460c8d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 00:06:01 -0700 Subject: [PATCH 02/15] fix: verify Windows release artifact namespace --- .github/scripts/verify-release-artifacts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/verify-release-artifacts.sh b/.github/scripts/verify-release-artifacts.sh index b35a74651..5d891302e 100644 --- a/.github/scripts/verify-release-artifacts.sh +++ b/.github/scripts/verify-release-artifacts.sh @@ -33,7 +33,7 @@ require_exactly_one_file() { expected_dirs=( "release-disksage-ubuntu-22.04-${run_attempt}" - "release-disksage-windows-latest-${run_attempt}" + "release-disksage-windows-2022-${run_attempt}" "release-disksage-macos-latest-${run_attempt}" ) From 839408743e0d054d40b403131fef3962d4091a20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 01:35:45 -0700 Subject: [PATCH 03/15] test: reject cross-platform release artifact placement --- ...eArtifactVerifierDirectoryContract.test.ts | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts index 1fedc5a41..79ead52d0 100644 --- a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts +++ b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts @@ -1,5 +1,5 @@ import { createHash } from 'node:crypto'; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; @@ -29,6 +29,33 @@ function addCli(artifactRoot: string, directory: string, name: string) { ); } +function materializeExactArtifactSet(artifactRoot: string) { + write(join(artifactRoot, platformDirectories.linux, 'bundle/deb/disksage.deb'), 'deb'); + write(join(artifactRoot, platformDirectories.linux, 'bundle/appimage/disksage.AppImage'), 'appimage'); + write(join(artifactRoot, platformDirectories.windows, 'bundle/msi/disksage.msi'), 'msi'); + write(join(artifactRoot, platformDirectories.windows, 'bundle/nsis/disksage-setup.exe'), 'nsis'); + write(join(artifactRoot, platformDirectories.macos, 'bundle/dmg/disksage.dmg'), 'dmg'); + + addCli(artifactRoot, platformDirectories.linux, 'disksage-cloud-plan-linux-x86_64'); + addCli(artifactRoot, platformDirectories.linux, 'disksage-duplicate-audit-linux-x86_64'); + addCli(artifactRoot, platformDirectories.windows, 'disksage-cloud-plan-windows-x86_64.exe'); + addCli(artifactRoot, platformDirectories.windows, 'disksage-duplicate-audit-windows-x86_64.exe'); + addCli(artifactRoot, platformDirectories.macos, 'disksage-cloud-plan-macos-arm64'); + addCli(artifactRoot, platformDirectories.macos, 'disksage-duplicate-audit-macos-arm64'); +} + +function verify(artifactRoot: string) { + return spawnSync( + 'bash', + [ + resolve(repositoryRoot, '.github/scripts/verify-release-artifacts.sh'), + artifactRoot, + runAttempt, + ], + { cwd: repositoryRoot, encoding: 'utf8' }, + ); +} + describe('release artifact verifier directory contract', () => { it.runIf(process.platform !== 'win32')( 'accepts the exact platform namespaces uploaded by the release matrix', @@ -36,28 +63,9 @@ describe('release artifact verifier directory contract', () => { const fixtureRoot = mkdtempSync(join(tmpdir(), 'disksage-release-artifact-verifier-')); const artifactRoot = join(fixtureRoot, 'release-artifacts'); try { - write(join(artifactRoot, platformDirectories.linux, 'bundle/deb/disksage.deb'), 'deb'); - write(join(artifactRoot, platformDirectories.linux, 'bundle/appimage/disksage.AppImage'), 'appimage'); - write(join(artifactRoot, platformDirectories.windows, 'bundle/msi/disksage.msi'), 'msi'); - write(join(artifactRoot, platformDirectories.windows, 'bundle/nsis/disksage-setup.exe'), 'nsis'); - write(join(artifactRoot, platformDirectories.macos, 'bundle/dmg/disksage.dmg'), 'dmg'); - - addCli(artifactRoot, platformDirectories.linux, 'disksage-cloud-plan-linux-x86_64'); - addCli(artifactRoot, platformDirectories.linux, 'disksage-duplicate-audit-linux-x86_64'); - addCli(artifactRoot, platformDirectories.windows, 'disksage-cloud-plan-windows-x86_64.exe'); - addCli(artifactRoot, platformDirectories.windows, 'disksage-duplicate-audit-windows-x86_64.exe'); - addCli(artifactRoot, platformDirectories.macos, 'disksage-cloud-plan-macos-arm64'); - addCli(artifactRoot, platformDirectories.macos, 'disksage-duplicate-audit-macos-arm64'); + materializeExactArtifactSet(artifactRoot); - const result = spawnSync( - 'bash', - [ - resolve(repositoryRoot, '.github/scripts/verify-release-artifacts.sh'), - artifactRoot, - runAttempt, - ], - { cwd: repositoryRoot, encoding: 'utf8' }, - ); + const result = verify(artifactRoot); expect(result.status, result.stderr).toBe(0); expect(result.stderr).toBe(''); @@ -66,4 +74,27 @@ describe('release artifact verifier directory contract', () => { } }, ); -}); + + it.runIf(process.platform !== 'win32')( + 'rejects a Windows bundle that escaped its Windows artifact directory', + () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), 'disksage-release-artifact-verifier-')); + const artifactRoot = join(fixtureRoot, 'release-artifacts'); + try { + materializeExactArtifactSet(artifactRoot); + const source = join(artifactRoot, platformDirectories.windows, 'bundle/msi/disksage.msi'); + const misplaced = join(artifactRoot, platformDirectories.linux, 'bundle/msi/disksage.msi'); + mkdirSync(dirname(misplaced), { recursive: true }); + renameSync(source, misplaced); + + const result = verify(artifactRoot); + + expect(result.status).not.toBe(0); + expect(result.stdout).toBe(''); + expect(result.stderr).toContain('Windows MSI bundle'); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } + }, + ); +}); \ No newline at end of file From e93f286101cea749f4e282e8567695c8ea62055d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 01:36:31 -0700 Subject: [PATCH 04/15] fix: bind release artifacts to platform directories --- .github/scripts/verify-release-artifacts.sh | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/scripts/verify-release-artifacts.sh b/.github/scripts/verify-release-artifacts.sh index 5d891302e..a6b344e16 100644 --- a/.github/scripts/verify-release-artifacts.sh +++ b/.github/scripts/verify-release-artifacts.sh @@ -23,10 +23,10 @@ require_exactly_one_path() { } require_exactly_one_file() { - local file_name="$1" count=0 matched_path="" - while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root" -type f -name "$file_name" -print0) + local directory="$1" file_name="$2" count=0 matched_path="" + while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root/$directory" -type f -name "$file_name" -print0) if [[ $count -ne 1 ]]; then - printf 'Expected exactly one release artifact named %s, found %s.\n' "$file_name" "$count" >&2 + printf 'Expected exactly one release artifact named %s in %s, found %s.\n' "$file_name" "$directory" "$count" >&2 exit 1 fi } @@ -55,22 +55,24 @@ if [[ -n "$unexpected_entry" ]]; then exit 1 fi -require_exactly_one_path '*/bundle/deb/*.deb' 'Debian bundle' -require_exactly_one_path '*/bundle/appimage/*.AppImage' 'AppImage bundle' -require_exactly_one_path '*/bundle/msi/*.msi' 'Windows MSI bundle' -require_exactly_one_path '*/bundle/nsis/*.exe' 'Windows NSIS bundle' -require_exactly_one_path '*/bundle/dmg/*.dmg' 'macOS DMG bundle' +require_exactly_one_path "$artifact_root/${expected_dirs[0]}/bundle/deb/*.deb" 'Debian bundle' +require_exactly_one_path "$artifact_root/${expected_dirs[0]}/bundle/appimage/*.AppImage" 'AppImage bundle' +require_exactly_one_path "$artifact_root/${expected_dirs[1]}/bundle/msi/*.msi" 'Windows MSI bundle' +require_exactly_one_path "$artifact_root/${expected_dirs[1]}/bundle/nsis/*.exe" 'Windows NSIS bundle' +require_exactly_one_path "$artifact_root/${expected_dirs[2]}/bundle/dmg/*.dmg" 'macOS DMG bundle' -for required_name in \ - disksage-cloud-plan-linux-x86_64 \ - disksage-duplicate-audit-linux-x86_64 \ - disksage-cloud-plan-windows-x86_64.exe \ - disksage-duplicate-audit-windows-x86_64.exe \ - disksage-cloud-plan-macos-arm64 \ - disksage-duplicate-audit-macos-arm64; do - require_exactly_one_file "$required_name" - require_exactly_one_file "$required_name.sha256" -done +require_exactly_one_file "${expected_dirs[0]}" disksage-cloud-plan-linux-x86_64 +require_exactly_one_file "${expected_dirs[0]}" disksage-cloud-plan-linux-x86_64.sha256 +require_exactly_one_file "${expected_dirs[0]}" disksage-duplicate-audit-linux-x86_64 +require_exactly_one_file "${expected_dirs[0]}" disksage-duplicate-audit-linux-x86_64.sha256 +require_exactly_one_file "${expected_dirs[1]}" disksage-cloud-plan-windows-x86_64.exe +require_exactly_one_file "${expected_dirs[1]}" disksage-cloud-plan-windows-x86_64.exe.sha256 +require_exactly_one_file "${expected_dirs[1]}" disksage-duplicate-audit-windows-x86_64.exe +require_exactly_one_file "${expected_dirs[1]}" disksage-duplicate-audit-windows-x86_64.exe.sha256 +require_exactly_one_file "${expected_dirs[2]}" disksage-cloud-plan-macos-arm64 +require_exactly_one_file "${expected_dirs[2]}" disksage-cloud-plan-macos-arm64.sha256 +require_exactly_one_file "${expected_dirs[2]}" disksage-duplicate-audit-macos-arm64 +require_exactly_one_file "${expected_dirs[2]}" disksage-duplicate-audit-macos-arm64.sha256 checksum_files=() checksum_file="" From af2e1b48482a7dc7088c1aac762c9448e0e542fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 01:37:52 -0700 Subject: [PATCH 05/15] test: bind operational CLIs to release platform namespace --- ...eArtifactVerifierDirectoryContract.test.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts index 79ead52d0..c788f9dab 100644 --- a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts +++ b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts @@ -97,4 +97,31 @@ describe('release artifact verifier directory contract', () => { } }, ); -}); \ No newline at end of file + + it.runIf(process.platform !== 'win32')( + 'rejects a Windows operational CLI and checksum outside the Windows artifact directory', + () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), 'disksage-release-artifact-verifier-')); + const artifactRoot = join(fixtureRoot, 'release-artifacts'); + try { + materializeExactArtifactSet(artifactRoot); + const cliName = 'disksage-cloud-plan-windows-x86_64.exe'; + const source = join(artifactRoot, platformDirectories.windows, cliName); + const sourceChecksum = `${source}.sha256`; + const misplaced = join(artifactRoot, platformDirectories.linux, cliName); + const misplacedChecksum = `${misplaced}.sha256`; + renameSync(source, misplaced); + renameSync(sourceChecksum, misplacedChecksum); + + const result = verify(artifactRoot); + + expect(result.status).not.toBe(0); + expect(result.stdout).toBe(''); + expect(result.stderr).toContain(cliName); + expect(result.stderr).toContain(platformDirectories.windows); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } + }, + ); +}); From 18b709d7bb6fe113abe48c68814dbcd949b11167 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 02:10:02 -0700 Subject: [PATCH 06/15] test: require exact tag release artifact verification --- ...releaseTagArtifactVerifierContract.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/lib/releaseTagArtifactVerifierContract.test.ts diff --git a/src/lib/releaseTagArtifactVerifierContract.test.ts b/src/lib/releaseTagArtifactVerifierContract.test.ts new file mode 100644 index 000000000..f88649b9f --- /dev/null +++ b/src/lib/releaseTagArtifactVerifierContract.test.ts @@ -0,0 +1,28 @@ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); + +function readReleaseWorkflow(): string { + return readFileSync(resolve(repositoryRoot, '.github/workflows/release.yml'), 'utf8').replace(/\r\n?/g, '\n'); +} + +describe('tag release artifact verifier contract', () => { + it('runs the shared exact build-artifact verifier before generating the SBOM', () => { + const workflow = readReleaseWorkflow(); + const attestStart = workflow.indexOf('\n attest-release:\n'); + const publishStart = workflow.indexOf('\n publish-release:\n', attestStart); + expect(attestStart).toBeGreaterThanOrEqual(0); + expect(publishStart).toBeGreaterThan(attestStart); + + const attestJob = workflow.slice(attestStart, publishStart); + const sharedVerifier = 'bash .github/scripts/verify-release-artifacts.sh release-artifacts "${{ github.run_attempt }}"'; + const verifierOffset = attestJob.indexOf(sharedVerifier); + const sbomOffset = attestJob.indexOf('- name: Generate and validate source-bound SBOM'); + + expect(verifierOffset).toBeGreaterThanOrEqual(0); + expect(sbomOffset).toBeGreaterThan(verifierOffset); + }); +}); From 1c78736a2a1f0d5b9fe938dda0a93f92578a8498 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 18:20:39 +0900 Subject: [PATCH 07/15] fix: verify tag artifacts before sbom --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 980672cd7..30a69e363 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -259,6 +259,10 @@ jobs: path: release-artifacts merge-multiple: false + - name: Verify downloaded release artifact contract + shell: bash + run: bash .github/scripts/verify-release-artifacts.sh release-artifacts "${{ github.run_attempt }}" + - name: Generate and validate source-bound SBOM shell: bash run: | From 49964b9ada98f8f8cf25e57334d87ec68cb6c809 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 03:32:44 -0700 Subject: [PATCH 08/15] docs: document release verifier test helpers --- src/lib/releaseArtifactVerifierDirectoryContract.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts index c788f9dab..e15cd8a43 100644 --- a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts +++ b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts @@ -14,11 +14,13 @@ const platformDirectories = { macos: `release-disksage-macos-latest-${runAttempt}`, } as const; +/** Writes one fixture file, creating only the parent directories required by that fixture. */ function write(path: string, bytes: Buffer | string) { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, bytes); } +/** Adds one operational CLI fixture together with its adjacent SHA-256 receipt. */ function addCli(artifactRoot: string, directory: string, name: string) { const bytes = Buffer.from(`cli:${name}`); const assetPath = join(artifactRoot, directory, name); @@ -29,6 +31,7 @@ function addCli(artifactRoot: string, directory: string, name: string) { ); } +/** Materializes the exact 17-file Linux, Windows, and macOS release artifact contract. */ function materializeExactArtifactSet(artifactRoot: string) { write(join(artifactRoot, platformDirectories.linux, 'bundle/deb/disksage.deb'), 'deb'); write(join(artifactRoot, platformDirectories.linux, 'bundle/appimage/disksage.AppImage'), 'appimage'); @@ -44,6 +47,7 @@ function materializeExactArtifactSet(artifactRoot: string) { addCli(artifactRoot, platformDirectories.macos, 'disksage-duplicate-audit-macos-arm64'); } +/** Runs the repository-owned verifier against one isolated downloaded-artifact fixture. */ function verify(artifactRoot: string) { return spawnSync( 'bash', @@ -124,4 +128,4 @@ describe('release artifact verifier directory contract', () => { } }, ); -}); +}); \ No newline at end of file From b248f1a58ccb885c3c3beb2b6fb3cb0f06bf0913 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 03:33:06 -0700 Subject: [PATCH 09/15] docs: document release workflow test helper --- src/lib/releaseTagArtifactVerifierContract.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/releaseTagArtifactVerifierContract.test.ts b/src/lib/releaseTagArtifactVerifierContract.test.ts index f88649b9f..c24e4368a 100644 --- a/src/lib/releaseTagArtifactVerifierContract.test.ts +++ b/src/lib/releaseTagArtifactVerifierContract.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest'; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +/** Reads the source-controlled release workflow with normalized line endings for stable assertions. */ function readReleaseWorkflow(): string { return readFileSync(resolve(repositoryRoot, '.github/workflows/release.yml'), 'utf8').replace(/\r\n?/g, '\n'); } @@ -25,4 +26,4 @@ describe('tag release artifact verifier contract', () => { expect(verifierOffset).toBeGreaterThanOrEqual(0); expect(sbomOffset).toBeGreaterThan(verifierOffset); }); -}); +}); \ No newline at end of file From c39deaeabb7d98aea2af3dcb89ef79a6b52ed6b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 03:33:37 -0700 Subject: [PATCH 10/15] docs: document release verifier shell helpers --- .github/scripts/verify-release-artifacts.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/verify-release-artifacts.sh b/.github/scripts/verify-release-artifacts.sh index a6b344e16..a90ca6ffe 100644 --- a/.github/scripts/verify-release-artifacts.sh +++ b/.github/scripts/verify-release-artifacts.sh @@ -13,6 +13,7 @@ if [[ ! -d "$artifact_root" ]]; then exit 1 fi +# Require exactly one regular file matching a path-scoped release artifact pattern. require_exactly_one_path() { local path_pattern="$1" label="$2" count=0 matched_path="" while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root" -type f -path "$path_pattern" -print0) @@ -22,6 +23,7 @@ require_exactly_one_path() { fi } +# Require exactly one named operational artifact inside its platform-scoped directory. require_exactly_one_file() { local directory="$1" file_name="$2" count=0 matched_path="" while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root/$directory" -type f -name "$file_name" -print0) @@ -120,4 +122,4 @@ while IFS= read -r -d '' matched_path; do regular_file_count=$((regular_file_cou if [[ $regular_file_count -ne 17 ]]; then printf 'Unexpected release artifact entries: expected exactly 17 regular files, found %s.\n' "$regular_file_count" >&2 exit 1 -fi +fi \ No newline at end of file From 4047ffd9f9aa7b368ebdab2b0ad8107e2d6c6f72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 09:32:14 -0700 Subject: [PATCH 11/15] chore(release): keep verifier shellcheck-clean --- .github/scripts/verify-release-artifacts.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/scripts/verify-release-artifacts.sh b/.github/scripts/verify-release-artifacts.sh index a90ca6ffe..4aef28d4a 100644 --- a/.github/scripts/verify-release-artifacts.sh +++ b/.github/scripts/verify-release-artifacts.sh @@ -15,8 +15,8 @@ fi # Require exactly one regular file matching a path-scoped release artifact pattern. require_exactly_one_path() { - local path_pattern="$1" label="$2" count=0 matched_path="" - while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root" -type f -path "$path_pattern" -print0) + local path_pattern="$1" label="$2" count=0 + while IFS= read -r -d '' _; do count=$((count + 1)); done < <(find "$artifact_root" -type f -path "$path_pattern" -print0) if [[ $count -ne 1 ]]; then printf 'Expected exactly one %s, found %s.\n' "$label" "$count" >&2 exit 1 @@ -25,8 +25,8 @@ require_exactly_one_path() { # Require exactly one named operational artifact inside its platform-scoped directory. require_exactly_one_file() { - local directory="$1" file_name="$2" count=0 matched_path="" - while IFS= read -r -d '' matched_path; do count=$((count + 1)); done < <(find "$artifact_root/$directory" -type f -name "$file_name" -print0) + local directory="$1" file_name="$2" count=0 + while IFS= read -r -d '' _; do count=$((count + 1)); done < <(find "$artifact_root/$directory" -type f -name "$file_name" -print0) if [[ $count -ne 1 ]]; then printf 'Expected exactly one release artifact named %s in %s, found %s.\n' "$file_name" "$directory" "$count" >&2 exit 1 @@ -117,9 +117,8 @@ for checksum_file in "${checksum_files[@]}"; do done regular_file_count=0 -matched_path="" -while IFS= read -r -d '' matched_path; do regular_file_count=$((regular_file_count + 1)); done < <(find "$artifact_root" -type f -print0) +while IFS= read -r -d '' _; do regular_file_count=$((regular_file_count + 1)); done < <(find "$artifact_root" -type f -print0) if [[ $regular_file_count -ne 17 ]]; then printf 'Unexpected release artifact entries: expected exactly 17 regular files, found %s.\n' "$regular_file_count" >&2 exit 1 -fi \ No newline at end of file +fi From d2127d4aa40584fbd6f1c030e38a5ace6fb42e84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:43:56 -0700 Subject: [PATCH 12/15] test: reject nested release bundle placement --- ...eArtifactVerifierDirectoryContract.test.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts index e15cd8a43..94971ad6e 100644 --- a/src/lib/releaseArtifactVerifierDirectoryContract.test.ts +++ b/src/lib/releaseArtifactVerifierDirectoryContract.test.ts @@ -102,6 +102,33 @@ describe('release artifact verifier directory contract', () => { }, ); + it.runIf(process.platform !== 'win32')( + 'rejects a bundle nested below its exact release matrix directory', + () => { + const fixtureRoot = mkdtempSync(join(tmpdir(), 'disksage-release-artifact-verifier-')); + const artifactRoot = join(fixtureRoot, 'release-artifacts'); + try { + materializeExactArtifactSet(artifactRoot); + const source = join(artifactRoot, platformDirectories.linux, 'bundle/deb/disksage.deb'); + const misplaced = join( + artifactRoot, + platformDirectories.linux, + 'bundle/deb/unexpected/disksage.deb', + ); + mkdirSync(dirname(misplaced), { recursive: true }); + renameSync(source, misplaced); + + const result = verify(artifactRoot); + + expect(result.status).not.toBe(0); + expect(result.stdout).toBe(''); + expect(result.stderr).toContain('Debian bundle'); + } finally { + rmSync(fixtureRoot, { recursive: true, force: true }); + } + }, + ); + it.runIf(process.platform !== 'win32')( 'rejects a Windows operational CLI and checksum outside the Windows artifact directory', () => { @@ -128,4 +155,4 @@ describe('release artifact verifier directory contract', () => { } }, ); -}); \ No newline at end of file +}); From a71d4d78d6969c24aa272ee1371006f92915f67b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:44:57 -0700 Subject: [PATCH 13/15] fix: bind release bundles to exact matrix directories --- .github/scripts/verify-release-artifacts.sh | 26 +++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/scripts/verify-release-artifacts.sh b/.github/scripts/verify-release-artifacts.sh index 4aef28d4a..2f9047c27 100644 --- a/.github/scripts/verify-release-artifacts.sh +++ b/.github/scripts/verify-release-artifacts.sh @@ -13,20 +13,26 @@ if [[ ! -d "$artifact_root" ]]; then exit 1 fi -# Require exactly one regular file matching a path-scoped release artifact pattern. +# Require exactly one regular bundle file directly inside its matrix-defined bundle directory. require_exactly_one_path() { - local path_pattern="$1" label="$2" count=0 - while IFS= read -r -d '' _; do count=$((count + 1)); done < <(find "$artifact_root" -type f -path "$path_pattern" -print0) + local directory="$1" file_pattern="$2" label="$3" count=0 + if [[ -d "$artifact_root/$directory" ]]; then + while IFS= read -r -d '' _; do count=$((count + 1)); done < <( + find "$artifact_root/$directory" -mindepth 1 -maxdepth 1 -type f -name "$file_pattern" -print0 + ) + fi if [[ $count -ne 1 ]]; then printf 'Expected exactly one %s, found %s.\n' "$label" "$count" >&2 exit 1 fi } -# Require exactly one named operational artifact inside its platform-scoped directory. +# Require exactly one named operational artifact directly inside its platform-scoped directory. require_exactly_one_file() { local directory="$1" file_name="$2" count=0 - while IFS= read -r -d '' _; do count=$((count + 1)); done < <(find "$artifact_root/$directory" -type f -name "$file_name" -print0) + while IFS= read -r -d '' _; do count=$((count + 1)); done < <( + find "$artifact_root/$directory" -mindepth 1 -maxdepth 1 -type f -name "$file_name" -print0 + ) if [[ $count -ne 1 ]]; then printf 'Expected exactly one release artifact named %s in %s, found %s.\n' "$file_name" "$directory" "$count" >&2 exit 1 @@ -57,11 +63,11 @@ if [[ -n "$unexpected_entry" ]]; then exit 1 fi -require_exactly_one_path "$artifact_root/${expected_dirs[0]}/bundle/deb/*.deb" 'Debian bundle' -require_exactly_one_path "$artifact_root/${expected_dirs[0]}/bundle/appimage/*.AppImage" 'AppImage bundle' -require_exactly_one_path "$artifact_root/${expected_dirs[1]}/bundle/msi/*.msi" 'Windows MSI bundle' -require_exactly_one_path "$artifact_root/${expected_dirs[1]}/bundle/nsis/*.exe" 'Windows NSIS bundle' -require_exactly_one_path "$artifact_root/${expected_dirs[2]}/bundle/dmg/*.dmg" 'macOS DMG bundle' +require_exactly_one_path "${expected_dirs[0]}/bundle/deb" '*.deb' 'Debian bundle' +require_exactly_one_path "${expected_dirs[0]}/bundle/appimage" '*.AppImage' 'AppImage bundle' +require_exactly_one_path "${expected_dirs[1]}/bundle/msi" '*.msi' 'Windows MSI bundle' +require_exactly_one_path "${expected_dirs[1]}/bundle/nsis" '*.exe' 'Windows NSIS bundle' +require_exactly_one_path "${expected_dirs[2]}/bundle/dmg" '*.dmg' 'macOS DMG bundle' require_exactly_one_file "${expected_dirs[0]}" disksage-cloud-plan-linux-x86_64 require_exactly_one_file "${expected_dirs[0]}" disksage-cloud-plan-linux-x86_64.sha256 From 4dfbe639259c5fd8708903ec9bfc35c2af3619e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 09:05:43 -0700 Subject: [PATCH 14/15] test: preserve attest v4.2.2 provenance contract --- src/lib/releaseProvenanceContract.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/releaseProvenanceContract.test.ts b/src/lib/releaseProvenanceContract.test.ts index f3d46120b..79ea10495 100644 --- a/src/lib/releaseProvenanceContract.test.ts +++ b/src/lib/releaseProvenanceContract.test.ts @@ -145,10 +145,11 @@ describe('release artifact provenance contract', () => { expect(attestJob).toContain('contents: read'); expect(attestJob).toContain('id-token: write'); expect(attestJob).toContain('attestations: write'); + expect(attestJob).not.toContain('artifact-metadata: write'); expect(attestJob).toContain('pattern: release-disksage-*'); expect(attestJob).toContain('merge-multiple: false'); expect(attestJob).toContain( - 'actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26', + 'actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6', ); expect(attestJob).toContain('subject-path: release-artifacts/**/*'); expect(attestJob).toContain('Generate and validate source-bound SBOM'); From 1de59f29bf9e55a31fd2241d8004509eb7311482 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 09:06:45 -0700 Subject: [PATCH 15/15] fix: preserve attest v4.2.2 on release verifier branch --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30a69e363..a1a0914d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -364,7 +364,7 @@ jobs: fi - name: Generate GitHub build provenance - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: release-artifacts/**/*