mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
fix(browser): reject an unrecognized identity mode at the IPC door
normalizeBrowserUserAgentMode turned any unrecognized value into 'clean', so the IPC door reported success for a mode it had quietly replaced, while the RPC door validates against z.enum(['clean', 'native']) and rejects. One concept answered an unknown value two different ways, and a future mode name was silently downgraded rather than refused. The handler now rejects, which is what the RPC door does and what the renderer already handles — its catch puts the message in the error slot. Returning a result instead would have meant inventing a fourth error code for a case no legitimate caller can reach. normalizeBrowserUserAgentMode had no other consumer, so it goes with the change: leaving a coercion helper called "normalize" in shared/ invites the behaviour straight back in.
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { handleMock, removeHandlerMock, createProfileMock, routeIdentityMock, detectBrowsersMock } =
|
||||
vi.hoisted(() => ({
|
||||
handleMock: vi.fn(),
|
||||
removeHandlerMock: vi.fn(),
|
||||
createProfileMock: vi.fn(),
|
||||
routeIdentityMock: vi.fn(),
|
||||
detectBrowsersMock: vi.fn(() => [])
|
||||
}))
|
||||
const {
|
||||
handleMock,
|
||||
removeHandlerMock,
|
||||
createProfileMock,
|
||||
routeIdentityMock,
|
||||
detectBrowsersMock,
|
||||
setBrowserIdentityModeMock
|
||||
} = vi.hoisted(() => ({
|
||||
handleMock: vi.fn(),
|
||||
removeHandlerMock: vi.fn(),
|
||||
createProfileMock: vi.fn(),
|
||||
routeIdentityMock: vi.fn(),
|
||||
detectBrowsersMock: vi.fn(() => []),
|
||||
setBrowserIdentityModeMock: vi.fn(async () => ({ ok: true }))
|
||||
}))
|
||||
|
||||
vi.mock('../browser/browser-identity-mode-store', () => ({
|
||||
setBrowserIdentityMode: setBrowserIdentityModeMock,
|
||||
getBrowserIdentityModeStatus: vi.fn(() => ({ identity: {}, migrationNotice: null }))
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
BrowserWindow: { fromWebContents: vi.fn() },
|
||||
@@ -51,6 +63,8 @@ describe('browser session profile IPC', () => {
|
||||
routeIdentityMock.mockReset()
|
||||
detectBrowsersMock.mockReset()
|
||||
detectBrowsersMock.mockReturnValue([])
|
||||
setBrowserIdentityModeMock.mockReset()
|
||||
setBrowserIdentityModeMock.mockResolvedValue({ ok: true })
|
||||
setTrustedBrowserRendererWebContentsId(null)
|
||||
})
|
||||
|
||||
@@ -63,6 +77,33 @@ describe('browser session profile IPC', () => {
|
||||
} as Electron.WebContents
|
||||
}
|
||||
|
||||
function identitySetHandler(): (
|
||||
event: { sender: Electron.WebContents },
|
||||
mode: unknown
|
||||
) => Promise<unknown> {
|
||||
registerBrowserHandlers()
|
||||
return handleMock.mock.calls.find(([channel]) => channel === 'browser:identity:set')?.[1]
|
||||
}
|
||||
|
||||
// Why reject rather than coerce: the RPC door validates mode against z.enum(['clean','native'])
|
||||
// and rejects. Coercing an unrecognized value to 'clean' here made one concept answer an unknown
|
||||
// value two different ways, and reported success for a mode that was quietly replaced.
|
||||
it('refuses an unrecognized identity mode instead of silently selecting Cleaned', async () => {
|
||||
setTrustedBrowserRendererWebContentsId(91)
|
||||
const handler = identitySetHandler()
|
||||
|
||||
await expect(handler({ sender: trustedSender() }, 'rotating')).rejects.toThrow(/rotating/)
|
||||
expect(setBrowserIdentityModeMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('commits a recognized identity mode unchanged', async () => {
|
||||
setTrustedBrowserRendererWebContentsId(91)
|
||||
const handler = identitySetHandler()
|
||||
|
||||
await expect(handler({ sender: trustedSender() }, 'native')).resolves.toEqual({ ok: true })
|
||||
expect(setBrowserIdentityModeMock).toHaveBeenCalledWith('native')
|
||||
})
|
||||
|
||||
function clientHostDetectHandler(): (
|
||||
event: { sender: Electron.WebContents },
|
||||
args: { environmentId: string }
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
getBrowserIdentityModeStatus,
|
||||
setBrowserIdentityMode
|
||||
} from '../browser/browser-identity-mode-store'
|
||||
import { normalizeBrowserUserAgentMode } from '../../shared/browser-user-agent-mode'
|
||||
|
||||
export function registerBrowserSessionProfileHandlers(): void {
|
||||
ipcMain.removeHandler('browser:session:listProfiles')
|
||||
@@ -62,7 +61,14 @@ export function registerBrowserSessionProfileHandlers(): void {
|
||||
if (!isTrustedBrowserRenderer(event.sender)) {
|
||||
return null
|
||||
}
|
||||
return setBrowserIdentityMode(normalizeBrowserUserAgentMode(mode))
|
||||
// Why reject rather than coerce: the RPC door validates against z.enum(['clean', 'native'])
|
||||
// and rejects. Coercing an unrecognized value to 'clean' made one concept answer an unknown
|
||||
// value two different ways, and reported success for a mode that was quietly replaced —
|
||||
// silently downgrading a future mode name the caller believed was honoured.
|
||||
if (mode !== 'clean' && mode !== 'native') {
|
||||
throw new Error(`Unsupported browser identity mode: ${String(mode)}`)
|
||||
}
|
||||
return setBrowserIdentityMode(mode)
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
|
||||
@@ -36,7 +36,3 @@ export type BrowserIdentityModeSetResult =
|
||||
}
|
||||
identity: BrowserIdentityModeSnapshot
|
||||
}
|
||||
|
||||
export function normalizeBrowserUserAgentMode(mode: unknown): BrowserUserAgentMode {
|
||||
return mode === 'native' ? 'native' : 'clean'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user