Multi-tenant reverse-tunnel service ("ngrok for Claude Code" with E2E): a
host-agent dials OUT to an operator-run relay; external devices reach the host
THROUGH the relay, routed by per-tenant subdomain, forwarding ciphertext only
(the relay never sees plaintext). Lets a customer reach their own self-hosted
web-terminal from anywhere with zero networking setup.
Packages — all tsc-strict + vitest green (656 tests), cross-package integration verified:
- relay-contracts: frozen shared contracts (mux frame codec, data model,
capability token, E2E envelope, pairing) — the src/types.ts analog
- term-relay: native WS mux + stateless data plane (subdomain routing, ciphertext forward)
- agent: host-agent (pairing, per-host Ed25519 + mTLS dial-out, forwards to 127.0.0.1:3000)
- control-plane: accounts/hosts registry, pairing-code flow, routing table, provisioning
- relay-e2e: browser<->agent E2E (X25519 ECDH through relay, AEAD, anti-replay, recoverable replay key)
- relay-auth: Passkey/WebAuthn, capability tokens, per-host certs, deny-by-default tenant isolation
- relay-web: browser login + Web Crypto E2E + client-side preview rendering
Security invariants INV1-15 enforced; cross-tenant isolation CI tripwire live
(.github/workflows/relay-tripwire.yml). Design + implementation-level plans in
docs/PLAN_RELAY_*.md and docs/EXPLORE_RELAY_SERVICE.md.
NOTE: generated autonomously per the reviewed plans. The security-critical
packages (relay-e2e, relay-auth) REQUIRE expert security audit before any real
deployment — passing tests prove self-consistency, not resistance to attackers.
Base app (src/, public/) unchanged; concurrent desktop work left uncommitted.
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
ContractDecodeError,
|
|
decodeCborMap,
|
|
decodeGoAwayReason,
|
|
decodeGoaway,
|
|
decodeOpen,
|
|
decodeWindowUpdate,
|
|
encodeCborMap,
|
|
encodeGoaway,
|
|
encodeOpen,
|
|
encodeWindowUpdate,
|
|
type MuxOpen,
|
|
} from '../src/index.js'
|
|
|
|
const sampleOpen = (): MuxOpen => ({
|
|
streamId: 7,
|
|
subdomain: 'alice',
|
|
requestPath: '/term?join=abc',
|
|
originHeader: 'https://alice.term.example.com',
|
|
remoteAddrHash: 'deadbeef',
|
|
capabilityTokenRef: 'jti-123',
|
|
})
|
|
|
|
describe('§4.1 CBOR primitive', () => {
|
|
it('KAT: decodes a hand-written CBOR map {"a":"b"} from fixed bytes', () => {
|
|
const bytes = Uint8Array.of(0xa1, 0x61, 0x61, 0x61, 0x62)
|
|
expect(decodeCborMap(bytes)).toEqual({ a: 'b' })
|
|
})
|
|
|
|
it('KAT: encodes {a:1} to fixed bytes (uint value)', () => {
|
|
const bytes = encodeCborMap({ a: 1 }, ['a'])
|
|
expect([...bytes]).toEqual([0xa1, 0x61, 0x61, 0x01])
|
|
})
|
|
|
|
it('rejects trailing bytes after the map', () => {
|
|
expect(() => decodeCborMap(Uint8Array.of(0xa1, 0x61, 0x61, 0x61, 0x62, 0x00))).toThrow(
|
|
/trailing bytes/,
|
|
)
|
|
})
|
|
|
|
it('rejects a duplicate key', () => {
|
|
// map(2) with key "a" twice
|
|
const bytes = Uint8Array.of(0xa2, 0x61, 0x61, 0x01, 0x61, 0x61, 0x02)
|
|
expect(() => decodeCborMap(bytes)).toThrow(/duplicate/)
|
|
})
|
|
})
|
|
|
|
describe('§4.1 OPEN payload (CBOR of MuxOpen)', () => {
|
|
it('round-trips MuxOpen through encode/decode (identity)', () => {
|
|
const open = sampleOpen()
|
|
expect(decodeOpen(encodeOpen(open))).toEqual(open)
|
|
})
|
|
|
|
it('is deterministic — same input yields identical bytes', () => {
|
|
expect([...encodeOpen(sampleOpen())]).toEqual([...encodeOpen(sampleOpen())])
|
|
})
|
|
|
|
it('rejects a MuxOpen with streamId 0 (reserved for connection-level control)', () => {
|
|
expect(() => encodeOpen({ ...sampleOpen(), streamId: 0 })).toThrow()
|
|
})
|
|
|
|
it('rejects a decoded payload with a missing field', () => {
|
|
const bad = encodeCborMap({ subdomain: 'x' }, ['subdomain'])
|
|
expect(() => decodeOpen(bad)).toThrow(ContractDecodeError)
|
|
})
|
|
})
|
|
|
|
describe('§4.1 WINDOW_UPDATE codec', () => {
|
|
it('KAT: encodes credit=255 to 4 big-endian bytes', () => {
|
|
expect([...encodeWindowUpdate(255)]).toEqual([0x00, 0x00, 0x00, 0xff])
|
|
})
|
|
|
|
it('round-trips a credit value', () => {
|
|
expect(decodeWindowUpdate(encodeWindowUpdate(0x0abbccdd))).toBe(0x0abbccdd)
|
|
})
|
|
|
|
it('rejects a wrong-length payload', () => {
|
|
expect(() => decodeWindowUpdate(Uint8Array.of(1, 2, 3))).toThrow(ContractDecodeError)
|
|
})
|
|
})
|
|
|
|
describe('§4.1 GOAWAY codec', () => {
|
|
it('KAT: encodes lastStreamId=10, reason=revoked to fixed bytes', () => {
|
|
expect([...encodeGoaway(10, 'revoked')]).toEqual([
|
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02,
|
|
])
|
|
})
|
|
|
|
it.each([
|
|
['operatorDrain', 1],
|
|
['revoked', 2],
|
|
['shutdown', 3],
|
|
] as const)('round-trips reason=%s (code %i)', (reason, code) => {
|
|
const bytes = encodeGoaway(1, reason)
|
|
expect([...bytes.subarray(4)]).toEqual([0x00, 0x00, 0x00, code])
|
|
expect(decodeGoaway(bytes)).toEqual({ lastStreamId: 1, reason })
|
|
})
|
|
|
|
it('decodeGoAwayReason maps frozen wire codes', () => {
|
|
expect(decodeGoAwayReason(1)).toBe('operatorDrain')
|
|
expect(decodeGoAwayReason(2)).toBe('revoked')
|
|
expect(decodeGoAwayReason(3)).toBe('shutdown')
|
|
})
|
|
|
|
it('decodeGoAwayReason throws on an unknown code', () => {
|
|
expect(() => decodeGoAwayReason(99)).toThrow(ContractDecodeError)
|
|
})
|
|
|
|
it('rejects a wrong-length GOAWAY payload', () => {
|
|
expect(() => decodeGoaway(Uint8Array.of(0, 0, 0, 1))).toThrow(ContractDecodeError)
|
|
})
|
|
})
|