diff --git a/apps/web/lib/sponsor-url.test.ts b/apps/web/lib/sponsor-url.test.ts index 5f42808..8a4277c 100644 --- a/apps/web/lib/sponsor-url.test.ts +++ b/apps/web/lib/sponsor-url.test.ts @@ -18,6 +18,12 @@ describe("normalizeSponsorUrl", () => { expect(normalizeSponsorUrl("mailto:test@example.com").ok).toBe(false); }); + it("rejects internal hosts", () => { + expect(normalizeSponsorUrl("http://localhost/sponsor").ok).toBe(false); + expect(normalizeSponsorUrl("http://10.0.0.5/sponsor").ok).toBe(false); + expect(normalizeSponsorUrl("http://[::1]/sponsor").ok).toBe(false); + }); + it("rejects invalid URLs", () => { expect(normalizeSponsorUrl("example dot com").ok).toBe(false); }); diff --git a/apps/web/lib/sponsor-url.ts b/apps/web/lib/sponsor-url.ts index f880340..d9e8e9c 100644 --- a/apps/web/lib/sponsor-url.ts +++ b/apps/web/lib/sponsor-url.ts @@ -1,3 +1,5 @@ +import { validateExternalUrl } from "./url-guard"; + export type SponsorUrlResult = | { ok: true; url: string | null } | { ok: false; error: string }; @@ -9,16 +11,16 @@ export function normalizeSponsorUrl(raw: string | null | undefined): SponsorUrlR return { ok: false, error: "Sponsor link is too long." }; } - let parsed: URL; - try { - parsed = new URL(trimmed); - } catch { - return { ok: false, error: "Sponsor link must be a valid URL." }; - } - - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { - return { ok: false, error: "Sponsor link must start with http:// or https://." }; + const externalUrl = validateExternalUrl(trimmed); + if (!externalUrl.ok) { + if (externalUrl.error === "Only http and https URLs are allowed.") { + return { ok: false, error: "Sponsor link must start with http:// or https://." }; + } + if (externalUrl.error === "Enter a valid URL.") { + return { ok: false, error: "Sponsor link must be a valid URL." }; + } + return { ok: false, error: "Sponsor link host is not allowed." }; } - return { ok: true, url: parsed.toString() }; + return { ok: true, url: externalUrl.url.toString() }; }