From 43bd49fce6805466c5c5cc426da3a1eb154832ba Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 10:00:07 -0700 Subject: [PATCH 1/2] fix(workflow-renderer): validate dropbox host in note embed renderer Replace the bare url.includes('dropbox.com') check with a parsed-hostname match so attacker-controlled hosts (dropbox.com.evil.com, evil.com/?dropbox.com) no longer get treated as direct dropbox videos. Resolves CodeQL js/incomplete-url-substring-sanitization (#430). --- .../src/note/note-block-view.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/workflow-renderer/src/note/note-block-view.tsx b/packages/workflow-renderer/src/note/note-block-view.tsx index cf1dac38e8f..8e789d56f15 100644 --- a/packages/workflow-renderer/src/note/note-block-view.tsx +++ b/packages/workflow-renderer/src/note/note-block-view.tsx @@ -17,6 +17,15 @@ function getTwitchParent(): string { return typeof window !== 'undefined' ? window.location.hostname : 'localhost' } +/** Parse a URL's lowercased hostname, returning null for non-absolute/invalid URLs. */ +function parseHostname(url: string): string | null { + try { + return new URL(url).hostname.toLowerCase() + } catch { + return null + } +} + /** * Get embed info for supported media platforms */ @@ -250,7 +259,12 @@ function getEmbedInfo(url: string): EmbedInfo | null { return { url: `https://drive.google.com/file/d/${googleDriveMatch[1]}/preview`, type: 'iframe' } } - if (url.includes('dropbox.com') && /\.(mp4|mov|webm)/.test(url)) { + const dropboxHost = parseHostname(url) + if ( + dropboxHost && + (dropboxHost === 'dropbox.com' || dropboxHost.endsWith('.dropbox.com')) && + /\.(mp4|mov|webm)/.test(url) + ) { const directUrl = url .replace('www.dropbox.com', 'dl.dropboxusercontent.com') .replace('?dl=0', '') From fe9abae739a67e7e1191e065489b17f5730447b9 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 10:07:07 -0700 Subject: [PATCH 2/2] fix(workflow-renderer): rewrite dropbox embed via parsed URL, tolerate scheme-less links Derive the direct video URL from the parsed URL object (rewrite hostname to dl.dropboxusercontent.com for any dropbox.com/*.dropbox.com host) instead of a www-only string replace, and accept scheme-less links. Fixes broken embeds for m.dropbox.com / bare-host links flagged in review. --- .../src/note/note-block-view.tsx | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/workflow-renderer/src/note/note-block-view.tsx b/packages/workflow-renderer/src/note/note-block-view.tsx index 8e789d56f15..70f6be2fbbb 100644 --- a/packages/workflow-renderer/src/note/note-block-view.tsx +++ b/packages/workflow-renderer/src/note/note-block-view.tsx @@ -17,13 +17,32 @@ function getTwitchParent(): string { return typeof window !== 'undefined' ? window.location.hostname : 'localhost' } -/** Parse a URL's lowercased hostname, returning null for non-absolute/invalid URLs. */ -function parseHostname(url: string): string | null { - try { - return new URL(url).hostname.toLowerCase() - } catch { - return null +/** Parse a URL, tolerating scheme-less inputs (https is assumed). Returns null if unparseable. */ +function parseUrl(url: string): URL | null { + for (const candidate of [url, `https://${url}`]) { + try { + return new URL(candidate) + } catch {} } + return null +} + +/** + * Resolve a Dropbox share link to a direct, embeddable video URL. Accepts only URLs + * whose host is `dropbox.com` or a `*.dropbox.com` subdomain (so attacker-controlled + * hosts like `dropbox.com.evil.com` are rejected), then rewrites the host to + * `dl.dropboxusercontent.com` so the file streams as media. Returns null for any + * non-Dropbox host or non-video path. + */ +function getDropboxDirectVideoUrl(url: string): string | null { + const parsed = parseUrl(url) + if (!parsed) return null + const host = parsed.hostname.toLowerCase() + if (host !== 'dropbox.com' && !host.endsWith('.dropbox.com')) return null + if (!/\.(mp4|mov|webm)$/i.test(parsed.pathname)) return null + parsed.hostname = 'dl.dropboxusercontent.com' + parsed.searchParams.delete('dl') + return parsed.toString() } /** @@ -259,16 +278,9 @@ function getEmbedInfo(url: string): EmbedInfo | null { return { url: `https://drive.google.com/file/d/${googleDriveMatch[1]}/preview`, type: 'iframe' } } - const dropboxHost = parseHostname(url) - if ( - dropboxHost && - (dropboxHost === 'dropbox.com' || dropboxHost.endsWith('.dropbox.com')) && - /\.(mp4|mov|webm)/.test(url) - ) { - const directUrl = url - .replace('www.dropbox.com', 'dl.dropboxusercontent.com') - .replace('?dl=0', '') - return { url: directUrl, type: 'video' } + const dropboxDirectVideoUrl = getDropboxDirectVideoUrl(url) + if (dropboxDirectVideoUrl) { + return { url: dropboxDirectVideoUrl, type: 'video' } } const tenorMatch = url.match(/tenor\.com\/view\/[^/]+-(\d+)/)