testvibe-tunnel
v0.1.0
Published
TestVibe tunnel agent — run it on a machine inside your network so TestVibe's cloud test runs can reach a private/on-prem app over an outbound-only tunnel, with the app's URL unchanged and TLS end-to-end.
Maintainers
Readme
testvibe-tunnel
The TestVibe tunnel agent. Run it on a machine inside your network and TestVibe's cloud test runs can reach a private / on-prem app you can't expose publicly — without changing the app's URL and with TLS end-to-end. The untrusted AI-generated test code stays in our sandbox; only this small, trusted agent runs on your side, dialing out to our relay (no inbound firewall holes).
Quick start
Requires Node 20+. Grab a workspace API key from Settings → CLI & API keys, then run (the exact command with your relay/server filled in is shown in Settings → Tunnels):
npx --yes testvibe-tunnel@latest --token <tvb_… key> \
--relay <relay wss:// URL> --server <https://your-testvibe-server>The agent self-registers by machine name (it appears under Settings → Tunnels)
and keeps a small pool of outbound backchannels open. The destination allowlist is
managed in-app from your tunneled projects' site URLs and refreshes on every
heartbeat — see --help for all flags, and the
TestVibe docs for the full setup guide.
How it works
It's the alternative to the Private Runner: the untrusted AI-generated test code stays in our Hyper-V sandbox; only this small, trusted, signed agent runs on the customer side.
Dynamic Session (our sandbox) Customer network
┌───────────────────────────────┐ ┌─────────┐ ┌────────────────────────┐
│ Chromium --proxy-server=:PORT │ │ relay │ │ tvtunnel (agent) │
│ │ │ │ (cloud) │ │ │ allowlist check │
│ ▼ │ ───► │ │ ◄─── │ ▼ │
│ proxy.js (CONNECT proxy) ─────┼──────┤ pairs ├──────┼─► raw TCP to real app │
└───────────────────────────────┘ └─────────┘ └────────────────────────┘
only egress = relay both legs dial OUT app.acme.internal:443The browser issues CONNECT app.acme.internal:443; the proxy forwards it to the
relay; the relay pairs it with an idle agent backchannel; the agent checks its
destination allowlist and dials the real app. The relay and agent only move
opaque TLS bytes — they never terminate TLS, so the browser validates the
app's real certificate and the URL is never rewritten.
Components
| File | Runs | Role |
| --- | --- | --- |
| relay.js | our cloud | Meeting point. Both legs connect outbound; pairs a client leg with an idle agent backchannel. Never sees plaintext. |
| agent.js / cli.js (tvtunnel) | customer network | Outbound-only. Keeps a pool of idle backchannels; enforces the destination allowlist; dials the app and splices bytes. |
| proxy.js | our sandbox | In-session HTTP CONNECT proxy Chromium points at via --proxy-server. |
Security boundary
The agent's allowlist is the whole containment control for tunneled runs:
untrusted test code drives the browser, so without it that code could ask the
tunnel to reach arbitrary internal hosts. The agent only bridges host:port
pairs it was explicitly given (--allow), and refuses everything else
(502 at the proxy). The sandbox itself has no general egress — its only
network path is the relay — so it needs no per-project egress firewall.
See docs/architecture/local-tunnel.md
for the full design and how it routes through the run spine.
Run the agent (customer side)
With a --server, the allowlist is managed in-app (recommended — see Quick start
above). For a self-hosted relay or local/TCP mode, pass the allowlist explicitly:
npx --yes testvibe-tunnel@latest --relay relay.testvibe.io:443 \
--token <account-token> --allow app.acme.internal:443--allow host:port may be repeated. To point an allowed host at a specific dial
target, use host:port=dialHost:dialPort. (Installed globally or from a local
tarball, the same command is available as tvtunnel.)
Tests
npm test # node --test: dep-free e2e + (if available) a real-Chromium e2etest/e2e.test.js— no external deps. Proves URL unchanged, TLS end-to-end (validates the app's real cert through the tunnel), and allowlist refusal.test/browser.test.js— launches the cached Chromium with--proxy-serverand navigates to the app's real URL. Skips ifplaywright-core/Chromium is absent.
