Files
orca/src/shared/web-origin-security.test.ts
T
OrcaWinandm4air fee083fa47 feat(runtime): session-only browser keys and opt-in for pairing from plain-http pages (host) (#27540)
* feat(runtime): session-only browser keys and an opt-in for pairing from plain-http pages (host)

- pairing.registerDeviceKey accepts ephemeral: true (advertised as
  pairing.device-key-ephemeral.v1). Such keyed rows are dropped when the host
  restarts and after 12 hours with no live socket; a sweep removes them and cuts
  any socket still bound to them, so later key auth gets device_revoked.
- The WebSocket upgrade Origin is recorded per socket. A first pairing (a
  never-used pairing code) from an http:// page on another machine is refused
  with a sealed insecure_origin error unless the operator sets
  ORCA_ALLOW_INSECURE_WEB_PAIRING=1. Already-paired browsers, https and loopback
  pages, and native clients are unchanged.
- Registry file reading moved to device-registry-file-read.ts to keep the
  registry under the size cap.

* fix(runtime): gate first pairings only for the marked web client, never phones

React Native sends an http Origin on every LAN WebSocket, so the Origin alone
refused first-time QR pairing from released phones. The web bundle now marks
its upgrade with ?orca-client=web; only a marked socket from an http page on
another machine is gated. Released hosts ignore the query string.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 14:40:36 -07:00

48 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
hasWebClientUpgradeMarker,
isInsecureRemoteWebOrigin,
withWebClientUpgradeMarker
} from './web-origin-security'
describe('isInsecureRemoteWebOrigin', () => {
it('flags plain http pages served to other machines', () => {
expect(isInsecureRemoteWebOrigin('http://192.168.1.20:6768')).toBe(true)
expect(isInsecureRemoteWebOrigin('http://desk.local')).toBe(true)
expect(isInsecureRemoteWebOrigin('http://[fe80::1]:6768')).toBe(true)
})
it('treats https and loopback pages as secure', () => {
expect(isInsecureRemoteWebOrigin('https://desk.example.com')).toBe(false)
expect(isInsecureRemoteWebOrigin('http://localhost:6768')).toBe(false)
expect(isInsecureRemoteWebOrigin('http://app.localhost')).toBe(false)
expect(isInsecureRemoteWebOrigin('http://127.0.0.1:6768')).toBe(false)
expect(isInsecureRemoteWebOrigin('http://127.8.0.1')).toBe(false)
expect(isInsecureRemoteWebOrigin('http://[::1]:6768')).toBe(false)
})
it('leaves non-browser and unparseable origins to token auth', () => {
expect(isInsecureRemoteWebOrigin(undefined)).toBe(false)
expect(isInsecureRemoteWebOrigin('null')).toBe(false)
expect(isInsecureRemoteWebOrigin('file://')).toBe(false)
expect(isInsecureRemoteWebOrigin('not a url')).toBe(false)
})
})
describe('web client upgrade marker', () => {
it('round-trips through the endpoint the web bundle dials', () => {
const marked = withWebClientUpgradeMarker('ws://192.168.1.20:6768')
expect(marked).toBe('ws://192.168.1.20:6768/?orca-client=web')
expect(hasWebClientUpgradeMarker(new URL(marked).pathname + new URL(marked).search)).toBe(true)
expect(withWebClientUpgradeMarker('wss://desk.example.com/orca?x=1')).toBe(
'wss://desk.example.com/orca?x=1&orca-client=web'
)
})
it('is absent for phones, CLIs and other paths', () => {
expect(hasWebClientUpgradeMarker('/')).toBe(false)
expect(hasWebClientUpgradeMarker(undefined)).toBe(false)
expect(hasWebClientUpgradeMarker('/?orca-client=phone')).toBe(false)
})
})