From bfc61a8c39f4b33879774666cb18e33f815c4922 Mon Sep 17 00:00:00 2001 From: jelveh Date: Sun, 26 Jul 2026 17:08:56 -0700 Subject: [PATCH] Refuse an origin the grant could never have named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The identity line elides a long host from the left, keeping the registrable domain visible, which it does with `direction: rtl`. That is sound for a host — every character in one resolves left-to-right, so the string reads in source order — but it is not sound for arbitrary text, whose neutral and RTL runs can render in an order they were not written in. On the one line of this dialog whose whole job is saying who is asking, that is the wrong thing to be lenient about. The entity resolver reached that state through its own fallback: when `new URL(origin)` threw it displayed the unparsed string and still marked it as a host. An origin the server cannot parse cannot name a grant target either — `AuthService#originFromUrl` rejects it, and rejects non-http(s) schemes with it — so there was never anything to prompt about. Deny at the gate, next to the existing "requester the grant can't name" check, on the same test the server applies. --- src/gui/src/UI/UIPermissionDialog.js | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/gui/src/UI/UIPermissionDialog.js b/src/gui/src/UI/UIPermissionDialog.js index 65ffca046..7c472a16e 100644 --- a/src/gui/src/UI/UIPermissionDialog.js +++ b/src/gui/src/UI/UIPermissionDialog.js @@ -71,6 +71,18 @@ async function UIPermissionDialog (options) { return false; } + // An origin the server would refuse to parse can't name a grant target + // either, so there is nothing to prompt about — and showing it anyway would + // put a string that is not a host into the identity line, which is styled to + // elide *hosts* from the left (`direction: rtl`). Arbitrary text there can + // render in an order it wasn't written in, on the one line of this dialog + // whose whole job is saying who is asking. Mirrors the server's own check in + // AuthService#originFromUrl: parseable, and http(s). + if ( options.origin && ! is_web_origin(options.origin) ) { + console.error('Permission dialog: unusable origin', options.origin); + return false; + } + // `||`, not `??`: the gate above treats an empty uid as absent, so the key // has to fall through to the origin too — otherwise two different origins // arriving with a blank uid would share one decision. @@ -326,6 +338,23 @@ async function undo_uncertain_grant (options) { } } +/** + * Whether `origin` is a real web origin: something `new URL()` accepts, on a + * scheme the platform actually serves apps over. `new URL()` alone is far too + * permissive here — it happily parses `javascript:`, `data:` and `file:`. + * + * @param {string} origin + * @returns {boolean} + */ +function is_web_origin (origin) { + try { + const { protocol } = new URL(origin); + return protocol === 'https:' || protocol === 'http:'; + } catch (e) { + return false; + } +} + /** * Resolves `promise`, or `fallback` if it hasn't settled within `ms`. *