Skip to content
ConduitConduit

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.

A Tunnel (conduit-tunnel) is a single static Go binary (~12 MB) an MSP runs as a service on their own infrastructure that:

  1. Dials WYRE’s relay (outbound wss://) — never accepts inbound connections.
  2. Enrolls as the customer’s org via an identity-only signed JWT — the token binds org identity and carries no capability grant.
  3. Registers empty, then receives its connector config (which connectors to run, and their endpoints and credentials) pushed down the tunnel from Conduit.
  4. 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.
  5. 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/forward frames, sends them down the same WSS, and the Tunnel’s built-in http-bridge re-issues them locally against the vendor server on the customer’s network — restricted to an allowlist derived from what org admins connected.
┌─────────────────────────────────────────────────────────────────┐
│ 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) │ │ │ │ │ │
│ └────────────────┘ └──────────────┘ └────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

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 LAN

The 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.

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.

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.

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.

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.

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.

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):

Terminal window
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.com

A 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.

The Tunnel path is audited on the WYRE side:

  • request_log (customer-visible Audit Log tab): collapsed status (a 404 for 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.

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.