Skip to content

fix(security): pin and verify the drift-check script the moq template fetches and executes - #22

Open
yakimoto wants to merge 1 commit into
mainfrom
fix/moq-drift-template-supply-chain
Open

fix(security): pin and verify the drift-check script the moq template fetches and executes#22
yakimoto wants to merge 1 commit into
mainfrom
fix/moq-drift-template-supply-chain

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

The MoQ draft-drift workflow template fetched a shell script over plain curl from the main branch of another repo, chmod +x'd it, and ran it:

- uses: actions/checkout@v5

- name: Fetch shared drift-check script if not present
  run: |
    if [[ ! -f scripts/check-moq-draft-version.sh ]]; then
      mkdir -p scripts
      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
    fi

- name: Run drift check
  run: bash scripts/check-moq-draft-version.sh --ci

No pin, no checksum, no permissions: block. Anyone who could push to wave-av/wave-moq-edge@main — or anyone who compromised it — got code execution on every PR, every push to main, and every Monday 06:00 cron, in every repo that adopted this template. actions/checkout@v5 is a mutable tag, so that is a second unpinned input on the same job.

The standard already existed in this repo

public-repo-guard.yml, three files over, states it in its own header comment:

Self-contained by design: the gitleaks config and the policy script are VENDORED into the repo alongside this workflow — they are NOT fetched at run time. The gate is therefore fully reviewable, deterministic, and cannot be reprogrammed out-of-band (a push to wave-av/.github must not be able to alter another repo's secret scanner). The gitleaks binary is version-pinned AND SHA-256-verified before it runs.

This template is now held to that same bar rather than a new one.

What changed

  • Vendored-first. If scripts/check-moq-draft-version.sh exists in the adopting repo, it runs and nothing is fetched.
  • The fallback fetch is pinned and verified. URL pinned to commit c47c67c8, content checked against sha256 0d671c34… via sha256sum -c - before chmod. --proto '=https' --tlsv1.2 on the curl.
  • actions/checkout pinned to 93cb6efe (v5.0.1) — the same pin public-repo-guard.yml in this repo already uses.
  • Least privilege: permissions: contents: read, persist-credentials: false, and a concurrency group.
  • A header comment documenting how to refresh the pin, because a pin bumped without its digest is the same hole with extra steps.

Receipt

$ zizmor --persona=auditor workflow-templates/moq-draft-drift.yml

before:  5 findings (2 unsafe fixes): 0 informational, 1 low, 3 medium, 1 high
after:   No findings to report. Good job!

Worth naming: the curl-and-execute is not one of zizmor's audits. The linter reported the unpinned action, the missing permissions and the missing concurrency — it never reported the remote script. The checksum gate closes a hole no tool in the pipeline was looking for.

Content at the pinned commit is byte-identical to main today (2488 bytes, 0d671c34…), so this changes what can happen, not what does.

Fleet sweep

I swept all 138 repos in the org for live copies rather than assuming the template was the only instance. Exactly one exists — wave-moq-edge/.github/workflows/moq-draft-drift.yml, the source repo, where the script is vendored so no fetch occurs. Its unpinned checkout and default permissions are fixed in wave-av/wave-moq-edge#150: 4 zizmor findings → 0.

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 and is left as-is. The header comment now says explicitly not to convert it to pull_request_target, which would hand a fork's code a write-scoped token.


Note

Medium Risk
Changes CI supply-chain and permission boundaries for a fleet-adopted workflow template; behavior for repos with a vendored script stays the same, but unpinned remote script execution is removed.

Overview
Hardens workflow-templates/moq-draft-drift.yml so the MoQ drift gate matches the vendored-first, pinned-and-verified posture documented in public-repo-guard.yml.

Drift script supply chain: When scripts/check-moq-draft-version.sh is not in the repo, the workflow no longer curls main from wave-moq-edge and runs it unchecked. It uses a vendored copy when present; otherwise it fetches a commit-pinned URL, runs sha256sum -c before chmod, and tightens curl with --proto '=https' --tlsv1.2.

Workflow hygiene: actions/checkout moves from the mutable v5 tag to the same commit SHA as public-repo-guard, with persist-credentials: false. The workflow adds permissions: contents: read, a concurrency group, and a header comment on pin refresh and why not to use pull_request_target.

Reviewed by Cursor Bugbot for commit ac0af14. Configure here.


Summary by cubic

Secures the MoQ draft-drift workflow by running a vendored script when present and using a pinned, checksum-verified fallback. Also pins actions/checkout and tightens permissions to close a cross-repo supply-chain risk.

  • Bug Fixes
    • Run vendored scripts/check-moq-draft-version.sh if present; otherwise fetch from wave-av/wave-moq-edge at a specific commit and verify SHA-256 before chmod.
    • Pin actions/checkout to a specific commit and set persist-credentials: false.
    • Add least-privilege permissions: contents: read and a concurrency group.
    • Document how to refresh the pin and note not to switch to pull_request_target.

Written for commit ac0af14. Summary will update on new commits.

Review in cubic

… fetches and executes

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.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 54 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 75b02596-6c81-4582-b105-b19ab31f5ceb

📥 Commits

Reviewing files that changed from the base of the PR and between f7d69b7 and ac0af14.

📒 Files selected for processing (1)
  • workflow-templates/moq-draft-drift.yml

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +60 to +62
# wave-av/wave-moq-edge @ scripts/check-moq-draft-version.sh
SCRIPT_COMMIT: c47c67c81c2b1acef0b54f07a26911ba3316d780
SCRIPT_SHA256: 0d671c3457b6bda5247681f5c12b9472908028b29f5a6f5074549349ab57363a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 README still describes the old unpinned fetch and org-wide single-source pin

workflow-templates/README.md:11-15 states the template "Pulls the canonical drift-check script from wave-av/wave-moq-edge so the IETF-current pin lives in exactly one place across the org." With the fetch now frozen to SCRIPT_COMMIT, repos that do not vendor the script will keep running a snapshot until this template's pin is bumped and copied downstream — the documented "single place, always current" behaviour no longer holds. The README (and possibly the template properties description mentioning "currently draft-17", workflow-templates/moq-draft-drift.properties.json) should be updated to describe the vendored-first + pinned-fallback model and the refresh procedure.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: medium. Approved — Cursor Bugbot passed with no findings needing human review. No reviewers assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@macroscopeapp

macroscopeapp Bot commented Aug 3, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved ac0af14

Security hardening change that pins dependencies to commit SHAs and adds SHA-256 integrity verification for a fetched script. The author owns this workflow template file. The unresolved comment requests only documentation updates (README), not code fixes.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant