From 08e6503e7a6a124c788006ee40d273669bf92d5a Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Mon, 13 Jul 2026 14:05:00 +0200 Subject: [PATCH 1/2] branding: validate favicon URL scheme before applying The branding response comes from an unauthenticated host-keyed endpoint; restrict the applied favicon to http(s) or same-origin relative URLs so a javascript:/data: value can't be injected. --- src/branding/applyBranding.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/branding/applyBranding.ts b/src/branding/applyBranding.ts index 78a57ec..d1c2248 100644 --- a/src/branding/applyBranding.ts +++ b/src/branding/applyBranding.ts @@ -16,6 +16,21 @@ interface BrandingShape { const CACHE_KEY = 'forail.branding' const TTL_MS = 5 * 60 * 1000 +// L14: the branding response is served from an unauthenticated, host-keyed +// endpoint. Only apply a favicon URL that is a plain http(s) absolute URL or a +// same-origin relative path — never a javascript:/data:/other scheme. +function isSafeIconUrl(url: string): boolean { + if (!url) return false + // Relative path (same origin) is fine. + if (url.startsWith('/') && !url.startsWith('//')) return true + try { + const parsed = new URL(url, window.location.origin) + return parsed.protocol === 'http:' || parsed.protocol === 'https:' + } catch { + return false + } +} + function apply(b: BrandingShape): void { if (!b) return if (b.primary_color) { @@ -27,7 +42,7 @@ function apply(b: BrandingShape): void { if (b.name) { document.title = `${b.name} — Forail` } - if (b.logo_url) { + if (b.logo_url && isSafeIconUrl(b.logo_url)) { const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement | null if (link) link.href = b.logo_url } From 0204ae8b308d7d7c4ee03d0b708c537ebd8f9030 Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Tue, 14 Jul 2026 11:15:00 +0200 Subject: [PATCH 2/2] docs: changelog for branding favicon validation --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d1307..8d08215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and the project adheres to SemVer until the first stable release. ## [Unreleased] +### Security +- **Branding favicon URL validation**: the favicon supplied by the + unauthenticated host-keyed branding response is only applied when it is an + `http(s)` or same-origin relative URL, blocking `javascript:`/`data:` schemes. + ## [2026.06.0] - 2026-06-14 ### Changed