From 33799b81353ea67ae2573f0480f7d89ee4e41ecc Mon Sep 17 00:00:00 2001 From: mrambossek Date: Sun, 2 Aug 2026 08:04:56 +0200 Subject: [PATCH] adminui: the enrolment link was rendered as a dead anchor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit html/template rewrites an href whose scheme it does not recognise to "#ZgotmplZ". echolot:// is not on its list, so "Open in the Echolot app" was not a link at all — tapping it did nothing, and nothing showed it: the markup reads correctly, the app resolves the scheme, and only the sanitised attribute in the served HTML gives it away. Marking the value template.URL opts out of that sanitising, which is only safe because the shape is now checked first. The link arrives in a query parameter, so without the check a crafted /devices?link=javascript:… would put a script URL into the page for an admin to click. Co-Authored-By: Claude Opus 5 --- server/internal/adminui/pages.go | 16 +++++++++++++++- server/internal/adminui/render.go | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/server/internal/adminui/pages.go b/server/internal/adminui/pages.go index d15136a..d2f8902 100644 --- a/server/internal/adminui/pages.go +++ b/server/internal/adminui/pages.go @@ -5,10 +5,12 @@ package adminui import ( "encoding/json" + "html/template" "log/slog" "net/http" "net/url" "sort" + "strings" "time" "echo-lot.app/server/internal/adminauth" @@ -122,9 +124,21 @@ func (s *Server) devices(w http.ResponseWriter, r *http.Request, sess *adminauth } rows = append(rows, row{Device: d, Runs: n}) } + // html/template rewrites an href whose scheme it does not recognise to "#ZgotmplZ", so the + // enrollment link rendered as a dead anchor that did nothing when tapped — silently, since the + // markup looks fine and only the sanitised attribute gives it away. + // + // Marking it template.URL opts out of that sanitising, which is only safe because the shape is + // checked first: this value arrives in a query parameter, so without the check a crafted + // /devices?link=javascript:… would put a script URL straight into the page. + link := r.URL.Query().Get("link") + var href template.URL + if strings.HasPrefix(link, "echolot://enroll?") { + href = template.URL(link) + } s.render(w, r, "devices", map[string]any{ "Session": sess, "CSRF": s.csrfToken(sess), "Rows": rows, - "Link": r.URL.Query().Get("link"), "Admin": sess.Admin, + "Link": link, "LinkHref": href, "Admin": sess.Admin, }) } diff --git a/server/internal/adminui/render.go b/server/internal/adminui/render.go index 8d00c18..d7089fc 100644 --- a/server/internal/adminui/render.go +++ b/server/internal/adminui/render.go @@ -327,7 +327,7 @@ const baseHTML = ` -

Open in the Echolot app

+ {{with $.LinkHref}}

Open in the Echolot app

{{end}}

Only works on the phone you are enrolling. From anywhere else, copy the link into the app's enrolment field, or deliver it over adb.

{{.}}