Skip to content

fix(github): follow repo redirects in release mirror#214

Closed
risu729 wants to merge 3 commits into
jdx:mainfrom
risu729:codex-20260604-github-redirects
Closed

fix(github): follow repo redirects in release mirror#214
risu729 wants to merge 3 commits into
jdx:mainfrom
risu729:codex-20260604-github-redirects

Conversation

@risu729

@risu729 risu729 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • follow safe GitHub API redirects in the release/attestation JSON helper so renamed or transferred repositories resolve through the mirror
  • rewrite mirrored GitHub release asset URLs back to the owner/repo requested through the proxy, preserving compatibility with released mise clients that validate the requested slug
  • reject unsafe off-API redirects and cap redirect chains with explicit 502/508 behavior
  • add regression coverage for a renamed repo redirect (jdx/rtx -> jdx/mise), canonical asset casing (Destructive_command_guard), redirect-chain limits, and unsafe redirects

How This Fixes It

mise uses the mise-versions GitHub release endpoint as a cache for public GitHub release metadata, but released clients validate the returned asset URLs before using them. That validation expects release asset URLs to match the backend slug the user requested, such as github:jdx/rtx or github:Dicklesworthstone/Destructive_command_guard.

GitHub can legitimately return a different owner/repo spelling in two cases:

  • repository owner/name casing is canonicalized in release asset URLs
  • renamed or transferred repositories redirect from the requested slug to the canonical repository, such as jdx/rtx redirecting to jdx/mise

If the proxy simply returns GitHub's canonical asset URLs, current mise clients warn that mise-versions returned invalid GitHub release asset URLs and fall back to the live GitHub API. For renamed/transferred repositories, the client also cannot safely decide on its own that a different owner/repo is valid unless the API contract includes trusted canonical-repository metadata.

This PR keeps the compatibility boundary in the proxy:

  1. githubJson manually follows redirects only while the current URL and the Location URL are on api.github.com.
  2. Redirect chains are limited to MAX_GITHUB_REDIRECTS; over-limit chains fail with 508.
  3. Redirect responses without a safe GitHub API target fail with 502 instead of being followed or misreported.
  4. After GitHub returns the release JSON, the proxy rewrites only the owner/repo path segments in known GitHub release asset URL shapes back to the slug the caller requested.
  5. The cache key remains the requested slug, so existing clients continue to validate and use the mirror response without changing their API expectations.

The companion mise client PR, jdx/mise#10240, handles the case-only part for future clients by making owner/repo validation case-insensitive while keeping host, path, and tag checks strict. It intentionally does not make the client accept arbitrary different repositories for rename/transfer cases; those stay proxy-side unless a future API contract adds trusted canonical-repository metadata.

Testing

  • node --test scripts/github-mirror.test.js
  • npm run build -w web
  • npm run test:js
  • npx tsc --noEmit
  • npx prettier --check web/src/lib/github/mirror.ts scripts/github-mirror.test.js
  • live local mirror smoke test for jdx/rtx redirect rewriting

Notes

  • npx eslint web/src/lib/github/mirror.ts could not run locally because ESLint 10 requires an eslint.config.* file and this repo does not currently provide one.
  • npm audit --omit=dev --audit-level=high reports existing dependency advisories (devalue, ws via Cloudflare tooling); no dependency changes are included here.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced GitHub release mirroring with bounded redirect handling to prevent infinite loops.
    • Release asset URLs now properly normalize to match the requested repository context.
    • Added safety checks to reject redirects to external destinations.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11f6a04a-0fa3-4577-a26a-c6fedf1724af

📥 Commits

Reviewing files that changed from the base of the PR and between 49a0285 and 3c9dfbf.

📒 Files selected for processing (2)
  • scripts/github-mirror.test.js
  • web/src/lib/github/mirror.ts

📝 Walkthrough

Walkthrough

This PR adds bounded GitHub API redirect handling with release asset URL normalization. The implementation introduces redirect detection, safe next-URL validation, and asset path rewriting helpers used by a new redirect-following loop in githubJson, alongside comprehensive tests for redirect chaining, safety limits, and URL casing normalization.

Changes

GitHub Redirect Handling and Asset URL Rewriting

Layer / File(s) Summary
Redirect and URL handling helpers
web/src/lib/github/mirror.ts
Introduces MAX_GITHUB_REDIRECTS constant, isRedirect status detection, githubRedirectUrl safe next-URL computation with host validation, and withRequestedGitHubRepo asset path rewriting. Testing exports extended to include the new helpers.
GitHub JSON redirect traversal
web/src/lib/github/mirror.ts
githubJson now loops on fetch(..., redirect: "manual"), detects redirect responses, validates and computes next URLs, enforces redirect count bounds, and throws specific GitHubErrors (502 for unsafe, 508 for too many) while preserving existing error and JSON parsing logic.
Release asset URL normalization
web/src/lib/github/mirror.ts
New normalizeGitHubAsset helper rewrites release asset browser_download_url and url to the requested repository context via withRequestedGitHubRepo, and normalizes absent digest to null.
Test coverage for redirect and asset rewriting
scripts/github-mirror.test.js
Four new tests verify release redirect following with asset URL rewriting, URL casing normalization to match caller context, rejection after 4-hop redirect chains, and rejection of redirects to unsafe external hosts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through redirects with care,
Each hop checked twice, no unsafe lair,
Asset URLs dressed in their proper coat,
Four redirects max—no endless vote,
GitHub's maze now safely traced,
With rewritten paths precisely placed! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(github): follow repo redirects in release mirror' directly and clearly summarizes the main change: implementing GitHub redirect following in the release mirror functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

scripts/github-mirror.test.js

Oops! Something went wrong! :(

ESLint: 10.4.0

A config object is using the "env" key, which is not supported in flat config system.

Flat config uses "languageOptions.globals" to define global variables for your files.

Please see the following page for information on how to convert your config object into the correct format:
https://eslint.org/docs/latest/use/configure/migration-guide#configure-language-options

If you're not using "env" directly (it may be coming from a plugin), please see the following:
https://eslint.org/docs/latest/use/configure/migration-guide#use-eslintrc-configs-in-flat-config


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

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds redirect-following to the GitHub release mirror so that renamed or transferred repositories (e.g. jdx/rtxjdx/mise) resolve correctly, and rewrites any canonicalized asset URLs in the response back to the originally-requested owner/repo slug so mise clients receive URLs that match their request.

  • githubJson now manually follows up to MAX_GITHUB_REDIRECTS (3) hops, validating that each Location header stays on api.github.com; unsafe or over-limit redirects surface as 502/508 errors respectively.
  • withRequestedGitHubRepo rewrites browser_download_url and url fields on release assets to replace whatever owner/repo GitHub returned with the slug the caller originally requested, covering both github.com download links and api.github.com asset API URLs.
  • Four new integration tests cover: redirect-following with URL rewriting, canonical-casing rewrite, redirect-chain limit, and unsafe-redirect rejection.

Confidence Score: 5/5

Safe to merge — the redirect loop is bounded, each hop is validated against the GitHub API domain, and asset URL rewriting is scoped to the two known URL shapes with no observable side effects on other code paths.

The redirect handling logic is correct and consistent with the new tests: redirects >= MAX_GITHUB_REDIRECTS fires on the fourth fetch (matching assert.equal(redirects, 4)), the 508 throw is now reachable (previous dead-code concern addressed), and withRequestedGitHubRepo replaces only the owner/repo segments of known URL patterns while returning the original value unchanged for anything it does not recognise.

No files require special attention.

Important Files Changed

Filename Overview
web/src/lib/github/mirror.ts Adds redirect-following loop in githubJson (up to MAX_GITHUB_REDIRECTS=3 hops), validates that each redirect stays on api.github.com, and normalizes asset URLs back to the requested owner/repo slug via withRequestedGitHubRepo; logic is correct and previously-flagged dead-code 508 path is now reachable.
scripts/github-mirror.test.js Adds four new test scenarios: redirect following with URL rewriting, canonical-casing rewrite without redirect, redirect-chain limit (expects 4 fetches and 508), and unsafe-redirect rejection (expects 502); all test assertions are consistent with the updated implementation.

Reviews (3): Last reviewed commit: "fix(github): reject unsafe release mirro..." | Re-trigger Greptile

Comment thread web/src/lib/github/mirror.ts Outdated
@risu729

risu729 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Applied the Greptile redirect-limit feedback in c73147a: a valid GitHub API redirect chain now throws the intended 508 once it exceeds MAX_GITHUB_REDIRECTS, and scripts/github-mirror.test.js covers that branch.

This comment was generated by an AI coding assistant.

@risu729 risu729 marked this pull request as ready for review June 5, 2026 19:20
Copilot AI review requested due to automatic review settings June 5, 2026 19:20

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves the GitHub release mirror’s robustness when repositories have been renamed/redirected by following safe GitHub API redirects and normalizing returned asset URLs back to the originally requested repo coordinates.

Changes:

  • Follow and validate limited redirect chains for api.github.com requests in githubJson.
  • Normalize/rewrite release asset URLs to the requested {owner}/{repo} to keep returned URLs consistent.
  • Add integration-style tests covering redirect following, URL rewriting, redirect limits, and unsafe redirect rejection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
web/src/lib/github/mirror.ts Adds redirect-following logic for GitHub API JSON fetches and rewrites release asset URLs to match the requested repo.
scripts/github-mirror.test.js Adds tests validating redirect behavior, safety checks, and asset URL rewriting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +356 to +370
let currentUrl = url;
let redirects = 0;
while (true) {
const headers = githubJsonHeaders(currentUrl, token);
const response = await fetch(currentUrl, { headers, redirect: "manual" });
if (isRedirect(response.status)) {
const nextUrl = githubRedirectUrl(currentUrl, response);
if (!nextUrl) {
throw new GitHubError(
502,
"Unsafe GitHub redirect",
response.headers,
currentUrl,
);
}
Comment on lines +362 to +370
const nextUrl = githubRedirectUrl(currentUrl, response);
if (!nextUrl) {
throw new GitHubError(
502,
"Unsafe GitHub redirect",
response.headers,
currentUrl,
);
}
Comment on lines +109 to +110
assert.equal(writes[0].key, "github:release:jdx/rtx:latest");
`);
Comment on lines +442 to +444
function isRedirect(status: number): boolean {
return [301, 302, 303, 307, 308].includes(status);
}
@risu729 risu729 marked this pull request as draft June 5, 2026 19:24
@risu729

risu729 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

I'm not too sure if we need this. I think we shouldn't rewrite the API response.

@risu729 risu729 closed this Jun 5, 2026
@risu729 risu729 deleted the codex-20260604-github-redirects branch June 5, 2026 19:26
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.

2 participants