Skip to content

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

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-5869-pin-cursor-cli
Open

ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869)#110
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-5869-pin-cursor-cli

Conversation

@mattmillerai

Copy link
Copy Markdown
Contributor

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 | sha256sum702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4. 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.

…ned artifact (BE-5869)

Replace the three `curl https://cursor.com/install -fsSL | bash` installs with a
direct download of Cursor's versioned release artifact
(downloads.cursor.com/lab/<version>/linux/x64/agent-cli-package.tar.gz), verified
against a pinned sha256 before anything is extracted or executed. The mutable
vendor installer script no longer runs on the runner at all.

The pin lives in two workflow-level env vars (CURSOR_CLI_VERSION /
CURSOR_CLI_SHA256) so it moves only through a reviewed PR. Old versioned URLs stay
downloadable, so a routine Cursor release can never break the panel fleet-wide —
which is why the artifact is pinned and cursor.com/install deliberately is not.
@mattmillerai mattmillerai added the agent-coded Authored by the agent-work loop label Aug 3, 2026
@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: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: 06c32658-d0b0-411d-a8ed-57e9d816dc84

📥 Commits

Reviewing files that changed from the base of the PR and between bcde90f and 140c4fd.

📒 Files selected for processing (4)
  • .github/cursor-review/README.md
  • .github/cursor-review/install-cursor-cli.sh
  • .github/workflows/cursor-review.yml
  • .github/workflows/test-cursor-review-scripts.yml

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

@mattmillerai
mattmillerai marked this pull request as ready for review August 3, 2026 00:09
@mattmillerai mattmillerai added the cursor-review Multi-model cursor review label Aug 3, 2026

@github-actions github-actions 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.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 9 finding(s).

Severity Count
🟡 Medium 2
🟢 Low 5
⚪ Nit 2

Panel: 7/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.7-code:edge-case (parse_error)

Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
Comment thread .github/workflows/cursor-review.yml Outdated
…E-5869)

Addresses the cursor-review panel findings on the sha256-pin change.

The 22-line install block was pasted verbatim into three jobs, so every fix
below would have had to land identically in all three — the exact drift mode
AGENTS.md warns about. Extract it to .github/cursor-review/install-cursor-cli.sh,
loaded at run time from the pinned assets ref like the rest of the review logic,
and add the missing assets checkout to the preflight job.

Hardening, now applied once:
- Stall detection (--speed-limit/--speed-time) instead of a flat --max-time 120
  wall clock, which would kill a slow-but-healthy ~83 MB transfer at 99%.
  --max-time is now only a 300s backstop; with --retry-max-time 180 the worst
  case (~8 min) still fits preflight's timeout-minutes: 10.
- Drop --retry-all-errors: a 404 from a typo'd or pruned CURSOR_CLI_VERSION is
  permanent, and retrying it turned a clear error into an opaque timeout across
  ~10 concurrent jobs. Verified a bad version now fails in <1s.
- --proto '=https' --proto-redir '=https' so -L can't follow a redirect down to
  plaintext http and force a checksum failure (a pipeline outage).
- --max-filesize bounds what a hostile/malfunctioning CDN can write to the
  runner volume before the digest is ever checked.
- rm -rf $dest before extracting, so the tree that executes is exactly the
  verified archive rather than whatever mkdir -p inherited.
- Fail loudly if the archive gains a top-level member, which
  --strip-components=1 would otherwise drop silently while exiting 0.
- trap 'rm -f "$pkg"' EXIT so the ~83 MB download is cleaned up on the failure
  path too.
- Assert `cursor-agent --version` equals CURSOR_CLI_VERSION instead of printing
  and discarding it, at install and again on the PATH-resolved binary right
  before the review runs — printing it alone would not catch the pinned bits
  being swapped.

Also shellcheck the cursor-review shell scripts in test-cursor-review-scripts.yml
(previously ungated), and fix the BE-5864 typo in the pin comments.

Verified against the real artifact: sha256 matches the pin, the archive is a
single dist-package/ tree so nothing is dropped, and the new curl flag set
downloads it cleanly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded Authored by the agent-work loop cursor-review Multi-model cursor review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants