From 0d9e4ce59c2bb2aa1bc02413c24a3b9b1914767f Mon Sep 17 00:00:00 2001 From: RinZ27 <222222878+RinZ27@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:10:23 +0700 Subject: [PATCH] Fix: Secure YouTube hostname validation --- apps/chrome-extension/src/entrypoints/background.ts | 2 +- packages/core/src/content/url.ts | 6 +++--- tests/content.url.test.ts | 13 +++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/chrome-extension/src/entrypoints/background.ts b/apps/chrome-extension/src/entrypoints/background.ts index d31a4698..71669900 100644 --- a/apps/chrome-extension/src/entrypoints/background.ts +++ b/apps/chrome-extension/src/entrypoints/background.ts @@ -396,7 +396,7 @@ function isYouTubeWatchUrl(value: string | null | undefined): boolean { const id = url.pathname.replace(/^\/+/, "").trim(); return Boolean(id); } - if (!host.endsWith("youtube.com")) return false; + if (host !== "youtube.com" && !host.endsWith(".youtube.com")) return false; const path = url.pathname.toLowerCase(); if (path === "/watch") return Boolean(url.searchParams.get("v")?.trim()); if (path.startsWith("/shorts/")) return true; diff --git a/packages/core/src/content/url.ts b/packages/core/src/content/url.ts index 9cf085cf..2bf78b7f 100644 --- a/packages/core/src/content/url.ts +++ b/packages/core/src/content/url.ts @@ -4,7 +4,7 @@ import { isTwitterBroadcastUrl, isTwitterStatusUrl } from "./link-preview/conten export const isYouTubeUrl = (rawUrl: string): boolean => { try { const hostname = new URL(rawUrl).hostname.toLowerCase(); - return hostname.includes("youtube.com") || hostname.includes("youtu.be"); + return hostname === "youtube.com" || hostname.endsWith(".youtube.com") || hostname === "youtu.be"; } catch { const lower = rawUrl.toLowerCase(); return lower.includes("youtube.com") || lower.includes("youtu.be"); @@ -48,7 +48,7 @@ export function isYouTubeVideoUrl(rawUrl: string): boolean { return Boolean(url.pathname.split("/").filter(Boolean)[0]); } - if (!hostname.includes("youtube.com")) { + if (hostname !== "youtube.com" && !hostname.endsWith(".youtube.com")) { return false; } @@ -75,7 +75,7 @@ export function extractYouTubeVideoId(rawUrl: string): string | null { if (hostname === "youtu.be") { candidate = url.pathname.split("/")[1] ?? null; } - if (hostname.includes("youtube.com")) { + if (hostname === "youtube.com" || hostname.endsWith(".youtube.com")) { if (url.pathname.startsWith("/watch")) { candidate = url.searchParams.get("v"); } else if (url.pathname.startsWith("/shorts/")) { diff --git a/tests/content.url.test.ts b/tests/content.url.test.ts index f6579f4e..bdbddfc3 100644 --- a/tests/content.url.test.ts +++ b/tests/content.url.test.ts @@ -78,4 +78,17 @@ describe("content/url", () => { ); expect(shouldPreferUrlMode("https://example.com/article")).toBe(false); }); + + it("should not be bypassed by malicious YouTube-like hostnames", () => { + const malicious = [ + "https://attacker-youtube.com/watch?v=dQw4w9WgXcQ", + "https://notyoutube.com/watch?v=dQw4w9WgXcQ", + "https://youtube.com.attacker.com/watch?v=dQw4w9WgXcQ", + ]; + for (const url of malicious) { + expect(isYouTubeUrl(url)).toBe(false); + expect(isYouTubeVideoUrl(url)).toBe(false); + expect(extractYouTubeVideoId(url)).toBeNull(); + } + }); });