OpenSSF Scorecard / ビルドの守り方

Dangerous-Workflow
CI に危険な書き方が無いか

GitHub Actions に、外部から任意のコードを実行させられる書き方が無いかを見るチェックです。この項目だけは 0 と 10 の意味が重く、0 のときは具体的な問題箇所が指摘されています。

何を見るチェックか

Scorecard は、ワークフローファイルの中に既知の危険な書き方が無いかを探します。

代表的なのは次の2つです。

1. pull_request_target と、フォーク側のコードの実行を組み合わせる

on: pull_request_target      # ← 書き込み権限つきのトークンが渡る
jobs:
  build:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}   # ← 他人のコードを取ってくる
      - run: npm install && npm test                        # ← それを実行する

pull_request_target は、フォークからの Pull Request でも 元リポジトリの権限つきトークンでワークフローを動かします。 そこで他人のコードを取得して実行すると、誰でもその権限を使えることになります。

2. 信頼できない入力をスクリプトに埋め込む(スクリプトインジェクション)

- run: echo "${{ github.event.issue.title }}"    # ← Issue のタイトルがそのまま shell に入る

Issue のタイトルやブランチ名は誰でも自由に決められます。 それを run: に直接埋め込むと、任意のコマンドを実行させられます。

見つかると 0 点、見つからなければ 10 点です。中間はありません。

点が低いと何を意味するか

0点は、他の項目の0点とは意味が違います。

多くの Scorecard の項目は「確認できなかった」を示しますが、 この項目の 0 は 「具体的な該当箇所が見つかった」 を意味します。 Scorecard の詳細結果には、ファイル名と行番号まで出ます。

該当した場合、そのリポジトリの CI は、 条件次第で第三者が任意のコードを実行できる状態にあります。 成立すればリポジトリの書き換えやシークレットの取得につながりえます。

ただし、実際に成立するかは権限の設定や実行条件にもよります。 0 だから今まさに悪用されている、という意味ではありません。

誤解しやすい点

「0点だから使えない」と即断しないでください。判断には2つの補足が要ります。

  1. これはそのプロジェクトの CI の問題であって、配布されるコードの問題ではありません。 あなたがそのライブラリをインストールして使うこと自体に、 直接この欠陥が持ち込まれるわけではありません。 影響するのは「そのプロジェクトのリリースが汚染されうるか」という経路です。

  2. pull_request_target を使っていること自体は問題ではありません。 フォークからの Pull Request でラベルを付けたりコメントしたりするために、 正当に使われる書き方です。危険になるのは **「権限つきで動く」×「他人のコードを取得して実行する」**が揃ったときだけです。

一方で、10点でも「CI が守られている」証明にはなりません。 Scorecard が見ているのは既知のパターンだけで、 自作スクリプトの中身までは追いません。

なお、この項目は修正が比較的はっきりしているという特徴があります。 0 のプロジェクトを見つけたら、Issue で知らせるとメンテナーの助けになります。

採用する側が確認すべきこと

  1. Scorecard の詳細結果で、該当ファイルと行を確認する。 この項目は具体的な位置を出すので、実際のワークフローを開いて読めます。

  2. 権限と合わせて読む。Token-Permissions 権限が読み取りのみに絞られていれば、成立しても被害は限られます。

  3. リリース経路を確認する。 リリースが CI から自動で公開されているなら、影響は実配布物まで届きます。 手動でリリースしているなら、経路は1つ挟まります。

  4. 自分のプロジェクトのワークフローも同じ目で見る。 pull_request_target を使っている箇所、 ${{ github.event.* }}run: に埋め込んでいる箇所を探してください。 多くのプロジェクトに1つ2つは見つかります。

メンテナーなら何を改善できるか

pull_request_target の場合

フォーク側のコードをチェックアウトしないのが基本です。 ラベル付けやコメントのように、コードを実行しない処理にだけ使います。

フォークのコードをテストしたい場合は、通常の pull_request イベントを使います。 こちらは権限つきトークンが渡らないので、他人のコードを動かしても被害が広がりません。

スクリプトインジェクションの場合

${{ }}run: に直接埋め込まず、環境変数を経由させます。

# 前
- run: echo "${{ github.event.issue.title }}"

# 後
- env:
    TITLE: ${{ github.event.issue.title }}
  run: echo "$TITLE"

環境変数に入れれば、値がシェルの構文として解釈されません。

あわせて

  • ワークフローの権限を読み取りのみに絞る → Token-Permissions
  • フォークからの Pull Request で走るワークフローに、承認を必須にする (Settings → Actions → Require approval for all external contributors

あわせて読む