Skip to content

Commit 6666ac1

Browse files
ralyodioclaude
andcommitted
ci(release): verify published artifact integrity before release
The build job's "Verify AppImage integrity" step runs before upload-artifact, so it validates good bytes but never sees the bytes that are actually published. v0.7.26 shipped a linux-x64 bundle whose AppImage, .deb AND .rpm were all corrupt (valid headers, unreadable compressed payloads) despite that gate passing -- the corruption was introduced in the upload-artifact -> download-artifact round-trip. Add a "Verify artifact integrity" step to the release job, after download-artifact and before Create Release, that fully extracts every AppImage (squashfs), .deb, .rpm and .zip and fails the release if any is corrupt. This guards the exact bytes that reach users; a transient transport corruption now blocks publish instead of shipping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7feb760 commit 6666ac1

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

‎.github/workflows/desktop-release.yml‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,78 @@ jobs:
292292
- name: List artifacts
293293
run: ls -la dist/
294294

295+
# Re-verify the EXACT artifacts that are about to be published. The
296+
# build job's "Verify AppImage integrity" step runs BEFORE
297+
# upload-artifact, so it cannot catch corruption introduced by the
298+
# artifact upload/download round-trip. v0.7.26 shipped a linux-x64
299+
# bundle whose AppImage, .deb AND .rpm were all corrupt (valid headers,
300+
# unreadable compressed payloads -> "sqfs_traverse_open error" /
301+
# "lzma error: compressed data is corrupt") even though the per-build
302+
# check passed — the corruption happened in transit. Verifying here,
303+
# after download and before the release is created, makes a corrupt
304+
# artifact fail the build instead of reaching users (re-run to rebuild).
305+
- name: Install verification tools
306+
run: sudo apt-get update && sudo apt-get install -y squashfs-tools binutils rpm cpio unzip
307+
308+
- name: Verify artifact integrity
309+
shell: bash
310+
run: |
311+
set -uo pipefail
312+
shopt -s nullglob
313+
fail=0
314+
315+
for img in dist/*.AppImage; do
316+
echo "::group::AppImage $img"
317+
# Locate the appended squashfs by its 'hsqs' magic; keep the first
318+
# offset with a valid superblock (the magic also occurs by chance
319+
# inside the ELF runtime). Arch-independent: unsquashfs reads the
320+
# gzip payload regardless of the runtime's CPU arch.
321+
off=""
322+
while IFS=: read -r cand _; do
323+
if unsquashfs -s -o "$cand" "$img" >/dev/null 2>&1; then off="$cand"; break; fi
324+
done < <(grep -abo 'hsqs' "$img")
325+
if [ -z "$off" ]; then
326+
echo "::error::no valid squashfs superblock in $img"; fail=1; echo "::endgroup::"; continue
327+
fi
328+
rm -rf /tmp/appverify
329+
if unsquashfs -o "$off" -d /tmp/appverify "$img" >/tmp/uns.log 2>&1 \
330+
&& { [ -e /tmp/appverify/AppRun ] || [ -d /tmp/appverify/usr ] || [ -d /tmp/appverify/resources ]; }; then
331+
echo "OK $img"
332+
else
333+
echo "::error::$img has a corrupt squashfs"; tail -20 /tmp/uns.log; fail=1
334+
fi
335+
echo "::endgroup::"
336+
done
337+
338+
for deb in dist/*.deb; do
339+
echo "::group::deb $deb"
340+
if dpkg-deb -x "$deb" /tmp/debverify >/tmp/deb.log 2>&1; then echo "OK $deb"; rm -rf /tmp/debverify
341+
else echo "::error::$deb is corrupt"; tail -20 /tmp/deb.log; fail=1; fi
342+
echo "::endgroup::"
343+
done
344+
345+
for rpm in dist/*.rpm; do
346+
echo "::group::rpm $rpm"
347+
if rpm2cpio "$rpm" 2>/tmp/rpm.log | cpio -t >/dev/null 2>>/tmp/rpm.log; then echo "OK $rpm"
348+
else echo "::error::$rpm is corrupt"; tail -20 /tmp/rpm.log; fail=1; fi
349+
echo "::endgroup::"
350+
done
351+
352+
# macOS .zip (Electron app bundle) — cheap to validate on Linux.
353+
# .dmg / Windows .exe aren't generically extractable here.
354+
for z in dist/*.zip; do
355+
echo "::group::zip $z"
356+
if unzip -t "$z" >/tmp/zip.log 2>&1; then echo "OK $z"
357+
else echo "::error::$z is corrupt"; tail -20 /tmp/zip.log; fail=1; fi
358+
echo "::endgroup::"
359+
done
360+
361+
if [ "$fail" -ne 0 ]; then
362+
echo "::error::One or more release artifacts are corrupt — aborting before publish. Re-run this workflow to rebuild."
363+
exit 1
364+
fi
365+
echo "All verifiable artifacts extracted cleanly."
366+
295367
- name: Generate checksums
296368
run: |
297369
cd dist

0 commit comments

Comments
 (0)