pnpm 供應鏈安全怎麼設?用 minimumReleaseAge 與 trustPolicy 驗證 lockfile
pnpm install 成功,不代表 lockfile 裡的每個版本都符合你今天的供應鏈政策。依賴可能是昨天由另一個環境解析的,也可能在新版本發布後才出現信任資訊變弱的情況。pnpm v11 把 minimumReleaseAge 和 trustPolicy 放進依賴解析與 lockfile 驗證流程,可以把「剛發布的版本」和「信任證據下降的版本」分開處理。
這篇不是把所有依賴都鎖死,而是示範一個可審查的 baseline:在 workspace root 設定成熟時間與信任策略,讓 CI 重新驗證 lockfile,遇到例外時只放行明確的套件或版本。
先分清楚兩個設定在擋什麼
| 設定 | 主要判斷 | 適合處理的風險 |
|---|---|---|
minimumReleaseAge | 套件版本發布後是否已經過指定分鐘數 | 新版本剛上架、尚未被發現的惡意版本 |
trustPolicy: no-downgrade | 新版本的信任證據是否比先前版本弱 | trusted publisher、provenance 或其他信任資訊退步 |
兩者不是同一個版本檢查。某個版本很舊,仍可能缺少你要求的信任證據;某個版本有 provenance,也可能才剛發布而尚未達到成熟時間。
在 pnpm-workspace.yaml 設定 baseline
這些設定應放在 workspace root 的 pnpm-workspace.yaml,不要放進單一 package 的 package.json:
packages: - "apps/*" - "packages/*"
# 1440 分鐘 = 一天;明確設定後,strict 模式也明確寫出來minimumReleaseAge: 1440minimumReleaseAgeStrict: true
trustPolicy: no-downgrade
# 舊版本若沒有足夠信任資訊,可依審查結果設定時間邊界# trustPolicyIgnoreAfter: 10080
# 例外應以套件或版本為單位,並留下審查原因# trustPolicyExclude:# - "[email protected]"pnpm 文件指出,v11 的 minimumReleaseAge 內建預設值是 1440 分鐘,但內建預設屬於非 strict 的相容行為;在 repository 裡明確寫出 minimumReleaseAge 時,最好同時寫 minimumReleaseAgeStrict: true,讓 CI 不會在找不到成熟版本時偷偷退回較新的未成熟版本。
trustPolicyIgnoreAfter 與 trustPolicyExclude 都是例外,不是修復錯誤的第一個按鈕。每個例外都應有套件版本、理由與重新檢查日期。
lockfile 驗證要保留,別急著開 trustLockfile
pnpm 在安裝時會重新檢查載入的 lockfile 是否符合 minimumReleaseAge 和 trustPolicy。如果把:
trustLockfile: true加進設定,pnpm 會把 lockfile 視為已信任,跳過這一輪供應鏈政策驗證。這可以降低大型 workspace 的記憶體使用,但也代表「由其他人或較弱政策產生的 lockfile」可能直接進入安裝流程。
如果 repository 允許外部 contributor 修改 lockfile,或 CI 需要保護從 pull request 進來的依賴變更,應保留預設的 trustLockfile: false。效能問題應先用實際 lockfile 大小、pnpm 版本與 CI heap 記錄定位,不要用跳過安全檢查來掩蓋原因。
用 CI 驗證「設定有生效」
先在本機確認目前版本和設定來源,再用 frozen install 驗證已提交的 lockfile:
pnpm --versionpnpm config get minimumReleaseAgepnpm config get trustPolicypnpm install --frozen-lockfilegit diff --exit-code -- pnpm-lock.yaml pnpm-workspace.yamlpnpm config get 的輸出只適合確認非敏感設定;不要把 registry token 或完整 authentication config 印進 CI log。CI 也要固定 pnpm major,否則本機與 runner 可能對同一份設定採不同版本行為。
當 policy 真的擋住解析時,常見訊號包括:
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION:版本尚未達到成熟時間。ERR_PNPM_NO_MATURE_MATCHING_VERSION:strict 模式下,指定範圍沒有可用的成熟版本。ERR_PNPM_TRUST_DOWNGRADE:信任證據相對先前版本下降。ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION:lockfile 解析驗證未通過,需回頭看具體套件與政策。
先記下套件、版本、registry 和 lockfile 變更,再決定是等待成熟時間、升級到有足夠信任證據的版本,還是做一次可審查的例外。不要只把錯誤訊息貼進 trustPolicyExclude。
什麼時候需要例外?
例外常見於三種情境:
- 內部套件必須在發布後立即被 CI 解析。
- 舊版套件沒有新的 provenance 或 publisher 記錄,但團隊已完成來源與內容審查。
- 目前依賴範圍只有一個未成熟版本,業務變更又不能等待。
每一種情境都要把「放行的是誰、哪個版本、為什麼安全」寫進 repository 的變更紀錄。若只想讓特定套件不受 cooldown 影響,可以用 minimumReleaseAgeExclude;若是信任證據問題,才使用 trustPolicyExclude。兩者不要混用成一個無限期的 wildcard。
這個流程和 pnpm tarball integrity 錯誤的 lockfile 檢查不同:integrity 著重 lockfile 與下載內容是否一致,這篇的兩個設定則著重發布時間和信任證據。實際 CI 可以把兩層都保留。
結論:讓 policy 失敗,而不是讓 CI 靜默放行
一個可審查的 pnpm 供應鏈 baseline 是:在 workspace root 明確設定 minimumReleaseAge、minimumReleaseAgeStrict: true 與 trustPolicy: no-downgrade,CI 保留 lockfile verification,例外只針對明確套件或版本。當安裝失敗時,先讀懂 maturity、trust 或 lockfile error,再決定等待、升級或留下有期限的例外。
Q: pnpm v11 不是已經預設 minimumReleaseAge: 1440 嗎?為什麼還要寫?
A: 內建預設值與 repository 政策不是同一件事。把設定寫進 pnpm-workspace.yaml 可以讓團隊看見預期值,也能明確搭配 minimumReleaseAgeStrict: true;否則不同 pnpm 版本或不同執行環境可能對「找不到成熟版本」採取不同處理。
Q: trustPolicy: no-downgrade 是檢查套件版本號變小嗎?
A: 不是。它比較的是發布時可取得的信任證據,例如 trusted publisher 或 provenance,不是 semver 大小。高版本不代表信任證據一定更強,版本選擇仍要配合 lockfile 和 registry 來源檢查。
Q: CI 失敗時可以用 trustLockfile: true 先通過嗎?
A: 只有在 lockfile 本身屬於可信任的受控輸入,而且團隊清楚接受跳過 verification 的代價時才考慮。對允許外部 contributor 修改 lockfile 的 repository,不應把它當成一般 CI 修復方式。
參考資料:
pnpm:Mitigating supply chain attacks
回報錯字、失效連結,或告訴我你想看的延伸主題。