ReviewOS

also looking at this

craft-native/craft

Gate releases on shipped artifact findings

#316
Merged glennmichael123 wants to merge codex/gate-shipped-artifacts into main
11 files +454 -25

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/actions/setup-release-scanners/action.ymladded+35-0
Changes to .github/actions/setup-release-scanners/action.yml
@@ -0,0 +1,35 @@
1name: Set up release scanners
2description: Install pinned Syft and Grype binaries for release artifact checks
3runs:
4 using: composite
5 steps:
6 - name: Install Syft and Grype
7 shell: bash
8 run: |
9 set -euo pipefail
10 case "$RUNNER_OS" in
11 Linux)
12 syft_platform=linux_amd64
13 grype_platform=linux_amd64
14 syft_hash=caeedb81fb0491615f1ebd1761e4145d41ee86dd2cc7bf80669f9f5ad9d6133d
15 grype_hash=3fa2dc4b924621ab65404cf08d0b8438d896d80ab949c9d5a4ca283c36004c9b
16 ;;
17 macOS)
18 syft_platform=darwin_arm64
19 grype_platform=darwin_arm64
20 syft_hash=014d561b6d13059124155f74a6c5a9a99501f5e209313638dd884f39eb418ee6
21 grype_hash=500c9b2b6c089d21481815f57a553fabbd441ec7d1e79d95e3aaf40c3bfc7e36
22 ;;
23 *) echo "Unsupported scanner platform: $RUNNER_OS" >&2; exit 1 ;;
24 esac
25 mkdir -p "$RUNNER_TEMP/release-scanners"
26 cd "$RUNNER_TEMP/release-scanners"
27 curl --fail --location --retry 3 --output syft.tar.gz \
28 "https://github.com/anchore/syft/releases/download/v1.52.0/syft_1.52.0_${syft_platform}.tar.gz"
29 echo "$syft_hash syft.tar.gz" | shasum -a 256 --check
30 tar -xzf syft.tar.gz syft
31 curl --fail --location --retry 3 --output grype.tar.gz \
32 "https://github.com/anchore/grype/releases/download/v0.119.0/grype_0.119.0_${grype_platform}.tar.gz"
33 echo "$grype_hash grype.tar.gz" | shasum -a 256 --check
34 tar -xzf grype.tar.gz grype
35 echo "$RUNNER_TEMP/release-scanners" >> "$GITHUB_PATH"
.github/workflows/ci.ymlmodified+1-1
Changes to .github/workflows/ci.yml
@@ -278,7 +278,7 @@ jobs:
278278 run: zig fmt --check src/ build.zig
279279
280280 - name: Test failure-safe vulnerability scanning
281 run: bun test scripts/scan-sbom.test.ts
281 run: bun test scripts/scan-sbom.test.ts scripts/scan-release-artifacts.test.ts
282282
283283 - name: Audit Zig C imports
284284 run: bun run verify:cimports
.github/workflows/release.ymlmodified+27-5
Changes to .github/workflows/release.yml
@@ -26,7 +26,9 @@ jobs:
2626 needs: validate-release
2727 uses: ./.github/workflows/sbom.yml
2828 with:
29 enforce_high: true
29 # Keep the full-source report for review. The High gate runs against
30 # the artifacts and production dependencies prepared for publication.
31 enforce_high: false
3032
3133 pantry:
3234 # The reusable SBOM workflow includes the vulnerability scan. Publishing
@@ -305,6 +307,13 @@ jobs:
305307 --timeout 30m
306308 codesign --verify --deep --strict "$binary"
307309 rm "$archive"
310
311 - uses: ./.github/actions/setup-release-scanners
312
313 - name: Scan binaries prepared for publication
314 env:
315 CRAFT_PLATFORM: ${{ runner.os == 'macOS' && 'macos' || 'linux' }}
316 run: bun scripts/scan-release-artifacts.ts "$CRAFT_PLATFORM" packages/zig/zig-out "$RUNNER_TEMP/native-release-scan"
308317 done
309318
310319 # ── Publish & Release ──────────────────────────────
@@ -535,16 +544,29 @@ jobs:
535544
536545 - name: Setup Pantry (provides Bun, Zig, etc.)
537546 uses: pantry-pm/pantry/packages/action@235036fa0f48bae99b2293df5a3dc35c809b1777 # pinned: last SHA whose bundled typescript resolves on linux-x64
547 - uses: actions/setup-node@v6
548 with:
549 node-version: '24'
550 registry-url: https://registry.npmjs.org
551 package-manager-cache: false
538552 - name: Install Dependencies
539553 run: bun install --frozen-lockfile
540554
555 - name: Install npm CLI with trusted publishing support
556 run: npm install --global npm@11.15.0
557
558 - uses: ./.github/actions/setup-release-scanners
559
541560 - name: Test package regressions
542561 run: bun run test:packages
543562
544563 - name: Build and verify every npm artifact
545 run: bun run verify:npm
564 run: bun scripts/npm-packages.ts --archive-dir "$RUNNER_TEMP/craft-npm-archives"
565
566 - name: Scan packed npm artifacts and production dependencies
567 run: bun scripts/scan-release-artifacts.ts npm "$RUNNER_TEMP/craft-npm-archives" "$RUNNER_TEMP/npm-release-scan"
546568
547 - name: Publish to npm
548 run: pantry publish --npm --access public
569 - name: Publish scanned npm archives
570 run: bun scripts/publish-npm-archives.ts "$RUNNER_TEMP/craft-npm-archives"
549571 env:
550 NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
572 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
scripts/release-workflow.test.tsmodified+13-3
Changes to scripts/release-workflow.test.ts
@@ -34,16 +34,26 @@ test('registry indexing waits for the final manifest, macOS downloads and attach
3434
3535test('npm publication follows artifact validation, and macOS downloads run on both matching architectures', () => {
3636 const npm = release.jobs.npm.steps!.map(step => step.run ?? '')
37 const verify = npm.indexOf('bun run verify:npm')
37 const verify = npm.indexOf('bun scripts/npm-packages.ts --archive-dir "$RUNNER_TEMP/craft-npm-archives"')
38 const scan = npm.indexOf('bun scripts/scan-release-artifacts.ts npm "$RUNNER_TEMP/craft-npm-archives" "$RUNNER_TEMP/npm-release-scan"')
39 const publish = npm.indexOf('bun scripts/publish-npm-archives.ts "$RUNNER_TEMP/craft-npm-archives"')
3840 expect(verify).toBeGreaterThan(-1)
39 expect(npm.indexOf('pantry publish --npm --access public')).toBeGreaterThan(verify)
41 expect(scan).toBeGreaterThan(verify)
42 expect(publish).toBeGreaterThan(scan)
43 expect(steps(release.jobs.npm)).not.toContain('pantry publish --npm')
44 expect(release.jobs.npm.steps!.find(step => step.name === 'Publish scanned npm archives')?.env?.NODE_AUTH_TOKEN).toBe('$')
45 const native = release.jobs.pantry.steps!
46 expect(native.findIndex(step => step.name === 'Scan binaries prepared for publication')).toBeGreaterThan(native.findIndex(step => step.name === 'Notarize macOS binaries'))
47 expect(native.findIndex(step => step.name === 'Publish & Release')).toBeGreaterThan(native.findIndex(step => step.name === 'Scan binaries prepared for publication'))
48 expect(native.filter(step => step.uses === './.github/actions/setup-release-scanners')).toHaveLength(1)
49 expect(release.jobs.npm.steps!.filter(step => step.uses === './.github/actions/setup-release-scanners')).toHaveLength(1)
4050 expect(release.jobs['verify-macos-downloads'].strategy?.matrix.platform).toEqual([
4151 { os: 'macos-15', name: 'darwin-arm64' },
4252 { os: 'macos-15-intel', name: 'darwin-x64' },
4353 ])
4454 expect(steps(release.jobs['verify-macos-downloads'])).toContain('scripts/verify-macos-release.ts')
4555 expect(release.jobs['release-sbom'].uses).toBe('./.github/workflows/sbom.yml')
46 expect(release.jobs['release-sbom'].with?.enforce_high).toBe(true)
56 expect(release.jobs['release-sbom'].with?.enforce_high).toBe(false)
4757 expect(needs(release.jobs.pantry)).toContain('release-sbom')
4858 expect(steps(release.jobs.pantry)).toContain('bun install --frozen-lockfile')
4959 expect(steps(release.jobs.npm)).toContain('bun install --frozen-lockfile')
scripts/scan-sbom.tsmodified+1-1
Changes to scripts/scan-sbom.ts
@@ -58,7 +58,7 @@ if (import.meta.main) {
5858 const setting = process.env.CRAFT_FAIL_HIGH ?? 'false'
5959 if (!['true', 'false'].includes(setting)) throw new Error('CRAFT_FAIL_HIGH must be true or false')
6060 const { counts } = scanSbom(process.argv[2] || 'sbom/full-sbom.cyclonedx.json', process.argv[3] || 'scan-results')
61 const summary = ['## Vulnerability scan results', '', '| Severity | Count |', '| --- | --- |', ...severities.map(severity => `| ${severity} | ${counts[severity]} |`), '', 'Policy: Critical findings fail every scan. High findings fail release scans; they remain review items on main (#279).', ''].join('\n')
61 const summary = ['## Vulnerability scan results', '', '| Severity | Count |', '| --- | --- |', ...severities.map(severity => `| ${severity} | ${counts[severity]} |`), '', 'Policy: Critical findings fail every scan. High findings in this full-source scan remain review items; release artifacts and production dependencies have a separate High gate (#279).', ''].join('\n')
6262 console.log(summary)
6363 if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary)
6464 if (counts.High > 0 && setting === 'false') console.warn('::warning::High vulnerability findings require review under #279')