From 68acf89b8393933940a70a62586e2d069bf23469 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 11 Jun 2026 18:08:02 -0700 Subject: [PATCH] =?UTF-8?q?#852:=20hide=20the=20whole=20app=20behind=20the?= =?UTF-8?q?=20lock=20screen=20=E2=80=94=20bypass=20reveals=20a=20blank=20p?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beckid's ask: bypassing the login/PIN overlay shouldn't show the app pages at all, not even the (data-less) chrome. The overlay was cosmetic-on-top; the static shell sat behind it, so "Hide Distracting Items" exposed the empty UI. Now the lock screens add body.app-locked, and a CSS rule hides every body child except the two lock overlays themselves (display:none !important). Safari's hide-element trick can only ADD hiding — it can't undo this rule — so removing the overlay leaves a blank page. initApp() drops the class once authenticated (first line, before component layout init). Defense-in-depth on top of the server-side HTTP + WebSocket gating, which already blocks any actual data. Targeted + safe: the app shows by default (no blank-screen risk); only an active lock hides it. Profile picker (not a security lock) is unaffected. --- webui/static/init.js | 10 ++++++++++ webui/static/style.css | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/webui/static/init.js b/webui/static/init.js index e6ea4816..4da3795f 100644 --- a/webui/static/init.js +++ b/webui/static/init.js @@ -404,6 +404,10 @@ async function initProfileSystem() { function showLoginScreen() { const overlay = document.getElementById('login-overlay'); if (!overlay) return; + // Hide the entire app while locked, so removing the overlay (Safari "Hide + // Distracting Items", devtools) reveals nothing — not even the empty chrome. + // initApp() reveals it again on a successful sign-in (#852). + document.body.classList.add('app-locked'); overlay.style.display = 'flex'; const u = document.getElementById('login-username'); if (u) setTimeout(() => u.focus(), 50); @@ -507,6 +511,8 @@ async function submitRecoveryReset() { function showLaunchPinScreen() { const overlay = document.getElementById('launch-pin-overlay'); if (!overlay) return; + // Hide the whole app while locked — bypassing the overlay reveals nothing (#852). + document.body.classList.add('app-locked'); overlay.style.display = 'flex'; const input = document.getElementById('launch-pin-input'); @@ -2322,6 +2328,10 @@ async function _continueAppInit() { } function initApp() { + // Unlocked / authenticated — reveal the app (the lock screens hide it via + // body.app-locked so a bypassed overlay shows nothing). Do this FIRST so + // component init below measures real layout, not a display:none container. + document.body.classList.remove('app-locked'); // Initialize components initializeNavigation(); initializeMobileNavigation(); diff --git a/webui/static/style.css b/webui/static/style.css index 69f45953..954ff067 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -68177,3 +68177,13 @@ body.em-scroll-lock { overflow: hidden; } .arec-row { grid-template-columns: 1fr; gap: 2px; } .arec-rowcopy { opacity: 1; justify-self: end; margin-top: -22px; } } + + +/* #852: while the launch-PIN / login lock screen is up, hide EVERYTHING in the + body except the lock overlays themselves — so removing the overlay (Safari + "Hide Distracting Items", devtools) reveals a blank page, not the empty chrome + or any of the on-demand modals/sidebars. The hide-element trick can only ADD + hiding, never undo this rule. initApp() drops body.app-locked once authenticated. */ +body.app-locked > *:not(#launch-pin-overlay):not(#login-overlay):not(script):not(style):not(noscript) { + display: none !important; +}