建置工具鏈稽核
對 autotools 建置鏈、GitHub Actions CI、 vendored 原始碼來源、安裝路徑進行風險審查。誠實地說 哪些已固定、哪些已簽名、哪些沒有。
1. 建置工具鏈風險
lha 原始碼用 GNU autotools。發佈二進位在
alpine:3.20 Docker 容器裡建置,宿主是
GitHub Actions Linux runner;然後 macOS / Windows runner
做打包。工具鏈:autoconf 2.72、
automake 1.16.5、libtool 2.5.4、
gcc 13.2.1、musl 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。 無
libcurl、libssl、
libxml2、libsystemd。
風險:低——全部來自套件管理員;唯一的軟體定義輸入是 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@v4、
msys2/setup-msys2@v2、
softprops/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 add 從
https://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. 分發 / 安裝路徑風險
使用者通過以下途徑之一獲得二進位:
x eget use lha——從ljh-sh/lha的releases/latest下載發佈資產。x i lha——路由到 x-cmd-install 的src/common/lha.yml裡的brew install lha/apt install lhasa指令。- 直接從 GitHub Releases 頁下載。
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 小時工作把這個工具鏈從「業界基準」提到 「充分加固」。