建置工具鏈稽核

對 autotools 建置鏈、GitHub Actions CI、 vendored 原始碼來源、安裝路徑進行風險審查。誠實地說 哪些已固定、哪些已簽名、哪些沒有。

工具鏈風險 · CI 風險 · 原始碼來源 · 分發風險 · 總結

1. 建置工具鏈風險

lha 原始碼用 GNU autotools。發佈二進位在 alpine:3.20 Docker 容器裡建置,宿主是 GitHub Actions Linux runner;然後 macOS / Windows runner 做打包。工具鏈:autoconf 2.72automake 1.16.5libtool 2.5.4gcc 13.2.1musl 1.2.5。在建置 宿主上跑的設定步驟:

bash scripts/build.sh
  ( cd "$SRC" && autoreconf -is )          # m4 巨集展開
  ( cd "$BUILD_DIR" && "$SRC/configure" )  # 產生的 shell 腳本
  ( cd "$BUILD_DIR" && make -j"$JOBS" )    # cc + ld

R1. autoreconf -is — M4 巨集展開

做了什麼:透過 autoconf + automake + libtool + autoheader + autopoint 把 configure.ac 展開成 configure。M4 是種巨集語言,可以呼叫 shell、 跑程式、讀檔案。

風險upstream/lha/configure.ac 或任何 .m4 檔案裡 的後門會在建置時跑任意命令。Vendored 原始碼位元級一致 於 upstream jca02266/lha@86094cb,所以這是 上游信任問題,不是 ljh-sh 自己的。

當前緩解:lha 1.14i 的 M4 輸入檔案很少,自 2003 年以來基本沒變。完整讀過一遍 configure.ac,沒 看到可疑的 AC_RUN_IFELSE / AC_TRY_RUN / AC_LINK_IFELSE 內容 (作為 5 維原始碼稽核 的一部分)。

R2. 產生的 configure 腳本

做了什麼:~10,000 行 shell,偵測工具鏈特性、跑 AC_TRY_RUN 探針(真的會 fork + exec + wait 一個小型測試程式)、 輸出 Makefile。

風險:被入侵的 autoconf 可能會產生一個跑任意命令的 configure。我們不直接稽核產生的 configure——它是個 ~10,000 行的檔案,由 ~300 行的 configure.ac 產生。 configure.ac 裡的後門可見;autoconf 的 M4 巨集裡 的後門只有在重新產生時才會暴露。

當前緩解:我們固定到 alpine:3.20(映像)。 風險:映像是個浮動 tag,不是 digest。Alpine 3.20 可能會被強制重建(資安事件),我們的 CI 會靜默採用。 一行修復:workflow 裡 docker: image: alpine@sha256:<digest>

R3. make + cc

做了什麼:用 gcc -g -O2 -DHAVE_CONFIG_H ... 編譯 C 原始碼,連結二進位。

風險:C 建置的標準作業。-g -O2 安全; 唯一建置期關注是 Makefile 呼叫 shell 的規則。我們審了 產生的 Makefile 看 shell 注入模式(變數 引號、未加引號的 glob 等),沒找到。

當前緩解:C 用標準 autoconf 偵測的 flag 編譯。無 LTO、無 PGO、無第三方編譯器包裝。建置是位元級可 重現的(在另一台主機重跑得到相同輸出,調 __TIME__ 打入的建置時間戳除外——但 lha 原始碼裡 __TIME__ 只用在版本字串列印,不影響執行時行為)。

R4. libiconv / libz / libSystem 相依

static-musl 建置只鏈 C 函式庫。macOS 建置用系統 libiconv(在 macOS 上屬於 libSystem)。Linux gnu 建置鏈系統 libz + libiconv libcurllibssllibxml2libsystemd

風險:低——全部來自套件管理員;唯一的軟體定義輸入是 workflow 裡固定的套件名稱。

2. CI 風險

lha 發佈流水線:

on:
  push:
    tags: ['v*']
  workflow_dispatch:
jobs:
  build:
    steps:
      - uses: actions/checkout@v4         # ❌ 沒固定
      - uses: msys2/setup-msys2@v2       # ❌ 沒固定
      - run: bash scripts/build.sh        # ✅ 儲存庫內
      - run: docker run alpine:3.20 ...  # ❌ 沒固定
      - uses: softprops/action-gh-release@v2  # ❌ 沒固定

R5. 沒固定的第三方 actions

風險:高。actions/checkout@v4msys2/setup-msys2@v2softprops/action-gh-release@v2——全部 浮動 tag。任何有寫權限的(包括帳號被盜的)都可以 發程式碼在我們的 CI 環境跑,帶有 secrets.GITHUB_TOKEN + release 寫權限。

一行修復:把每個 uses: 固定到 commit SHA。GitHub 自己的 資安加固指南 推薦對所有第三方 action 這麼做。

當前緩解:無。風險被限制在上游儲存庫有寫權限的人數 (少)和我們注意到可疑發佈的機率(低——得注意 artifacts 變了)。

R6. 沒固定的 alpine:3.20

風險:中。跟上面一樣是浮動 tag。固定到 digest。

R7. CI runner 的 secrets / 寫權限

風險:任何有 push 權限到 ljh-sh/lha 的 都能改 workflow(用維護者身份跑,帶 secrets.GITHUB_TOKEN + release 寫權限)。

當前緩解:分支保護規則(要求 PR review)——已驗證。Release 只在 v* tag push 觸發,需要維護者推簽名 tag。 PR 作者不能自己觸發 release。

一行改進:加一個 CODEOWNERS 檔案,讓 workflow 改動需要特定維護者 review(而不是任何貢獻者)。

3. Vendored 原始碼來源

upstream/lha/ 位元級一致於 jca02266/lha@86094cb。 Vendoring 是用 git subtree addhttps://github.com/jca02266/lha.git master 86094cb 做的;subtree 歷史保留在 upstream/lha/.git 和 squash-merge 提交裡。

R9. Subtree vendoring 是可重現的

Vendored 提交(我們歷史裡的 merge commit 4ccece7...)引用精確的上游提交。我們可以隨時 驗證:

git -C upstream/lha log -1 --format='%H %s'
git ls-remote https://github.com/jca02266/lha.git 86094cb

如果 SHA 匹配,vendored 原始碼就是上游的 bit-for-bit。 應該在 CI 裡加一步在每次 release 時跑這個。

R10. 供應商信任點是上游

如果 jca02266/lha 的歷史後來被改(force-push、 rebase、刪除),我們的 vendored 副本不會變—— 我們有快照。但如果我們的儲存庫在 release 之前被入侵, 攻擊者可以用看起來和上游快照一樣的後門版本 替換 vendored 原始碼。

一行修復:CI 裡在 checkout 後,驗證 upstream/lha/.git 匹配預期 86094cb SHA。 不匹配則建置失敗。這是「我們的 lha 真的來自上游」 的檢查。

4. 分發 / 安裝路徑風險

使用者通過以下途徑之一獲得二進位:

R11. Release 未簽名

風險:中。Release artifacts(tarball、zip)未被任何 物簽名。GitHub 的 commit 簽名保護 commit,但不保護 release artifact。沒有 GPG sig、沒有 cosign 簽名、沒有 sigstore envelope。

一行修復:release workflow 加一步 cosign sign-blob。GitHub 提供 OIDC keyless 簽名(cosign sign-blob)。

R12. x eget use lha 會驗證 GitHub 提供的 SHA256SUMS,但 checksum 檔本身未簽名

對前一版本頁的更正。eget 預設會驗證 checksum——它從 GitHub release 拉取 SHA256SUMS(或每個 asset 的 .sha256)檔,並把它和已下載 asset 的 hash 做比對。所以一般使用者每次 x eget use lha 都會拿到 checksum 驗證。本頁先前的文字(說 eget 只靠 TLS 驗證)是錯的。

剩下的。SHA256SUMS 檔和二進位 asset 來自同一個 GitHub release。GitHub 帳號被入侵——或者維護 者濫用編輯 release 的權限——可以一次同時置換兩者。 Checksum 檔本身沒被簽名,所以 eget 驗證的是 asset 與 checksum 之間的內部一致性,而不是其中任何一個 的出處。

一行修復:同 R11——cosign sign-blob, 針對每個 release asset(keyless,透過 GitHub OIDC)。 這樣在 GitHub 的 checksum 之上再加一層 sigstore 簽名; 要求強完整性的使用者下載後跑 cosign verify-blob。補上了帳號入侵後的 缺口。

R13. 沒有 SBOM

風險:對小型靜態二進位低。靜態二進位的唯一「元件」 是 libc(macOS 上是 libSystem,屬於 OS)。lha 本身是唯一的 應用程式碼。SBOM 只會顯示 lha。對這個專案不高價值。

5. 總結——我們實際會改的

按 impact-to-effort 排序(最高的在前):

#改動 工作量降低的風險
R9 CI 驗證 subtree SHA 5 min 抓住「我們的 lha 不是上游的 lha」攻擊面
R5 固定每個 uses: 到 commit SHA 15 min 消除 action tag 移動攻擊面
R6 固定 alpine:3.20 到 digest 5 min 消除映像重建攻擊面
R11 cosign 簽名每個 release artifact 30 min 分發通道的完整性底
R7 CODEOWNERS for .github/ 10 min 限制誰能改 CI 行為
R13 SBOM 作為 release asset 30 min 合規

總計 ~1.5 小時工作把這個工具鏈從「業界基準」提到 「充分加固」。