
ci-cd-security
掃描 GitHub Actions 工作流程檔案中的安全漏洞,直接讀取 YAML 並回報發現結果 — 無需外部工具、無需安裝、無需執行 Shell。當使用者分享 `.github/workflows/` 檔案、貼上工作流程 YAML、要求 CI/CD 安全審查、提及 `pull_request_target`、`workflow_run`、Action 固定版本、`GITHUB_TOKEN` 權限、pwn 請求、模板注入、快取中毒、機密外洩、供應鏈風險或任何 GitHub Actions 強化主題時,請使用此技能。當使用者正在強化開源儲存庫、進行 CI/CD 紅隊評估、評估供應鏈掃描目標,或公開撰寫 CI/CD 安全相關文章時,也請觸發。傾向於觸發此技能而非憑記憶回答 — CI/CD 安全預設值幾乎到處都是錯的,而且規則違反直覺。
掃描 GitHub Actions 工作流程檔案中的安全漏洞,直接讀取 YAML 並回報發現結果 — 無需外部工具、無需安裝、無需執行 Shell。當使用者分享 `.github/workflows/` 檔案、貼上工作流程 YAML、要求 CI/CD 安全審查、提及 `pull_request_target`、`workflow_run`、Action 固定版本、`GITHUB_TOKEN` 權限、pwn 請求、模板注入、快取中毒、機密外洩、供應鏈風險或任何 GitHub Actions 強化主題時,請使用此技能。當使用者正在強化開源儲存庫、進行 CI/CD 紅隊評估、評估供應鏈掃描目標,或公開撰寫 CI/CD 安全相關文章時,也請觸發。傾向於觸發此技能而非憑記憶回答 — CI/CD 安全預設值幾乎到處都是錯的,而且規則違反直覺。
CI/CD 安全掃描器
此技能將模型轉變為工作流程 YAML 掃描器。讀取檔案、逐一執行偵測規則、回報發現結果及其嚴重性與具體的改寫建議。無需安裝工具、無需執行指令 — 分析過程就是模型讀取 YAML。
這些規則編碼了來自 Astral、OpenSSF、GitHub Security Lab、Chainguard 以及 zizmor 審計集的當前共識。目標是標記出這些工具會標記的相同模式,而無需實際執行它們。
心智模型
每個工作流程都落在一個 2x2 矩陣中:特權 vs 無特權 與 受信任 vs 不受信任程式碼。危害只發生在一個格子:執行不受信任程式碼的特權工作流程。以下規則是用來偵測工作流程何時落入該格子。
- 特權 = 擁有機密、寫入權限,或產生敏感工件(發行版、部署、留言、標籤)。
- 不受信任程式碼 = Fork PR 作者能影響的任何內容:PR 原始碼、PR 標題、PR 內文、提交訊息、分支名稱、工作流程讀取的檔案、快取、由另一個不受信任工作流程產生的工件。
若不確定某個值是否受信任,請將其視為不受信任。誤報的代價只是一個程式碼審查留言;漏報的代價則是供應鏈遭入侵。
掃描程序
針對使用者提供的每個工作流程檔案,依序執行以下掃描。每個掃描對應一類攻擊。
掃描 1:危險觸發條件
檢查 on: 區塊。立即標記:
pull_request_target— P0,除非有明確理由。使用機密和寫入權限執行,可由 Fork PR 觸發。這是典型的 pwn-request 向量。即使沒有checkouthead,攻擊者輸入仍會出現在 PR 標題、分支名稱、提交訊息中,並被插值。workflow_run— P0。與pull_request_target問題相同,但間接透過鏈結的pull_request工作流程的工件或元資料。issue_comment、issues、pull_request_review、pull_request_review_comment— P1。使用機密執行,任何能留言的人都能觸發。僅當工作流程未將使用者控制的欄位模板插值到 Shell 中時才安全。push搭配寬泛萬用字元(branches: ['*']或無分支過濾)— P2。攻擊者若合併 PR,可透過推送後續分支來觸發特權工作流程。
針對每個發現:指出觸發條件名稱,說明為何在此特定工作流程環境中危險,並提出改寫建議(通常是 pull_request,有時拆分為兩個工作流程,有時「這需要 GitHub App 而非 Actions」)。
掃描 2:權限
檢查工作流程和工作層級的 permissions: 區塊。
- 無頂層
permissions:區塊 — P1。GITHUB_TOKEN的預設權限取決於儲存庫和組織設定;在較舊的儲存庫上可能為write-all。標記為:「在頂層加上permissions: {},按工作授予權限。」 - 任何地方的
permissions: write-all— P1。 - 工作層級
permissions:授予超出工作明確需求的權限 — P2。例如,僅執行測試的工作卻有contents: write。建議最小權限原則。 - 與掃描 1 的危險觸發條件結合 — 嚴重性提升一級。
掃描 3:Action 固定版本
檢查每個 uses: 行。
- 固定到標籤(
uses: actions/checkout@v4、@v4.1.1)— P1。標籤是可變的;攻擊者若入侵 Action 儲存庫,可強制推送標籤。 - 固定到分支(
uses: actions/checkout@main)— P0。比標籤固定更糟;任何提交到該分支的內容都會立即流入。 - 固定到 SHA 但無版本註解 — P3 風格發現。建議格式
uses: owner/action@<sha> # v4.1.1,以便固定版本更新的審查保持可讀。 - 固定到看起來不尋常的 SHA(第三方 Action、可疑擁有者、近期建立的儲存庫)— 標記為需手動驗證;若無 GitHub API 無法確認冒名提交狀態,但值得注意。
標籤/分支固定的改寫方式一律為:替換為他們意圖版本的完整 40 字元提交 SHA,並加上 # vX.Y.Z 註解。
掃描 4:Shell 注入(模板注入)
針對每個 run: 區塊,掃描 ${{ ... }} 替換。
常數或非攻擊者控制的值沒問題(例如 ${{ matrix.os }}、${{ secrets.MY_TOKEN }},但即使在某些環境中也有風險)。危險欄位包括:
github.event.pull_request.title、body、head.ref、head.sha、head.labelgithub.event.issue.title、bodygithub.event.comment.body、user.logingithub.event.review.bodygithub.head_refgithub.event.workflow_run.head_branch、head_commit.messagegithub.event.commits.*.message、author.name、author.email- 來自
workflow_dispatch的任何inputs.*(若工作流程在特權環境中執行) - 任何最終來自
actions/github-script、下載的工件或外部 API 回應的欄位
偵測規則: 若 run: 區塊直接在腳本主體中包含 ${{ github.event.* }} 或 ${{ github.head_ref }},則為 P0 模板注入。修正方式一律為:
# 有漏洞
- run: echo "Branch is ${{ github.head_ref }}"
# 安全
- env:
BRANCH: ${{ github.head_ref }}
run: echo "Branch is $BRANCH"
同時標記(P1):
echo "VAR=${{ untrusted }}" >> $GITHUB_ENV— 環境檔案注入。攻擊者可透過包含換行符跳出變數。echo "::set-env name=VAR::${{ untrusted }}"— 已棄用的工作流程指令,相同問題。- 將不受信任檔案
cat到$GITHUB_ENV或$GITHUB_OUTPUT的內聯腳本。
掃描 5:不受信任的 Checkout
針對每個 actions/checkout 步驟:
ref: ${{ github.event.pull_request.head.sha }}(或head.ref)位於由pull_request_target或workflow_run觸發的工作流程中 — P0。這是典型的 pwn-request:特權環境執行 Fork 作者程式碼。persist-credentials: true(預設值)用於不需要推回的工作流程 — P2。建議除非工作流程明確需要內嵌 Token,否則使用persist-credentials: false。
掃描 6:特權環境中的快取
針對每個使用 cache: 輸入的步驟(最常見於 actions/setup-node、setup-python、setup-go、setup-java 或直接 actions/cache):
- 在發行版或發布工作流程中快取 — P0。來自預設分支上任何其他工作流程的快取中毒,可將惡意建置輸入流入發行版。Trivy 和 TeamPCP 攻擊都透過此路徑。
- 在處理機密的工作流程中快取 — P1。
- 快取金鑰未限定範圍以防止不受信任的 PR 工作流程寫入與預設分支建置相同的金鑰 — P2。
發行版工作流程的改寫方式:完全移除 cache:,並加上註解說明原因(例如 # Do not cache: see https://github.com/actions/setup-node/issues/1445)。
掃描 7:工件注入
若工作流程從另一個工作流程下載工件(actions/download-artifact、dawidd6/action-download-artifact 等):
- 工件內容未經驗證就用於
run:或$GITHUB_ENV— 若產生工作流程在不受信任程式碼上執行(例如來自 Fork 的pull_request),則為 P0。攻擊者可將任意內容放入工件。 - 建議嚴格驗證:若工件應為 PR 編號,則拒絕任何非數字內容。若為結構化檔案,則解析並驗證結構。
掃描 8:發行版特定強化
若工作流程看起來像發行版/發布工作流程(發布到 npm、PyPI、crates.io、Docker 註冊表;建立 GitHub 發行版;推送標籤):
- 發布工作未宣告
environment:— P1。發行版憑證應限定於部署環境,而非儲存庫/組織機密。 - 使用長期註冊表 Token(
secrets.NPM_TOKEN、secrets.PYPI_TOKEN)而非 OIDC/Trusted Publishing — P2。建議相關註冊表的 OIDC 路徑。 - 未產生證明(
actions/attest-build-provenance、npm 的--provenance、PyPI 的 PEP 740)— P3 強化建議,非漏洞。 - 發行版路徑中任何地方有快取 — P0,請見掃描 6。
掃描 9:自託管執行器
若工作流程使用 runs-on: 搭配非 GitHub 託管執行器(ubuntu-*、windows-*、macos-*):
- 自託管執行器可被 Fork PR 觸及 — P0。自託管執行器在工作之間共享狀態,已導致重大入侵(PyTorch)。標記為需手動審查執行器範圍。
- 此項超出預設威脅模型 — 記錄發現並建議使用者在 GitHub 設定中驗證執行器限制。
發現格式
以以下結構回報每個發現。按嚴重性分組,P0 優先。
[P0] template-injection in .github/workflows/ci.yml:23
Run block interpolates github.event.pull_request.title directly into shell.
An attacker controls the PR title and can execute arbitrary code in the
workflow context, which has access to GITHUB_TOKEN.
Vulnerable:
- run: echo "Title: ${{ github.event.pull_request.title }}"
Fix:
- env:
TITLE: ${{ github.event.pull_request.title }}
run: echo "Title: $TITLE"
若使用者貼上原始 YAML 而無檔名,則稱之為「該工作流程」並使用片段內的行號。
嚴重性等級
- P0 — 可立即利用,無需鏈結。Fork PR 作者或任意 GitHub 使用者可入侵機密、儲存庫內容或發行版。封鎖合併。
- P1 — 需額外一步才能利用(例如需與另一個發現結合,或需維護者失誤)。若在發行版路徑上發現,則封鎖合併。
- P2 — 強化缺口。無法直接利用,但若與未來錯誤結合,可減少爆炸半徑。在正常審查週期中修復。
- P3 — 風格或一致性發現。值得為可讀性修復,無安全影響。
當工作流程看起來沒問題時
完成所有九個掃描後,若無任何觸發:
- 明確說明。「未發現違反標準規則集的問題。」
- 註明 未 檢查的項目:組織層級設定(預設 Token 權限、規則集強制執行、2FA)、儲存庫層級設定(分支保護、標籤保護、不可變發行版)、Action 原始碼(固定的 Action 本身是否在執行時安裝可變二進位檔),以及相依項目的執行時期行為。
- 建議使用者檢查
references/checklist.md中「每個儲存庫」和「每個組織」下的項目 — 這些需要 GitHub 設定存取權限,而非工作流程 YAML。
乾淨的工作流程掃描不代表安全狀態良好。
參考檔案
references/triggers.md— 每個 GitHub Actions 觸發條件的詳細表格,說明每個觸發條件的危險或安全原因,以及人們常用危險觸發條件時的安全模式。當工作流程使用使用者特別詢問的觸發條件,或當您想解釋觸發條件為何危險(超越一行摘要)時,請閱讀此檔案。references/checklist.md— 每個工作流程/每個儲存庫/每個組織的平面檢查清單。當使用者要求完整稽核、掃描多個儲存庫或大規模分類時很有用。包含分類優先順序。references/patterns.md— 常見的「我想安全地做 X」模式。當使用者詢問如何 取代 被標記的危險模式(而非僅識別)時,請閱讀此檔案。
此技能不會做的事
- 不會安裝 zizmor、pinact 或其他工具。掃描是模型讀取 YAML 並套用這些規則。
- 除非使用者明確問「我應該執行什麼工具」,否則不會建議安裝工具。即使如此,直接指向模式 — 此技能中的規則 就是 那些工具編碼的稽核。
- 不會用緩解措施來粉飾
pull_request_target的發現。若工作流程使用該觸發條件且未使用 GitHub App,則為開放性發現。 - 不會告訴使用者一切沒問題而不說明檢查了什麼和未檢查什麼。對「這樣安全嗎」的預設回答是「以下是掃描涵蓋的範圍以及發現的結果。」





