Tunnels — architecture context
This doc is for the security / compliance review that gates many MSP deployments. If you’re doing first-deploy, you want the Quickstart. This page exists because procurement / CISO reviews need to understand what a Tunnel is before approving deploy.
What this thing does
Section titled “What this thing does”A Tunnel (conduit-tunnel) is a single static Go binary (~12 MB) an MSP runs as a service on their own infrastructure that:
- Dials WYRE’s relay (outbound
wss://) — never accepts inbound connections. - Enrolls as the customer’s org via an identity-only signed JWT — the token binds org identity and carries no capability grant.
- Registers empty, then receives its connector config (which connectors to run, and their endpoints and credentials) pushed down the tunnel from Conduit.
- Routes incoming MCP requests from authorized Conduit clients to the configured connectors (e.g. a read-only SQL Server database) and returns responses back through the same WSS.
- Carries on-prem vendor egress (the Via Tunnel deployment on a vendor connection — see the on-prem vendors guide): Conduit’s cloud vendor integration wraps its API calls into
http/forwardframes, sends them down the same WSS, and the Tunnel’s built-inhttp-bridgere-issues them locally against the vendor server on the customer’s network — restricted to an allowlist derived from what org admins connected.
Data flow
Section titled “Data flow”┌─────────────────────────────────────────────────────────────────┐│ YOUR ON-PREM NETWORK ││ ││ ┌──────────────────────┐ ┌──────────────────────┐ ││ │ conduit-tunnel │ ──────► │ Your local system │ ││ │ (systemd service, │ local │ (e.g. a SQL Server │ ││ │ no inbound port) │ │ database) │ ││ └──────────┬───────────┘ └──────────────────────┘ ││ │ ││ │ wss:// (outbound only) │└──────────────┼──────────────────────────────────────────────────┘ │ ▼┌──────────────────────────────────────────────────────────────────┐│ WYRE CLOUD ││ ││ ┌────────────────┐ ┌──────────────┐ ┌────────────────┐ ││ │ Relay │ ◄─ │ Conduit │ ◄─ │ Authenticated │ ││ │ (terminates │ │ gateway │ │ /v1/mcp client │ ││ │ WSS, holds │ │ (RLS, audit, │ │ │ ││ │ sockets; │ │ allowlists, │ │ │ ││ │ never persists │ │ config store)│ │ │ ││ │ config) │ │ │ │ │ ││ └────────────────┘ └──────────────┘ └────────────────┘ │└──────────────────────────────────────────────────────────────────┘On-prem vendor egress (Via Tunnel)
Section titled “On-prem vendor egress (Via Tunnel)”When an org connects a vendor with the Via Tunnel deployment (e.g. a self-hosted ConnectWise PSA), the same WSS carries a second traffic shape:
vendor integration (WYRE cloud) conduit-tunnel (customer host) │ HTTPS to Conduit's │ │ /egress/v1/<signed token> │ ▼ ▼ egress route ── http/forward frame ──► http-bridge ── HTTPS ──► vendor (over the SAME outbound (allowlist check, server on WSS the Tunnel dialed) per-host CA trust) the LANThe vendor integration itself still runs in WYRE’s cloud; only its API
calls traverse the tunnel and are re-issued locally by the bridge. The
egress URL embeds a short-TTL HMAC-signed token binding org + vendor —
the /egress/v1/* route rejects anything else (fail-closed 401). Responses
return over the same socket; redirects are returned, not followed, and
responses are capped at 10 MiB.
Security spine — load-bearing properties
Section titled “Security spine — load-bearing properties”These are the invariants the Tunnel depends on. Each is enforced in code, not by-convention.
1. No inbound listener
Section titled “1. No inbound listener”The Tunnel binds zero LAN-listening sockets. The outbound WSS dial is the only ingress. This eliminates the “we opened a new attack surface on the customer network” failure class structurally.
2. Must-use-wss
Section titled “2. Must-use-wss”The RELAY_URL env var MUST be wss://. Plain non-TLS ws, http://, https:// are all rejected at boot. No silent fallback to a non-TLS connection.
3. Identity-only enrollment (the token carries no capability grant)
Section titled “3. Identity-only enrollment (the token carries no capability grant)”The enrollment token binds only the customer’s org identity. It grants no capabilities. The Tunnel registers with zero capabilities and comes online empty. This is a deliberate narrowing from the v1 model (where capabilities were baked into the token): a leaked or over-scoped token can no longer smuggle capability grants, because the token doesn’t carry any. What the Tunnel is allowed to do is decided in Conduit, after enrollment.
4. Cloud-managed config, pushed over the tunnel
Section titled “4. Cloud-managed config, pushed over the tunnel”The Tunnel’s config — which connectors run, plus their endpoints and credentials — is authored and stored in Conduit, not on the host.
- Encrypted at rest in Conduit with AES-256-GCM.
- Decrypted only by the gateway at push time; the relay never persists config — it only forwards the frame.
- Travels only over the TLS tunnel the Tunnel dialed.
- Held in memory only on the Tunnel — never written to disk. A restart clears it, and Conduit re-pushes automatically within ~60s.
5. One tunnel per org; routing to the user’s primary org
Section titled “5. One tunnel per org; routing to the user’s primary org”Conduit maintains one live tunnel per org. When an authenticated user makes an on-prem request, Conduit routes it to the requesting user’s primary org’s live tunnel. There is no cross-org fan-out and no way for a user to reach another org’s Tunnel.
The Connections → Tunnels page in Conduit can list more than one tunnel row per org (e.g. after redeploying the connector on a new host) — that’s a management/history view for naming, disabling, and retiring tunnels, not concurrent multi-tunnel routing. Routing still resolves to a single live tunnel per org; if more than one is ever online at once, the most recently seen wins.
6. Frame parser strict by design
Section titled “6. Frame parser strict by design”The wire frames between the Tunnel and relay are validated strictly at the parser; unparseable input drops the socket. Trust-boundary discipline at the protocol layer.
7. Derived, exact egress allowlist (the bridge cannot browse the network)
Section titled “7. Derived, exact egress allowlist (the bridge cannot browse the network)”The http-bridge will only re-issue a request to a host on its allowlist,
and that allowlist is derived, never operator-authored: it is synced from
the org’s Via-Tunnel vendor connections — one entry per connected server,
added on save, removed on delete or on switching the connection back to
Cloud — on every credential write. Matching is component-wise
(scheme, host, port, path-prefix) with dot-segment normalization and
encoded-path-separator rejection, closing the classic allowlist parse
differentials. A compromised cloud gateway cannot use a customer tunnel to
scan or reach anything an org admin didn’t explicitly wire up.
TLS to the vendor server is verified on the customer’s host: each
allowlist entry may carry a private CA certificate (PEM) trusted for that
one host only — internal PKI works without any global or cloud-side trust
change. A per-host insecureSkipVerify exists in the API as a documented
last resort for appliances with unexportable certificates.
What data flows where
Section titled “What data flows where”| Direction | Data | Notes |
|---|---|---|
| Tunnel → WYRE (WSS outbound) | Enrollment token (one-shot at register) | Identity-only; binds org, no capabilities. |
| WYRE → Tunnel (WSS, frame-routed) | Connector config (which connectors + their settings) | Encrypted at rest in Conduit; forwarded by the relay; held in Tunnel memory only. |
| WYRE → Tunnel (WSS, frame-routed) | MCP request payloads from authorized clients | tools/call for the configured connectors. |
| Tunnel → WYRE (WSS outbound) | MCP response payloads | Whatever the local system returns (e.g. read-only query rows). |
| Tunnel ← Local network | Request → local system → response | The Tunnel is a router; it does NOT cache or persist payloads. |
| WYRE → Tunnel (WSS, frame-routed) | http/forward requests (Via-Tunnel vendor API calls) |
Allowlist-checked by the bridge before any LAN connection is made. |
| Tunnel → WYRE (WSS outbound) | http/forward responses (status/headers/body) |
Relayed byte-for-byte to the cloud vendor integration; 10 MiB cap; never persisted on the host. |
The Tunnel does NOT store payloads. Request frame in → call the local system → response frame out. No payload data persists.
Credential handling
Section titled “Credential handling”For the echo connector, no customer credentials are involved. For mssql, the SQL login and password are part of the connector config: authored in Conduit, encrypted at rest (AES-256-GCM), pushed down the tunnel, and held in the Tunnel’s memory only.
For a Via-Tunnel vendor connection (e.g. on-prem ConnectWise PSA) the
split is different — and deliberate: the vendor API credentials are stored
in Conduit (AES-256-GCM at rest, same as any cloud connection) and
injected into each API call cloud-side; they transit the customer host
only inside individual http/forward frames and are never part of the
Tunnel’s pushed config, never held in its memory beyond the request, and
never written to disk. Customers who require full credential custody on
their own network use the mcp-proxy connector
pattern instead — the two models are complementary, chosen per customer.
The real enforcement boundary for mssql is that the SQL principal is a scoped read-only login (see the SQL Server guide) — the mssql connector’s tools are read-only, and the login should be too. The gMSA / Windows Integrated Auth path stores zero SQL credentials: the Tunnel authenticates as its own Windows service identity (a gMSA), so no password exists in Conduit or on the host — see the reference.
Image verification
Section titled “Image verification”The legacy v1 Docker container image is signed via cosign keyless GHA OIDC (sigstore Fulcio); the customer-facing compose file pins by digest (NOT :latest). Cosign image verification therefore applies to the legacy container today.
Verify command (legacy container):
cosign verify ghcr.io/WYRE-AI/conduit-onprem-gateway@sha256:<digest> \ --certificate-identity-regexp 'https://github.com/WYRE-AI/conduit/.github/workflows/release-onprem-gateway.yml@.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.comA successful verify confirms the image was built by the canonical WYRE release workflow and the digest matches what we published. A failed verify means do NOT deploy.
Audit logging
Section titled “Audit logging”The Tunnel path is audited on the WYRE side:
request_log(customer-visible Audit Log tab): collapsed status (a404for both “no tunnel” and “capability not granted” — preserves no-topology-leak for the customer).admin_audit_log(operator-only, NEVER customer-visible): precise per-request relay-return reasons (capability_not_granted/tunnel_offline/tunnel_timeout).
The split is deliberate: customers cannot enumerate “which connectors does org X run” via error codes; WYRE operators get the precise reason for debugging.
Questions for a security review
Section titled “Questions for a security review”If your security review needs more detail, contact WYRE engineering with:
- Specific threat-model questions (WYRE’s threat model for the relay + config store).
- Specific compliance frameworks (SOC 2, ISO 27001, etc.) — we can provide attestations on the WYRE-managed half.
- Penetration-test reports — available under NDA.
The Tunnel’s source is public — WYRE-AI/conduit-tunnel — so a reviewer can audit the binary’s behavior (including the http-bridge allowlist matching) directly; WYRE engineering is happy to walk through it.

