vatt'ghern jaskier's ballads
本文 6 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

Cooldown 在 2026 上半年成為 npm、pnpm、pip、uv 預設配置的「供應鏈攻擊一線防禦」—— 一個 RFC 出來說:cooldown 只是把當 canary 的責任推給 Asia-Pacific 的早起工程師、 且對下載量低的小套件幾乎無效;該換成 phased rollout—— 用 hash(project_id, package_name, version, digest) 把每個專案打散到 14 天的釋出窗口裡。 一個技術選擇要在六月份的 npm/pnpm release planning 上拍板。

Cooldown 還是 phased rollout——兩種對抗供應鏈攻擊的提案

月份 axios 的 npm 套件被植入惡意版本、上線 2-3 小時就被撤下; 四月 Nx 的 npm 攻擊有 4-5 小時的活躍窗口; LiteLLM 的 PyPI 惡意版本在 10:39 UTC 上線、13:38 UTC 被 PyPI 隔離—— 三起事件的共通點是:攻擊存活時間短於一個工作日。 cooldowns.dev 收集的十起 2024-2026 主要供應鏈攻擊裡,「八起在一週內結束、僅一起超過兩週」。 在這個資料底下,pip 26.1 引入 PIP_UPLOADED_PRIOR_TO="P3D"、 pnpm 用 minimumReleaseAge: 4320(3 天分鐘數)、 npm 用 min-release-age=3、uv 用 exclude-newer = "3 days"—— 四個生態系幾乎同時把「三天 cooldown」訂為預設或建議值。

cooldowns.dev 自己給的數字是:這個簡單的 config 變更帶來「約 80-90% 的曝險降低」。 這個說法的邏輯很直接——既然攻擊存活時間中位數遠短於三天, 那麼只要強制 package manager 不要立刻拉新版本, 就讓自家專案躲過了大多數攻擊的活躍窗口。 這個邏輯在 2026 年初看起來無懈可擊,所以四個生態系都選擇採用。

五月 21 日 illegalcode.net 上一篇 RFC 對這套邏輯提出了反論。 作者的核心觀察可以濃縮成一句: 「Cooldowns work against fast-acting supply-chain attacks. But they have an awkward property: they implicitly rely on someone else installing first.」 cooldown 的有效性依賴於「有別人先當 canary」—— 如果你跟所有人一樣設三天 cooldown,那「第一天的受害者」其實沒被保護到, 他們仍然要負擔發現惡意版本的責任。 RFC 接著指出:在「常見的(誤)實踐」下,這個「別人」通常指的是 Asia-Pacific 時區的工程師。

這不是抽象的公平性論述。 npm 與 PyPI 的伺服器主要在 us-east-1, 新版本 publish 後 CDN propagation 到亞太大約 30 秒, 而美西工程師上線時間(PST 09:00)距離亞太工程師上線(JST/CST 09:00)有 16-17 小時—— 如果三天 cooldown 從 publish 起算,那麼亞太地區的「第 N+1 天工作日」永遠是 cooldown 解除的時間點。 換言之,亞太工程師永遠是先碰到剛解除 cooldown 的新版本的那一批, 美西工程師則永遠晚 16 小時——對攻擊者的活躍窗口而言,這 16 小時是巨大的差距。

RFC 提出的替代方案是 phased rollout—— 把每個下游專案打散到一個 14 天的釋出窗口裡, 每個專案進入新版本的時間點由 deterministic hash 決定: 「Package managers should deterministically map projects into a rollout window based on stable inputs: a project-specific identifier, package name, version, and artifact digest.」 hash 的輸入是「project_id(每個下游專案各自獨立)、套件名、版本號、artifact digest」—— 這四項加起來確保任兩個專案在任一新版本上的釋出時間是無關的。

這篇對照要走過七個維度——小套件公平性、攻擊窗口、傳遞性依賴、 訊號品質、opt-in 預設、維護者負擔、企業政策—— 最後給出 production 上應該怎麼選的判斷規則。

大表先看:七個維度上兩個方案的表現

下面這張表把 cooldown(fixed N-day delay)與 phased rollout(hash-mapped 14-day window) 在七個關鍵維度上的能力擺出來——點擊 column header 可以重排, 方便依不同的優先順序(公平性優先?最大窗口優先?)做不同的排序。 表格只是大表,後續每個 H2 章節會逐軸展開各自的論證與資料來源。

click column header to sort · 3 columns × 7 rows

cooldown vs phased rollout 在七個維度上的對照(資料來源:illegalcode.net RFC 2026-05-21、cooldowns.dev 統計、Nx/axios/LiteLLM 公開 timeline)
維度 Cooldown (N = 3 days) Phased rollout (14-day window)
小套件公平性 依賴別人先當 canary、亞太工程師永遠先碰 hash 把專案打散、與下載量無關
最大攻擊窗口 3 天後仍 100% 曝險 14 天均勻曝險、任何時刻最多 ~7% 已採用
傳遞性依賴覆蓋 套用於所有 transitive(pnpm/npm 都是) 同源 RFC 設計也涵蓋 transitive,但需 lockfile 配合
訊號品質(量測攻擊存活) cliff edge——0% 或 100% 採用 線性升曲線、任何時間點都有可比基準
opt-in vs opt-out 預設 已是 npm/pnpm/pip/uv 預設或建議 需要 registry 與 client 雙邊配合、尚未實作
OSS 維護者負擔 零——所有邏輯在 client 端 需要在 publish flow 加 stage metadata
企業政策粒度 只能調 N,無法控制收新版本的順序 可以 opt-in stage 1(早期),stage 3(保守)
cooldown 在「立即部署、不改維護者流程」上贏,phased rollout 在「公平性與訊號品質」上贏。後半篇逐軸展開兩個方案是怎麼到達這個分數的。

cooldown 在「立即部署、不改維護者流程」上贏,phased rollout 在「公平性與訊號品質」上贏

Cooldown 在「立即可用、零維護負擔」勝;phased rollout 在公平性、訊號品質、企業粒度三項勝。

讀者第一眼會發現兩個方案在七個維度上沒有壓倒性的勝負—— cooldown 在「現成可用、零維護者負擔」這側贏, phased rollout 在「公平性、訊號品質、企業粒度」這側贏。 RFC 不是要證明 cooldown 是錯的, 而是要證明「80-90% 曝險降低」這個數字背後有不均勻的代價分配。 架構上:cooldown 是「客戶端策略」、phased rollout 是「協議層策略」—— 前者一個 config flag 就 ship,後者需要 npm registry、PyPI、pnpm CLI、pip CLI 一起改 protocol。

小套件公平性:cooldown 的隱藏前提是「有別人先當 canary」

這是 RFC 整篇論證的核心。 cooldown 的工作原理是「拒絕安裝任何未存在至少 N 天的版本」—— 這只在「N 天內有人發現了惡意版本並讓 registry 撤下它」這個前提下才有效。 如果沒有人安裝它,那麼這個惡意版本就會在那邊待著、過了 N 天、cooldown 解除、 然後所有遵守 cooldown 的人同時開始安裝——這時 cooldown 一點保護都沒提供。

對下載量高的套件(react、axios、lodash),這個前提幾乎是自動滿足的—— 每天有成千上萬個專案在新版本 publish 後立刻拉新版本, 其中總有人會被攻擊命中、被 EDR 警告、上 Twitter 抱怨、被 Snyk 或 Socket 自動偵測, 然後 registry 在數小時內就會收到 takedown 請求。 cooldowns.dev 引用的「八起攻擊在一週內結束」就是這類高下載量套件的歷史經驗。

但對下載量低的套件——例如某個 PyPI 上每週只有幾百次下載的 OCR helper、 或某個 npm 上服務於特定電商的 i18n 包—— 這個前提就不成立了。 攻擊者推一個惡意版本上去,可能要等 3-7 天才有人下載, 那個下載者可能是個 hobbyist 在家裡的 dev 機器上跑、 沒有 EDR、沒有 Snyk、沒有 Socket——攻擊就這樣靜悄悄地完成了。

cooldown 對這類「長尾」套件的保護效果其實非常弱。 用簡單的算術估算:一個套件每週 100 次下載, 那麼三天裡平均有 43 次下載—— 這 43 個下載者裡有多少個會偵測到惡意版本? 取決於他們的工具鏈,但 OSS 維護者經驗法則是「個位數」。 這個比例與每週百萬下載的 axios 之間的差距是四到五個數量級。

更尖銳的觀察是「Asia-Pacific 工程師當 canary」這個 framing。 美國西岸下午發布的新版本,cooldown 三天後解除是美國東岸下午—— 亞太工程師會在當地時間夜裡碰到解除窗口(多數 CI 排程跑在這個時段)、 而美國工程師會在隔天早上 9 點才碰到。 RFC 的原話:「In common (mal)practice, that 'someone else' means Asia-Pacific」—— 不是說設計上歧視亞太,而是「每個專案都用同樣的 3 天 cooldown」這個 deployment pattern 在實際的時區分布下,會讓亞太工程師承擔不成比例的 canary 角色。

phased rollout 完全不依賴這個 implicit assumption。 每個下游專案的 rollout 時間由 hash(project_id, package_name, version, digest) 決定, 時間分布是 14 天均勻—— 跟你下載量多少、地理位置在哪都沒有關係。 一個下載量低的長尾套件,其新版本仍然會在 14 天裡均勻地散佈到 14 個專案 (假設這個套件總共只有 14 個下游 user); 一個下載量高的熱門套件,會在 14 天裡均勻散佈到上萬個下游。

把這個差距具象化:下面這張圖顯示了在相同的攻擊(假設惡意版本在 publish 後 6 小時被偵測撤下)下, cooldown 與 phased rollout 對「不同下載量區間」的套件分別提供多少實際保護。 Y 軸是「在攻擊存活期內安裝到惡意版本的下游專案比例」——數字越低越好。

3-day cooldown 之下被命中比例 14-day phased rollout 之下被命中比例
假設惡意版本在 publish 後 6 小時被偵測撤下。X 軸是套件每週下載量區段,Y 軸是攻擊存活期內被安裝到的下游比例。Cooldown 對熱門套件(每週 10萬+下載)效果好,因為熱門套件總有人先當 canary;對長尾套件(每週 100 下載)效果差,因為前 6 小時可能完全沒有人下載。Phased rollout 在所有下載量區段上提供同等的隨機分散保護,命中率均為 ~1.8%(= 6h / 14 days)。資料模型基於 cooldowns.dev 引用的攻擊偵測中位時間 4-5 小時。

假設惡意版本在 publish 後 6 小時被偵測撤下

Cooldown 對長尾套件命中率 4.6%、對熱門套件僅 0.15%;phased rollout 各區段均約 1.8%。

這張圖最值得注意的是 cooldown 那組柱子的斜率—— 從熱門套件的 0.15% 命中率, 到長尾套件的 4.6% 命中率, 跨了 30 倍的差距。 這就是「依賴別人先當 canary」這個 implicit assumption 的量化結果—— 當你的套件下載量越低、越沒人會比你先碰到那個惡意版本,cooldown 給你的保護就越弱。

反過來,phased rollout 那組柱子是水平的—— 每個區段都是 1.8%。 這個數字怎麼來的? 14 天的釋出窗口、攻擊存活 6 小時—— 任意專案的 rollout 時間點落在那 6 小時窗口內的機率就是 6/336 ≈ 1.8%。 這是純機率,跟下載量無關。

要小心比較標的:cooldown 對熱門套件的 0.15% 是「群體平均」、 個別專案因時區與 CI 排程可能直接是 100% 命中; phased rollout 的 1.8% 對個別專案是 deterministic 的—— 你會在 14 天的哪個時間點碰到新版本,可以精確算出來。

攻擊窗口形狀:cliff edge 與 ramp 的差異

cooldown 對攻擊者是 cliff edge:第 1 到第 N 天 0% 下游可安裝、第 N+1 天瞬間 100%。 從攻擊者視角,「只要熬過 N 天」就能拿到那一天瞬間湧入的整個下游集合—— cliff 對攻擊者反而是好事、放大了攻擊影響面。 phased rollout 是 14 天線性 ramp(第 1 天 7%、第 7 天 50%、第 14 天 100%), 攻擊者每多熬一天只拿到那一天的 1/14——沒有「集中收割」的甜蜜點。

cliff edge 與 phased rollout staircase 的並置示意

cooldown N=3:cliff edge 100% cliff at day N+1 0% adoption → 0% detection D+0 D+3 D+14 100% 0% days since publish phased rollout 14d:staircase ramp stage 1 stage 2 stage 3 stage 4 7% canary day 1 D+0 D+3 D+7 D+14 100% 0% days since publish
同樣的 X 軸(publish 後天數),同樣的 Y 軸(已採用下游 %)——兩個方案的形狀差異一目了然。左圖:cliff 之前是大片「0% 偵測」的紅色陰影區(沒人安裝、沒人能舉報)。右圖:14 級階梯,每階都有 ~7% 新下游進入「真實偵測樣本」、攻擊者沒有可瞄準的 cliff peak。

同樣的 X 軸(publish 後天數),同樣的 Y 軸(已採用下游 %)——兩個方案的形狀差異一目了然

Cooldown 在 N+1 天讓 100% 下游同時曝險;phased rollout 每天只讓 7% 進入,無集中峰值。

更重要的是「偵測機會密度」的差別。 cooldown 之下,第 1 天到第 N 天之間 0% 採用—— 意味著 0% 偵測機會(沒有人安裝,就沒有 EDR 警告、Socket scan、Snyk audit)。 所有偵測都集中在 N+1 天那一刻——但那一刻已經太晚了,因為「同時湧入」意味著 從偵測到 registry takedown 的反應時間(典型 1-4 小時)內, 已經有大量下游被命中。

phased rollout 之下,第 1 天就有 7% 的下游採用—— 這 7% 包含了大量 production 系統、有 EDR、有 Socket scan—— 任何惡意行為在 publish 後 24 小時內就會被偵測到, 然後 registry takedown,剩下的 13/14 = 93% 的下游永遠不會碰到這個惡意版本。 這個「早期 7% 當 canary、保護後期 93%」的模式是真正的 progressive rollout 哲學。

下面這個互動 chart 讓讀者用滑桿拖到 publish 後第 N 天, 看 cooldown(紅)與 phased rollout(綠)兩條曲線的累積採用比例—— 曲線下方的灰色面積是「攻擊存活期內被命中的下游比例」,越小越好。

drag slider · sweep publish day from 0 to 21

3.00
cooldown N=3:第 3 天前 0%、第 3 天後 100% phased rollout 14d:均勻線性 0% → 100%
同一時刻看兩個方案累積採用比例的差距。Cooldown 在 publish 後三天內所有下游都不會碰到新版本——這三天裡攻擊偵測機會為 0;phased rollout 第 1 天就有 7% 下游採用(這些早期採用者扮演 canary 角色),偵測機會被均勻分布在 14 天裡。

同一時刻看兩個方案累積採用比例的差距

Cooldown 前三天偵測機會為零;phased rollout 第一天就有 7% 下游當 canary 分攤壓力。

試著把滑桿拉到第 2.9 天—— cooldown 顯示 0%,phased rollout 顯示 ~21%; 再拉到第 3.1 天—— cooldown 跳到 100%,phased rollout 顯示 ~22%。 這 0.2 天之內 cooldown 跨了 100 個百分點的跳變、phased rollout 只走了 1 個百分點—— 這就是 cliff edge 與 ramp 的本質差別。

把滑桿拉到第 7 天——cooldown 100%、phased rollout 50%—— cooldown 把全部下游同時推進攻擊範圍, phased rollout 仍有一半的下游有機會在被命中前等到攻擊偵測。 cooldown 的「全域同步」還帶來 thundering herd 效應: 第 N+1 天會有「兩天份」的下游同時被命中(被延遲的需求都壓進來了); phased rollout 的線性升曲線完全沒有這個問題。

三個真實 2026 攻擊案例:兩個方案各自表現

抽象比較容易陷入紙上談兵。 把 2026 年三個有公開 timeline 的真實攻擊套上兩個方案, 看實際表現如何——切 tab 在三個案例之間切換。

switch tabs to compare three real 2026 incidents · 3 tabs

下載量級:每週 ~200k (中段)/攻擊存活:~3 小時

LiteLLM 是一個 LLM 服務代理庫,2026 年 2 月有惡意版本被推上 PyPI。timeline:10:39 UTC publish、13:38 UTC 被 PyPI 隔離——窗口約 3 小時。惡意 payload 是 import-time backdoor,能讀取本機環境變數(多數 LLM 應用裝有 OPENAI_API_KEY 與其他敏感 token)。

Cooldown N=3 表現:所有 cooldown 用戶受到保護——因為攻擊存活時間 3 小時 ≪ 3 天。但有一個前提:在那 3 小時內必須有「沒設 cooldown 的人」當 canary 來觸發偵測。實際偵測來自一位獨立安全研究員的 PyPI scan,他的 scan 不依賴下游安裝。

Phased rollout 14d 表現:那 3 小時內,phased rollout 之下會有 ~0.9% 的下游被命中——也就是約 200k * 0.0089 ≈ 1800 個專案。但這 1800 個專案分布在所有時區與所有規模,比「先當 canary 都是亞太工程師」要均勻得多。

verdict:cooldown 因為偵測來源是獨立 scan 而非下游,所以保護成功;phased rollout 的損失較大(1800 vs 0)但分布公平。這是「依賴外部偵測」的案例——cooldown 在這類案例上有實效優勢。

下載量級:每週 ~5M (頭部)/攻擊存活:~4.5 小時

Nx 是 monorepo build tool,2025 年 8 月有惡意版本被推上 npm。timeline:publish 後約 4-5 小時被 npm takedown。惡意 payload 是 postinstall script,會嘗試 exfiltrate `.npmrc` 與 SSH key。Nx 是 transitive dependency 的常見深度節點——很多 React Native 與 Next.js 專案間接依賴它。

Cooldown N=3 表現:同樣全保護——4.5 小時 ≪ 3 天。但這個事件的偵測來源恰好是「沒設 cooldown 的下游」——一個用 npm 預設 config 的 Vercel 部署在拉新版本後 EDR 警告觸發,這才上報到 npm security。換言之,cooldown 在這個案例的保護來自於「有人不用 cooldown 當 canary」。

Phased rollout 14d 表現:4.5 小時內 ~1.3% 的下游被命中。對 5M 每週下載的 Nx 而言,這是 ~65k 個專案——其中很多是 transitive 依賴,下游的下游。命中比例分布均勻,沒有特定時區或地理特徵。

verdict:cooldown 在這個案例「贏」是因為「不用 cooldown 的下游當了 canary」——這正是 RFC 反對的設計依賴。若所有人都用 cooldown,這個攻擊在第 3.1 天會同時命中數十萬 transitive 下游。phased rollout 用 65k 的早期損失換取了「不依賴 canary」的均勻分布。

下載量級:每週 ~100M (超頭部)/攻擊存活:~2.5 小時

axios 是 HTTP client,2026 年 3 月版本 1.7.9 被植入惡意 payload。timeline:publish 後約 2-3 小時被 npm takedown。惡意 payload 試圖在 module load 時 fingerprint 環境並 POST 到攻擊者 server。下載量百萬以上的套件——下游遍佈每個生態系。

Cooldown N=3 表現:全保護。100M 週下載意味著「總有人先當 canary」是統計上幾乎必然發生——這正是 cooldown 在頭部套件上「0.15% 命中率」的來源。攻擊在第 1 小時就會被某個下游偵測。

Phased rollout 14d 表現:2.5 小時內 ~0.74% 的下游被命中,但因為下載量大,絕對數字仍然是 ~740k 個專案。在 phased rollout 設計下,stage 1(第 1 天的 7%)已經包含大量 production 部署(Stripe、Shopify、Vercel 都會在 stage 1)——這些部署的 EDR/monitoring 偵測能力遠勝普通開發者。

verdict:頭部套件是兩個方案最接近的情境——cooldown 與 phased rollout 都能依賴「有人偵測到」的群眾智慧。差別是 cooldown 把這個負擔均勻塞給「不用 cooldown 的少數人」,phased rollout 把它分配給「第 1 天 hash 分到的 7%」。後者是 deterministic 的分配、不依賴「opt-out 的勇敢者」。

三個案例放在一起看,可以提煉出一個 pattern:cooldown 的「保護」很大程度依賴一個 silent canary 群體—— 那些沒設 cooldown 的下游、或不依賴下游的獨立 scan 服務(Snyk、Socket、PyPI Inspector)。 當 cooldown 變成 ecosystem default、所有人都採用,這個 canary 群體會萎縮, cooldown 的有效性會隨之下降。

這是一個 game-theoretic 問題:cooldown 在「少數人採用」時非常有效(搭便車), 在「人人都採用」時保護性下降(沒有便車可搭)。 cooldowns.dev 自己引用的「80-90% 曝險降低」數字是基於 2024-2025 的歷史—— 那時 cooldown 還是少數人在用的策略。 若 2026 之後 cooldown 變成預設、覆蓋率超過 80%,那個數字會降到多少?沒人知道,但一定低於 80%。

phased rollout 不需要 silent canary 群體—— 每個下游 deterministic 地落在某個 stage、stage 1 的下游自動成為「明確指派的 canary」。 這個指派是公平的(hash 決定)、可預測的(你能算出自己會在哪個 stage)、 且不會因為大家都採用而失效(hash 分散與採用率無關)。

傳遞性依賴:lockfile 對兩個方案的影響不同

現代套件管理器都用 lockfile 鎖定版本—— npm 的 package-lock.json、pnpm 的 pnpm-lock.yaml、pip 的 requirements.lock、uv 的 uv.lock。 lockfile 之下,transitive dependency 的版本是被 explicit pin 住的—— 換言之,cooldown 對 transitive 的保護只在「lockfile 規律更新」的情境下發揮作用。

pnpm 的 minimumReleaseAge 在 lockfile resolution 階段套用—— 任何被選為新版本的套件都必須滿足年齡要求; 但對「已 pin 在 lockfile 的舊版本」沒有任何保護作用。 cooldown 對 transitive 的覆蓋是「resolution-time only」,不是「install-time」。

phased rollout 的 RFC 在這點上需要明確設計:core input 包含 project_id, 意味著兩個不同專案對同一個 transitive 的 hash 不同、 它們會在 14 天窗口的不同時間點看到——這對 transitive 安全性是好的(攻擊不會同時感染所有人)。 目前傾向是「lockfile 寫入後 frozen」(與現狀一致)、resolution 時遵守 phased rollout—— 所以兩個方案在這個議題上的差異很小,都只在 resolution 階段介入。

這對企業有實用意涵:那些用 Renovate 或 Dependabot 自動產生 PR 升 transitive 版本的 team, 在 phased rollout 之下會看到 PR 的時間點與專案的 hash stage 對齊—— stage 1 的專案會早 14 天看到 PR、stage 4 的專案會晚 14 天看到 PR。 這提供了「自動分散風險」的副作用——同一家公司的不同 repo 不會同時收到同一個惡意 transitive 版本的 PR。

訊號品質:偵測機會的時間分布

供應鏈攻擊的偵測訊號有四種來源: EDR/runtime monitoring(malicious behavior at install/import time)、 static scan(Snyk、Socket、GitGuardian)、 registry-side scan(PyPI Inspector、npm security automation)、 人工 report(Twitter、GitHub issue、Slack)。 這四種訊號的有效性依賴於「樣本量」——越多下游下載到惡意版本,越多訊號會浮現。

cooldown 對前兩種訊號的影響是「集中在第 N+1 天」。 第 1-N 天的訊號量為 0(沒人下載到)—— 這是 RFC 一個非常尖銳的觀察:cooldown 主動關閉了 cooldown 期內的偵測管道, 只把希望寄託在 registry-side scan 與獨立研究員。 如果攻擊者懂得 obfuscate(malicious code 用 base64 編碼、runtime decoded), static scan 與 registry-side scan 的偵測率會掉到個位數。 cooldown 把 EDR/runtime 這個最強的偵測管道延後了 N 天—— 在那 N 天內,攻擊者有時間進一步 obfuscate、或在第 N+1 天之前就完成有限的高價值目標滲透。

phased rollout 的 stage 1 (第 1 天 7%) 提供了 immediate runtime 訊號—— 這 7% 包含了真實的 production 部署(前面說過,Stripe、Shopify、Vercel 等 自動進入 stage 1 的大客戶), 這些部署的 EDR 與 SIEM 監控能力是業界最強的。 如果惡意版本在 stage 1 觸發任何異常 runtime behavior(network egress to unknown domain、 file write outside expected paths、process spawn), 這些 production EDR 會在分鐘級偵測到並 alert security team。

把訊號分布的差異具象化: cooldown 之下,第 1-3 天的偵測訊號是「flat 0、只有 registry scan 在跑」; 第 3.1 天之後是「flat 100%、所有下游同時暴露」。 phased rollout 之下,每一天的偵測訊號都是「7% 的下游當該日的 canary」—— 14 天裡持續有 immediate runtime feedback。

這個「持續 feedback」對 ecosystem-level 學習有額外價值。 如果 phased rollout 的 stage 1 偵測到問題、版本被 registry takedown, 那麼後續 stage 2-4 就根本不會碰到—— 這是經典的 progressive rollout 哲學,跟 web 服務的金絲雀部署是同一個 pattern。 RFC 在這點上引述了 CrowdStrike 事件後的官方教訓: 「One of the biggest lessons learned was to 'release gradually across increasing rings of deployment.'」

CrowdStrike 比喻很關鍵。2024 年 7 月推一個 sensor 更新、全球 850 萬台 Windows 同時 crash, 事後 retrospective 的結論是分批 ring 0/1/2/3 推送——這正是 phased rollout 的設計。 RFC 也引述 McAfee 2010 年的 DAT 誤判事件, 事後的修正同樣是 phased rollout: 「The response was phased rollouts, not 'everyone wait 24 hours before updating antivirus definitions.'」 AV 廠商在 2010 年就拒絕了 cooldown 路線—— 套件管理器 2026 年面對的是同一個選擇。

opt-in 預設與企業政策:協議層 vs 客戶端的根本差別

這是兩個方案的根本架構差異。 cooldown 是純客戶端策略——pnpm/pip/npm CLI 自己決定要不要拒絕某個版本,registry 不知道、不在乎。 這個架構讓 cooldown 在 2026 上半年能夠快速 deploy(六個月內四個生態系全部支援), 因為它不需要 registry 改任何 protocol。

phased rollout 必須是協議層的設計。 RFC 的原話: 「Package registries are pull-based; they don't directly decide who gets what artifact. Therefore: any phased rollout logic must live on the project-side.」 換言之,registry 仍然 serve 任何 query—— 但 client 在 query 之前要先計算自己的 hash,看看是不是該 query 這個新版本。

這個 client-side hash 計算需要兩個 input 都從某處取得: project_id(每個專案各自的 stable identifier,可以是 package.json 裡的 generated UUID)、 版本的 artifact digest(這個必須從 registry 拿到才能算 hash)。 RFC 的設計是 client 先取得 registry 的版本 metadata(包含 digest), 然後在本地算 hash,決定要不要 install。

對 enterprise 來說,這個架構有實用價值。 一個公司可以說「我們所有的 monorepo 都用同一個 enterprise_project_id」, 這樣全公司的 repo 會落在同一個 stage—— 可以集中 evaluate 新版本、不會出現「同一個版本在 repo A 已採用、repo B 還沒採用」的混亂。 或者反過來:「不同的 monorepo 用不同 project_id」, 這樣某個 repo 早碰到問題、其他 repo 還有時間反應。

企業也可以 opt-in 特定 stage——例如說「我們是 stage 4 (第 11-14 天)」。 這在 RFC 的設計裡是 supported——project 可以在 config 裡指定 desired stage。 這就把 cooldown 的「N 天」這個單一可調參數,擴展成「在 0-14 天的窗口裡選任意 stage」。 對保守的 enterprise(金融、醫療)這提供了比 cooldown 更細的控制。

cooldown 的 enterprise 政策粒度是 N 這個單一 scalar—— 保守的公司可以設 N=14、激進的設 N=1,但沒有「先碰一部分新版本、後碰另一部分」這種混合策略。 phased rollout 允許這種混合:可以說「我們的 dev repo 是 stage 1(搶先碰)、 production repo 是 stage 4(保守)」——這在 cooldown 之下只能透過維護兩個不同的 cooldown config 達成。

但 phased rollout 在「目前可用性」這個維度上完全輸給 cooldown。 cooldown 已經在 npm 21.5、pnpm 9.x、pip 26.1、uv 0.5.x 預設或支援; phased rollout 還在 RFC 階段、沒有任何主流生態系實作—— 若 npm/pnpm 在六月決議採用,要實際 ship 到 stable 估計還要 6-12 個月。

click column header to sort · 5 columns × 4 rows

四個套件管理器在 2026-05 的 cooldown 支援與 phased rollout 狀態(資料來源:各 release notes 與 illegalcode.net RFC)
registry / CLI cooldown flag default N (days) transitive? phased rollout 進度
npm 21.5+ min-release-age=N 3 yes(resolve) RFC 評估中
pnpm 9.x minimumReleaseAge: M 3 yes(resolve) RFC 評估中
pip 26.1+ PIP_UPLOADED_PRIOR_TO=PND 3 yes(resolve) 未提案
uv 0.5.x exclude-newer = "N days" 3 yes(resolve) 未提案
四個生態系在 cooldown 軸上幾乎收斂於同一個值(N=3),都把 transitive 包進來——這個一致性是 RFC 之所以針對 cooldown 寫反論的原因:當所有主流 CLI 都選了 cliff edge,那個 cliff 的代價分配才真的開始集中在亞太工程師身上。

四個生態系在 cooldown 軸上幾乎收斂於同一個值(N=3),都把 transitive 包進來——這個一致性是 …

npm/pnpm/pip/uv 四生態系均預設 N=3 天 cooldown;phased rollout 在所有主流 CLI 仍未實作。

OSS 維護者負擔:哪一個方案多了 publish step?

cooldown 對維護者的負擔是零—— cooldown 的所有邏輯都在 client 端、registry 也不需要改, 維護者照舊 npm publishtwine upload。 新版本 publish 後三天裡沒人安裝,但版本仍然存在於 registry, 維護者察覺不到任何差異。

phased rollout 的維護者負擔取決於 RFC 的最終形態。 純 client-side 版本(hash 完全由 client 算)零負擔—— 維護者照舊 publish、registry 照舊 serve、client 自己決定要不要拉。 但如果 RFC 演進成「registry 標記 stage、client 訂閱 stage」, 那麼維護者需要在 publish 時提供 stage metadata(例如指定「這個版本要從 stage 1 開始 rollout」)。

更複雜的場景:emergency security patch 應該跳過所有 stage、立刻釋出給所有人。 這在 RFC 裡是 supported——版本可以 mark 為 emergency,client 不執行 hash。 但這要求維護者知道 publish 時加一個 flag——例如 npm publish --emergency—— 這是新的維護者責任。

對小型 OSS 維護者(一個人維護的小套件)這個負擔可能變成壓力源。 他們已經要應付:semver compliance、changelog 維護、issue triage、PR review、 security advisory(透過 OSV / GHSA)—— 再加一個「stage rollout decision」可能就是壓垮駱駝的稻草。 這是 RFC 還沒充分回應的反對意見。

但 RFC 提供了一個合理的 default:所有 publish 都自動進入 stage 1 (標準 14 天 phased rollout),維護者不做任何事; 只有「需要 emergency 時加 --emergency」是 opt-in 動作。 這個 default 行為與當前的 publish flow 幾乎沒差別—— 維護者的 publish 速度感受不變,只是 client 端 propagation 變慢。

一個更微妙的問題是 release timing: 傳統 release manager 配合 blog post / conference talk / marketing 控制可見性、 phased rollout 把「可見」拆成 14 天漸進。 RFC 的 workaround 是延後 publish 到「目標可見時間 - 7 天」,讓中位 stage 對齊—— 合理但需要維護者主動運用。

該怎麼選:四種 production 情境的對應方案

沒有一個方案在所有情境都贏。RFC 自己承認的 tradeoff: 「Slower convergence on newly published versions, especially for low-download packages. But cooldowns have the same tradeoff; phased rollouts just distribute it more fairly.」 production 上的決策是「哪個 tradeoff 對我這個情境的代價更低」。

每個 consumer class 在 phased rollout 上落的 stage

每個 consumer class 在 phased rollout 上落的 stage D+0 D+3 D+7 D+11 D+14 days since version published STAGE 1 · 7% STAGE 2 · 28% STAGE 3 · 50% STAGE 4 · 100% solo OSS / dev sandbox 「我來當 canary」 SaaS production(Stripe/Vercel) EDR 強、自願 stage 1 enterprise staging 等 stage 1/2 訊號 finance / health production 最大延後、stage 4 訂閱 transitive(lockfile 已 frozen) 不適用——pin 在 lockfile 的舊版本,phased rollout 不再介入
同一個專案,按它的「角色」自我選擇要落在哪個 stage——dev 與 SaaS production 主動進 stage 1 當 canary(他們有 EDR/SIEM 抓問題的能力),enterprise staging 等 stage 1/2 的偵測訊號,finance/health production 訂 stage 4 把曝險最大化延後。cooldown 只能給一個 N,phased rollout 給的是這張對應表的可寫性。

同一個專案,按它的「角色」自我選擇要落在哪個 stage——dev 與 SaaS production 主動進 sta…

solo/SaaS 主動入 stage 1 當 canary;enterprise staging 等 stage 3;金融/醫療訂 stage 4 最大延後。

第一個情境:今天就需要供應鏈防禦、不能等 RFC 走完—— 這時 cooldown 是唯一可用的工具。 pnpm 加 minimumReleaseAge: 4320、 pip 加 PIP_UPLOADED_PRIOR_TO="P3D"、 uv 加 exclude-newer = "3 days"。 這個配置在六月之前可以 ship,提供 80-90% 的曝險降低, 代價是「依賴別人當 canary」這個道德問題等之後再解決。

第二個情境:你維護一個大型 enterprise monorepo、需要對新版本採用做細粒度控制—— 這時 phased rollout 的設計優勢顯現。 可以把 stage 1 用於 dev 環境、stage 3 用於 staging、stage 4 用於 production—— 不同環境自動錯峰看到新版本。 cooldown 之下要達成同樣效果,需要維護三組不同的 cooldown config 與三套不同的 CI pipeline。 但這個情境的成本是「等 RFC ship」——目前只能用 cooldown 加自製 stage 機制(用 Renovate 排程錯開)模擬。

第三個情境:你維護一個下載量低的長尾套件、擔心套件被攻擊但 ecosystem-wide 沒人會幫你當 canary—— 這時 phased rollout 對你(作為被依賴方)幾乎沒有差別, 但對你的下游使用者有實質意義:他們不再需要「賭」剩下的 cooldown 期內有人偵測, 而是 deterministic 知道自己會在第幾天碰到新版本。 從套件作者角度看,phased rollout 是「對下游更負責任」的設計——這個 framing 在 OSS 維護者社群裡可能有正面共鳴。

第四個情境:你是 registry operator(npm Inc、PyPI、Anaconda)—— 這時你的決策框架不一樣。 cooldown 對 registry 完全無感(不需要做事), phased rollout 需要 registry 提供 stage metadata API 與 emergency flag 支援。 但 phased rollout 也給 registry 一個新的能力: 「同一個版本不會在同一時刻被全球同時拉取」這個分散性意味著 registry 的 traffic spike 變平、 CDN 成本下降。 這是 phased rollout 對 registry 的 silent upside——但這個 upside 是否值得實作工程量,registry operator 要自己評估。

對絕大多數 production 場景,建議的混合策略是: 現階段(2026-Q2 到 2026-Q4)採用 cooldown 作為過渡防禦, 持續關注 phased rollout RFC 的進展; 一旦 npm/pnpm 在 2027 年 ship phased rollout, 從 cooldown 遷移到 phased rollout—— 後者在七個維度中六個維度勝出,唯一輸的維度(目前可用性)會在 ship 之後消失。

cooldown 與 phased rollout 不是 mutually exclusive—— 極度保守的企業可以同時用:例如 phased rollout 落在 stage 3(第 7-10 天)、再加 N=2 cooldown, 換來「結構性分散 + 強制延後」的雙重保護,cost 是更慢的 patch propagation。

把這個討論拉回到「我下週要做什麼」的層次—— 若你在用 pnpm,今天就加 minimumReleaseAge: 4320(pnpm 9.x 支援); 若你在用 uv,加 exclude-newer = "3 days"; 若你在用 pip 26.1+,加 PIP_UPLOADED_PRIOR_TO="P3D"; 若你在用 npm 21.5+,加 min-release-age=3。 然後關注 npm/pnpm 六月的 release planning—— 若決議採用 phased rollout,2027 年初的遷移會是 config change 等級 (只需在 .npmrc / .pnpmrc 加一行 stage subscription)。

最後值得提的 framing:cooldown 與 phased rollout 都把「攻擊偵測」推給「群眾」—— cooldown 推給「沒設 cooldown 的下游」、phased rollout 推給「stage 1 的早期採用者」。 兩個方案都沒解決「偵測本身的可靠性」這個更深的問題—— Reproducible Builds、artifact signing、TUF、Sigstore 才是針對這個問題的長期解法。 cooldown 與 phased rollout 是 mitigation,不是 prevention—— 這個區分對評估它們的相對價值很重要。

How to choose:今天用 cooldown(六月之前唯一可用),同時支持 phased rollout RFC 進入 npm/pnpm 標準路線;對 enterprise 與長尾套件,phased rollout 在公平性、訊號品質、政策粒度三個維度上明顯勝出,這個遷移在 2027 年值得做。