fix(desktop): recognize the LAN-bound local server when tagging client tokens

The client-create gate added in 1.13.9 rejects client tokens without the
desktop-local kind, but the kind was only attached when the runtime
origin exactly matched the injected local origin — an empty (same-origin)
api base, loopback aliases, and the embedded server addressed via a LAN
interface (0.0.0.0 binds) all minted untagged tokens, which then hit 403
and surfaced as "Local — Auth required" plus the unreachable-server
screen. The renderer now treats same-origin and loopback targets as
local, the Electron main additionally matches any of the machine's own
interface addresses on the local server's port, and a deduped kind-tagged
mint migrates away legacy same-label tokens that predate client kinds.
The client-create gate itself is unchanged.
This commit is contained in:
Bohdan Triapitsyn
2026-07-05 09:50:53 +03:00
parent b09e073e86
commit 0820778763
3 changed files with 58 additions and 3 deletions
+29 -1
View File
@@ -537,9 +537,37 @@ const readDesktopLocalClientToken = () => {
return sanitizeClientTokenForStorage(readSettingsRoot().desktopLocalClientToken) || '';
};
const isMachineLocalHostname = (hostname) => {
const clean = String(hostname || '').replace(/^\[|\]$/g, '');
if (!clean) return false;
if (clean === 'localhost' || clean === '127.0.0.1' || clean === '::1' || clean === '0.0.0.0' || clean === '::') {
return true;
}
try {
return Object.values(os.networkInterfaces()).some((entries) =>
(entries || []).some((entry) => entry?.address === clean));
} catch {
return false;
}
};
const isLocalRuntimeUrl = (targetUrl) => {
const localUrl = state.sidecarUrl || state.localOrigin || '';
return Boolean(localUrl && sameOrigin(targetUrl, localUrl));
if (!localUrl) return false;
if (sameOrigin(targetUrl, localUrl)) return true;
// The embedded server bound to 0.0.0.0 for LAN access is still THIS
// machine's server when addressed via any of its own interfaces on the same
// port — the minted client token must carry the desktop-local kind, or the
// server's client-create gate rejects it (the "Local — Auth required" +
// unreachable-screen regression).
try {
const target = new URL(targetUrl);
const local = new URL(localUrl);
const portOf = (url) => url.port || (url.protocol === 'https:' ? '443' : '80');
return portOf(target) === portOf(local) && isMachineLocalHostname(target.hostname);
} catch {
return false;
}
};
const readDesktopHostsConfig = () => {