Files
holesail-browser/docs/VIRTUAL-HOSTS.md
T
Raven Scott f5c349a4a3
CI / Build & Test (push) Successful in 2m56s
docs
2026-02-28 22:29:56 -05:00

5.9 KiB

Virtual Hosts

Virtual hosts let you access remote services over Holesail tunnels using human-readable HTTPS URLs in your browser — for example https://myapp.hole.sail/ instead of http://127.0.0.1:19042/.

How it works

  1. You add a virtual host in the dashboard: a hostname (e.g. myapp.hole.sail) and an hs:// key
  2. The native host opens a Holesail client tunnel to the remote peer and assigns it a local port (e.g. 127.0.0.1:19042)
  3. The extension updates the PAC script so the browser routes that TLD through the local CONNECT proxy
  4. When you navigate to https://myapp.hole.sail/, the browser sends CONNECT myapp.hole.sail:443 to the CONNECT proxy, which pipes it to the HTTPS proxy
  5. The HTTPS proxy performs a TLS handshake with a wildcard cert for that TLD, then proxies your HTTP request to the local tunnel port

Supported hostname formats

Any hostname with at least 3 labels and a private (non-public) TLD is accepted:

Hostname Valid? Notes
myapp.hole.sail Default TLD
api.haha.wooo Custom TLD
i.love.hole.sail Deep hostname — 4 labels
a.b.c.my.internal Any depth supported
myapp.com Real public TLD
api.co.uk Real public SLD
myapp Single label — no TLD
hole.sail Two labels — no host prefix

TLD requirements

  • Must be two-tier: the TLD portion must be two labels (e.g. .hole.sail, .my.internal, .haha.wooo) — single-label TLDs like .holesail are not allowed
  • Must not be a real public TLD or second-level domain (.com, .net, .co.uk, .github.io, etc.)
  • The full hostname must have at least 3 labels total

Custom TLDs

You are not limited to .hole.sail. Any private two-tier TLD works:

https://api.haha.wooo/
https://dashboard.my.internal/
https://service.dev.local/

When you add a virtual host with a new TLD for the first time:

  1. PAC script updated — the extension adds a dnsDomainIs(host, ".haha.wooo") clause so the browser routes all *.haha.wooo traffic through the proxy
  2. Host permissions requested — the extension requests *://*.haha.wooo/* at runtime via chrome.permissions.request()
  3. Certificate generated on first connection — the HTTPS proxy generates a wildcard cert for the exact wildcard parent on demand (see below)

Certificate generation

The HTTPS proxy uses JS-layer SNI to select the right certificate per connection:

  1. The raw TLS ClientHello bytes are read from each incoming TCP connection

  2. The SNI hostname is extracted (RFC 5246 extension type 0x0000)

  3. The wildcard parent is derived by stripping the leftmost label:

    SNI hostname Wildcard parent Certificate
    myapp.hole.sail hole.sail *.hole.sail
    api.haha.wooo haha.wooo *.haha.wooo
    i.love.hole.sail love.hole.sail *.love.hole.sail
    a.b.c.my.internal b.c.my.internal *.b.c.my.internal
  4. certificate-authority.getOrCreateWildcardCert(parent) returns an existing cert or generates a new one signed by the local root CA

  5. A bare-tls.Socket is created with that cert and the TLS handshake proceeds

Certs are stored in ~/.holesail-browser/holesail-browser-certs/wildcard.<parent>/ (the * in the wildcard is stored literally as wildcard in the directory name, e.g. wildcard.hole.sail/) and reused on subsequent connections. They are valid for 1 year and auto-renewed on expiry.

No proxy restart is needed when adding new TLDs — the cert is generated on the first connection.

Deep hostnames

Hostnames with more than 3 labels work at any depth. Each unique wildcard parent gets its own certificate:

https://i.love.hole.sail/        → cert: *.love.hole.sail
https://also.love.hole.sail/     → cert: *.love.hole.sail  (same cert, reused)
https://deep.a.b.c.my.internal/  → cert: *.a.b.c.my.internal

The PAC script matches all depths automatically — dnsDomainIs(host, ".hole.sail") returns true for i.love.hole.sail as well as myapp.hole.sail.

PAC script

The PAC script is generated dynamically from the set of active virtual host TLDs:

function FindProxyForURL(url, host) {
  if (dnsDomainIs(host, ".hole.sail")) return "PROXY 127.0.0.1:8442";
  if (dnsDomainIs(host, ".haha.wooo")) return "PROXY 127.0.0.1:8442";
  return "DIRECT";
}

It is updated whenever:

  • A virtual host is added or removed
  • The native host connects or reconnects
  • The proxy port changes

WebSocket support

WebSocket connections (ws:// / wss://) through virtual hosts are supported. The HTTPS proxy handles Upgrade: websocket requests by opening a raw TCP connection to the backend tunnel and piping the socket bidirectionally.

Troubleshooting

DNS_PROBE_FINISHED_NXDOMAIN

The PAC script has not yet been updated for this TLD. This can happen if:

  • The virtual host was just added and the background script hasn't refreshed yet — wait a moment and reload
  • The extension is not active — check the extension is enabled in chrome://extensions

ERR_CERT_AUTHORITY_INVALID

The local root CA is not trusted by your browser. Go to the dashboard → Proxy & CA and click Install Root CA, then restart Chrome.

ERR_SSL_SERVER_CERT_BAD_FORMAT / cert mismatch

The HTTPS proxy presented a certificate that doesn't cover the requested hostname. This should not happen with the SNI-aware proxy. If it does:

  1. Check the native host is running (dashboard should show connected)
  2. Delete the stale cert directory: rm -rf ~/.holesail-browser/holesail-browser-certs/wildcard.<parent>/
  3. Reload the page — the proxy will regenerate the cert on the next connection

502 Bad Gateway

The virtual host tunnel is not connected. Check the Virtual Hosts page in the dashboard — the tunnel may still be connecting or may have failed. Click Reconnect if it shows an error state.