Build audit
CI matrix, SHA256SUMS, reproducibility.
Build matrix
Releases are produced by a 5-target GitHub Actions matrix on native runners:
| target | runner | linkage | archive |
|---|---|---|---|
x86_64-linux-musl |
ubuntu-latest + Alpine 3.20 docker |
fully static musl | .tar.gz |
aarch64-linux-musl |
ubuntu-24.04-arm + Alpine 3.20 docker |
fully static musl | .tar.gz |
aarch64-macos |
macos-14 |
static, system libc++/libSystem | .tar.gz |
x86_64-macos |
macos-14 (cross from aarch64) |
static, system libc++/libSystem | .tar.gz |
x86_64-windows |
windows-latest + MSYS2 + mingw64 |
fully static | .zip |
aarch64-windows is deferred — MSYS2 MINGW64 doesn't ship a
mingw-w64-aarch64-gcc package. Restoring would
require either LLVM clang with
-target aarch64-w64-windows-msvc, or building
mingw-w64-aarch64 from source.
Two-stage CI
-
build-and-test.yml— runs on every push tomainand every pull request. Build the full matrix, run smoke, uploaddist/as workflow artifacts (7-day retention) so a human reviewer can grab any per-target binary. The matrix is the gate for merging a PR. -
release.yml— runs on tag push (v*) andworkflow_dispatch. The build matrix is intentionally identical; only after the per-target artifact uploads, a final job publishes to GitHub Releases via softprops/action-gh-release with bundled per-archive.sha256and a top-levelSHA256SUMS.
Two stages avoids the trigger / dispatch pain of
gh workflow run; a tag push produces a release in
one flow, and build-and-test.yml stays cheap because
it runs on every PR but doesn't publish.
Smoke test policy
The smoke script (scripts/smoke.sh) does two things:
- Version check — confirm the freshly-built
lhareports the expected version banner. - CLI decode round-trip — run
lha landlha xqagainst the first.lzh/.lzs/.pmaunderupstream/lhasa/test/archives/— the regression corpus upstream uses formake check. Confirms the binary we shipped actually drives liblhasa end-to-end on real archive content.
We don't run upstream's make check directly: its
test driver sources test_extract.sh via relative
path, which breaks under the parallel-build out-of-tree pattern
this CI uses for musl+glibc isolation. Driving lha
against the same archive corpus covers the same ground from the
CLI side.
Checksums
Every release ships:
- One
.sha256per archive (next to it, same name with.sha256appended). - One top-level
SHA256SUMSlisting all archives (one line each, basename-keyed sosha256sum -c SHA256SUMSworks from any directory).
x eget ljh-sh/lhasa verifies each archive's
per-tarball .sha256 against the
SHA256SUMS it pulls from the release. Verification
is end-to-end TLS + SHA256, no further signature layer.
Dependency footprint
The vendored lhasa tree has zero external library
dependencies — no zlib, no libiconv, no libcurl, no
libarchive. The autotools configure.ac only probes
for the C compiler and Libtool; nothing optional.
What this means for each platform:
- Linux musl — fully static, no
glibcdependency. The same binary runs on Alpine, Debian, Ubuntu, RHEL, Fedora, Arch, and every other Linux distro. Verified vialddin CI. - macOS — system
libc++/libSystemonly — those are part of macOS and don't ship as dyld-private install paths. Verified byotool -L— the CI fails if the output links to a Homebrew path. - Windows — MinGW-built static archive; no VC redistributables needed.
OpenSSF Scorecard
OpenSSF Scorecard badges are not currently enabled for this repository. Two prerequisites that would need to happen before they’re meaningful:
- Upstream commit signing — Scorecard's
Signed-Commits check requires upstream
fragglet/lhasamaintainer (Simon Howard) to GPG-sign release commits. Currently the subtree pull records the upstream SHA in the merge commit body, but it’s not cryptographically bound to an identity. - Reproducible builds — Scorecard would promote us if independent rebuilds of the release tarballs (from source) produce matching SHA256SUMS. We don’t have that infrastructure yet; CI matrix builds per-target binaries, but no second channel re-derives the hashes from a clean source checkout.
The repo currently passes most non-cryptographic Scorecard checks (License, Token-Permissions, Pinned-Dependencies). To make the OpenSSF story honest we need to ship the two items above before flipping the badge on. The full per-check scorecard inspection is left as a future task; for now this section documents what’s missing rather than claiming a passing posture.