kakakakakku blog

Weekly Tech Blog: Keep Learning!

zizmor: GitHub Actions ワークフローのセキュリティ課題を検出しよう

はじめに

zizmor を使うと GitHub Actions ワークフローのセキュリティ課題を検出できる.少し前に「zizmor イイぞ❗️」という話を聞いて試してみたいと思っていたところ👌

docs.zizmor.sh

普段は GitHub Actions の Linter として actionlint を使っている.actionlint と重複する検出項目もあるけど,たとえば「ピン留め」「permissions の制限」などなど zizmor で検出できる項目も多くあって,併用するのも良さそう👌

環境

  • zizmor v1.26.1
  • zizmor-action v0.5.7

zizmor 検出項目

現時点では「計39種類」の検出項目がある.詳しくはドキュメントにまとまっている📝

  • adhoc-packages
  • anonymous-definition
  • archived-uses
  • artipacked
  • bot-conditions
  • cache-poisoning
  • concurrency-limits
  • dangerous-triggers
  • dependabot-cooldown
  • dependabot-execution
  • excessive-permissions
  • orbidden-uses
  • github-app
  • github-env
  • hardcoded-container-credentials
  • mpostor-commit
  • nsecure-commands
  • known-vulnerable-actions
  • misfeature
  • obfuscation
  • overprovisioned-secrets
  • ef-confusion
  • ef-version-mismatch
  • secrets-inherit
  • secrets-outside-env
  • self-hosted-runner
  • stale-action-refs
  • superfluous-actions
  • emplate-injection
  • yposquat-uses
  • undocumented-permissions
  • unpinned-images
  • unpinned-tools
  • unpinned-uses
  • unredacted-secrets
  • unsound-condition
  • unsound-contains
  • unsound-ternary
  • use-trusted-publishing

docs.zizmor.sh

セットアップ

$ brew install zizmor

$ zizmor --version
zizmor 1.26.1

GitHub Actions ワークフローを作る

まずは GitHub Actions ワークフロー .github/workflows/workflow.yml を作る.あくまで検証用のワークフローで,プルリクエストトリガーで実行するとコードをチェックアウトしてプルリクエストのタイトルを出力するのみ❗️

name: Sandbox Workflow

on:
  pull_request:
  workflow_dispatch:

jobs:
  sandbox:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          echo "New Pull Request: ${{ github.event.pull_request.title }}"

zizmor を実行する

さっそく zizmor コマンドを実行する.zizmor . のように実行することもできるけど,今回は .github/workflows/workflow.yml を個別に指定している.

すると4つも検出された🛑

  • artipacked: actions/checkout を実行した後に認証情報を .git/config に残さないようにするべき
  • excessive-permissions: permissions を設定して予期せず GITHUB_TOKEN の権限が広くならないようにするべき
  • template-injection: テンプレート展開でコードインジェクションをされないようにするべき
  • unpinned-uses: アクションをピン留めするべき
$ zizmor .github/workflows/workflow.yml
 INFO zizmor: 🌈 zizmor v1.26.1
 INFO audit: zizmor: 🌈 completed .github/workflows/workflow.yml
warning[artipacked]: credential persistence through GitHub Actions artifacts
  --> .github/workflows/workflow.yml:11:9
   |
11 |       - uses: actions/checkout@v4
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false
   |
   = note: audit confidence → Low
   = note: this finding has an auto-fix

warning[excessive-permissions]: overly broad permissions
  --> .github/workflows/workflow.yml:8:3
   |
 8 | /   sandbox:
 9 | |     runs-on: ubuntu-latest
10 | |     steps:
11 | |       - uses: actions/checkout@v4
12 | |       - run: |
13 | |           echo "New Pull Request: ${{ github.event.pull_request.title }}"
   | |                                                                          ^
   | |                                                                          |
   | |__________________________________________________________________________this job
   |                                                                            default permissions used due to no permissions: block
   |
   = note: audit confidence → Medium

error[template-injection]: code injection via template expansion
  --> .github/workflows/workflow.yml:13:39
   |
12 |       - run: |
   |         --- this run block
13 |           echo "New Pull Request: ${{ github.event.pull_request.title }}"
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code
   |
   = note: audit confidence → High
   = note: this finding has an auto-fix

error[unpinned-uses]: unpinned action reference
  --> .github/workflows/workflow.yml:11:15
   |
11 |       - uses: actions/checkout@v4
   |               ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

7 findings (3 suppressed, 2 unsafe fixes): 0 informational, 0 low, 2 medium, 2 high

今回の検出結果としては High 2件Medium 2件 だった.重大度でフィルタして High のみに絞り込む場合は --min-severity=high オプションを使う.

$ zizmor --min-severity=high .github/workflows/workflow.yml
 INFO zizmor: 🌈 zizmor v1.26.1
 INFO audit: zizmor: 🌈 completed .github/workflows/workflow.yml
error[template-injection]: code injection via template expansion
  --> .github/workflows/workflow.yml:13:39
   |
12 |       - run: |
   |         --- this run block
13 |           echo "New Pull Request: ${{ github.event.pull_request.title }}"
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code
   |
   = note: audit confidence → High
   = note: this finding has an auto-fix

error[unpinned-uses]: unpinned action reference
  --> .github/workflows/workflow.yml:11:15
   |
11 |       - uses: actions/checkout@v4
   |               ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)
   |
   = note: audit confidence → High

7 findings (2 ignored, 3 suppressed, 1 unsafe fixes): 0 informational, 0 low, 0 medium, 2 high

個別に検出項目を ignore する場合は zizmor.yml もしくは zizmor.yaml を作れば OK👌

docs.zizmor.sh

検出項目を修正する

4つの検出項目を修正すると以下のようになる.

自分で直すもヨシ!コーディングエージェントに任せるもヨシ!また自動修復に対応した検出項目であれば zizmor --fix コマンドもしくは zizmor --fix=all コマンドで修正することもできる.

docs.zizmor.sh

name: Sandbox Workflow

on:
  pull_request:
  workflow_dispatch:

permissions: {}

jobs:
  sandbox:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
        with:
          persist-credentials: false
      - run: |
          echo "New Pull Request: ${GITHUB_EVENT_PULL_REQUEST_TITLE}"
        env:
          GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}

もう一度実行すると検出項目が0件になった🎉

$ zizmor .github/workflows/workflow.yml
 INFO zizmor: 🌈 zizmor v1.26.1
 INFO audit: zizmor: 🌈 completed .github/workflows/workflow.yml
No findings to report. Good job! (2 suppressed)

--persona オプションを使う

検出項目が0件になったものの,まだ 2 suppressed という表示が残っている.

zizmor には「ペルソナ」という概念があって,探索する厳格さを指定することができる.現時点では3種類あって,pedantic にするとセキュリティ以外の検出項目も含めて厳格に探索をする.また auditor にすると誤検出 (false positive) の可能性も許容しつつ広く探索をするようだった.詳しくはドキュメント参照で!

  • regular(デフォルト)
  • pedantic
  • auditor

実際に --persona=pedantic オプションを使うとさらに追加で2つ検出された.

  • anonymous-definition: ジョブレベルで name: を設定するべき
  • concurrency-limits: ワークフローの同時実行数を制限するべき
$ zizmor --persona=pedantic .github/workflows/workflow.yml
 INFO zizmor: 🌈 zizmor v1.26.1
 INFO audit: zizmor: 🌈 completed .github/workflows/workflow.yml
info[anonymous-definition]: workflow or action definition without a name
  --> .github/workflows/workflow.yml:10:3
   |
10 |   sandbox:
   |   ^^^^^^^ this job
   |
   = note: audit confidence → High
   = tip: use 'name: ...' to give this job a name

help[concurrency-limits]: insufficient job-level concurrency limits
  --> .github/workflows/workflow.yml:3:1
   |
 3 | / on:
 4 | |   pull_request:
 5 | |   workflow_dispatch:
   | |____________________^ workflow is missing concurrency setting
...
10 |     sandbox:
   |     ------- job affected by missing workflow concurrency
   |
   = note: audit confidence → High

2 findings: 1 informational, 1 low, 0 medium, 0 high

zizmor は Dependabot もサポートしている

実は zizmore は GitHub Actions ワークフローだけではなく,Dependabot の設定ファイル .github/dependabot.yml も自動的に収集して検査してくれる.普段 Dependabot も使ってるから助かる.まずは .github/dependabot.yml を作る.

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly

そして同じように zizmor コマンドを実行すると1つ検出された.cooldown 設定は仕事でも基本的に付けるようにしてるから zizmor で自動的に検出できるのは便利👏

  • dependabot-cooldown: cooldown を設定するべき
$ zizmor .github/dependabot.yml
 INFO zizmor: 🌈 zizmor v1.26.1
 INFO audit: zizmor: 🌈 completed .github/dependabot.yml
warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates
 --> .github/dependabot.yml:3:5
  |
3 |   - package-ecosystem: npm
  |     ^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration
  |
  = note: audit confidence → High
  = note: this finding has an auto-fix

1 findings (1 safe fixes): 0 informational, 0 low, 1 medium, 0 high

以下のようにすれば解決❗️

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    cooldown:
      default-days: 7

GitHub Actions に組み込む

zizmor 自体を GitHub Actions で実行すれば GitHub Actions ワークフローの静的解析を自動化できる.zizmor-action を使って簡単に組み込める.

github.com

zizmor を実行する GitHub Actions ワークフロー .github/workflows/zizmor.yml を以下のように実装した.

ポイントは2つあって,まず今回は個人 GitHub アカウントの Private リポジトリで試しているため GitHub Advanced Security が使えず,advanced-security: false オプションを指定した.そして普段は静的解析などの軽量な処理では ubuntu-slim ランナーを使っているけど,ubuntu-slim ランナーには Docker 環境が入ってなく zizmor-action は Docker に依存しているため ubuntu-slim ランナーが使えなかった.

name: zizmor

on:
  push:
    branches: [main]
  pull_request:

permissions: {}

jobs:
  zizmor:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      actions: read
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
        with:
          persist-credentials: false
      - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
        with:
          advanced-security: false

イイ感じに GitHub Actions で zizmor を実行できるようになった❗️

まとめ

GitHub Actions ワークフローのセキュリティ課題を検出できる zizmor を試してみた.GitHub Actions だけではなく Dependabot の検出項目もあってとても良かった❗️さっそく個人でも仕事でも使っていこう \( 'ω')/

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

blog.flatt.tech

関連書籍