Day 10,我們沿著使用者輸入找到 SQL、Shell 與 HTML 等 Interpreter,避免資料跨越成指令。
但應用程式執行的不只有自己寫的程式碼。每次 npm install,專案也會下載直接依賴、間接依賴與可能在安裝期間執行的 Script。
今天會用 Demo 專案回答四個問題:
package.json 只有幾個套件,為什麼實際安裝數量會大很多?npm audit 顯示 Critical,就能直接判定產品有 Critical 漏洞嗎?軟體供應鏈包含從原始碼、套件 Registry、建置工具、CI/CD 到部署 Artifact 的整條路徑。
常見風險包含:
因此,掃描 CVE 是必要的一層,但不是完整的供應鏈安全。
今天的腳本放在:
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 與套件版本改變。日後重跑得到不同數字是正常現象,不應為了讓截圖維持相同數字而忽略新結果。
目前 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,無法修正它;必須更新上游套件,或由上游發布新的依賴組合。
依賴數量本身不是漏洞,但每個套件都會增加:
加入套件前應先確認功能是否真的值得增加這些成本。
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。它不是自動產生後就不用看的雜訊,而是實際交付內容的一部分。
本次盤點的 873 個套件都有 Integrity Hash。下載內容若與 Lockfile 記錄不符,npm 會拒絕使用。
這可以防止:
但 Integrity 不能證明套件本身安全。
如果開發者第一次安裝的就是惡意版本,Lockfile 只會忠實鎖住那份惡意內容。Hash 回答的是「內容是否相同」,不是「內容是否值得信任」。
因此版本固定、來源審查、弱點掃描與權限限制仍然缺一不可。
npm 套件可以透過 preinstall、install 或 postinstall 等 Lifecycle Script 在安裝期間執行程式。
Demo 的 Lockfile 標示六個套件具有 Install Hook。這不代表它們是惡意套件;例如 Native Module 可能需要下載或編譯平台對應的 Binary。
真正要注意的是執行環境。Install Script 可能取得:
因此不要在持有 Production Secret 的長期工作站或高權限 CI Job 中隨意測試陌生套件。可採取的措施包括隔離 Build Job、縮短 Token 有效期、限制權限與 Egress,並在導入新套件前檢查發布者、Repository 與 Lifecycle Script。
npm install --ignore-scripts 可以在特定盤點環境停用 Lifecycle Script,但部分套件會因此無法建置或運作。它是一項需要測試的控制措施,不是所有專案都能直接套用的開關。
npm audit 會把依賴樹送往設定的 Registry,並比對已知 Security Advisory:
npm audit
只檢查正式環境依賴,可以執行:
npm audit --omit=dev
本次完整掃描回報 7 個受影響套件,其中最高嚴重度為 Critical。這個數字適合當作調查入口,不能直接等同於「產品存在一個可從網路利用的 Critical 漏洞」。
還要確認:
例如 firebase-tools 是 Dev Dependency,不會因為 Vite 打包就自動進入 Browser Bundle;但它會在開發者電腦與部署流程執行。這降低部分 Runtime 攻擊情境,卻不代表可以忽略,因為部署工具通常能讀取專案並操作 Cloud Resource。
npm audit fix 會嘗試在既有版本範圍內安裝修正版:
npm audit fix
加上 --force 後,npm 可能安裝超出目前範圍的 SemVer Major Version。Major Upgrade 可能改變 CLI、設定格式、Node.js 版本需求或程式 API。
因此正式修正流程應是:
npm explain <package> 找到引入路徑。如果暫時無法升級,應記錄原因、適用範圍、補償控制、負責人與重新檢查日期,而不是永久把 Advisory 加入忽略清單。
完全不更新會累積已知弱點;完全自動合併也可能把不相容或遭入侵的版本直接送進正式環境。
較安全的做法是讓 Dependabot 或 Renovate 建立小型 Pull Request,再經過:
版本與來源檢查
│
├── Lockfile Diff Review
├── Build / Test / Audit
├── Release Note Review
└── Staging 驗證
│
└── 合併與部署
更新頻率也要符合修正時限。Critical 且可利用的 Runtime 弱點,不應等待每季例行更新;低風險的 Dev Tool 更新則可以配合固定維護窗口。
即使依賴版本安全,建置流程仍可能被竄改。
SLSA(Supply-chain Levels for Software Artifacts)強調 Provenance:Artifact 應附帶可驗證資訊,說明由哪份 Source、哪個 Build Process 與哪些參數產生。
在一般 Web 專案中,可以先做到:
SBOM 是「用了什麼」,Provenance 是「怎麼建出來」,Signature 是「誰為這份內容背書」。三者回答不同問題。
Agent 不應只執行 Scanner,再把每一列原樣貼進報告。
它至少要收集:
可操作的 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 能不能在使用者回報前告訴我們發生了什麼?