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.
131 lines
3.8 KiB
TypeScript
131 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
AccountRecordSchema,
|
|
HostRecordSchema,
|
|
KillSignalSchema,
|
|
RELAY_REVOCATIONS_CHANNEL,
|
|
REVOCATION_PUSH_BUDGET_MS,
|
|
RevocationScopeSchema,
|
|
RouteEntrySchema,
|
|
SessionRecordSchema,
|
|
INV8_VERSION_TABLE_DDL,
|
|
HOST_VERSIONS_DDL,
|
|
HOSTS_CURRENT_DDL,
|
|
} from '../src/index.js'
|
|
|
|
const UUID = '11111111-1111-4111-8111-111111111111'
|
|
const TS = '2026-07-01T00:00:00.000Z'
|
|
|
|
describe('§4.2 account/host/session Zod schemas', () => {
|
|
it('accepts a valid HostRecord', () => {
|
|
const rec = {
|
|
hostId: UUID,
|
|
accountId: UUID,
|
|
subdomain: 'alice',
|
|
agentPubkey: new Uint8Array([1, 2, 3]),
|
|
enrollFpr: 'fpr:abc',
|
|
status: 'online',
|
|
lastSeen: TS,
|
|
createdAt: TS,
|
|
revokedAt: null,
|
|
}
|
|
expect(HostRecordSchema.parse(rec)).toEqual(rec)
|
|
})
|
|
|
|
it('rejects a HostRecord with an invalid status', () => {
|
|
expect(() =>
|
|
HostRecordSchema.parse({
|
|
hostId: UUID,
|
|
accountId: UUID,
|
|
subdomain: 'a',
|
|
agentPubkey: new Uint8Array(),
|
|
enrollFpr: 'f',
|
|
status: 'zombie',
|
|
lastSeen: TS,
|
|
createdAt: TS,
|
|
revokedAt: null,
|
|
}),
|
|
).toThrow()
|
|
})
|
|
|
|
it('rejects a HostRecord with a non-UUID hostId', () => {
|
|
const bad = {
|
|
hostId: 'not-a-uuid',
|
|
accountId: UUID,
|
|
subdomain: 'a',
|
|
agentPubkey: new Uint8Array(),
|
|
enrollFpr: 'f',
|
|
status: 'online',
|
|
lastSeen: TS,
|
|
createdAt: TS,
|
|
revokedAt: null,
|
|
}
|
|
expect(HostRecordSchema.safeParse(bad).success).toBe(false)
|
|
})
|
|
|
|
it('rejects unknown extra keys (strict)', () => {
|
|
expect(
|
|
AccountRecordSchema.safeParse({
|
|
accountId: UUID,
|
|
plan: 'pro',
|
|
createdAt: TS,
|
|
status: 'active',
|
|
extra: 1,
|
|
}).success,
|
|
).toBe(false)
|
|
})
|
|
|
|
it('accepts a valid AccountRecord across all plan tiers', () => {
|
|
for (const plan of ['free', 'personal', 'pro', 'team'] as const) {
|
|
expect(AccountRecordSchema.parse({ accountId: UUID, plan, createdAt: TS, status: 'active' }).plan).toBe(
|
|
plan,
|
|
)
|
|
}
|
|
})
|
|
|
|
it('validates SessionRecord and RouteEntry', () => {
|
|
expect(
|
|
SessionRecordSchema.parse({
|
|
sessionId: UUID,
|
|
hostId: UUID,
|
|
accountId: UUID,
|
|
createdAt: TS,
|
|
lastAttachAt: TS,
|
|
}).sessionId,
|
|
).toBe(UUID)
|
|
expect(RouteEntrySchema.parse({ relayNodeId: 'node-1', updatedAt: TS }).relayNodeId).toBe('node-1')
|
|
})
|
|
})
|
|
|
|
describe('§4.2 FIX 4 revocation teardown shapes', () => {
|
|
it('exposes the frozen channel + budget constants', () => {
|
|
expect(RELAY_REVOCATIONS_CHANNEL).toBe('relay:revocations')
|
|
expect(REVOCATION_PUSH_BUDGET_MS).toBe(2000)
|
|
})
|
|
|
|
it('accepts each RevocationScope variant', () => {
|
|
expect(RevocationScopeSchema.parse({ kind: 'host', hostId: UUID }).kind).toBe('host')
|
|
expect(RevocationScopeSchema.parse({ kind: 'account', accountId: UUID }).kind).toBe('account')
|
|
expect(RevocationScopeSchema.parse({ kind: 'global' }).kind).toBe('global')
|
|
})
|
|
|
|
it('rejects a host scope missing hostId', () => {
|
|
expect(RevocationScopeSchema.safeParse({ kind: 'host' }).success).toBe(false)
|
|
})
|
|
|
|
it('validates a full KillSignal', () => {
|
|
const signal = { scope: { kind: 'global' }, at: 1719800000, reason: 'operator drain' }
|
|
expect(KillSignalSchema.parse(signal)).toEqual(signal)
|
|
})
|
|
})
|
|
|
|
describe('§4.2 INV8 version-table DDL contract', () => {
|
|
it('exposes host_versions + hosts_current DDL in order', () => {
|
|
expect(INV8_VERSION_TABLE_DDL).toEqual([HOST_VERSIONS_DDL, HOSTS_CURRENT_DDL])
|
|
expect(HOST_VERSIONS_DDL).toContain('host_versions')
|
|
expect(HOST_VERSIONS_DDL).toContain('supersedes')
|
|
expect(HOSTS_CURRENT_DDL).toContain('hosts_current')
|
|
expect(HOSTS_CURRENT_DDL).toContain('version_id')
|
|
})
|
|
})
|