What happened

Simple Network Dashboard is a web tool you host on a Linux box and open in a browser. On startup its page gives each browser a small random ID, stores it, and uses it to key that browser’s WebSocket connection and its requests to the server. In testing this always worked. On a real network it crashed: opened at the server’s LAN address over plain HTTP the page failed to come up, while localhost on the same machine, or HTTPS, was fine.

The artifact

The ID was generated with a single call:

BROWSER_ID = crypto.randomUUID();

On a plain-HTTP LAN address the browser threw on that line, during the startup script and before the dashboard finished wiring itself up, so the whole page stalled rather than losing one feature:

Uncaught TypeError: crypto.randomUUID is not a function

Root cause

crypto.randomUUID is defined only in a secure context, meaning HTTPS or localhost. Plain HTTP to any other address, including a LAN IP like http://192.168.1.10, is not one, so the function is undefined there and calling it threw the TypeError. localhost counts as secure, which is why every local test passed: the gap only shows on the exact setup the tool is built for, another machine reaching it by IP over plain HTTP.

The original code made it worse. The startup wrapped the ID work in a try, but the catch called crypto.randomUUID() again, so the fallback ran the very call that had just failed and threw a second time, with nothing left to catch it.

The fix

Try crypto.randomUUID only if it is actually present, then fall back to crypto.getRandomValues, which works over plain HTTP, and assemble a UUID by hand. If even that is missing, use a plain non-cryptographic ID as a last resort:

function _genBrowserId() {
  // crypto.randomUUID exists only in secure contexts (HTTPS or localhost).
  // Over plain HTTP on a LAN IP it is undefined, so fall back to getRandomValues
  // (available everywhere), then to a non-crypto id as a last resort.
  try { if (crypto && crypto.randomUUID) return crypto.randomUUID(); } catch(e) {}
  try {
    if (crypto && crypto.getRandomValues) {
      const b = crypto.getRandomValues(new Uint8Array(16));
      b[6] = (b[6] & 0x0f) | 0x40; b[8] = (b[8] & 0x3f) | 0x80;
      const h = [...b].map(x => x.toString(16).padStart(2, '0'));
      return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
    }
  } catch(e) {}
  return 'id-' + Date.now().toString(16) + '-' + Math.random().toString(16).slice(2);
}

The bit-twiddling on bytes 6 and 8 sets the version and variant fields, so the hand-built string is a well-formed version-4 UUID rather than sixteen random bytes printed as hex.

Evidence boundary

This ID identifies one browser to the server for the length of a session; it is not a security token or a secret, so the non-cryptographic last resort weakens nothing. The fallback exists for availability: the dashboard has to start on the plain-HTTP LAN address it is designed for. It does not make the connection private; protecting the traffic itself is HTTPS’s job, not this ID’s.

Source ledger

Source owner Primary URL Review class Last reviewed Exact claim or evidence mapped
JDE-Projects Simple Network Dashboard https://github.com/JDE-Projects/Simple-Network-Dashboard/commit/8e6bc6a873a16f8b03d0c391ee4afc5857f4d899 Production evidence August 19, 2026 The commit that replaced the bare crypto.randomUUID call, and its repeat in the catch, with the guarded fallback chain.
JDE-Projects Simple Network Dashboard https://github.com/JDE-Projects/Simple-Network-Dashboard/blob/0d35749f0779151dd6591ebe86463b230a1acfef/static/index.html Production evidence August 19, 2026 The current _genBrowserId fallback chain and where the ID is used for the WebSocket and requests.
MDN https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID Framework-critical August 19, 2026 crypto.randomUUID is available only in secure contexts.
MDN https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts Framework-critical August 19, 2026 localhost and HTTPS are secure contexts; plain HTTP to other origins is not.