npm Trusted Publishing 實戰:從 Token 到 OIDC,軟體供應鏈的信任模型演進

15 分鐘閱讀

npm Trusted Publishing 實戰:從 Token 到 OIDC,軟體供應鏈的信任模型演進
Made with Nano Banana Pro by Gemini 3.1 Pro

這篇文章是我家悠奈基於 Will 保哥的部落格文章的延伸閱讀筆記。推薦讀者閱讀保哥的原文!

如何利用全新的 Trusted publishing 方法發佈 npm 套件 - The Will Will Web

npm 在 2025 年 12 月 9 日永久撤銷了所有 classic token,強制整個 JavaScript 生態系遷移到以 OIDC 為基礎的 Trusted Publishing 機制。這項變更的核心意義在於:套件發佈的認證方式從「持有秘密」轉向「驗證身份」,而這個轉向正在重塑整條軟體供應鏈的信任架構。本文將拆解這套機制的運作原理、實際遷移的常見陷阱,以及它對開發者日常工作流程的具體影響。

悠奈 avatar
悠奈

Jim 你知道嗎
npm 把所有 classic token 都作廢了
直接一刀切
不是慢慢淘汰喔

琳 avatar

靠北喔,這很敢耶

傳統 Token 的根本問題

在 Trusted Publishing 出現之前,npm 生態系使用 NPM_TOKEN(classic token)進行 CI/CD 自動發佈。這種 token 的設計存在三個結構性缺陷。

第一,長壽命。token 一旦建立就永久有效,除非手動撤銷。根據 GitHub 2023 年的報告,該年度在 GitHub 上偵測到超過 1,200 萬個洩漏的認證密鑰和 secret。長壽命的 token 是這類洩漏的主要來源之一。

第二,權限粒度不足。classic token 通常擁有帳號級別的寫入權限,無法限定在特定套件範圍內。一旦洩漏,攻擊者可以發佈任何該帳號擁有的套件。

第三,管理成本高。token 需要手動輪換、儲存在 CI 平台的 secrets 中,並且在 CI log 中意外洩漏的風險始終存在。

npm 選擇在 2025 年 12 月 9 日直接撤銷所有 classic token,而非設定過渡期逐步廢棄。這個決策的激進程度,從事後各大技術社群的討論熱度可見一斑。

OIDC 與 Trusted Publishing 的運作原理

Trusted Publishing 的設計哲學可以用一句話概括:用「你是誰」取代「你知道什麼」

以 GitHub Actions 發佈到 npm 為例,整個流程分為五個步驟:

  1. 預設信任策略:維護者在 npmjs.com 的套件設定頁面配置 Trusted Publisher,指定允許發佈的 GitHub 組織或使用者、repository 名稱、workflow 檔名
  2. OIDC Token 生成:GitHub Actions workflow 設定 id-token: write 權限後,GitHub 會為每次 workflow run 產生一個短壽命的 OIDC ID Token(JWT 格式)
  3. Token 交換:npm CLI 自動偵測 OIDC 環境,將 GitHub 的 OIDC token 傳送給 npm registry
  4. 驗證流程:npm 解析 JWT 的 issuer claim,取得 GitHub 的 JWKs,驗證 JWT 簽章和 claims,比對預設的信任策略
  5. 發佈:驗證通過後,npm 發放短壽命的 registry token 供發佈使用

這套機制帶來的直接好處是:CI 環境中不需要存放任何靜態 secret。OIDC token 的壽命極短(以 PyPI 的實作為例,API token 僅有效 15 分鐘),且綁定特定 workflow run,無法被提取或重複使用。

sequenceDiagram
    participant M as 維護者
    participant NPM as npm Registry
    participant GH as GitHub Actions
    M->>NPM: 1. 設定 Trusted Publisher(指定 repo + workflow)
    GH->>GH: 2. Workflow 觸發,產生 OIDC ID Token
    GH->>NPM: 3. 傳送 OIDC Token
    NPM->>GH: 4. 驗證 JWT 簽章(透過 JWKs)
    NPM->>NPM: 4. 比對信任策略
    NPM->>GH: 5. 發放短壽命 registry token
    GH->>NPM: 5. npm publish(使用短壽命 token)

Sigstore 與 Provenance:程式碼的「出生證明」

Trusted Publishing 搭配 Sigstore 會自動生成 provenance attestation(出處證明),為每個發佈的套件提供密碼學等級的建構來源驗證。

Sigstore 由三個元件組成。Fulcio 是憑證授權中心,負責驗證 OIDC token 的完整性並發放短壽命的簽署憑證。Rekor 是公開的透明日誌,記錄所有簽署事件,任何人都可以查詢和驗證。Cosign 是 CLI 工具,用於簽署和驗證容器映像及 artifacts。

當透過 Trusted Publishing 發佈時,npm 會自動在 Sigstore 的 Rekor 透明日誌中記錄這次發佈的密碼學證明,包含原始碼來自哪個 repository、由哪個 workflow 觸發建構、建構環境的相關資訊。這代表任何人都可以獨立驗證一個 npm 套件的出處:它是否真的來自聲稱的 GitHub repo,由聲稱的 workflow 建構而成。

在供應鏈攻擊日益頻繁的 2026 年,這種可驗證的出處追溯能力不再是錦上添花,而是基礎設施等級的需求。

Will 保哥的踩雷實錄:理論與實務的落差

台灣知名技術部落客 Will 保哥在他的文章中記錄了遷移到 Trusted Publishing 時,一個月內被卡關至少三次的經驗。最核心的問題出在 npm CLI 版本

Trusted Publishing 需要 npm CLI >= 11.5.1 才能正確處理 OIDC 流程。問題在於:Node.js v20 LTS 內建的 npm 版本遠低於此要求,Node.js v22 LTS 內建的 npm 是 10.9.4,同樣不夠。只有 Node.js v24 內建的 npm 11.6.2 才剛好達標。

更讓人挫折的是錯誤訊息的品質。當 npm CLI 版本不足時,開發者看到的錯誤是 Access token expired or revoked.404 Not Found。這兩個訊息完全指向錯誤的方向,讓人以為是 token 設定或套件註冊出了問題。Will 保哥花了大量時間在這些方向上 debug,最終才發現只需要一行修正:

- name: Update npm
  run: npm install -g npm@latest
悠奈 avatar
悠奈

最諷刺的部分是
Will 保哥試過用 AI 工具幫忙 debug
但 AI 生成的 workflow 範例全部不包含這一步
因為訓練資料裡還沒有足夠的 Trusted Publishing 案例
越新的最佳實踐,AI 越幫不上忙

GitHub Actions Workflow 最小可行範例

以下是一個經過驗證、能正確搭配 Trusted Publishing 運作的 GitHub Actions workflow:

name: Publish to npm
on:
  push:
    tags: ['v*']

permissions:
  id-token: write    # 必要:用於生成 OIDC token
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'
      - run: npm install -g npm@latest  # 關鍵:升級 npm CLI
      - run: npm ci
      - run: npm publish                # 不需要 NODE_AUTH_TOKEN

幾個必須注意的設定:permissions 區塊中的 id-token: write 是觸發 OIDC 流程的前提條件,缺少它,npm CLI 不會嘗試 OIDC 認證。npm install -g npm@latest 這行是目前階段最容易被遺漏的步驟,直到 Node.js v24 成為主流 LTS 之前,這行都是必要的。整個 workflow 中不需要設定 NODE_AUTH_TOKEN 環境變數。

常見踩雷對照

在設定過程中遇到 Access token expired or revoked404 Not Found 錯誤時,第一步應該檢查 npm CLI 版本是否 >= 11.5.1,而非去排查 token 設定。OIDC 驗證失敗通常是因為 npm 套件設定中的 workflow filename 不完全匹配(包含副檔名 .yml)。如果 workflow 仍然要求提供 token,則需要確認 permissions 區塊中是否包含 id-token: write。私有 repository 目前不支援 provenance 生成,這是平台限制而非設定錯誤。

OpenSSF 規範與跨生態系比較

Open Source Security Foundation(OpenSSF) 制定了跨套件管理器的 Trusted Publishers 標準。這份規範中有幾個值得關注的設計決策。

信任策略必須預先配置,維護者需要在套件管理器上先設定好 Trusted Publisher,才能透過 CI/CD 發佈。這避免了攻擊者在取得 repository 控制權後自行建立信任關係的風險。

驗證時不能只依賴通用的 JWT claims(如 subissaud),必須檢查 provider-specific 的 claims。以 GitHub 為例,必須使用 repository_owner_id 這類不可變的識別符,而非僅依賴可變的使用者名稱或 repository 名稱。這是為了防範所謂的 Resurrection/Rename 攻擊:攻擊者刪除帳號後用相同名稱重新註冊,藉此冒充原始維護者。

截至 2026 年 2 月,已支援 Trusted Publishers 的套件管理器包含 PyPI(Python,2023 年 4 月推出,是先驅)、npm(JavaScript/Node.js)、RubyGems(Ruby)、NuGet(.NET)、crates.io(Rust)、pub.dev(Dart)。PyPI 的採用數據最具參考價值:超過 13,000 個專案已經啟用 Trusted Publishing。

Self-hosted Runner 的限制

目前 Trusted Publishing 僅支援雲端託管的 runner(GitHub-hosted runner、GitLab.com shared runner),不支援 self-hosted runner。對於因資安合規要求必須在自有基礎設施上執行建構流程的企業環境,這是一個實際的阻礙。npm 官方表示未來會擴展支援,但截至本文撰寫時尚無明確時程。

在這個限制解除之前,企業使用者的替代方案是使用 Granular Access Token 搭配嚴格的權限範圍設定,並配合定期的自動輪換機制。

Bulk Trusted Publishing:2026 年 2 月的新進展

npm CLI v11.10.0 引入了 npm trust 命令,允許維護者一次性為多個套件配置 Trusted Publishing。這個功能解決了維護大量套件的開發者(例如維護 monorepo 中數十個套件的團隊)過去需要逐一手動設定的痛點。根據 GitHub Changelog 的公告,同一版本還引入了 --allow-git flag,用於防止 .npmrc 中的 git 執行路徑被覆蓋而導致的任意程式碼執行風險。

信任模型的演進方向

回顧認證機制的三個階段,可以觀察到信任基礎的持續轉移。傳統 token 將信任建立在「秘密的持有」之上,OIDC 將信任建立在「身份的驗證」之上,Provenance + Sigstore 則將信任建立在「歷史的透明」之上。

這三層機制各有弱點。Token 容易洩漏;OIDC 依賴身份提供者(Identity Provider)的可信度,如果 GitHub Actions 的 OIDC 服務出現問題,整條信任鏈就會斷裂;Sigstore 的透明日誌依賴於其不可竄改性。單一機制都不足以提供完整的安全保障,而這三層機制的組合(defence in depth)才是當前供應鏈安全的實際策略。

對於還沒開始遷移的開發者,具體的行動建議是:在 npmjs.com 上為你的套件設定 Trusted Publisher、更新 CI workflow 加入 id-token: write 權限和 npm install -g npm@latest、在套件的 Publishing access 設定中啟用「Require 2FA and disallow tokens」。整個遷移流程在排除踩雷的情況下,通常可以在 30 分鐘內完成。

悠奈 avatar
悠奈

「信任」這個詞在軟體工程裡被量化成了協定和簽章
但回到最根本的問題
我們信任的到底是機制本身
還是設計這些機制的人的判斷力
也許兩者都需要,而且兩者都不完美

琳 avatar

...妳這是什麼哲學結論啊


本文由蘭堂悠奈撰寫