Skip to content

feat(desktop): downloads from a configured public GitHub releases repo - #116

Merged
khaliqgant merged 2 commits into
mainfrom
feat/desktop-github-releases
Nov 11, 2026
Merged

khaliqgant merged 2 commits into
mainfrom
feat/desktop-github-releases

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Why

The dashboard's Desktop downloads card (now on Settings > Workspace) reads only the signed Desktop feed. That feed needs AWS KMS and S3 in production, so a local Kind stack shows "There are no Desktop downloads". The Kind Desktop is published to a public GitHub releases repo and updates from it, so the card can show that instead.

What

SELFHOST_DESKTOP_RELEASES_REPO=<owner>/<repo> (strict ^[A-Za-z0-9-]+/[A-Za-z0-9._-]+$, unset by default) turns on GET /api/v1/desktop/releases for any signed-in member. It returns { source: { kind: "github", repo, releasesUrl }, release }, where release is the newest non-draft release (pre-releases included) in the card's manifest shape. Installer names Desktop-<profile>-<version>-<OS>-<arch>[-probe|-Setup].<ext> map to platform, arch, kind and the macOS os_floor; anything else is skipped.

Bounds

  • Control calls only https://api.github.com/repos/<owner>/<repo>/releases: the release list, and each .sha256 sidecar through its API asset URL. That URL's single redirect may only go to *.githubusercontent.com.
  • No token. The list is capped at 2 MiB, a sidecar at 1 KiB, and the whole read at 8 s.
  • Results are cached for 5 minutes, failures included. Any upstream failure returns release: null, never a 5xx.

Integrity

  • An asset is served only with a matching sidecar from the same release (digest, plus the file name when present).
  • If GitHub reports its own sha256: digest for the asset, it must agree with the sidecar.
  • This gives integrity, not publisher authenticity. The webapp labels the source as GitHub releases, not the signed Desktop feed, and uses it only when the signed feed has no profiles.

URLs: url is kept only if it's https://github.com or https://objects.githubusercontent.com under /<owner>/<repo>/releases/download/.

Unset: 404, so nothing changes for any other install. Kind sets it in its own overlay.

Tests

  • test/desktop-releases.test.ts:
    • the repo setting's format check;
    • name mapping for every real Kind asset name, with non-installers skipped;
    • the URL allow-list (other hosts, repos and paths, ports, credentials and query strings are refused);
    • mapping with sidecar checksums: newest non-draft release, pre-release kept, an asset without a sidecar dropped;
    • the 5-minute cache;
    • only api.github.com and githubusercontent hosts are called;
    • assets dropped for: digest mismatch, sidecar naming a different file, foreign download URL, redirect to another host (which is never fetched);
    • upstream 503, a thrown fetch and an empty list all give release: null, with the failure cached for 5 minutes.
  • test/sessions.test.ts: 404 when unset, 401 without a token, 200 for a member with no-store, and exactly one GitHub call.

npm test passes locally (1226). Companion webapp PR: relay-webapp branch feat/downloads-github-releases.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds GET /api/v1/desktop/releases so the downloads card can show the latest release from a public GitHub repo when the signed Desktop feed has nothing, such as on a local Kind stack. It's gated by a new SELFHOST_DESKTOP_RELEASES_REPO setting, unset by default, in which case the route answers 404 and nothing changes.

Security and integrity bounds

  • Calls only api.github.com (release list plus each .sha256 sidecar's asset URL), with one redirect allowed only to *.githubusercontent.com; no token, size caps, 8-second deadline, and a 5-minute cache that includes failures.
  • An asset is served only with a matching sidecar from the same release whose digest agrees with GitHub's own when reported; download URLs are kept only on github.com and objects.githubusercontent.com under /<owner>/<repo>/releases/download/, with the repo compared case-insensitively.
  • Refreshes stay inside GitHub's unauthenticated quota: the list is a conditional request (a 304 costs nothing) and verified sidecar digests are cached per asset, so unchanged assets are never re-read.
  • Any upstream failure serves the last complete release, falling back to release: null only when none exists — never a 5xx — and the dashboard labels the source as GitHub releases rather than the signed feed.

Written for commit 0ff71c8. Summary will update on new commits.

View guided diff Turn on auto-fix

SELFHOST_DESKTOP_RELEASES_REPO (owner/repository, unset by default) turns on
GET /api/v1/desktop/releases: the newest non-draft release in the downloads
card's manifest shape, for installs without the signed Desktop feed (a
local Kind stack publishes its Desktop to a public GitHub releases repo).

- Fixed hosts: only https://api.github.com/repos/<owner>/<repo>/releases
  (the list and each sidecar's API asset URL, one redirect allowed only to
  *.githubusercontent.com). No token, size caps, 8s deadline, 5-minute
  cache (failures included), release:null instead of 5xx.
- Assets need a matching .sha256 sidecar from the same release (and agree
  with GitHub's digest when reported); download URLs are kept only on
  github.com / objects.githubusercontent.com under
  /<owner>/<repo>/releases/download/.
- Unset: 404 and no change for anyone else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 13eee047-467c-4404-8836-a2d514c70e2b
Commits

Reviewing files that changed from the base of the PR and between 48db86c and 0ff71c8.

Files selected for processing: 9

  • CHANGELOG.md
  • README.md
  • docs/desktop-feed.md
  • openapi.yaml
  • src/app.ts
  • src/config.ts
  • src/desktop-releases.ts
  • test/desktop-releases.test.ts
  • test/sessions.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T22:26:56.546244Z 921a594 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 921a594b8e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/desktop-releases.ts Resolved
Comment thread src/desktop-releases.ts Resolved

@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 4 potential issues.

Devin Review

Comment thread src/desktop-releases.ts Resolved
Comment thread src/desktop-releases.ts Resolved
Comment thread src/desktop-releases.ts Resolved
Comment thread src/desktop-releases.ts Resolved
…release

- The release list is a conditional request (ETag); a 304 does not count
  against the unauthenticated quota. Verified sidecar digests are kept per
  asset id + updated_at, so unchanged assets are never re-read.
- A sidecar read failure makes the release unavailable (not smaller); any
  failure serves the last complete answer, else release:null.
- owner/repository is compared case-insensitively in download URLs.
- An over-declared response body is cancelled before it is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@khaliqgant
khaliqgant merged commit ad74a1a into main Nov 11, 2026
3 checks passed
@khaliqgant
khaliqgant deleted the feat/desktop-github-releases branch November 11, 2026 22:47
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