fix(desktop): isolate Electron auth cookies per app

Make the legacy Electron desktop client generate and pass a per-launch auth cookie name too, so parallel desktop instances stop clobbering each other's localhost session cookie just like the Tauri client.
This commit is contained in:
Pascal André
2026-03-15 09:38:00 +01:00
parent 800133361d
commit 2abda0e6b4
2 changed files with 11 additions and 4 deletions

View File

@@ -327,7 +327,6 @@ function finalizeCliSwap(url: string) {
mainWindow.loadURL(url).catch((error) => console.error("[cli] failed to load CLI view:", error)) mainWindow.loadURL(url).catch((error) => console.error("[cli] failed to load CLI view:", error))
} }
const SESSION_COOKIE_NAME = "codenomad_session"
let bootstrapExchangeInFlight = false let bootstrapExchangeInFlight = false
function extractCookieValue(setCookieHeader: string | string[] | undefined, name: string): string | null { function extractCookieValue(setCookieHeader: string | string[] | undefined, name: string): string | null {
@@ -350,6 +349,7 @@ function extractCookieValue(setCookieHeader: string | string[] | undefined, name
} }
async function exchangeBootstrapToken(baseUrl: string, token: string): Promise<boolean> { async function exchangeBootstrapToken(baseUrl: string, token: string): Promise<boolean> {
const sessionCookieName = cliManager.getAuthCookieName()
const target = new URL("/api/auth/token", baseUrl) const target = new URL("/api/auth/token", baseUrl)
const body = JSON.stringify({ token }) const body = JSON.stringify({ token })
@@ -380,14 +380,14 @@ async function exchangeBootstrapToken(baseUrl: string, token: string): Promise<b
return false return false
} }
const sessionId = extractCookieValue(result.setCookie, SESSION_COOKIE_NAME) const sessionId = extractCookieValue(result.setCookie, sessionCookieName)
if (!sessionId) { if (!sessionId) {
return false return false
} }
await session.defaultSession.cookies.set({ await session.defaultSession.cookies.set({
url: baseUrl, url: baseUrl,
name: SESSION_COOKIE_NAME, name: sessionCookieName,
value: sessionId, value: sessionId,
httpOnly: true, httpOnly: true,
path: "/", path: "/",

View File

@@ -11,6 +11,7 @@ import { buildUserShellCommand, getUserShellEnv, supportsUserShell } from "./use
const nodeRequire = createRequire(import.meta.url) const nodeRequire = createRequire(import.meta.url)
const BOOTSTRAP_TOKEN_PREFIX = "CODENOMAD_BOOTSTRAP_TOKEN:" const BOOTSTRAP_TOKEN_PREFIX = "CODENOMAD_BOOTSTRAP_TOKEN:"
const SESSION_COOKIE_NAME_PREFIX = "codenomad_session"
type CliState = "starting" | "ready" | "error" | "stopped" type CliState = "starting" | "ready" | "error" | "stopped"
type ListeningMode = "local" | "all" type ListeningMode = "local" | "all"
@@ -122,6 +123,7 @@ export class CliProcessManager extends EventEmitter {
private stdoutBuffer = "" private stdoutBuffer = ""
private stderrBuffer = "" private stderrBuffer = ""
private bootstrapToken: string | null = null private bootstrapToken: string | null = null
private authCookieName = `${SESSION_COOKIE_NAME_PREFIX}_${process.pid}_${Date.now()}`
private requestedStop = false private requestedStop = false
async start(options: StartOptions): Promise<CliStatus> { async start(options: StartOptions): Promise<CliStatus> {
@@ -132,6 +134,7 @@ export class CliProcessManager extends EventEmitter {
this.stdoutBuffer = "" this.stdoutBuffer = ""
this.stderrBuffer = "" this.stderrBuffer = ""
this.bootstrapToken = null this.bootstrapToken = null
this.authCookieName = `${SESSION_COOKIE_NAME_PREFIX}_${process.pid}_${Date.now()}`
this.requestedStop = false this.requestedStop = false
this.updateStatus({ state: "starting", port: undefined, pid: undefined, url: undefined, error: undefined }) this.updateStatus({ state: "starting", port: undefined, pid: undefined, url: undefined, error: undefined })
@@ -328,6 +331,10 @@ export class CliProcessManager extends EventEmitter {
return { ...this.status } return { ...this.status }
} }
getAuthCookieName(): string {
return this.authCookieName
}
private resolveListeningMode(): ListeningMode { private resolveListeningMode(): ListeningMode {
return readListeningModeFromConfig() return readListeningModeFromConfig()
} }
@@ -416,7 +423,7 @@ export class CliProcessManager extends EventEmitter {
} }
private buildCliArgs(options: StartOptions, host: string): string[] { private buildCliArgs(options: StartOptions, host: string): string[] {
const args = ["serve", "--host", host, "--generate-token"] const args = ["serve", "--host", host, "--generate-token", "--auth-cookie-name", this.authCookieName]
if (options.dev) { if (options.dev) {
// Dev: run plain HTTP + Vite dev server proxy. // Dev: run plain HTTP + Vite dev server proxy.