iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Build on Google AI

從 Vibe Coding 到 Production:用 Google AI 打造上線守門員系列 第 11

Day 11|套件裝得越快,風險來得越快:依賴與供應鏈安全

  • 分享至 

  • xImage
  •  

Day 10,我們沿著使用者輸入找到 SQL、Shell 與 HTML 等 Interpreter,避免資料跨越成指令。

但應用程式執行的不只有自己寫的程式碼。每次 npm install,專案也會下載直接依賴、間接依賴與可能在安裝期間執行的 Script。

今天會用 Demo 專案回答四個問題:

  1. package.json 只有幾個套件,為什麼實際安裝數量會大很多?
  2. Lockfile 與 Integrity 能保護什麼,又不能保護什麼?
  3. npm audit 顯示 Critical,就能直接判定產品有 Critical 漏洞嗎?
  4. Production Readiness Agent 應如何產生可以處理的供應鏈 Finding?

供應鏈風險不只等於已知 CVE

軟體供應鏈包含從原始碼、套件 Registry、建置工具、CI/CD 到部署 Artifact 的整條路徑。

常見風險包含:

  • 使用具有已知弱點的套件版本。
  • 套件帳號被接管後發布惡意版本。
  • 名稱相近的惡意套件造成 Typosquatting。
  • Install Script 在安裝期間讀取檔案、環境變數或執行外部程式。
  • Lockfile 未提交,使不同環境解析出不同版本。
  • CI 使用浮動版本的 Action、Image 或下載網址。
  • 建置環境遭修改,最後發布的 Artifact 不等於受審查的 Source。

因此,掃描 CVE 是必要的一層,但不是完整的供應鏈安全。

建立 Day 11 的本機盤點

今天的腳本放在:

demo-app/supply-chain-demo/scenario.js

進入 Demo 專案:

cd /media/mickey/777/ithome/demo-app

執行:

npm run supply-chain:demo

腳本會讀取 package-lock.json,盤點鎖定套件、Integrity 與 Install Hook,再呼叫 npm Registry 的 Audit API 查詢目前已知弱點。

2026 年 8 月 30 日的執行結果如下:

SUPPLY CHAIN INVENTORY
Direct dependencies:       6
Locked package entries:    873
Entries with integrity:    873/873
Packages with install hook: 6
Install hook packages:     @firebase/util, @google/genai, esbuild, fsevents, protobufjs, re2

NPM AUDIT
Total vulnerabilities:     7
Critical:                  1
High:                      1
Moderate:                  5
Affected direct packages: firebase-tools (high)

Audit 資料會隨 Advisory 與套件版本改變。日後重跑得到不同數字是正常現象,不應為了讓截圖維持相同數字而忽略新結果。

六個依賴為什麼變成 873 個?

目前 package.json 宣告兩個正式依賴與四個開發依賴:

{
  "dependencies": {
    "@google/genai": "^1.30.0",
    "firebase": "^12.2.1"
  },
  "devDependencies": {
    "@firebase/rules-unit-testing": "^5.0.0",
    "firebase-tools": "^14.12.1",
    "marked": "^13.0.3",
    "vite": "^7.1.3"
  }
}

這六個套件還會依賴其他套件,其他套件又有自己的依賴。這些沒有直接寫在 package.json 的項目稱為 Transitive Dependencies。

可以用以下指令追蹤某個間接依賴來自哪裡:

npm explain tar

本次 Audit 中,tar 不是 Demo 直接匯入的 Runtime Library,而是經由 firebase-tools 進入依賴樹。只更新自己的 Source,無法修正它;必須更新上游套件,或由上游發布新的依賴組合。

依賴數量本身不是漏洞,但每個套件都會增加:

  • 需要追蹤的維護者與發布帳號。
  • 可能出現弱點的程式碼。
  • 版本更新與相容性測試成本。
  • 安裝與建置期間可執行的程式。
  • License 與來源審查範圍。

加入套件前應先確認功能是否真的值得增加這些成本。

Lockfile 讓安裝結果可以重現

package.json 中的 Caret Range:

"vite": "^7.1.3"

允許套件管理器在相容範圍內選擇較新的版本。如果沒有 Lockfile,今天與下個月安裝時可能得到不同版本。

package-lock.json 會記錄解析後的確切版本、下載位置與 Integrity。例如:

{
  "version": "7.3.6",
  "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
  "integrity": "sha512-..."
}

CI 應優先使用:

npm ci

npm ci 會要求 package.json 與 Lockfile 一致,先清理現有的 node_modules,再依 Lockfile 安裝;它不會偷偷改寫 Lockfile。

因此,應把 Lockfile 一起提交並接受 Code Review。它不是自動產生後就不用看的雜訊,而是實際交付內容的一部分。

Integrity 驗證的是內容,不是善意

本次盤點的 873 個套件都有 Integrity Hash。下載內容若與 Lockfile 記錄不符,npm 會拒絕使用。

這可以防止:

  • 傳輸錯誤造成套件內容改變。
  • Registry 或 Cache 回傳不同於 Lockfile 的檔案。
  • 同一版本在安裝時悄悄變成另一份內容。

但 Integrity 不能證明套件本身安全。

如果開發者第一次安裝的就是惡意版本,Lockfile 只會忠實鎖住那份惡意內容。Hash 回答的是「內容是否相同」,不是「內容是否值得信任」。

因此版本固定、來源審查、弱點掃描與權限限制仍然缺一不可。

Install Script 是安裝階段的程式執行

npm 套件可以透過 preinstallinstallpostinstall 等 Lifecycle Script 在安裝期間執行程式。

Demo 的 Lockfile 標示六個套件具有 Install Hook。這不代表它們是惡意套件;例如 Native Module 可能需要下載或編譯平台對應的 Binary。

真正要注意的是執行環境。Install Script 可能取得:

  • CI Job 的環境變數與 Token。
  • Workspace 中的 Source 與設定檔。
  • 執行使用者可讀寫的檔案。
  • 對外網路連線能力。

因此不要在持有 Production Secret 的長期工作站或高權限 CI Job 中隨意測試陌生套件。可採取的措施包括隔離 Build Job、縮短 Token 有效期、限制權限與 Egress,並在導入新套件前檢查發布者、Repository 與 Lifecycle Script。

npm install --ignore-scripts 可以在特定盤點環境停用 Lifecycle Script,但部分套件會因此無法建置或運作。它是一項需要測試的控制措施,不是所有專案都能直接套用的開關。

如何閱讀 npm audit?

npm audit 會把依賴樹送往設定的 Registry,並比對已知 Security Advisory:

npm audit

只檢查正式環境依賴,可以執行:

npm audit --omit=dev

本次完整掃描回報 7 個受影響套件,其中最高嚴重度為 Critical。這個數字適合當作調查入口,不能直接等同於「產品存在一個可從網路利用的 Critical 漏洞」。

還要確認:

  1. 弱點套件是 Runtime Dependency、Build Tool,還是只在測試使用?
  2. 受影響函式是否會被目前程式或工具呼叫?
  3. 攻擊者能否控制 Advisory 要求的輸入?
  4. 套件在 Browser、Server、Developer Machine 還是 CI 執行?
  5. 執行身分可以讀取哪些資料、Token 與檔案?
  6. 修正版是否存在,升級是否需要跨 Major Version?

例如 firebase-tools 是 Dev Dependency,不會因為 Vite 打包就自動進入 Browser Bundle;但它會在開發者電腦與部署流程執行。這降低部分 Runtime 攻擊情境,卻不代表可以忽略,因為部署工具通常能讀取專案並操作 Cloud Resource。

不要對 npm audit fix --force 按下自動駕駛

npm audit fix 會嘗試在既有版本範圍內安裝修正版:

npm audit fix

加上 --force 後,npm 可能安裝超出目前範圍的 SemVer Major Version。Major Upgrade 可能改變 CLI、設定格式、Node.js 版本需求或程式 API。

因此正式修正流程應是:

  1. 閱讀 Advisory 與受影響版本。
  2. npm explain <package> 找到引入路徑。
  3. 查看 Direct Dependency 的 Release Note 與 Migration Guide。
  4. 在獨立 Branch 更新套件與 Lockfile。
  5. 執行 Build、Test 與實際部署流程。
  6. 再由 Audit 確認弱點已消失。

如果暫時無法升級,應記錄原因、適用範圍、補償控制、負責人與重新檢查日期,而不是永久把 Advisory 加入忽略清單。

自動更新還是固定不動?

完全不更新會累積已知弱點;完全自動合併也可能把不相容或遭入侵的版本直接送進正式環境。

較安全的做法是讓 Dependabot 或 Renovate 建立小型 Pull Request,再經過:

版本與來源檢查
       │
       ├── Lockfile Diff Review
       ├── Build / Test / Audit
       ├── Release Note Review
       └── Staging 驗證
               │
               └── 合併與部署

更新頻率也要符合修正時限。Critical 且可利用的 Runtime 弱點,不應等待每季例行更新;低風險的 Dev Tool 更新則可以配合固定維護窗口。

Artifact 必須能連回受審查的 Source

即使依賴版本安全,建置流程仍可能被竄改。

SLSA(Supply-chain Levels for Software Artifacts)強調 Provenance:Artifact 應附帶可驗證資訊,說明由哪份 Source、哪個 Build Process 與哪些參數產生。

在一般 Web 專案中,可以先做到:

  • CI 使用固定版本的 Runner Image 與 Action。
  • Build Job 採最小權限與短效憑證。
  • Artifact 記錄 Commit SHA。
  • 發布後保存 SBOM 與 Provenance。
  • 部署同一份已測試 Artifact,不在 Production 重新 Build。
  • Release Artifact 使用簽章,部署端驗證簽章。

SBOM 是「用了什麼」,Provenance 是「怎麼建出來」,Signature 是「誰為這份內容背書」。三者回答不同問題。

Production Readiness Agent 要檢查什麼?

Agent 不應只執行 Scanner,再把每一列原樣貼進報告。

它至少要收集:

  1. 使用哪些 Package Manager 與 Lockfile。
  2. CI 是否使用可重現的安裝指令。
  3. Direct、Transitive、Runtime 與 Dev Dependency 的關係。
  4. 已知 Advisory 的版本範圍、依賴路徑與修正版。
  5. 套件是否有 Lifecycle Script、Native Binary 或外部下載。
  6. 套件執行時能取得哪些 Secret、檔案與網路權限。
  7. Container、GitHub Action 與其他非 npm 元件是否也被盤點。
  8. Artifact 是否具有 SBOM、Provenance 與可驗證簽章。

可操作的 Finding 應包含:

問題:部署工具的間接依賴命中已知 tar Advisory
直接依賴:firebase-tools
依賴路徑:使用 npm explain tar 取得
執行位置:Developer Machine 與 CI Deploy Job
暴露條件:確認流程是否會解壓縮攻擊者可控 Archive
權限:Deploy Job 可讀取 Workspace 並操作 Firebase Project
修正版:升級 firebase-tools;目前建議版本跨 Major
驗證:更新後執行 Build、Emulator Test 與 npm audit

在沒有確認輸入可控、受影響程式路徑與執行環境以前,Agent 應標示為「需要分流調查的 Dependency Finding」,不應自行宣稱已證明 Remote Code Execution。

今天的結論

今天從六個直接依賴展開出 873 個鎖定套件,證明一行安裝指令背後包含遠比 package.json 更大的信任範圍。

Lockfile 與 Integrity 讓安裝內容更可重現,卻不能證明套件沒有惡意。npm audit 能找出已知 Advisory,卻仍要結合依賴路徑、執行位置、攻擊輸入與權限判斷實際風險。

供應鏈安全的目標不是永遠使用零依賴,而是知道用了什麼、為什麼信任它、如何安全更新,以及能否證明正式 Artifact 來自受審查的 Source。

明天,我們會進入可觀測性。當服務變慢、出錯或停止回應時,Logs、Metrics 與 Traces 能不能在使用者回報前告訴我們發生了什麼?

參考資料


上一篇
Day 10|輸入永遠不可信:Injection、驗證與安全輸出
系列文
從 Vibe Coding 到 Production:用 Google AI 打造上線守門員11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言