Build audit

CI matrix, SHA256SUMS, reproducibility.

Build matrix

Releases are produced by a 5-target GitHub Actions matrix on native runners:

targetrunnerlinkagearchive
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

  1. build-and-test.yml — runs on every push to main and every pull request. Build the full matrix, run smoke, upload dist/ 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.
  2. release.yml — runs on tag push (v*) and workflow_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 .sha256 and a top-level SHA256SUMS.

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:

  1. Version check — confirm the freshly-built lha reports the expected version banner.
  2. CLI decode round-trip — run lha l and lha xq against the first .lzh / .lzs / .pma under upstream/lhasa/test/archives/ — the regression corpus upstream uses for make 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:

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:

OpenSSF Scorecard

OpenSSF Scorecard badges are not currently enabled for this repository. Two prerequisites that would need to happen before they’re meaningful:

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.