From ac0af14b24cc7ee8206b12bd770fb9977d32d2c3 Mon Sep 17 00:00:00 2001 From: Jake Fineman Date: Mon, 3 Aug 2026 10:28:26 -0400 Subject: [PATCH] fix(security): pin and verify the drift-check script the moq template fetches and executes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MoQ draft-drift workflow template pulled a shell script from the `main` branch of wave-av/wave-moq-edge over plain `curl`, chmod'd it, and ran it — no pin, no checksum. Anyone who could push to that branch, or anyone who compromised it, got code execution on every PR in every repo that adopted this template. `actions/checkout` was on the mutable `v5` tag and the job carried default token permissions. The fix follows the standard this repo already states in `public-repo-guard.yml` — vendored-first, version-pinned AND SHA-256-verified: * vendored `scripts/check-moq-draft-version.sh` wins and nothing is fetched * the fallback fetch is pinned to commit c47c67c8 and checked against its SHA-256 before chmod * actions/checkout pinned to 93cb6efe (v5.0.1), the same pin this repo already uses * least-privilege `permissions: contents: read`, `persist-credentials: false`, and a concurrency group Receipt — zizmor --persona=auditor on the template: before: 5 findings (1 high, 3 medium, 1 low) after: No findings to report. The curl-and-execute itself is not one of zizmor's audits, so the checksum gate closes a hole the linter never reported. Content at the pinned commit is byte-identical to `main` today (sha256 0d671c34…), so this changes what CAN happen, not what does. Verified: raw.githubusercontent fetch at the pinned SHA returns 2488 bytes matching the recorded digest; `yaml.safe_load` parses; wave-moq-edge is public so the unauthenticated fetch resolves. --- workflow-templates/moq-draft-drift.yml | 54 ++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/workflow-templates/moq-draft-drift.yml b/workflow-templates/moq-draft-drift.yml index 2381f6a..5f3fffa 100644 --- a/workflow-templates/moq-draft-drift.yml +++ b/workflow-templates/moq-draft-drift.yml @@ -1,5 +1,26 @@ name: MoQ draft drift +# Notices when the IETF advances the MoQ Transport draft past the version this repo targets. +# +# Supply-chain posture (matches public-repo-guard.yml, the house standard in this repo): +# * The drift-check script is VENDORED-FIRST. If `scripts/check-moq-draft-version.sh` exists in +# the repo, that file runs and nothing is fetched — the gate is reviewable in-tree. +# * When it is absent, the fallback fetch is pinned to a COMMIT SHA (not `main`) and its SHA-256 is +# verified before the file is made executable. Previously this pulled `main` unpinned with no +# integrity check, so anyone who could push to wave-av/wave-moq-edge — or anyone who compromised +# it — got code execution in every repo that adopted this template, on every PR. +# * `actions/checkout` is pinned to a commit SHA, not a mutable `v5` tag. +# * The job declares least-privilege `contents: read` and does not persist the checkout credential. +# +# Refreshing the pin: bump SCRIPT_COMMIT to the new wave-moq-edge commit and set SCRIPT_SHA256 to +# curl -fsSL https://raw.githubusercontent.com/wave-av/wave-moq-edge//scripts/check-moq-draft-version.sh | shasum -a 256 +# Both values change together, in a reviewed commit. A pin that is bumped without the digest is the +# same hole with extra steps. +# +# Note on `pull_request`: this trigger runs with a read-only token and no secrets, so executing a +# contributor's vendored script is ordinary CI behaviour. Do not convert this to +# `pull_request_target` — that would hand a fork's code a write-scoped token. + on: pull_request: paths: @@ -18,22 +39,39 @@ on: - cron: "0 6 * * 1" workflow_dispatch: +permissions: + contents: read + +concurrency: + group: moq-draft-drift-${{ github.ref }} + cancel-in-progress: true + jobs: drift: name: Check MoQ Transport draft version drift runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false - - name: Fetch shared drift-check script if not present + - name: Fetch shared drift-check script if not vendored + env: + # wave-av/wave-moq-edge @ scripts/check-moq-draft-version.sh + SCRIPT_COMMIT: c47c67c81c2b1acef0b54f07a26911ba3316d780 + SCRIPT_SHA256: 0d671c3457b6bda5247681f5c12b9472908028b29f5a6f5074549349ab57363a run: | - if [[ ! -f scripts/check-moq-draft-version.sh ]]; then - mkdir -p scripts - # Pull canonical script from wave-moq-edge (the reference impl) - curl -fsSL -o scripts/check-moq-draft-version.sh \ - https://raw.githubusercontent.com/wave-av/wave-moq-edge/main/scripts/check-moq-draft-version.sh - chmod +x scripts/check-moq-draft-version.sh + set -euo pipefail + if [[ -f scripts/check-moq-draft-version.sh ]]; then + echo "Using the vendored scripts/check-moq-draft-version.sh — nothing fetched." + exit 0 fi + mkdir -p scripts + curl -fsSL --proto '=https' --tlsv1.2 \ + -o scripts/check-moq-draft-version.sh \ + "https://raw.githubusercontent.com/wave-av/wave-moq-edge/${SCRIPT_COMMIT}/scripts/check-moq-draft-version.sh" + echo "${SCRIPT_SHA256} scripts/check-moq-draft-version.sh" | sha256sum -c - + chmod +x scripts/check-moq-draft-version.sh - name: Run drift check run: bash scripts/check-moq-draft-version.sh --ci