ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869) - #110
ci(cursor-review): install the Cursor CLI from a sha256-pinned versioned artifact (BE-5869)#110mattmillerai wants to merge 2 commits into
Conversation
…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.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Comment |
There was a problem hiding this comment.
🔍 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)
…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.
ELI-5
Every Cursor review job used to install the Cursor CLI by piping a web page straight into
bash— whatevercursor.com/installserved 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.enventries —CURSOR_CLI_VERSION(2026.07.23-e383d2b) andCURSOR_CLI_SHA256— so the pin is one place, moved only by a reviewed PR (or, later, the scheduled bump workflow from phase 2).Install Cursor agent CLIstep bodies (preflight, review matrix, consolidate) now downloadhttps://downloads.cursor.com/lab/${CURSOR_CLI_VERSION}/linux/x64/agent-cli-package.tar.gz, verify it withsha256sum -c, extract to~/.local/share/cursor-agent/versions/<version>, symlink~/.local/bin/cursor-agent, run--versionas a liveness assertion, and put~/.local/binon$GITHUB_PATH. The vendor installer script never executes.Log Cursor agent versioncomment 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_KEYskip 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/installitself is not pinnedThat 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
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 120sizing note in the step.actionlintclean on.github/workflows/cursor-review.yml, run with-shellcheckso everyrun:body is shellchecked too — clean.check_agents_md.pypasses (its one warning — no CODEOWNERS — is pre-existing onmain).--strip-components=1and the symlink are the load-bearing lines: the tarball has exactly one top-level entry,dist-package/, so--strip-components=1putscursor-agentdirectly in$dest.dist-package/cursor-agentships mode0755.cursor-agentis a 1,074-byte bash launcher thatexecs a bundlednodesibling in its own directory. It resolves that directory withdirname "$(realpath "$0")"under an explicit# handles symlinkscomment, so invoking it through~/.local/bin/cursor-agentfinds the bundled node correctly. This is the same versions-dir + symlink layout the vendor installer produces. The--versioncall in the same step is a belt-and-braces runtime proof before any later step depends on the binary.Judgment calls
2026.07.23-e383d2bverified 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.agentsymlink — nothing in this workflow invokes that name, per the ticket.--max-time 120with--retry 3 --retry-max-time 300bounds the download at 300s, comfortably inside preflight'stimeout-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-callersSHA bump after merge.