From 201169519cd6b17d7b534cbd3a2eb3ea69b3843c Mon Sep 17 00:00:00 2001 From: Bill Yen Date: Wed, 12 Aug 2026 18:08:19 +0800 Subject: [PATCH 1/4] ci: pin github actions to commit shas Part of SEC-254. Co-Authored-By: Claude Fable 5 --- .github/workflows/actions-pinned-check.yml | 39 ++++++++++++++++++++++ .github/workflows/check-pr-basic.yml | 4 +-- .github/workflows/test-pr-title.yml | 4 +-- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/actions-pinned-check.yml diff --git a/.github/workflows/actions-pinned-check.yml b/.github/workflows/actions-pinned-check.yml new file mode 100644 index 0000000..a5ff688 --- /dev/null +++ b/.github/workflows/actions-pinned-check.yml @@ -0,0 +1,39 @@ +# SEC-254: every `uses:` must reference an immutable 40-char commit SHA. +# Mutable tags/branches do not pin — a compromised maintainer token can repoint +# them at a malicious commit (CVE-2025-30066 hit 23k+ repos via tj-actions). +# Deliberately implemented with shell only: the gate itself must not add a +# third-party action dependency. +name: actions-pinned-check +on: + pull_request: + paths: + - ".github/workflows/**" + - ".github/actions/**" + workflow_dispatch: + +permissions: {} + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - name: Fail on unpinned action refs + run: | + set -euo pipefail + bad=$(grep -rnE '^[[:space:]]*(-[[:space:]]+)?uses:' .github \ + --include='*.yml' --include='*.yaml' \ + | sed 's/#.*//' \ + | grep 'uses:' \ + | grep -vE "uses:[[:space:]]*[\"']?\./" \ + | grep -vE 'docker://[^[:space:]]+@sha256:' \ + | grep -vE "@[0-9a-f]{40}[\"']?[[:space:]]*$" \ + || true) + if [ -n "$bad" ]; then + echo "Unpinned action references (must be 40-char commit SHAs):" + echo "$bad" + exit 1 + fi + echo "All action references are SHA-pinned." diff --git a/.github/workflows/check-pr-basic.yml b/.github/workflows/check-pr-basic.yml index 758c8ab..0ef97af 100644 --- a/.github/workflows/check-pr-basic.yml +++ b/.github/workflows/check-pr-basic.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 - name: Skip check for dependabot if: ${{ github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' }} run: echo "Skipping PR basics check for dependabot." && exit 0 - - uses: thehanimo/pr-title-checker@v1.4.2 + - uses: thehanimo/pr-title-checker@1d8cd483a2b73118406a187f54dca8a9415f1375 # v1.4.2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} configuration_path: pull_request_title.json \ No newline at end of file diff --git a/.github/workflows/test-pr-title.yml b/.github/workflows/test-pr-title.yml index 675df82..e7fc5a9 100644 --- a/.github/workflows/test-pr-title.yml +++ b/.github/workflows/test-pr-title.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: '3.10' From 1ac243a391fd34cf5a286ead580478b041a8f267 Mon Sep 17 00:00:00 2001 From: Bill Yen Date: Wed, 12 Aug 2026 20:20:17 +0800 Subject: [PATCH 2/4] ci: grant contents:read to the actions-pinned-check workflow The gate ran with permissions: {}, so actions/checkout could not fetch the private repo and the job failed before the scan. Part of SEC-254. Co-Authored-By: Claude Fable 5 --- .github/workflows/actions-pinned-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions-pinned-check.yml b/.github/workflows/actions-pinned-check.yml index a5ff688..2ccecce 100644 --- a/.github/workflows/actions-pinned-check.yml +++ b/.github/workflows/actions-pinned-check.yml @@ -11,7 +11,8 @@ on: - ".github/actions/**" workflow_dispatch: -permissions: {} +permissions: + contents: read # actions/checkout needs this to fetch a private repo jobs: check: From 075d6e886469be1bc7ce9257f22cc359cb70bf26 Mon Sep 17 00:00:00 2001 From: Bill Yen Date: Wed, 12 Aug 2026 23:34:08 +0800 Subject: [PATCH 3/4] ci: run actions-pinned-check on opus-runner-standard Use the shared self-hosted standard runner instead of ubuntu-latest. Part of SEC-254. Co-Authored-By: Claude Fable 5 --- .github/workflows/actions-pinned-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions-pinned-check.yml b/.github/workflows/actions-pinned-check.yml index 2ccecce..d7b8ebe 100644 --- a/.github/workflows/actions-pinned-check.yml +++ b/.github/workflows/actions-pinned-check.yml @@ -16,7 +16,7 @@ permissions: jobs: check: - runs-on: ubuntu-latest + runs-on: opus-runner-standard steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: From 3b7c7a67c8468a296243b31d5b2aab60759f3d04 Mon Sep 17 00:00:00 2001 From: Bill Yen Date: Wed, 12 Aug 2026 23:47:42 +0800 Subject: [PATCH 4/4] ci: add yaml document-start to actions-pinned-check yamllint's document-start rule requires a leading ---. Part of SEC-254. Co-Authored-By: Claude Fable 5 --- .github/workflows/actions-pinned-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/actions-pinned-check.yml b/.github/workflows/actions-pinned-check.yml index d7b8ebe..70c3aa5 100644 --- a/.github/workflows/actions-pinned-check.yml +++ b/.github/workflows/actions-pinned-check.yml @@ -1,3 +1,4 @@ +--- # SEC-254: every `uses:` must reference an immutable 40-char commit SHA. # Mutable tags/branches do not pin — a compromised maintainer token can repoint # them at a malicious commit (CVE-2025-30066 hit 23k+ repos via tj-actions).