workflowは、権限を持ったprogramとして読む

GitHub ActionsのYAMLは設定fileに見えるが、token、secret、artifact、release権限を持つcommandを実行するprogramだ。pull_requestのtitleやbranch名のような外部入力をshellへ直接展開すればcode injectionが起きる。第三者Actionの参照がtagだけなら、参照先が変わるsupply-chain riskもある。

zizmorはGitHub ActionsのworkflowとAction定義に特化したstatic analysis toolだ。general-purpose YAML linterでは見つけにくい、template injection、過剰permission、credential persistence、unpinned usesなどをruleとして検出する。local fileをofflineで検査でき、online modeではGitHub APIから追加情報も取得する。

検査対象はsyntaxではなく、実行時のtrust boundary

たとえば${{ github.event.issue.title }}run:へ直接書くと、GitHubがYAMLを展開したあとshellが実行する。issue titleへquoteやcommand substitutionを混ぜられると、作者の想定と違うcommandになる可能性がある。zizmorのtemplate-injectionは、この境界を追跡する。

uses: actions/checkout@v4は読みやすいが、tagが指すcommitは理論上変更できる。unpinned-usesは既定で、GitHub公式を含むすべてのActionへfull SHA pinを要求する。厳しすぎる場合はpolicyを設定できるが、例外を入れる前に更新方法と責任者を決める。

代表rule検出するrisk主な修正
template-injection外部入力をshell等へ直接展開envへ渡し、shell側で変数展開
unpinned-usesAction参照先の変更full commit SHAへ固定
artipackedcheckout credentialがartifact等へ混入persist-credentials: false
excessive-permissions不要に強いGITHUB_TOKENjob単位で最小permission
dangerous-triggersfork由来codeと強権限triggerの組み合わせeventとcheckout対象を分離

危険なsampleから、offlineで6件を検出した

2026年9月17日、macOS x86_64の一時directoryでzizmor 1.30.1をuvxから実行した。sample workflowにはpermissions: write-allactions/checkout@v4、issue titleのrun:直接展開を意図的に入れた。tokenを渡さずzizmor --offline .を実行した。

結果は6 findingsで、3件は表示上suppressed、unsafe fix候補は2件だった。画面にはartipackedがmedium、template-injectionunpinned-usesがhighとして表示され、processはexit code 14で終了した。非0終了するためCI gateに使えるが、既存workflowへ初導入すると大量検出で止まる可能性がある。baselineと修正順を先に決める。

実測項目結果補足
zizmor1.30.1uvxの隔離cacheから実行
modeoffline・tokenなしlocal filesだけを検査
findings6件3 suppressed、2 unsafe fix候補
visible severitymedium 1 / high 2artipacked、template injection、unpinned
exit14findingありとしてCIで検出可能

最初はtokenなしのlocal auditから始める

local repositoryは、zizmorへdirectoryまたはfileを渡せば検査できる。tokenがない場合はofflineが基本となり、--offlineを明示すればnetwork前提を避けられる。まず読み取りだけでfindingを一覧化し、何を直すかreviewする。experimentalなfix機能を最初から全fileへ適用しない。

remote repositoryを直接検査するmodeもあるが、再現性が必要ならcommit SHAを指定する。branch名だけでは検査中に対象が動く。GitHub tokenを渡す場合はread-onlyの最小scopeにし、command historyやCI logへ露出しない。

local workflowをoffline検査
uvx zizmor --version
uvx zizmor --offline .

# machine-readable output
uvx zizmor --offline --format json .
uvx zizmor --offline --format sarif .github/workflows

template injectionは、envを境界にする

GitHub expressionをrun:のscript本文へ直接展開せず、stepのenvへ渡す。scriptでは${ISSUE_TITLE}のようにshell variableとして参照する。これにより、値はYAML展開でcodeへ埋め込まれず、shellの通常のquoting ruleでdataとして扱える。PowerShellではsyntaxが異なるため、必要ならshellを明示する。

envへ置けばどんな使い方も安全になるわけではない。後でevalへ渡す、quoteせずcommand引数を組み立てる、file pathとして無検証で使う場合は別のinjectionが起こる。入力の用途に合わせてvalidationとquotingを行う。

issue titleをscriptへ直接展開しない
- name: Check issue title
  shell: bash
  env:
    ISSUE_TITLE: ${{ github.event.issue.title }}
  run: |
    if [[ ! "${ISSUE_TITLE}" =~ ^.*:\ .*$ ]]; then
      echo "Bad issue title"
      exit 1
    fi

SHA pinは、更新automationとセットにする

full SHAへ固定すると、tagの移動による意図しないcode変更を防げる。しかし固定したままではsecurity updateも入らない。DependabotやRenovateで新しいSHAをPRにし、release noteとpermission差分をreviewする運用が必要だ。commentへ元tagを残すと、人がversionを読みやすい。

zizmor 1.20.0以降の既定policyは、actions/*を含むすべてのActionへhash pinを要求する。組織policyで公式Actionだけtagを許すこともできるが、例外はzizmor設定へ明示し、口頭ルールにしない。

Action参照とcredentialを明示する
- uses: actions/checkout@<40-character-commit-sha> # v4
  with:
    persist-credentials: false

permissions:
  contents: read

CI導入は、既存findingと新規findingを分ける

成熟したrepositoryでは、初回auditのfindingを一日で全修正できないことがある。severityとexploitabilityで優先順位を付け、template injectionや危険なtriggerを先に直す。例外を設定する場合は、理由、対象file、期限、ownerを残す。単にCIをgreenにするためのglobal ignoreは避ける。

pull requestではGitHub annotationやSARIFを使うと、該当行へfindingを出せる。CI Action自体もSHA pinし、zizmor versionを固定する。offlineで十分なruleと、online metadataが必要なruleを分けると、fork PRへtokenを渡さずに済む。

static analysisの限界を理解して使う

zizmorはworkflowの静的な構造を検査する。called workflowの実行時data、外部scriptの中身、自前Actionのbinary、組織secretの設定、GitHub側permissionまですべて保証するものではない。findingが0でも安全証明にはならない。

それでも、Actions固有の危険なpatternをreviewerの記憶だけに頼らず検出できる価値は大きい。導入の完了条件はtoolを置くことではなく、high severityを修正し、例外policyをdocument化し、新しいworkflowで同じriskが増えないことだ。

  • tokenなし・offlineで現状を読むところから始める
  • high severityと外部入力のinjectionを優先する
  • SHA pinとdependency update automationを組み合わせる
  • fixはdiffをreviewしてから採用する
  • static analysis、GitHub設定、外部script reviewを併用する