From b2e4b77e0a8a00eee7c70bab3810ee0db1f5a0cf Mon Sep 17 00:00:00 2001 From: Dean Rie Date: Sun, 25 Oct 2026 11:32:43 +0300 Subject: [PATCH] Encode repo paths in raw.githubusercontent.com URLs fetchGitHubFile and the relative logo URL interpolated tree paths into the raw URL as they came from the GitHub tree API, unencoded. A path with a # stopped at the fragment, a ? at the query, and a % was read as a broken escape, so a rule such as rules/c#.mdc fetched rules/c, got a 404, and was dropped from the import without a message. A sub-plugin directory with such a character lost its plugin.json the same way. Each path segment is now percent-encoded and the slashes between them kept, through one rawGitHubUrl helper used by both call sites. Plain paths produce the same URL as before. --- apps/cursor/src/lib/github-plugin/parse.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/cursor/src/lib/github-plugin/parse.ts b/apps/cursor/src/lib/github-plugin/parse.ts index 6fb62149..aef23b1a 100644 --- a/apps/cursor/src/lib/github-plugin/parse.ts +++ b/apps/cursor/src/lib/github-plugin/parse.ts @@ -187,12 +187,25 @@ export async function fetchWithRateLimit( */ export type FetchOptions = { maxWaitMs?: number }; +/** + * Raw-content URL for a repo path. Tree paths arrive unencoded, so a `#` + * would start a URL fragment, a `?` a query, and a `%` a broken escape; each + * segment is encoded while the `/` between them is kept. + */ +function rawGitHubUrl(owner: string, repo: string, path: string): string { + const encodedPath = path + .split("/") + .map((segment) => encodeURIComponent(segment)) + .join("/"); + return `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${encodedPath}`; +} + async function fetchGitHubFile( owner: string, repo: string, path: string, ): Promise { - const url = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`; + const url = rawGitHubUrl(owner, repo, path); try { const res = await fetch(url, { cache: "no-store" }); if (!res.ok) return null; @@ -593,7 +606,7 @@ export async function parseGitHubPlugin( logo = manifest.logo; } } catch { - logo = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${manifest.logo.replace(/^\.?\//, "")}`; + logo = rawGitHubUrl(owner, repo, manifest.logo.replace(/^\.?\//, "")); } }