When Voicebox routes actions between the server and a browser-owned root (opfs or handle) or a paired remote environment, messages travel over a structured JSON wire envelope (core/wire.ts) managed by the channel RPC broker (lib/channel.mjs).
core/wire.ts)core/wire.ts is a zero-dependency module that validates call and answer envelopes across both Node and browser workers:
// Call Envelope
{ "v": 1, "callId": "call_1", "tool": "read_notes", "descriptorId": "notes", "args": {}, "boundsEcho": { ... } }
// Success Answer Envelope
{ "v": 1, "callId": "call_1", "ok": true, "observed": { ... } }
// Refusal Answer Envelope
{ "v": 1, "callId": "call_1", "ok": false, "refused": "<rule-id>", "why": "Human-readable explanation" }
descriptorId & boundsEcho): Every tool call names the admitted extension (descriptorId) and echoes its admitted bounds. The receiving executor re-verifies the descriptor and bounds locally (unattributed-call if not admitted; bounds-mismatch if altered).decision: "allow" or grant: "everything"); any unrecognized property is rejected with unknown-field.ok: true must include an observed object containing measured results; bare assertions without observed data are rejected.lib/channel.mjs)createChannel and createExecutorDoor in lib/channel.mjs provide a 3-method bidirectional RPC interface (connected(), send(msg), answer(callId, answer)):
ask() is called, it refuses immediately without transmitting frames.abandon(), settling all in-flight requests with a named refusal rather than leaving callers hanging.createExecutorDoor catches the error and returns { ok: false, refused: "exec-threw" }.host-not-allowed, fetch-failed, budget-exhausted) cross the wire unchanged with their original rule identifiers and request budget accounting.| Failure Condition | When Browser Page is Peer | When Machine Host is Peer |
|---|---|---|
| Peer not connected | no-page |
machine-unreachable |
| Call timed out waiting for answer | page-timeout |
machine-timeout |
| Connection closed with call in flight | page-closed |
machine-closed |
/channel Authentication (server.mjs)The /channel WebSocket endpoint in server.mjs enforces an entitlement check before any connection is registered as the active pageSocket executor:
Origin header (http://127.0.0.1:<port>, http://localhost:<port>, or http://[::1]:<port>, plus a valid session cookie when VOICEBOX_LOOPBACK_AUTH=1 is enabled) are admitted directly. Because browsers enforce Origin headers on WebSocket upgrades, this blocks Cross-Site WebSocket Hijacking (CSWSH) from external web pages.Origin must send an authenticated hello frame within VOICEBOX_HELLO_BOUND_MS (default 5000ms):
{ "type": "hello", "role": "environment", "bearer": "vbx_..." }
The server validates bearer against .pairings.json (readPairings()).
executor-unauthenticated or bearer-refused, close with WebSocket status 1008, and are never attached as pageSocket.(For protecting loopback endpoints against untrusted local processes running under other OS accounts, see 13-local-browser-authentication-options.md and 18-loopback-session-auth.md).
node --test tests/channel.test.mjs