mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
fix(wsl): publish the resolved WSL hosts gate state to child processes (kill switch follow-up) (#35543)
* fix(wsl): publish the resolved WSL hosts gate state to child processes Children inherit ORCA_INTERNAL_WSL_HOSTS=1 even when a remote policy block closed the gate, and none inherit serve-only. Main now publishes its resolved state as ORCA_WSL_HOSTS_GATE_STATE at startup, and the shared serving check in a child trusts it over the internal gate env. A process that resolves the gate itself ignores an inherited value, so a relaunch never carries a stale state. ORCA_INTERNAL_WSL_HOSTS is left intact so a relaunched or updated Orca keeps fetching the policy. Co-Authored-By: Claude <noreply@anthropic.com> * fix(wsl): main ignores an inherited WSL hosts gate state from its first line A relaunch or the updater's installer inherits the parent's published ORCA_WSL_HOSTS_GATE_STATE. Main's entry now marks the gate as its own to resolve before preflight, so even a read before configure falls back to the internal gate env, and configure always overwrites the value. Tests pin that a stale inherited closed or open is ignored and overwritten. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import { leaseAppImageGenerationForCurrentProcess } from './cli/appimage-generat
|
||||
import { wakeRememberedWslHosts } from './wsl/wsl-host-early-wake'
|
||||
import { wslHostsServing } from './wsl/wsl-host-gate'
|
||||
import { configureWslHostsGateFromDisk } from './wsl/wsl-host-gate-startup'
|
||||
import { ignoreInheritedWslHostsGateState } from '../shared/wsl-host-internal-gate'
|
||||
|
||||
function openMainWindow(options: { revealOnDidFinishLoad?: boolean } = {}): BrowserWindow {
|
||||
return openMainWindowController(options)
|
||||
@@ -101,6 +102,8 @@ const handleMacAppActivation = createMacAppActivationHandler({
|
||||
requestActivation: requestDesktopActivation
|
||||
})
|
||||
|
||||
// A relaunch or installer inherits the parent's published WSL gate state; this process recomputes.
|
||||
ignoreInheritedWslHostsGateState()
|
||||
// The uninstaller's holder release runs instead of the app, never alongside it.
|
||||
const preflightReady =
|
||||
!runWslHolderUninstallPreflight() &&
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
wslHostsWindowArguments,
|
||||
WSL_HOSTS_GATED_OFF_MESSAGE
|
||||
} from './wsl-host-gate'
|
||||
import { isWslHostsServing } from '../../shared/wsl-host-internal-gate'
|
||||
import {
|
||||
ignoreInheritedWslHostsGateState,
|
||||
isWslHostsServing,
|
||||
WSL_HOSTS_GATE_STATE_ENV
|
||||
} from '../../shared/wsl-host-internal-gate'
|
||||
|
||||
const wslHost = {
|
||||
orcadDeployment: { kind: 'wsl' as const, distro: 'Ubuntu', localPort: 41000, remotePort: 6768 }
|
||||
@@ -161,4 +165,48 @@ describe('WSL hosts gate', () => {
|
||||
expect(() => assertWslHostReachable(sshHost)).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('publishing the resolved state to child processes', () => {
|
||||
it.each([
|
||||
['open', '1', facts(false, false), undefined],
|
||||
['serve-only', undefined, facts(true, false), undefined],
|
||||
['closed', undefined, facts(false, false), undefined],
|
||||
['closed', '1', facts(false, false), 'block' as const],
|
||||
['serve-only', '1', facts(false, true), 'block' as const]
|
||||
])('publishes %s', (state, gateEnv, saved, policy) => {
|
||||
vi.stubEnv('ORCA_INTERNAL_WSL_HOSTS', gateEnv)
|
||||
configureWslHostsGate({ ...saved, ...(policy ? { policy: () => policy } : {}) })
|
||||
|
||||
expect(process.env[WSL_HOSTS_GATE_STATE_ENV]).toBe(state)
|
||||
})
|
||||
|
||||
// A relaunch or the updater's installer inherits the parent's value; main must recompute.
|
||||
it.each([
|
||||
['closed', '1', 'open', true],
|
||||
['open', undefined, 'closed', false]
|
||||
])(
|
||||
'ignores a stale inherited %s and overwrites it',
|
||||
(inherited, gateEnv, recomputed, serving) => {
|
||||
vi.stubEnv(WSL_HOSTS_GATE_STATE_ENV, inherited)
|
||||
vi.stubEnv('ORCA_INTERNAL_WSL_HOSTS', gateEnv)
|
||||
ignoreInheritedWslHostsGateState()
|
||||
expect(isWslHostsServing(process.env)).toBe(gateEnv === '1')
|
||||
|
||||
configureWslHostsGate(facts(false, false))
|
||||
|
||||
expect(wslHostsGateState()).toBe(recomputed)
|
||||
expect(process.env[WSL_HOSTS_GATE_STATE_ENV]).toBe(recomputed)
|
||||
expect(wslHostsServing()).toBe(serving)
|
||||
expect(isWslHostsServing(process.env)).toBe(serving)
|
||||
}
|
||||
)
|
||||
|
||||
it('withdraws it on reset', () => {
|
||||
vi.stubEnv('ORCA_INTERNAL_WSL_HOSTS', '1')
|
||||
configureWslHostsGate(facts(false, false))
|
||||
resetWslHostsGateForTests()
|
||||
|
||||
expect(process.env[WSL_HOSTS_GATE_STATE_ENV]).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
isWslHostsInternalGateOpen,
|
||||
noteWslHostsPolicyBlocked,
|
||||
noteWslHostsServeOnly,
|
||||
publishWslHostsGateState,
|
||||
resolveWslHostsGateState,
|
||||
type WslHostsGateState
|
||||
} from '../../shared/wsl-host-internal-gate'
|
||||
@@ -58,22 +59,23 @@ export function configureWslHostsGate(facts: {
|
||||
policy?: () => WslHostsPolicyState
|
||||
}): void {
|
||||
notePolicy(readPolicy(facts.policy))
|
||||
if (wslHostsGateOpen()) {
|
||||
promoted = null
|
||||
noteWslHostsServeOnly(false)
|
||||
return
|
||||
}
|
||||
promoted = {
|
||||
hasSavedWslHost: readFact(facts.hasSavedWslHost),
|
||||
hasWslMoveCutover: readFact(facts.hasWslMoveCutover)
|
||||
}
|
||||
noteWslHostsServeOnly(wslHostsGateState() === 'serve-only')
|
||||
promoted = wslHostsGateOpen()
|
||||
? null
|
||||
: {
|
||||
hasSavedWslHost: readFact(facts.hasSavedWslHost),
|
||||
hasWslMoveCutover: readFact(facts.hasWslMoveCutover)
|
||||
}
|
||||
const state = wslHostsGateState()
|
||||
noteWslHostsServeOnly(state === 'serve-only')
|
||||
// Children inherit ORCA_INTERNAL_WSL_HOSTS even when a block closed it; this tells them the truth.
|
||||
publishWslHostsGateState(process.env, state)
|
||||
}
|
||||
|
||||
export function resetWslHostsGateForTests(): void {
|
||||
promoted = null
|
||||
noteWslHostsServeOnly(false)
|
||||
notePolicy(null)
|
||||
publishWslHostsGateState(process.env, null)
|
||||
}
|
||||
|
||||
export function wslHostsGateState(): WslHostsGateState {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
ignoreInheritedWslHostsGateState,
|
||||
isWslHostsServing,
|
||||
publishWslHostsGateState,
|
||||
WSL_HOSTS_GATE_STATE_ENV
|
||||
} from './wsl-host-internal-gate'
|
||||
|
||||
describe('WSL hosts gate state inherited from main', () => {
|
||||
afterEach(() => {
|
||||
publishWslHostsGateState({}, null)
|
||||
})
|
||||
|
||||
// A child of a blocked run inherits ORCA_INTERNAL_WSL_HOSTS=1 but must not serve WSL state.
|
||||
it('lets a child trust the state its parent published over the internal gate env', () => {
|
||||
const gate = { ORCA_INTERNAL_WSL_HOSTS: '1' }
|
||||
expect(isWslHostsServing({ ...gate, [WSL_HOSTS_GATE_STATE_ENV]: 'closed' })).toBe(false)
|
||||
expect(isWslHostsServing({ ...gate, [WSL_HOSTS_GATE_STATE_ENV]: 'open' })).toBe(true)
|
||||
expect(isWslHostsServing({ [WSL_HOSTS_GATE_STATE_ENV]: 'serve-only' })).toBe(true)
|
||||
})
|
||||
|
||||
it('falls back to the internal gate env when nothing valid was published', () => {
|
||||
expect(isWslHostsServing({ ORCA_INTERNAL_WSL_HOSTS: '1' })).toBe(true)
|
||||
expect(isWslHostsServing({ [WSL_HOSTS_GATE_STATE_ENV]: 'bogus' })).toBe(false)
|
||||
})
|
||||
|
||||
it('ignores an inherited state once this process resolved the gate itself', () => {
|
||||
const env: Record<string, string | undefined> = { [WSL_HOSTS_GATE_STATE_ENV]: 'open' }
|
||||
publishWslHostsGateState(env, 'closed')
|
||||
|
||||
expect(env[WSL_HOSTS_GATE_STATE_ENV]).toBe('closed')
|
||||
expect(isWslHostsServing({ [WSL_HOSTS_GATE_STATE_ENV]: 'open' })).toBe(false)
|
||||
})
|
||||
|
||||
it("ignores an inherited state from main's entry on, before the gate is configured", () => {
|
||||
ignoreInheritedWslHostsGateState()
|
||||
|
||||
expect(isWslHostsServing({ [WSL_HOSTS_GATE_STATE_ENV]: 'open' })).toBe(false)
|
||||
expect(
|
||||
isWslHostsServing({ ORCA_INTERNAL_WSL_HOSTS: '1', [WSL_HOSTS_GATE_STATE_ENV]: 'closed' })
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -37,8 +37,44 @@ export function noteWslHostsPolicyBlocked(blocked: boolean): void {
|
||||
policyBlockNoted = blocked
|
||||
}
|
||||
|
||||
/**
|
||||
* Main publishes its resolved state here so the processes it spawns (CLI, servers, terminals) read
|
||||
* the gate as main does: they inherit the internal gate env var but neither note above.
|
||||
*/
|
||||
export const WSL_HOSTS_GATE_STATE_ENV = 'ORCA_WSL_HOSTS_GATE_STATE'
|
||||
// True once this process resolved the gate itself; an inherited value is then a parent's, so stale.
|
||||
let resolvedHere = false
|
||||
|
||||
export function publishWslHostsGateState(
|
||||
env: Record<string, string | undefined>,
|
||||
state: WslHostsGateState | null
|
||||
): void {
|
||||
resolvedHere = state !== null
|
||||
if (state === null) {
|
||||
delete env[WSL_HOSTS_GATE_STATE_ENV]
|
||||
} else {
|
||||
env[WSL_HOSTS_GATE_STATE_ENV] = state
|
||||
}
|
||||
}
|
||||
|
||||
/** Main's entry, before anything reads the gate: it recomputes its own, so an inherited value is stale. */
|
||||
export function ignoreInheritedWslHostsGateState(): void {
|
||||
resolvedHere = true
|
||||
}
|
||||
|
||||
function inheritedGateState(
|
||||
env: Readonly<Record<string, string | undefined>>
|
||||
): WslHostsGateState | null {
|
||||
const value = env[WSL_HOSTS_GATE_STATE_ENV]
|
||||
return value === 'open' || value === 'serve-only' || value === 'closed' ? value : null
|
||||
}
|
||||
|
||||
/** Open, or main found saved WSL work behind the closed gate. */
|
||||
export function isWslHostsServing(env: Readonly<Record<string, string | undefined>>): boolean {
|
||||
const inherited = resolvedHere ? null : inheritedGateState(env)
|
||||
if (inherited) {
|
||||
return inherited !== 'closed'
|
||||
}
|
||||
return (isWslHostsInternalGateOpen(env) && !policyBlockNoted) || serveOnlyNoted
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user