[PR #110] [MERGED] ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869) #119

Closed
opened 2026-10-26 22:36:26 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/github-workflows/pull/110
Author: @mattmillerai
Created: 8/3/2026
Status: ✅ Merged
Merged: 8/4/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-5869-pin-cursor-cli


📝 Commits (3)

  • 83902ff ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869)
  • 140c4fd fix(cursor-review): harden + de-duplicate the pinned CLI installer (BE-5869)
  • c98bd09 Merge remote-tracking branch 'origin/main' into matt/be-5869-pin-cursor-cli

📊 Changes

4 files changed (+132 additions, -161 deletions)

View changed files

📝 .github/cursor-review/README.md (+1 -0)
➕ .github/cursor-review/install-cursor-cli.sh (+91 -0)
📝 .github/workflows/cursor-review.yml (+33 -160)
📝 .github/workflows/test-cursor-review-scripts.yml (+7 -1)

📄 Description

ELI-5

Every Cursor review job used to install the Cursor CLI by piping a web page straight into bash — whatever cursor.com/install served at that moment ran on the runner, unverified. This PR downloads a specific, versioned Cursor release file instead, checks its sha256 against a hash written into the workflow, and only then unpacks it. If the file ever comes back different from what we pinned, the job goes red instead of quietly running unknown code.

What changed

Single file: .github/workflows/cursor-review.yml.

  • Two new workflow-level env entries — CURSOR_CLI_VERSION (2026.07.23-e383d2b) and CURSOR_CLI_SHA256 — so the pin is one place, moved only by a reviewed PR (or, later, the scheduled bump workflow from phase 2).
  • All three Install Cursor agent CLI step bodies (preflight, review matrix, consolidate) now download https://downloads.cursor.com/lab/${CURSOR_CLI_VERSION}/linux/x64/agent-cli-package.tar.gz, verify it with sha256sum -c, extract to ~/.local/share/cursor-agent/versions/<version>, symlink ~/.local/bin/cursor-agent, run --version as a liveness assertion, and put ~/.local/bin on $GITHUB_PATH. The vendor installer script never executes.
  • The Log Cursor agent version comment in the review job was rewritten — it used to explain that the install was unpinned; it now states the install is pinned + checksum-verified and that the step exists for run forensics. The step itself is unchanged.

The three step bodies are byte-identical apart from the preflight's existing CURSOR_API_KEY skip guard (verified programmatically, not by eye — see below).

Supersedes #105

#105 is still open at the time of writing. It rewrites these same three step bodies to download-then-execute the installer script. This PR supersedes those blocks entirely — the installer script no longer runs at all, so there is nothing left for #105's download-then-execute hardening to protect. Recommendation: merge this and close #105 (or drop its install-step hunks if it carries anything else). This PR is branched off main, not stacked on #105, per the ticket.

Why cursor.com/install itself is not pinned

That URL is mutable and regenerated on every Cursor release, so pinning its bytes would hard-red the review panel across every consumer repo the next time Cursor ships. Only the immutable, versioned downloads.cursor.com/lab/... artifact is pinned. Old versioned URLs stay retained (spike BE-5864 probed back to 2026-06-04), so a routine Cursor release cannot break this — the pin only moves deliberately.

Verification

  • Digest re-verified live before opening this PR: curl -fsSL https://downloads.cursor.com/lab/2026.07.23-e383d2b/linux/x64/agent-cli-package.tar.gz | sha256sum → 702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4. Matches the pin. Artifact size 82,521,188 bytes (~83 MB), consistent with the --max-time 120 sizing note in the step.
  • actionlint clean on .github/workflows/cursor-review.yml, run with -shellcheck so every run: body is shellchecked too — clean.
  • Three-body byte-identity checked mechanically, not visually: the bodies were extracted from the YAML and compared; preflight-minus-guard == review == consolidate, exactly.
  • Repo suites green (all unaffected by this change, run anyway): cursor-review 40 tests, agents-md-integrity 18, groom 170; check_agents_md.py passes (its one warning — no CODEOWNERS — is pre-existing on main).
  • Archive layout confirmed empirically, since --strip-components=1 and the symlink are the load-bearing lines: the tarball has exactly one top-level entry, dist-package/, so --strip-components=1 puts cursor-agent directly in $dest. dist-package/cursor-agent ships mode 0755.
  • Symlink safety confirmed by reading the shipped launcher, which is the riskiest assumption in the diff: cursor-agent is a 1,074-byte bash launcher that execs a bundled node sibling in its own directory. It resolves that directory with dirname "$(realpath "$0")" under an explicit # handles symlinks comment, so invoking it through ~/.local/bin/cursor-agent finds the bundled node correctly. This is the same versions-dir + symlink layout the vendor installer produces. The --version call in the same step is a belt-and-braces runtime proof before any later step depends on the binary.

Judgment calls

  • Kept the ticket's pin rather than bumping to a newer Cursor release. The ticket permits a bump; 2026.07.23-e383d2b verified clean against its recorded digest today, and shipping the version the spike cross-checked against nixpkgs is the lower-risk option. A bump is a one-line follow-up whenever it's wanted.
  • Did not add the vendor installer's second agent symlink — nothing in this workflow invokes that name, per the ticket.
  • Timeout arithmetic: --max-time 120 with --retry 3 --retry-max-time 300 bounds the download at 300s, comfortably inside preflight's timeout-minutes: 10 (the tightest of the three jobs). The other two jobs are 30 and 15 minutes.

Rollout

No new secrets, inputs, or caller-side changes. Consumers pick this up through the normal bump-cursor-review-callers SHA bump after merge.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/github-workflows/pull/110 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 8/3/2026 **Status:** ✅ Merged **Merged:** 8/4/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-5869-pin-cursor-cli` --- ### 📝 Commits (3) - [`83902ff`](https://github.com/Comfy-Org/github-workflows/commit/83902ffc1db445c3facd1ba234e60ca9be64b607) ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869) - [`140c4fd`](https://github.com/Comfy-Org/github-workflows/commit/140c4fdf53308c3401ebb041c43974fe4590e87d) fix(cursor-review): harden + de-duplicate the pinned CLI installer (BE-5869) - [`c98bd09`](https://github.com/Comfy-Org/github-workflows/commit/c98bd0962d25875127c14fce2017a53e616c7442) Merge remote-tracking branch 'origin/main' into matt/be-5869-pin-cursor-cli ### 📊 Changes **4 files changed** (+132 additions, -161 deletions) <details> <summary>View changed files</summary> 📝 `.github/cursor-review/README.md` (+1 -0) ➕ `.github/cursor-review/install-cursor-cli.sh` (+91 -0) 📝 `.github/workflows/cursor-review.yml` (+33 -160) 📝 `.github/workflows/test-cursor-review-scripts.yml` (+7 -1) </details> ### 📄 Description ## ELI-5 Every Cursor review job used to install the Cursor CLI by piping a web page straight into `bash` — whatever `cursor.com/install` served at that moment ran on the runner, unverified. This PR downloads a specific, versioned Cursor release file instead, checks its sha256 against a hash written into the workflow, and only then unpacks it. If the file ever comes back different from what we pinned, the job goes red instead of quietly running unknown code. ## What changed Single file: `.github/workflows/cursor-review.yml`. - Two new workflow-level `env` entries — `CURSOR_CLI_VERSION` (`2026.07.23-e383d2b`) and `CURSOR_CLI_SHA256` — so the pin is one place, moved only by a reviewed PR (or, later, the scheduled bump workflow from phase 2). - All three `Install Cursor agent CLI` step bodies (preflight, review matrix, consolidate) now download `https://downloads.cursor.com/lab/${CURSOR_CLI_VERSION}/linux/x64/agent-cli-package.tar.gz`, verify it with `sha256sum -c`, extract to `~/.local/share/cursor-agent/versions/<version>`, symlink `~/.local/bin/cursor-agent`, run `--version` as a liveness assertion, and put `~/.local/bin` on `$GITHUB_PATH`. The vendor installer script never executes. - The `Log Cursor agent version` comment in the review job was rewritten — it used to explain that the install was unpinned; it now states the install is pinned + checksum-verified and that the step exists for run forensics. The step itself is unchanged. The three step bodies are byte-identical apart from the preflight's existing `CURSOR_API_KEY` skip guard (verified programmatically, not by eye — see below). ## Supersedes #105 **#105 is still open at the time of writing.** It rewrites these same three step bodies to download-then-execute the installer script. This PR **supersedes those blocks entirely** — the installer script no longer runs at all, so there is nothing left for #105's download-then-execute hardening to protect. Recommendation: merge this and close #105 (or drop its install-step hunks if it carries anything else). This PR is branched off `main`, not stacked on #105, per the ticket. ## Why `cursor.com/install` itself is not pinned That URL is mutable and regenerated on every Cursor release, so pinning its bytes would hard-red the review panel across every consumer repo the next time Cursor ships. Only the immutable, versioned `downloads.cursor.com/lab/...` artifact is pinned. Old versioned URLs stay retained (spike BE-5864 probed back to 2026-06-04), so a routine Cursor release cannot break this — the pin only moves deliberately. ## Verification - **Digest re-verified live before opening this PR:** `curl -fsSL https://downloads.cursor.com/lab/2026.07.23-e383d2b/linux/x64/agent-cli-package.tar.gz | sha256sum` → `702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4`. Matches the pin. Artifact size 82,521,188 bytes (~83 MB), consistent with the `--max-time 120` sizing note in the step. - **`actionlint` clean** on `.github/workflows/cursor-review.yml`, run with `-shellcheck` so every `run:` body is shellchecked too — clean. - **Three-body byte-identity checked mechanically**, not visually: the bodies were extracted from the YAML and compared; preflight-minus-guard == review == consolidate, exactly. - **Repo suites green** (all unaffected by this change, run anyway): cursor-review 40 tests, agents-md-integrity 18, groom 170; `check_agents_md.py` passes (its one warning — no CODEOWNERS — is pre-existing on `main`). - **Archive layout confirmed empirically**, since `--strip-components=1` and the symlink are the load-bearing lines: the tarball has exactly one top-level entry, `dist-package/`, so `--strip-components=1` puts `cursor-agent` directly in `$dest`. `dist-package/cursor-agent` ships mode `0755`. - **Symlink safety confirmed by reading the shipped launcher**, which is the riskiest assumption in the diff: `cursor-agent` is a 1,074-byte bash launcher that `exec`s a bundled `node` sibling in its own directory. It resolves that directory with `dirname "$(realpath "$0")"` under an explicit `# handles symlinks` comment, so invoking it through `~/.local/bin/cursor-agent` finds the bundled node correctly. This is the same versions-dir + symlink layout the vendor installer produces. The `--version` call in the same step is a belt-and-braces runtime proof before any later step depends on the binary. ## Judgment calls - **Kept the ticket's pin rather than bumping to a newer Cursor release.** The ticket permits a bump; `2026.07.23-e383d2b` verified clean against its recorded digest today, and shipping the version the spike cross-checked against nixpkgs is the lower-risk option. A bump is a one-line follow-up whenever it's wanted. - **Did not add the vendor installer's second `agent` symlink** — nothing in this workflow invokes that name, per the ticket. - **Timeout arithmetic:** `--max-time 120` with `--retry 3 --retry-max-time 300` bounds the download at 300s, comfortably inside preflight's `timeout-minutes: 10` (the tightest of the three jobs). The other two jobs are 30 and 15 minutes. ## Rollout No new secrets, inputs, or caller-side changes. Consumers pick this up through the normal `bump-cursor-review-callers` SHA bump after merge. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:36:26 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/github-workflows#119
No description provided.