Four verified gaps in the release path. Grouped because they share a theme: things that ship without being checked.
1. Deno is fetched from a mutable latest URL with no checksum, into every published image
build/Dockerfile:38:
curl -fsSL -o /tmp/deno.zip "https://github.com/denoland/deno/releases/latest/download/deno-${DENO_ARCH}.zip"
No version pin, no SHA256. Every GHCR image build takes whatever Deno published that day and ships it to users. Deno is not incidental here -- yt-dlp uses it as the JS runtime for YouTube format extraction, so it executes against untrusted input.
This is inconsistent with the project's own standard. scripts/macos/make-runtime-pack.sh:186-194 pins QuickJS by asset and SHA256 and verifies it:
QJS_SHA256="f6200e9856c45578a5d42ac873a32f3f994b421e29df9f63b452d9c7145015fc"
echo "${QJS_SHA256} ${QJS_DIR}/qjs" | shasum -a 256 -c - >/dev/null || { ... exit 1; }
The scripts even carry the rationale in a comment: an unverified binary fetched at package time is a supply-chain hole whether or not it is small. Docker/Deno is the one path that does not follow it.
Fix: pin a Deno version and verify its SHA256, matching the QuickJS pattern.
2. Linux and Windows releases can publish an incomplete asset set and stay green
linux-release.yml:145, windows-release.yml:96 use softprops/action-gh-release with no fail_on_unmatched_files (it defaults to false) and no pre-upload existence assertions.
Verified asymmetry: macos-release.yml has 8 test -f checks before uploading; the Linux and Windows workflows have none.
The updater assets are produced only by the one invocation carrying PUBLISH_UPDATER_ASSETS=1 / -PublishUpdaterAssets. If that step is edited, reordered, or its build leg fails in a way the script swallows (see 3), StemDeck-Linux-x64-app.tar.gz, *-app.zip and *-runtime-version.json never appear -- the release publishes without them, the job goes green, and the in-app updater 404s for every installed user.
Fix: add test -f assertions mirroring the macOS job, and/or set fail_on_unmatched_files: true.
3. The Windows packaging script silently ignores failed native commands
scripts/windows/make-portable.ps1:12 sets:
$PSNativeCommandErrorActionPreference = "Stop"
That variable only exists in PowerShell 7+. windows-release.yml:54,60 invokes the script with powershell -NoProfile -ExecutionPolicy Bypass -File ... -- Windows PowerShell 5.1, where it does nothing, and where $ErrorActionPreference does not apply to native commands either.
Consequence: the CPU-variant torch swap (pip install torch==2.6.0+cpu ... --force-reinstall --no-deps) can fail on a transient network error and the script continues. Whatever torch was already resolved stays installed, and the later import checks still pass because torch imports fine. The zip labelled CPU ships a non-CPU torch. The same applies to pip install "$Root" and pip install --upgrade pip.
Fix: invoke with pwsh (PowerShell 7+), or check $LASTEXITCODE after each native command.
4. trivy-action@master is the only unpinned action in the repo
.github/workflows/ci.yml:181 and :193. Every other action is SHA-pinned (e.g. actions/checkout@3d3c42e5..., softprops/action-gh-release@3d0d9888...). These two resolve master at run time on every PR and push to main.
Fix: SHA-pin both, consistent with the rest of the repo.
Four verified gaps in the release path. Grouped because they share a theme: things that ship without being checked.
1. Deno is fetched from a mutable
latestURL with no checksum, into every published imagebuild/Dockerfile:38:No version pin, no SHA256. Every GHCR image build takes whatever Deno published that day and ships it to users. Deno is not incidental here -- yt-dlp uses it as the JS runtime for YouTube format extraction, so it executes against untrusted input.
This is inconsistent with the project's own standard.
scripts/macos/make-runtime-pack.sh:186-194pins QuickJS by asset and SHA256 and verifies it:The scripts even carry the rationale in a comment: an unverified binary fetched at package time is a supply-chain hole whether or not it is small. Docker/Deno is the one path that does not follow it.
Fix: pin a Deno version and verify its SHA256, matching the QuickJS pattern.
2. Linux and Windows releases can publish an incomplete asset set and stay green
linux-release.yml:145,windows-release.yml:96usesoftprops/action-gh-releasewith nofail_on_unmatched_files(it defaults to false) and no pre-upload existence assertions.Verified asymmetry:
macos-release.ymlhas 8test -fchecks before uploading; the Linux and Windows workflows have none.The updater assets are produced only by the one invocation carrying
PUBLISH_UPDATER_ASSETS=1/-PublishUpdaterAssets. If that step is edited, reordered, or its build leg fails in a way the script swallows (see 3),StemDeck-Linux-x64-app.tar.gz,*-app.zipand*-runtime-version.jsonnever appear -- the release publishes without them, the job goes green, and the in-app updater 404s for every installed user.Fix: add
test -fassertions mirroring the macOS job, and/or setfail_on_unmatched_files: true.3. The Windows packaging script silently ignores failed native commands
scripts/windows/make-portable.ps1:12sets:That variable only exists in PowerShell 7+.
windows-release.yml:54,60invokes the script withpowershell -NoProfile -ExecutionPolicy Bypass -File ...-- Windows PowerShell 5.1, where it does nothing, and where$ErrorActionPreferencedoes not apply to native commands either.Consequence: the CPU-variant torch swap (
pip install torch==2.6.0+cpu ... --force-reinstall --no-deps) can fail on a transient network error and the script continues. Whatever torch was already resolved stays installed, and the later import checks still pass because torch imports fine. The zip labelled CPU ships a non-CPU torch. The same applies topip install "$Root"andpip install --upgrade pip.Fix: invoke with
pwsh(PowerShell 7+), or check$LASTEXITCODEafter each native command.4.
trivy-action@masteris the only unpinned action in the repo.github/workflows/ci.yml:181and:193. Every other action is SHA-pinned (e.g.actions/checkout@3d3c42e5...,softprops/action-gh-release@3d0d9888...). These two resolvemasterat run time on every PR and push to main.Fix: SHA-pin both, consistent with the rest of the repo.