GitHub Actions cache-mode 怎麼設才安全?restore-keys 仍不是可信來源
GitHub Actions 的 cache 很適合存 package manager 已下載的依賴;它不適合存 token、.env、部署憑證或任何一旦被讀到就有風險的內容。原因不是 cache 一定會外洩,而是能還原 cache 的 workflow 必須把它當成外部輸入,而不是你剛剛在同一個 job 寫入的私有檔案。
直接答案是:cache 只存可重新下載或重新產生的資料;低信任事件通常使用 cache-mode: read,trusted push 才負責寫入;restore-keys 只當效能 fallback,不能成為信任邊界。
先理解 cache-mode 的四種權限
cache-mode 把 restore 和 save 拆成四種可 review 的模式:
| mode | 可以 restore | 可以 save | 適合情境 |
|---|---|---|---|
read | 可以 | 不可以 | fork PR、外部貢獻或只需加速安裝 |
write | 可以 | 可以 | trusted push CI,既還原又更新快取 |
write-only | 不可以 | 可以 | 只建立新快取,不使用既有內容 |
none | 不可以 | 不可以 | 明確不讓該 workflow 接觸 Actions cache |
未設定時,GitHub 會依事件信任層級選預設:低信任事件傾向 read,trusted event 才能使用 write。既有 workflow 可以保留這個安全預設,但仍要搜尋 repository 內是否有明確覆寫。
workflow-level 可以先設整體基線,job-level 再縮小單一工作:
name: test
on: pull_request: push:
cache-mode: read
jobs: test: cache-mode: read runs-on: ubuntu-latest steps: - run: pnpm install --frozen-lockfile - run: pnpm testjob 的設定優先於 workflow。若 job 呼叫 reusable workflow,caller 也能把被呼叫工作流限制為 read 或 none,避免被呼叫端把 cache 權限向上放大。
先理解命中順序
actions/cache 先找完整的 key,再找 key 的部分匹配;若仍找不到,才依序搜尋 restore-keys。部分匹配有多筆時,會使用最近建立的一筆。
- uses: actions/cache@v4 with: path: ~/.cache/pnpm key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | pnpm-${{ runner.os }}-這段設定的目標是「lockfile 不變時拿到精確 cache;變了時可暫時還原同平台的較舊依賴下載快取」。它不是「永遠拿到正確的 node_modules」。安裝步驟仍要根據 lockfile 驗證依賴,例如使用你專案既有的 frozen lockfile 安裝方式。
不要把 cache 當成 artifact 或 secret store
cache 與 artifact 的使用目的不同:cache 用於可重建、但重新取得成本高的資料;artifact 則是用於保存 workflow 產物、供後續 job 或人工查看。兩者都不適合裝 secrets。
GitHub 文件也明確警告:能讀取 cache 的人可能透過 pull request 存取其內容。因此以下資料不應放進 cache path:
.npmrc、.pypirc、.netrc或含 token 的設定檔。.env、雲端憑證、SSH key 與暫存 deployment credential。- 有可能在下一個高權限 job 被直接執行的產物。
即使低信任 workflow 對 default branch cache 是 read-only,還原後的檔案仍應當成不可信資料。快取隔離能降低寫入風險,不會把快取內容變成可直接執行的安全輸入。
低信任事件採 restore-only 更清楚
fork PR 或其他低信任觸發來源如果只需要加速安裝,使用 restore-only 操作能把意圖寫明:
- name: Restore dependency cache uses: actions/cache/restore@v4 with: path: ~/.cache/pnpm key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | pnpm-${{ runner.os }}-這避免 job 嘗試儲存但因權限被拒時留下令人誤解的 warning。真正負責更新 cache 的 trusted workflow,則可以放在預設分支的 push CI。
若低信任 workflow 明確指定 write 或 write-only,GitHub 會以 warning 提醒可能重新打開 cache poisoning 風險。除非你能證明輸入、權限、執行環境與下游使用者都在同一個信任邊界,否則不要為了 cache hit 改掉安全預設。
當 cache operation 因 mode 被跳過時,GitHub 會留下資訊訊息並繼續 workflow;不要把「沒有 save」誤判成 failure,也不要把 job 綠燈當成 cache 內容已驗證。
快取設定檢查表
- path 只包含 package manager cache,沒有 secrets 或 build deploy output。
- 完整 key 包含 OS 與 lockfile hash;有 matrix 時也加入會改變二進位相容性的版本。
- restore key 由最具體到最寬鬆排序,並接受它只改善速度、不保證內容正確。
- 還原後仍執行依賴安裝與測試;不要跳過 integrity 驗證。
- review workflow-level、job-level、reusable workflow caller 與
ACTIONS_CACHE_MODE的最終值。 - 不可信 PR 與有 secrets 的寫入/部署 workflow 分離。
快取風險和 pull_request_target 的風險都來自信任邊界被混在同一個工作流程;若 workflow 還會 checkout PR 程式碼,請再看 GitHub Actions 的 fork PR checkout 保護。
參考資料:
GitHub Changelog:Control GitHub Actions cache access with cache-mode