/** * Phase 0 P1 integration — proves the REAL data-plane wiring works IN-PROCESS: * browser upgrade → real `authorizeUpgrade` → real relay-auth `onUpgrade` (Origin/CSWSH + token * verify + DPoP PoP + single-use jti) → real `createRelayNode` opaque splice → a REAL agent-side * `createMuxSession`. One application payload is sealed by the browser (P4 E2E), spliced through * the relay, and opened by the agent — and vice versa. INV2 is asserted directly: every byte the * relay handled on the browser socket is ciphertext (never the plaintext). */ import { describe, it, expect } from 'vitest' import { APP_SUBPROTOCOL, encodeTokenSubprotocol } from 'relay-contracts' import { createMuxSession, type MuxStreamHandle } from 'term-relay/mux/mux-session.js' import type { WebSocketLike } from 'term-relay/data-plane/ws-like.js' import type { UpgradeRequest } from 'term-relay/data-plane/upgrade.js' import type { ScheduleHandle } from 'term-relay/mux/heartbeat.js' import { buildPhase0World, type Phase0World } from '../src/wiring/relay-world.js' import { buildDataPlane } from '../src/wiring/data-plane.js' import { makeSocketPair } from '../src/wiring/socket-pipe.js' const enc = new TextEncoder() const dec = new TextDecoder() /** A scheduler that never fires — keeps heartbeat timers out of the deterministic test. */ const manualSchedule = (_fn: () => void, _ms: number): ScheduleHandle => ({ cancel: () => {} }) /** Records every buffer the relay handled on the browser socket (both directions) for INV2. */ function recordingWs(inner: WebSocketLike, seen: Uint8Array[]): WebSocketLike { return { send: (d) => { seen.push(d) inner.send(d) }, close: (c) => inner.close(c), onMessage: (h) => inner.onMessage((d) => { seen.push(d) h(d) }), onClose: (h) => inner.onClose(h), } } interface AgentSide { stream(): MuxStreamHandle | null inbox: Uint8Array[] } /** Attach a REAL agent-side mux to the relay listener over an in-memory socket pair. */ function attachAgent(dp: ReturnType, world: Phase0World): AgentSide { const { a: relaySide, b: agentSide } = makeSocketPair() const inbox: Uint8Array[] = [] let agentStream: MuxStreamHandle | null = null const agentMux = createMuxSession({ role: 'agent', sendWire: (frame) => agentSide.send(frame), onOpen: (_open, stream) => { agentStream = stream stream.onData((cipher) => inbox.push(cipher)) // opaque cipher from the relay splice stream.onClose(() => {}) }, onData: () => {}, onClose: () => {}, onDead: () => {}, maxFrameBytes: 1024 * 1024, initialWindowBytes: 256 * 1024, schedule: manualSchedule, }) agentSide.onMessage((b) => agentMux.onWire(b)) agentSide.onClose(() => agentMux.close()) // The relay accepts the agent tunnel (verifyPeer → registry-bound hostId). dp.listener.attach(relaySide, new Uint8Array([0xde, 0xad])) return { stream: () => agentStream, inbox } } function upgradeRequest(world: Phase0World, capRaw: string, dpopProof: string, origin?: string): UpgradeRequest { return { host: `${world.host.subdomain}.${world.baseDomain}`, origin: origin ?? world.host.origin, url: '/term', subprotocols: [APP_SUBPROTOCOL, encodeTokenSubprotocol(capRaw)], cookies: {}, remoteAddr: '127.0.0.1', dpop: { proof: dpopProof, publicKeyThumbprint: null }, activeSessionCount: 0, } } describe('Phase 0 · real relay-node splice (P1)', () => { it('round-trips one E2E payload through the real splice; relay sees only ciphertext (INV2)', async () => { const world = await buildPhase0World() const dp = buildDataPlane({ config: (await import('../src/wiring/data-plane.js')).makeDataPlaneConfig({ baseDomain: world.baseDomain }), authorizer: world.authorizer, resolver: world.resolver, mtls: world.mtls, now: () => world.now, schedule: manualSchedule, }) // P4: establish the real browser↔agent E2E session out-of-band (authenticated ECDH). const e2e = await world.establishSession() const agent = attachAgent(dp, world) // Browser upgrade — the REAL authz path (Origin/CSWSH + capability verify + DPoP PoP). const cap = await world.issueCap() const { a: relayBrowserWs, b: browserWs } = makeSocketPair() const relaySeen: Uint8Array[] = [] const browserReceived: Uint8Array[] = [] browserWs.onMessage((d) => browserReceived.push(d)) await dp.node.handleBrowserUpgrade( upgradeRequest(world, cap.raw, cap.dpop.proofJws), recordingWs(relayBrowserWs, relaySeen), ) // Authorization passed → the relay opened a stream on the agent tunnel → splice is live. expect(agent.stream()).not.toBeNull() // Browser → agent: seal 'whoami\n' (c2h), splice opaque, agent opens it. const c2h = e2e.client.seal(enc.encode('whoami\n')) browserWs.send(c2h) expect(agent.inbox.length).toBe(1) expect(dec.decode(e2e.host.open(agent.inbox[0]!))).toBe('whoami\n') // Agent → browser: seal 'uid=501\n' (h2c), splice opaque, browser opens it. const h2c = e2e.host.seal(enc.encode('uid=501\n')) agent.stream()!.writeData(h2c) expect(browserReceived.length).toBe(1) expect(dec.decode(e2e.client.open(browserReceived[0]!))).toBe('uid=501\n') // INV2: every byte the relay handled on the browser socket is ciphertext, never plaintext. expect(relaySeen.length).toBeGreaterThan(0) const asText = relaySeen.map((b) => dec.decode(b)).join('') expect(asText).not.toContain('whoami') expect(asText).not.toContain('uid=501') }) it('denies a foreign Origin (real CSWSH check) → 401, no stream opened', async () => { const world = await buildPhase0World() const dp = buildDataPlane({ config: (await import('../src/wiring/data-plane.js')).makeDataPlaneConfig({ baseDomain: world.baseDomain }), authorizer: world.authorizer, resolver: world.resolver, mtls: world.mtls, now: () => world.now, schedule: manualSchedule, }) const agent = attachAgent(dp, world) const cap = await world.issueCap() const { a: relayBrowserWs } = makeSocketPair() let closedWith: number | undefined const ws: WebSocketLike = { send: () => {}, close: (c) => { closedWith = c }, onMessage: () => {}, onClose: () => {}, } void relayBrowserWs await dp.node.handleBrowserUpgrade( upgradeRequest(world, cap.raw, cap.dpop.proofJws, 'https://evil.example.com'), ws, ) expect(closedWith).toBe(401) expect(agent.stream()).toBeNull() expect(world.audit.events.some((e) => e.reason === 'bad_origin')).toBe(true) }) it('denies a missing DPoP proof (real PoP check) → 401, no stream opened', async () => { const world = await buildPhase0World() const dp = buildDataPlane({ config: (await import('../src/wiring/data-plane.js')).makeDataPlaneConfig({ baseDomain: world.baseDomain }), authorizer: world.authorizer, resolver: world.resolver, mtls: world.mtls, now: () => world.now, schedule: manualSchedule, }) const agent = attachAgent(dp, world) const cap = await world.issueCap() let closedWith: number | undefined const ws: WebSocketLike = { send: () => {}, close: (c) => { closedWith = c }, onMessage: () => {}, onClose: () => {}, } await dp.node.handleBrowserUpgrade(upgradeRequest(world, cap.raw, '' /* no DPoP */), ws) expect(closedWith).toBe(401) expect(agent.stream()).toBeNull() }) })