Skip to content

feat: add Origin (Cursor) adapter - #133

Merged
Meldiron merged 15 commits into
mainfrom
feat-origin
Aug 19, 2026
Merged

feat: add Origin (Cursor) adapter#133
Meldiron merged 15 commits into
mainfrom
feat-origin

Conversation

@Meldiron

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a new Git adapter for Origin, Cursor's code hosting platform (launched 2026-08-17), plus its test class.

Adapter (src/VCS/Adapter/Git/Origin.php)

  • Auth: EdDSA (Ed25519) app JWT — signed with ext-sodium, since Origin apps use Ed25519 keys — exchanged for a short-lived installation access token (oit_…), cached like the GitHub adapter's tokens.
  • Content writes over Git HTTPS: Origin's partner API has no create-file/branch/tag endpoints (writes travel over Git), so createFile, createBranch and createTag shell out to git with the installation token (Basic auth, username x-access-token), with the token scrubbed from any error output.
  • Check runs: mapped onto Origin's upsert-by-key model while preserving this library's create/update-by-id semantics (stable suite key = run name, unique run key per created run).
  • Webhooks: Origin signs deliveries with its own Ed25519 key (webhook-signature: v1ed,BASE64 over the hex SHA-256 of <webhook-id>.<webhook-timestamp>.<raw body>); validateWebhookEvent() verifies that. getEvents() parses the delivery envelope, maps PR lifecycle event types to GitHub-style actions, and expands multi-ref push deliveries into one event per ref.
  • Full partner API surface beyond the abstract contract: pull request update/merge/listing, PR comments/commits listings, reviews (create/list/update/dismiss), commit listing/files/compare, blob fetch, batch contents fetch, batch check-run upsert, check suite/run listings, app metadata, installation listing/deletion, webhook delivery listing/redelivery, rate limit, mirror sync.
  • Repository deletion uses the Cursor web app's API (POST cursor.com/api/origin/delete-repo) — the partner API has no deletion endpoint. Auth compatibility with app tokens is pending E2E verification.

Not supported by Origin (methods throw or degrade gracefully)

Commit statuses (check runs only), repository visibility flags, archive/presigned downloads, language statistics, user lookup, per-repository webhooks (Origin delivers per app installation), namespace listing.

Test changes

  • tests/VCS/Adapter/OriginTest.php: full Base suite wiring plus Origin-specific tests (Ed25519 webhook validation incl. PEM keys, PR lifecycle action mapping, multi-ref pushes, merge/update/list PRs, reviews, batch contents, batch check runs, rate limit, app/installation metadata). Needs TESTS_ORIGIN_PRIVATE_KEY, TESTS_ORIGIN_APP_IDENTIFIER, TESTS_ORIGIN_INSTALLATION_ID.
  • tests/VCS/Base.php: new capability flags ($supportsRepositoryDeletion, $reportsRepositoryVisibility, $supportsRepositoryArchives) and teardown now goes through discardRepositories() everywhere.
  • src/VCS/Adapter.php: empty JSON POST bodies encode as {} instead of [] (Origin's proto3-JSON endpoints reject bare arrays).

Status

  • PHPStan level 8 clean, Pint clean
  • Credential-free smoke tests (JWT against real OpenSSL Ed25519 keys, webhook verification, event parsing)
  • E2E suite against a live Origin app (pending credentials)

🤖 Generated with Claude Code

Meldiron and others added 6 commits August 18, 2026 14:15
Adds a Git adapter for Origin, Cursor's code hosting platform:

- EdDSA (Ed25519) app JWTs signed with ext-sodium, exchanged for
  short-lived installation access tokens
- Content writes (files, branches, tags) over Git HTTPS, since the
  partner API is read-only for repository contents
- Check runs mapped onto Origin's upsert-by-key model
- Ed25519 webhook signature validation and delivery-envelope parsing,
  including multi-ref push deliveries
- Full partner API surface: pull request update/merge/listing, reviews,
  commit listing/files/compare, batch contents, batch check-run upsert,
  app/installation/webhook-delivery management, rate limit, mirror sync
- Repository deletion via the Cursor web API (not in the partner API)

Base test additions: capability flags for providers without repository
deletion, visibility flags, or archive downloads. Empty JSON POST bodies
now encode as {} so proto3-JSON endpoints accept them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Origin models CI feedback as check runs only, so updateCommitStatus()
upserts a check run keyed on the status context - repeated updates for
one context land on one run - and getCommitStatuses() reads the
commit's check runs back in commit-status shape. Consumers that only
speak commit statuses now work against Origin unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every other adapter reports repositories with a pushed_at timestamp, so
consumers read that name - Appwrite's repository listing does, and was
rendering an unknown date for every Origin repository. Alias Origin's
camelCase pushedAt (falling back to updatedAt/createdAt) on every
repository object the adapter returns, keeping the provider's own
fields intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Origin answers every failure with a Google-RPC body whose message names
the exact rule that refused the request (missing namespace access, plan
eligibility, stale page tokens). Bare status codes hid that, so every
failed-call exception now appends it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/commits/{sha} takes one path segment, so a branch like feature/x read
as extra segments and answered 404. Origin's gateway accepts a
percent-encoded slash, so the commits endpoints (commit, files, check
runs) now fully encode the ref. The git/ref path keeps literal slashes;
its binding spans segments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Meldiron and others added 5 commits August 18, 2026 17:30
Origin renders comment markdown without an image proxy, so images on a
consumer's own host cannot display there; consumers can now ask and
fall back to text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cally

Repository creation is denied to app installations and the partner API
has no repository deletion endpoint, so the shared adapter tests can
neither create nor clean up their fixture repositories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Origin scopes every repository to its workspace, so nothing it hosts is
anonymously reachable. The new Base test proves publicness end to end:
a public repository has to answer an anonymous git ref advertisement,
and a private one has to refuse the same request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Origin E2E suite that needed deletion-less teardown and gated
visibility checks is gone, so the supportsRepositoryDeletion,
reportsRepositoryVisibility and supportsRepositoryArchives test flags
guarded nothing. Base.php now diverges from main only by the anonymous
public-access test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
supportsRepositoryArchives(), supportsCommentImages(),
supportsPublicRepositories(), the anonymous public-access test and the
non-Origin README marks now land through #134; Origin
keeps only its own overrides here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Meldiron
Meldiron marked this pull request as ready for review August 19, 2026 09:24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds an Origin/Cursor Git provider with Ed25519 authentication and webhook verification, normalized repository and pull-request operations, Git-backed content writes, and provider-specific tests.

  • Adds the Origin adapter and its API, webhook, check-run, and Git-write integrations.
  • Registers credential-free Origin tests and adjusts empty JSON request serialization.
  • Documents Origin as a supported provider.

Confidence Score: 3/5

The PR is not safe to merge until createFile validates the destination before any recursive directory creation can follow repository-controlled symlinks.

Recursive mkdir can modify a writable host path through a checked-out symlink before the subsequent confinement check rejects the file operation.

Files Needing Attention: src/VCS/Adapter/Git/Origin.php

Security Review

The attempted checkout-confinement fix still permits recursive directory creation through repository-controlled symlinks before validating the resolved parent, allowing persistent directories to be created outside the temporary checkout.

How this was verified: The checkout materializes repository symlinks, recursive mkdir runs before realpath confinement, and cleanup removes only the checkout.

Important Files Changed

Filename Overview
src/VCS/Adapter/Git/Origin.php Adds the Origin integration, but createFile performs recursive directory creation before validating symlink-resolved confinement.
tests/VCS/Adapter/OriginTest.php Adds runnable credential-free Origin behavior tests while isolating the unsupported live suite.
src/VCS/Adapter.php Serializes empty JSON parameter arrays as an empty object for APIs requiring object-shaped request bodies.
phpunit.xml Registers the Origin test suite, although existing CI suite matrices do not select it.
README.md Lists Origin as a supported VCS adapter.

Fix all with Greploop Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
src/VCS/Adapter/Git/Origin.php:1805
**Symlink followed before confinement**

When a checked-out repository contains a directory symlink to a writable host directory and `createFile()` targets a missing descendant, recursive `mkdir()` follows the symlink before the parent is validated, creating a persistent directory outside the temporary checkout. Cleanup removes only the checkout.

**How this was verified:** The checkout materializes repository symlinks, recursive `mkdir()` runs before `realpath()` confinement, and cleanup removes only the checkout.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (3): Last reviewed commit: "fix: refuse symlinked write targets and ..." | Re-trigger Greptile

Comment thread src/VCS/Adapter/Git/Origin.php Outdated
Comment thread src/VCS/Adapter/Git/Origin.php Outdated
Comment thread tests/VCS/Adapter/OriginTest.php

@Meldiron Meldiron left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

lgtm

Meldiron and others added 2 commits August 19, 2026 11:38
…he end

A caller-supplied path could climb out of the temporary checkout
through ../ segments and write onto the host filesystem before git add
ever saw it; paths are now normalized lexically and rejected once they
escape. Repository listing also stopped after a thousand entries even
with a next-page token in hand, hiding later repositories from lookups;
it now follows the token until the provider stops advancing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Webhook signature verification and delivery parsing never touch the
network, so they run in CI again: Ed25519 validation with rotation and
PEM keys, push and pull request normalization, lifecycle action
mapping, and installation events. Only the live half of the shared
suite stays skipped, since fixture repositories can neither be created
nor deleted through the partner API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/VCS/Adapter/Git/Origin.php
Lexical confinement was not enough once the checkout itself carried
symlinks - a linked directory or file would carry the write outside the
temporary directory - so createFile() now resolves the parent against
the checkout and refuses link targets, validating the path before any
network call.

createRepository() and deleteRepository() no longer pretend: creation
is denied to app installations wholesale and the partner API has no
deletion endpoint, so both report themselves unsupported, matching the
new capability methods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/VCS/Adapter/Git/Origin.php
@Meldiron
Meldiron merged commit 780f3c8 into main Aug 19, 2026
18 of 21 checks passed
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