diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index fb983aa9c..bce8bbfd9 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -182,7 +182,7 @@ dependencies: owner_id: 47606891 repo_id: 331103973 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938': - ref: 'v4.37.9' + ref: 'cdf488f595d80d6e07e03d4674febd5ab45fa938' commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' owner_id: 9919 repo_id: 259445878 @@ -202,7 +202,7 @@ dependencies: owner_id: 1841483 repo_id: 220799100 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': - ref: 'v2.4.4' + ref: '2d1146689b8cda280b9bc96326124645441f03bc' commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' owner_id: 67707773 repo_id: 421101922 diff --git a/.github/workflows/scorecard-reusable.yml b/.github/workflows/scorecard-reusable.yml index c09abc2c6..8750b94ac 100644 --- a/.github/workflows/scorecard-reusable.yml +++ b/.github/workflows/scorecard-reusable.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. name: OSSF Scorecard Reusable Workflow @@ -5,21 +6,22 @@ name: OSSF Scorecard Reusable Workflow on: workflow_call: -# NOTE: This workflow requires security-events: write and id-token: write -# to publish scorecard results. Callers MUST grant these permissions. -# The previous attempt to add only `actions: read` at top-level (0ced540e) failed -# because it exceeded caller grants. The fix is to declare ALL required permissions -# at the top-level so callers know what to grant. +# Callers must grant contents: read, security-events: write and id-token: write. +# Only default-branch publication receives OIDC; PR analysis cannot publish +# unmerged results to the public Scorecard dataset or badge. permissions: contents: read - security-events: write - id-token: write jobs: scorecard: + if: github.event_name != 'pull_request' name: Run Scorecard runs-on: ubuntu-latest timeout-minutes: 15 + permissions: + contents: read + security-events: write + id-token: write steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -34,7 +36,7 @@ jobs: # publish_results feeds the public OpenSSF API and the badge, and is # INDEPENDENT of results_format -- so emitting SARIF does not cost us # the badge. Both outputs are live with this shape. - publish_results: true + publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} # RESTORED 2026-08-25. Commit 5a93d9d5 (PR #393, "Ci/gitleaks self hosted # fix", 2026-06-21) deleted this step and switched results_format to json @@ -54,3 +56,84 @@ jobs: name: scorecard-results path: results.sarif retention-days: 90 + + pull-request: + if: github.event_name == 'pull_request' + name: Run Scorecard PR + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + steps: + - name: Checkout pull request + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + # OpenSSF labels pull_request support experimental. Upload actual results; + # a failed scan or upload must fail the workflow, not become a clean gate. + - name: Analyze pull request + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: false + # scorecard-action forces Local (filesystem) mode on pull_request events, + # so the PR run only ever produces the supply-chain/local category. The + # code-scanning ruleset evaluates Scorecard per category and main's + # scheduled run also carries supply-chain/branch-protection and + # supply-chain/online-scm, so a PR is "expecting 2 results" forever. + # Those two categories are repo-state properties (branch protection, CI, + # code review, maintenance), not properties of a commit, and scorecard + # refuses to compute them for a non-HEAD --commit. So measure them at + # repository HEAD with the pinned scorecard CLI, restricted to exactly the + # repo-level checks, and merge the runs into the same SARIF. The local + # category stays the scorecard-action measurement of the PR tree. + - name: Install scorecard CLI (pinned + checksum-verified) + run: | + set -euo pipefail + SCORECARD_VERSION=5.5.0 + SHA256=83b90a05c1540ef1390db1cd5711e5fd04be9c1d8537fb84d39d02092d6a8dff + cd "$RUNNER_TEMP" + curl -fsSL --proto "=https" --proto-redir "=https" -o scorecard.tar.gz \ + "https://github.com/ossf/scorecard/releases/download/v${SCORECARD_VERSION}/scorecard_${SCORECARD_VERSION}_linux_amd64.tar.gz" + echo "${SHA256} scorecard.tar.gz" | sha256sum -c - + tar -xzf scorecard.tar.gz scorecard + chmod +x scorecard + ./scorecard version + - name: Measure repo-level checks at HEAD + env: + ENABLE_SARIF: "1" + GITHUB_AUTH_TOKEN: ${{ github.token }} + # Policy file from scorecard-action at the same pinned SHA as above. + POLICY_URL: https://raw.githubusercontent.com/ossf/scorecard-action/2d1146689b8cda280b9bc96326124645441f03bc/policies/template.yml + POLICY_SHA256: 6e75dcc0df989d333492c0e1f6e484fc5b4d26ab768333ddcd0e3788539689b0 + run: | + set -euo pipefail + curl -fsSL --proto "=https" --proto-redir "=https" -o "$RUNNER_TEMP/policy.yml" "$POLICY_URL" + echo "${POLICY_SHA256} $RUNNER_TEMP/policy.yml" | sha256sum -c - + "$RUNNER_TEMP/scorecard" \ + --repo="github.com/${GITHUB_REPOSITORY}" \ + --checks=Branch-Protection,CI-Tests,CII-Best-Practices,Code-Review,Contributors,Maintained,Signed-Releases \ + --format=sarif --policy="$RUNNER_TEMP/policy.yml" --show-details \ + > "$RUNNER_TEMP/repo-state.sarif" + # Merge: keep every run from the PR-tree scan, append the repo-level + # runs. Fail if the result is not exactly the three categories. + jq -s '.[0] as $pr | .[1] as $head | $pr | .runs += $head.runs' \ + results.sarif "$RUNNER_TEMP/repo-state.sarif" > merged.sarif + mv merged.sarif results.sarif + jq -r '.runs[].automationDetails.id' results.sarif + n=$(jq '[.runs[].automationDetails.id | split("/")[0:2] | join("/")] | unique | length' results.sarif) + test "$n" -eq 3 || { echo "expected 3 Scorecard categories, got $n" >&2; exit 1; } + - name: Upload SARIF to code scanning + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + sarif_file: results.sarif + - name: Retain scan evidence + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-pr-results + path: results.sarif + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0f4b09e49..a0d96c27f 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,8 +1,12 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. name: Scorecard on: + push: + branches: [main] + pull_request: schedule: - cron: '0 0 * * 0' workflow_dispatch: @@ -15,11 +19,13 @@ concurrency: # id-token: write is the OIDC token Scorecard uses to publish results; # security-events: write uploads the SARIF. permissions: - actions: read contents: read - security-events: write - id-token: write jobs: scorecard: + permissions: + actions: read + contents: read + security-events: write + id-token: write uses: ./.github/workflows/scorecard-reusable.yml