fix(desktop): refuse a foreign backend's session token after readiness

The served-token fallback adopts whatever token the dashboard HTML
injects. That is correct when our own child regenerated the token (env
pin lost across a shell-wrapped spawn), but wrong when the readiness
probe answered from a process we did not spawn: /api/status is public,
so an orphaned dashboard squatting the port passes waitForHermes while
our child dies on the bind conflict. Silently adopting that process's
token would authenticate the renderer against a foreign backend,
possibly on the wrong profile.

Discriminate on child liveness: the desktop pins
HERMES_DASHBOARD_SESSION_TOKEN on every spawn, so a live child always
serves our token. Served-token mismatch + dead child = foreign backend;
fail the boot loudly instead of connecting. Mismatch + live child keeps
the adopt-served-token salvage from #43720.
This commit is contained in:
Brooklyn Nicholson
2026-06-11 18:18:22 -05:00
parent 7a2d498b9d
commit e3ed7722b5
3 changed files with 73 additions and 1 deletions
+22
View File
@@ -78,10 +78,32 @@ async function resolveServedDashboardToken(baseUrl, fallbackToken, options = {})
return servedToken || fallbackToken
}
/**
* Decide whether a served-token mismatch means we are talking to a backend we
* did NOT spawn.
*
* The desktop pins HERMES_DASHBOARD_SESSION_TOKEN on every backend it spawns,
* and the dashboard honors that env at import — so a LIVE child of ours always
* serves our token. The only way the served token differs while our child is
* dead is that the readiness probe (public /api/status) answered from a
* different process: an orphaned dashboard or port squatter that won the bind
* race while our child exited. Adopting that process's token would silently
* authenticate the renderer against a foreign backend (possibly the wrong
* profile), so callers must fail loudly instead.
*
* A mismatch with a live child is the benign case the served-token fallback
* exists for: our own child served a regenerated token because the env pin
* did not survive the spawn (e.g. shell-wrapped CLI shims).
*/
function isForeignBackendToken({ servedToken, spawnToken, childAlive }) {
return Boolean(servedToken) && servedToken !== spawnToken && !childAlive
}
module.exports = {
DEFAULT_TOKEN_FETCH_TIMEOUT_MS,
dashboardIndexUrl,
extractInjectedDashboardToken,
fetchPublicText,
isForeignBackendToken,
resolveServedDashboardToken
}