#852: hide the whole app behind the lock screen — bypass reveals a blank page

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.
This commit is contained in:
BoulderBadgeDad 2026-06-11 18:08:02 -07:00
parent b5b9d6e5f4
commit 68acf89b83
2 changed files with 20 additions and 0 deletions

View file

@ -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();

View file

@ -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;
}