Skip to content

Commit d84dacc

Browse files
fix(plugins): rewrite relative README image URLs to source-host raw URLs
1 parent 08bba63 commit d84dacc

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

src/lib/rehypeProxyImages.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ function rewriteImgSrc(src: string, assetBaseUrl?: string) {
3030
}
3131

3232
describe("rehypeProxyImages", () => {
33+
it("allows relative README assets to reference parent folders inside the same commit tree", () => {
34+
expect(
35+
rewriteImgSrc(
36+
"../shared/logo.png",
37+
"https://raw.githubusercontent.com/owner/repo/abcdef/sub/",
38+
),
39+
).toBe(
40+
"/_vercel/image?url=https%3A%2F%2Fraw.githubusercontent.com%2Fowner%2Frepo%2Fabcdef%2Fshared%2Flogo.png&w=1024&q=75",
41+
);
42+
});
43+
44+
it("does not rewrite relative README assets that escape above the commit root", () => {
45+
expect(
46+
rewriteImgSrc(
47+
"../../../outside.png",
48+
"https://raw.githubusercontent.com/owner/repo/abcdef/sub/dir/",
49+
),
50+
).toBe("../../../outside.png");
51+
});
52+
3353
it("does not treat explicit non-http schemes as relative README assets", () => {
3454
expect(
3555
rewriteImgSrc("javascript:alert(1)", "https://raw.githubusercontent.com/owner/repo/abcdef/"),

src/lib/rehypeProxyImages.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ const ABSOLUTE_HTTP = /^https?:\/\//i;
2626
const EXPLICIT_SCHEME = /^[a-z][a-z0-9+\-.]*:/i;
2727
const PROTOCOL_RELATIVE = /^\/\//;
2828

29+
function getRawGitHubCommitRoot(assetBaseUrl: string): URL | null {
30+
try {
31+
const baseUrl = new URL(assetBaseUrl);
32+
if (baseUrl.protocol !== "https:" || baseUrl.hostname !== "raw.githubusercontent.com") {
33+
return null;
34+
}
35+
const [owner, repo, commit] = baseUrl.pathname.split("/").filter(Boolean);
36+
if (!owner || !repo || !commit) return null;
37+
return new URL(`/${owner}/${repo}/${commit}/`, baseUrl.origin);
38+
} catch {
39+
return null;
40+
}
41+
}
42+
2943
function resolveRelativeSrc(src: string, assetBaseUrl: string | undefined): string | null {
3044
if (!assetBaseUrl) return null;
3145
if (!src) return null;
@@ -38,7 +52,12 @@ function resolveRelativeSrc(src: string, assetBaseUrl: string | undefined): stri
3852
// pulling random repo-root files.
3953
if (src.startsWith("/")) return null;
4054
try {
41-
return new URL(src, assetBaseUrl).toString();
55+
const resolved = new URL(src, assetBaseUrl);
56+
const commitRoot = getRawGitHubCommitRoot(assetBaseUrl);
57+
if (!commitRoot) return null;
58+
if (resolved.origin !== commitRoot.origin) return null;
59+
if (!resolved.pathname.startsWith(commitRoot.pathname)) return null;
60+
return resolved.toString();
4261
} catch {
4362
return null;
4463
}

0 commit comments

Comments
 (0)