79 lines
4.4 KiB
TypeScript
79 lines
4.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { desktopEnvironment, desktopStateHome, isDesktopPermissionAllowed, isExternalUrl, isInternalUrl, storedPort } from '../../electron/runtime'
|
|
import { bindDesktopLifecycle, desktopRequestGuard, desktopUpdateGuard, isDesktopRequestAllowed } from '../../packages/server/src/services/desktop-runtime'
|
|
|
|
afterEach(() => { vi.unstubAllEnvs(); vi.unstubAllGlobals() })
|
|
|
|
describe('desktop runtime boundary', () => {
|
|
it('pins the server to loopback and keeps data under the selected state home', () => {
|
|
const env = desktopEnvironment('/app', '/state', 54321, { BIND_HOST: '0.0.0.0', PORT: '8648', HERMES_HOME: '/agent' })
|
|
expect(env).toMatchObject({ BIND_HOST: '127.0.0.1', PORT: '54321', HERMES_HOME: '/agent', HERMES_WEB_UI_HOME: '/state', HERMES_WEBUI_STATE_DIR: '/state', HERMES_DESKTOP: 'true' })
|
|
expect(desktopStateHome('/profile', {})).toBe('/profile/state')
|
|
expect(desktopStateHome('/profile', { HERMES_WEB_UI_HOME: '/custom' })).toBe('/custom')
|
|
})
|
|
|
|
it('accepts only the local Host and same-origin browser traffic', () => {
|
|
expect(isDesktopRequestAllowed({ headers: { host: '127.0.0.1:8648' } })).toBe(true)
|
|
expect(isDesktopRequestAllowed({ headers: { host: '127.0.0.1:8648', origin: 'http://127.0.0.1:8648' } })).toBe(true)
|
|
expect(isDesktopRequestAllowed({ headers: { host: 'attacker.test:8648' } })).toBe(false)
|
|
expect(isDesktopRequestAllowed({ headers: { host: '127.0.0.1:8648', origin: 'https://attacker.test' } })).toBe(false)
|
|
})
|
|
|
|
it('does not restrict the standalone web server', async () => {
|
|
vi.stubEnv('HERMES_DESKTOP', '')
|
|
const next = vi.fn()
|
|
await desktopRequestGuard()({ req: { headers: { host: 'example.com' } } } as any, next)
|
|
expect(next).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('blocks requests and web self-update in the desktop', async () => {
|
|
vi.stubEnv('HERMES_DESKTOP', 'true')
|
|
const next = vi.fn()
|
|
const ctx = { req: { headers: { host: 'attacker.test' } }, status: 200 } as any
|
|
await desktopRequestGuard()(ctx, next)
|
|
expect(ctx.status).toBe(403)
|
|
await desktopUpdateGuard()(ctx, next)
|
|
expect(ctx.status).toBe(409)
|
|
expect(ctx.body.code).toBe('DESKTOP_INSTALLER_REQUIRED')
|
|
expect(next).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('signals readiness and requests graceful shutdown through the private parent port', () => {
|
|
vi.stubEnv('HERMES_DESKTOP', 'true')
|
|
const postMessage = vi.fn()
|
|
const on = vi.fn()
|
|
vi.stubGlobal('process', { ...process, env: process.env, parentPort: { on, postMessage } })
|
|
const shutdown = vi.fn().mockResolvedValue(undefined)
|
|
bindDesktopLifecycle('http://127.0.0.1:12345', shutdown)
|
|
expect(postMessage).toHaveBeenCalledWith({ type: 'hermes:ready', url: 'http://127.0.0.1:12345' })
|
|
on.mock.calls[0]
|
|
expect(shutdown).not.toHaveBeenCalled()
|
|
on.mock.calls[0]
|
|
expect(shutdown).toHaveBeenCalledWith('electron-quit')
|
|
})
|
|
|
|
it('prevents navigation escapes and opening unsafe URL schemes', () => {
|
|
expect(isInternalUrl('http://127.0.0.1:1234/#/hermes/chat', 'http://127.0.0.1:1234')).toBe(true)
|
|
expect(isInternalUrl('http://127.0.0.1:4321/', 'http://127.0.0.1:1234')).toBe(false)
|
|
for (const url of ['javascript:alert(1)', 'file:///etc/passwd', 'data:text/html,test', 'invalid']) expect(isExternalUrl(url)).toBe(false)
|
|
expect(isExternalUrl('https://example.com')).toBe(true)
|
|
expect(isExternalUrl('mailto:hello@example.com')).toBe(true)
|
|
})
|
|
|
|
it('validates saved ports before reusing them', () => {
|
|
for (const value of [undefined, '8648', -1, 80, 65536, 1.5]) expect(storedPort(value)).toBe(0)
|
|
expect(storedPort(18648)).toBe(18648)
|
|
})
|
|
|
|
it('keeps microphone, copy and completion notifications available only to the app', () => {
|
|
const origin = 'http://127.0.0.1:12345'
|
|
expect(isDesktopPermissionAllowed('notifications', origin, origin)).toBe(true)
|
|
expect(isDesktopPermissionAllowed('clipboard-sanitized-write', origin, origin)).toBe(true)
|
|
expect(isDesktopPermissionAllowed('media', origin, origin, ['audio'])).toBe(true)
|
|
expect(isDesktopPermissionAllowed('media', origin, origin, ['video'])).toBe(false)
|
|
expect(isDesktopPermissionAllowed('media', origin, origin)).toBe(false)
|
|
expect(isDesktopPermissionAllowed('notifications', 'https://other.test', origin)).toBe(false)
|
|
expect(isDesktopPermissionAllowed('geolocation', origin, origin)).toBe(false)
|
|
})
|
|
})
|