ci-cd-security について
CI/CD Security Scannerは、GitHub Actionsのワークフロー YAMLを確認するエージェントスキルです。ツールをインストール・実行せずに定義済みルールを適用し、深刻度と具体的な修正案を示します。
主な機能
- 01ワークフロー YAMLの確認
- 02トリガーと権限の検査
- 03アクションの固定方法のリスク検出
- 04シェルへのテンプレートインジェクション検出
- 05キャッシュとアーティファクト処理の検査
- 06具体的な書き換え案の提示
活用シーン
- 01マージ前のワークフロー確認
- 02リリースワークフローの監査
- 03GitHub Actionsのセキュリティ問題の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ superagent-ai/ skills --skill ci-cd-securityインストール後、Claude Code で/ci-cd-securityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/checklist.mdpatterns.mdtriggers.md詳細
CI/CD セキュリティスキャナー
このスキルは、モデルをワークフロー YAML スキャナーとして機能させます。ファイルを読み込み、検出ルールを順に確認し、重大度と具体的な書き換え案を添えて検出結果を報告します。インストールするツールも、実行するコマンドもありません。分析は、モデルが YAML を読むことで行います。
このルールは、Astral、OpenSSF、GitHub Security Lab、Chainguard、および zizmor の監査セットから得られた、現在の合意事項を反映しています。ツールを実行しなくても、これらのツールと同じパターンを検出することが目標です。
基本的な考え方
すべてのワークフローは、特権あり/特権なしと信頼できるコード/信頼できないコードを組み合わせた 2×2 の枠組みで捉えられます。侵害が起きるのは、特権を持つワークフローが信頼できないコードを実行するセルです。以下のルールは、ワークフローがこのセルに該当してしまうケースを検出する方法です。
- 特権あり = シークレット、書き込み権限、または機密性の高い成果物(リリース、デプロイ、コメント、ラベル)を扱います。
- 信頼できないコード = フォークからの PR 作成者が影響を与えられるものすべてです。PR のソースコード、PR のタイトル、PR の本文、コミットメッセージ、ブランチ名、ワークフローが読み込むファイル、キャッシュ、別の信頼できないワークフローが生成した成果物などが含まれます。
値が信頼できるかどうか判断できない場合は、信頼できないものとして扱ってください。誤検出の代償はコードレビューでの指摘ですが、見逃しの代償はサプライチェーンの侵害です。
スキャン手順
ユーザーが提示した各ワークフローファイルについて、以下のパスを順番に確認してください。各パスは、それぞれ異なる攻撃の種類に対応しています。
パス 1:危険なトリガー
on: ブロックを確認します。次の項目はすぐに警告します。
pull_request_target— 明確な根拠がない限り P0 です。シークレットと書き込み権限を持って実行され、フォークからの PR でトリガーできます。典型的な pwn-request 攻撃ベクトルです。head をcheckoutしていなくても、PR のタイトル、ブランチ名、コミットメッセージに攻撃者の入力が含まれ、展開される可能性があります。workflow_run— P0 です。pull_requestワークフローの成果物やメタデータを介して間接的に起こる点を除けば、問題はpull_request_targetと同じです。issue_comment、issues、pull_request_review、pull_request_review_comment— P1 です。シークレットを使って実行され、コメントできる人なら誰でも到達できます。ユーザーが制御するフィールドをシェルにテンプレート展開しないワークフローでのみ安全です。- 広範なワイルドカードを使った
push(branches: ['*']またはブランチフィルターなし) — P2 です。攻撃者は PR をマージさせた後、追加のブランチを push して、特権を持つワークフローを実行できます。
検出結果ごとに、トリガーを示し、このワークフローの具体的な状況において危険な理由を説明し、書き換え案を提案してください(通常は pull_request への変更、2 つのワークフローへの分割、または「これは Actions ではなく GitHub App が必要です」のいずれかです)。
パス 2:権限
ワークフローレベルとジョブレベルの permissions: ブロックを確認します。
- トップレベルに
permissions:ブロックがない — P1 です。デフォルトのGITHUB_TOKEN権限はリポジトリと組織の設定によって異なり、古いリポジトリではwrite-allの場合があります。「トップレベルにpermissions: {}を追加し、ジョブごとに権限を付与してください」と指摘します。 - どこかに
permissions: write-allがある — P1 です。 - ジョブレベルの
permissions:が、そのジョブに明らかに必要な範囲を超えている — P2 です。たとえば、テストだけを実行するジョブにcontents: writeが設定されている場合です。最小権限を推奨します。 - パス 1 の危険なトリガーと組み合わさっている — 重大度を 1 段階引き上げます。
パス 3:アクションのピン留め
すべての uses: 行を確認します。
- タグにピン留め(
uses: actions/checkout@v4、@v4.1.1)— P1。タグは変更可能です。アクションのリポジトリを侵害した攻撃者は、タグを強制プッシュできます。 - ブランチにピン留め(
uses: actions/checkout@main)— P0。タグへのピン留めより危険です。そのブランチへのコミットがすぐに反映されます。 - SHA にピン留めされているが、バージョンコメントがない — P3 のスタイル上の指摘です。ピン留めの更新をレビューするときに分かりやすいよう、
uses: owner/action@<sha> # v4.1.1の形式を推奨します。 - 通常とは異なる SHA にピン留め(サードパーティ製アクション、不審な所有者、作成されたばかりのリポジトリ)— 手動での検証を促します。GitHub API がなければ偽装コミットかどうかを確認できませんが、注意点として記載する価値があります。
タグ/ブランチにピン留めされている場合は、必ず、意図したバージョンの 40 文字のコミット SHA 全体と # vX.Y.Z コメントに置き換えます。
パス 4:シェルインジェクション(テンプレートインジェクション)
すべての 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.emailworkflow_dispatchからのinputs.*のうち、ワークフローが特権コンテキストで実行される場合actions/github-script、ダウンロードしたアーティファクト、外部 API レスポンスに由来するすべてのフィールド
検出ルール: run: ブロックのスクリプト本体に ${{ github.event.* }} または ${{ github.head_ref }} が直接含まれている場合、P0 のテンプレートインジェクションです。修正方法は常に次のとおりです。
# vulnerable
- run: echo "Branch is ${{ github.head_ref }}"
# safe
- 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:信頼できないチェックアウト
すべての actions/checkout ステップを確認します。
pull_request_targetまたはworkflow_runでトリガーされるワークフロー内のref: ${{ github.event.pull_request.head.sha }}(またはhead.ref)— P0。これは、特権コンテキストでフォーク作成者のコードを実行する、典型的な pwn-request です。persist-credentials: true(デフォルト)が、プッシュバックを必要としないワークフローで設定されている — P2。埋め込みトークンを明示的に必要とするワークフローでない限り、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で使用されるアーティファクトの内容 — 生成元のワークフローが信頼できないコード(例:フォークからのpull_request)で実行される場合は P0 です。攻撃者はアーティファクトに任意の内容を入れられます。 - 厳格な検証を推奨します:アーティファクトが PR 番号を想定している場合は、数字以外をすべて拒否してください。構造化ファイルの場合は、スキーマを解析して検証してください。
パス 8:リリース固有の強化
ワークフローがリリース/公開ワークフローに見える場合(npm、PyPI、crates.io、Docker レジストリへの公開、GitHub リリースの作成、タグのプッシュなど):
- 公開ジョブに
environment:が宣言されていない — P1 です。リリース用認証情報はリポジトリ/組織のシークレットではなく、デプロイ環境にスコープを限定してください。 - OIDC/Trusted Publishing ではなく、長期間有効なレジストリトークン(
secrets.NPM_TOKEN、secrets.PYPI_TOKEN)を使用している — P2 です。該当するレジストリでは OIDC の利用を推奨してください。 - アテステーションを生成していない(
actions/attest-build-provenance、npm の--provenance、PyPI の PEP 740)— 脆弱性ではなく、P3 の強化推奨事項です。 - リリース経路のどこかでキャッシュを使用している — P0 です。パス 6 を参照してください。
パス 9:セルフホストランナー
ワークフローで、GitHub ホストランナー(ubuntu-*、windows-*、macos-*)以外の runs-on: が使用されている場合:
- フォークからの 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 を貼り付けた場合は、「the workflow」として言及し、スニペット内の行番号を使用してください。
重大度の基準
- P0 — 追加の手順なしで、今すぐ悪用可能です。フォーク PR の作成者や任意の GitHub ユーザーが、シークレット、リポジトリの内容、またはリリースを侵害できます。マージをブロックしてください。
- P1 — 追加の手順が 1 つ必要です(例:別の検出事項との組み合わせ、またはメンテナーのミスが必要)。リリース経路で見つかった場合はマージをブロックしてください。
- P2 — 強化上の不備です。直接悪用はできませんが、将来のバグと組み合わさった場合の被害範囲を広げます。通常のレビューサイクルで修正してください。
- P3 — スタイルまたは一貫性に関する検出事項です。読みやすさのために修正する価値はありますが、セキュリティへの影響はありません。
ワークフローに問題がなさそうな場合
9 つすべてのパスを確認して何も該当しなかった場合:
- その旨を明確に伝えてください。「標準ルールセットに対する検出事項はありません。」
- 確認していない事項を記載してください:組織レベルの設定(デフォルトのトークン権限、ルールセットの適用、2FA)、リポジトリレベルの設定(ブランチ保護、タグ保護、不変リリース)、アクションのソースコード(ピン留めされたアクション自体が実行時に変更可能なバイナリをインストールするかどうか)、依存関係の実行時の動作。
references/checklist.mdの「Per repository」と「Per organization」にある項目を確認するようユーザーに推奨してください。これらの確認にはワークフロー YAML ではなく、GitHub の設定へのアクセスが必要です。
ワークフローのスキャン結果が問題なしでも、セキュリティ体制に問題がないとは限りません。
参照ファイル
references/triggers.md— GitHub Actions のすべてのトリガーに関する詳細な表です。それぞれを危険または安全にする要因と、危険なトリガーが使われる一般的な用途に対する安全なパターンを記載しています。ワークフローで、ユーザーが特に質問しているトリガーが使われている場合や、そのトリガーが危険な理由を一行の要約以上に説明したい場合に参照してください。references/checklist.md— ワークフロー/リポジトリ/組織ごとの項目をまとめたチェックリストです。ユーザーが完全な監査を依頼した場合、多数のリポジトリをスキャンする場合、大規模なトリアージを行う場合に役立ちます。トリアージの優先順位も含まれています。references/patterns.md— 「X を安全に実行したい」という一般的なパターンをまとめています。危険なパターンを特定するだけでなく、置き換える方法をユーザーが尋ねた場合に参照してください。
このスキルで対応しないこと
- zizmor、pinact、その他のツールはインストールしません。スキャンでは、モデルが YAML を読み取り、これらのルールを適用します。
- ユーザーから「実行すべきツールは何ですか」と明示的に尋ねられない限り、ツールのインストールは推奨しません。その場合でも、パターンを直接示してください。このスキルのルールは、そうしたツールに組み込まれている監査そのものです。
pull_request_targetに関する指摘を、緩和策で取り繕うことはしません。ワークフローでこのトリガーが使われており、GitHub App の配下にない場合は、未解決の指摘です。- 何を確認し、何を確認していないかを示さずに、問題ないとユーザーに伝えることはしません。「これは安全ですか」という質問には、「スキャンで確認する内容」と「見つかったこと」を示すのが基本の回答です。