From 1dd8474ce45b4965487f68acb6c1b6a777077033 Mon Sep 17 00:00:00 2001 From: Cam Proudlock Date: Fri, 21 Aug 2026 15:23:18 -0400 Subject: [PATCH] Secret links: one mount, not two --- plugins/tools/frontend/views/SecureSendCreate.vue | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/tools/frontend/views/SecureSendCreate.vue b/plugins/tools/frontend/views/SecureSendCreate.vue index 5c2621a8..591acd99 100644 --- a/plugins/tools/frontend/views/SecureSendCreate.vue +++ b/plugins/tools/frontend/views/SecureSendCreate.vue @@ -151,12 +151,19 @@ async function createLink() { // withBase for the path: a site served under /shopdb or /ops carries that // in front of every route, and leaving it off produced a 404 from IIS for a // secret that existed - with the sender shown a link that could never work. - const origin = (await getSiteBaseUrl()).replace(/\/+$/, '') - const path = withBase(`/s/${token}`) + // site_base_url is DOCUMENTED as scheme + host, but sites really do set the + // full base including the subpath - and appending the mount to one that + // already carries it produced /shopdb/shopdb/s/. Tolerate both + // rather than trusting the description: add the mount only when it is not + // there already. + const configured = (await getSiteBaseUrl()).replace(/\/+$/, '') + const mount = withBase('/').replace(/\/+$/, '') + const needsMount = mount && !configured.endsWith(mount) // The key rides in the FRAGMENT. Browsers never send it, so it reaches the // recipient and nowhere else. - link.value = `${origin}${path}#${sealed.fragmentKey}` + link.value = + `${configured}${needsMount ? mount : ''}/s/${token}#${sealed.fragmentKey}` // Drop the plaintext as soon as it is sealed: no reason to keep it in memory // behind the link that replaced it. plaintext.value = ''