fix(desktop): keep cookie partitions colon-free so Windows jars work

Electron escapes ':' in a session partition name as '%3A' for the on-disk
profile folder. On Windows, a profile folder whose name contains '%3A' gets a
cookie store the network stack can neither read nor write: a jar placed there
reads back zero cookies, `cookies.set()` never reaches disk, and every request
goes out with no cookies at all — the gateway answers 401 `no_cookie` and the
desktop concludes the user is signed out.

Per-connection partitions (#92183) are the only ones carrying a colon beyond
the 'persist:' prefix, so every NON-primary cookie-auth remote hits this: the
dial mints no ticket, the reauth copy fires ("Remote Hermes gateway uses
OAuth, but you are not signed in…"), and the login window — riding the same
partition — writes into the same invisible jar. Nothing ever persists, so the
prompt returns on every connect. The registry primary and the v1 remote keep
the legacy `persist:hermes-remote-oauth` jar, whose folder has no '%3A', which
is why a single-gateway setup never shows the symptom.

Verified against the app's own Electron runtime (40.10.2, Windows): identical
jar bytes seeded into two partition folders read 3 cookies and mint a
ws-ticket (200) from `…-conn-…`, and 0 cookies / 401 from `…%3Aconn%3A…`.

Keep the partition path component inside [A-Za-z0-9._-]; a regression test
pins that it never contains ':' or '%'.
This commit is contained in:
Peyton Lu
2026-09-16 01:57:46 +08:00
committed by Teknium
parent 1e2cb57973
commit 401a7d087a
2 changed files with 27 additions and 1 deletions
@@ -131,6 +131,24 @@ describe('resolveOauthPartition (#92183 per-connection cookie jars)', () => {
expect(resolveOauthPartition('https://gw-a.example.com', { registry: reg })).toBe(got)
})
it('keeps the on-disk path component colon-free and %-free (Windows cookie-store regression)', () => {
// Electron escapes ':' in a partition name to '%3A' for the folder name.
// A Windows profile folder containing '%3A' gets a cookie store that reads
// empty and never persists, so every cookie-auth connection would 401 and
// re-prompt for sign-in on each dial. The partition path component must
// therefore never need escaping, for ANY connection id.
for (const id of ['10-0-0-88-9119', 'we ird/id:€', 'a:b/c%3Ad']) {
const reg = registry('local', [remote(id, 'https://gw-a.example.com')])
const pathComponent = resolveOauthPartition('https://gw-a.example.com/api/auth/ws-ticket', {
registry: reg
}).slice('persist:'.length)
expect(pathComponent).not.toContain(':')
expect(pathComponent).not.toContain('%')
}
})
it('breaks same-URL ties deterministically (identical jar for identical gateway)', () => {
const reg = registry('local', [
remote('zeta', 'https://gw-a.example.com'),
+9 -1
View File
@@ -36,7 +36,15 @@
export const LEGACY_OAUTH_PARTITION = 'persist:hermes-remote-oauth'
const CONNECTION_PARTITION_PREFIX = `${LEGACY_OAUTH_PARTITION}:conn:`
// Colon-free ON PURPOSE: Electron escapes ':' in a partition name to '%3A' in
// the on-disk profile folder, and a Windows profile folder whose name contains
// '%3A' gets a cookie store the network stack can neither read nor write (a
// jar seeded into it reads back zero cookies, `cookies.set()` never reaches
// disk, and every cookie-authenticated request 401s as `no_cookie`). A jar
// the app cannot see means the dial always looks signed-out and the user is
// asked to sign in again on every connect. Keep this path component to
// [A-Za-z0-9._-] — never a character Electron has to escape.
const CONNECTION_PARTITION_PREFIX = `${LEGACY_OAUTH_PARTITION}-conn-`
export interface PartitionRegistrySnapshot {
primary?: unknown