The gap
EngineClient.signedInAccount() is set when a start resolves against an engine and cleared on teardown or on a promotion that could not cold-start one. It is not cleared when a follower's leadership goes away and no successor can serve it — the tab keeps reporting a session over an origin that hosts no engine at all.
Reachable without an attacker: a tab whose promotion aborted retires from the election for good (abortPromotion closes it), so an origin can reach a state where no tab is able to lead. A follower there still answers signedInAccount() with its account.
Since #914 the web UI derives isAuthenticated from exactly this value, so RequireAuth admits and FileBrowser mounts over a nonexistent engine. Every facade call then parks indefinitely rather than failing: BroadcastTransport.brokerPort awaits leaderReady with no timeout, and the 5 s brokerage deadline starts only after it resolves. No plaintext is exposed — the tab renders a vault it can never read.
This is not a regression: before #914 the same state left authStore.isAuthenticated true just as permanently. The session is now sourced from the engine plane, which is where the fix belongs.
Why it was not fixed with #914
The obvious signal — "leadership moved" — fires on every ordinary failover, where the tab is still signed in a moment later. Clearing on it would sign a member out on every leader handoff. The honest signal is narrower: this tab is no longer served and no leadership can serve it.
Shape of the fix
Two halves, either of which alone leaves a hole:
- Give
brokerPort's wait for a leader the same deadline every other brokerage step has, so a leaderless origin fails a read rather than parking it forever.
- Clear the published account when a re-brokerage settles as a refusal naming a different account, or as a leaderless timeout — not on the leadership change itself.
Consider also whether abortPromotion should re-queue instead of retiring the tab from the election permanently. It closes the election today to avoid a hot spin against a deterministic failure (a spawnWorker that always throws); a bounded re-entry would remove the "no tab can lead" state that makes this reachable at all.
Gate
packages/client unit suite, plus a Client Browser Suite case over real Web Locks: no tab able to lead, and the surviving tab must report no session rather than render a vault.
Related
Raised by the /security-review gate on the PR that made the session engine-derived (#1342), and accepted as residual there.
Part of #655
The gap
EngineClient.signedInAccount()is set when a start resolves against an engine and cleared on teardown or on a promotion that could not cold-start one. It is not cleared when a follower's leadership goes away and no successor can serve it — the tab keeps reporting a session over an origin that hosts no engine at all.Reachable without an attacker: a tab whose promotion aborted retires from the election for good (
abortPromotioncloses it), so an origin can reach a state where no tab is able to lead. A follower there still answerssignedInAccount()with its account.Since #914 the web UI derives
isAuthenticatedfrom exactly this value, soRequireAuthadmits andFileBrowsermounts over a nonexistent engine. Every facade call then parks indefinitely rather than failing:BroadcastTransport.brokerPortawaitsleaderReadywith no timeout, and the 5 s brokerage deadline starts only after it resolves. No plaintext is exposed — the tab renders a vault it can never read.This is not a regression: before #914 the same state left
authStore.isAuthenticatedtrue just as permanently. The session is now sourced from the engine plane, which is where the fix belongs.Why it was not fixed with #914
The obvious signal — "leadership moved" — fires on every ordinary failover, where the tab is still signed in a moment later. Clearing on it would sign a member out on every leader handoff. The honest signal is narrower: this tab is no longer served and no leadership can serve it.
Shape of the fix
Two halves, either of which alone leaves a hole:
brokerPort's wait for a leader the same deadline every other brokerage step has, so a leaderless origin fails a read rather than parking it forever.Consider also whether
abortPromotionshould re-queue instead of retiring the tab from the election permanently. It closes the election today to avoid a hot spin against a deterministic failure (aspawnWorkerthat always throws); a bounded re-entry would remove the "no tab can lead" state that makes this reachable at all.Gate
packages/clientunit suite, plus aClient Browser Suitecase over real Web Locks: no tab able to lead, and the surviving tab must report no session rather than render a vault.Related
Raised by the
/security-reviewgate on the PR that made the session engine-derived (#1342), and accepted as residual there.Part of #655