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

9.2 KiB

Security

Certificate Authority

What it is

Holesail Browser generates a local root CA called Holesail Browser CA and installs it into your OS trust store. This CA is used to sign per-TLD wildcard TLS certificates that the HTTPS proxy presents to your browser — one certificate per wildcard parent domain, generated on demand.

Why it's needed

Browsers require HTTPS for modern web features and refuse to load mixed content. The HTTPS proxy must present a valid TLS certificate for each virtual host's domain — but no public CA will sign certificates for non-public domains. A locally-trusted CA is the standard approach used by tools like mkcert, Burp Suite, and corporate proxies.

What the CA can do

The CA is a standard X.509 root CA. In principle, it could be used to sign certificates for any domain — not just Holesail virtual host domains. However:

  • The CA private key (ca.key.pem) is stored at ~/.holesail-browser/holesail-browser-certs/ca.key.pem, readable only by your user account
  • The native host only ever uses the CA to sign wildcard certificates for configured virtual host TLDs
  • The CA is installed with the SSL trust policy only (macOS: -p ssl), limiting its scope

Certificate issuance

A separate wildcard certificate is generated for each unique wildcard parent domain:

Virtual host hostname Wildcard parent Certificate covers
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

Certificates are generated on the first HTTPS connection to a new wildcard parent and cached on disk. No proxy restart is required.

Lifetime

  • Root CA: 10 years
  • Wildcard domain certs: 1 year (auto-renewed on expiry)

Fingerprint verification

isRootCAInstalled and installRootCA both verify the SHA-1 fingerprint of the CA cert in the keychain against the CA cert on disk. This detects stale entries — for example, after reinstalling Holesail Browser, the new CA will be installed and the old one removed automatically.

Installation

Platform Method Requires admin?
macOS security add-trusted-cert -r trustRoot -p ssl -k ~/Library/Keychains/login.keychain-db No
Linux cp to /usr/local/share/ca-certificates/ + update-ca-certificates Yes (sudo)
Windows certutil -addstore -f ROOT Yes (admin)

On macOS, the CA is installed to the user's login keychain with the SSL trust policy. Chrome evaluates TLS trust using the SSL policy against all user keychains, so this is sufficient without admin privileges.


P2P encryption

All traffic between the native host and remote peers is encrypted using the Noise protocol (specifically the Noise_XX handshake pattern used by Holesail). This provides:

  • Mutual authentication — both peers verify each other's identity using their public keys
  • Forward secrecy — session keys are ephemeral; compromise of long-term keys does not expose past sessions
  • Integrity — all messages are authenticated with a MAC

The hs:// key is the remote peer's public key. Connecting to a peer requires knowing their key — there is no way to enumerate or discover peers without a key.


Native host privilege model

The native host runs as your user account (not root). It:

  • Binds only to 127.0.0.1 (loopback) — no network interfaces are exposed
  • Reads and writes only to ~/.holesail-browser/ (state, certs, logs, backups)
  • Spawns child processes: ssh (for SSH sessions), tar (for backups), security/certutil/update-ca-certificates (for CA installation)
  • Does not require elevated privileges for normal operation

Proxy security

Loopback-only binding

Both the HTTPS proxy (8443) and CONNECT proxy (8442) bind to 127.0.0.1 only. They are not accessible from other machines on the network.

SNI-based certificate selection

The HTTPS proxy reads the TLS ClientHello from each raw TCP connection to extract the SNI hostname before the TLS handshake begins. This allows it to present the correct wildcard certificate for each TLD without requiring a proxy restart. The SNI parsing is done entirely in JavaScript — no native TLS SNI callback is used.

Unknown hostname handling

If the HTTPS proxy receives a request for a hostname with no registered tunnel, it returns a 502 Bad Gateway response with a plain HTML error page. It does not attempt to forward the request to any external host.

PAC script scope

The PAC script routes only configured TLD traffic through the proxy. For each active TLD (e.g. .hole.sail, .haha.wooo), a dnsDomainIs clause is added. All other traffic goes DIRECT. The extension monitors proxy.settings.onChange and re-applies the PAC script if it is overridden by another extension.

Custom TLD validation

The dashboard enforces that virtual host hostnames:

  • Have at least 3 labels (host + two-label TLD minimum)
  • Use only letters, digits, and hyphens per label
  • Do not use any real public TLD (.com, .net, .co.uk, and hundreds of others)

This prevents accidentally routing real internet traffic through the local proxy.


Threats mitigated

Threat Mitigation
Network eavesdropping on tunnel traffic Noise protocol encryption (end-to-end)
Man-in-the-middle on browser↔proxy TLS Locally-trusted per-TLD wildcard certs; CA key stored locally
Wrong cert served for custom TLD JS-layer SNI: cert selected per-connection based on ClientHello hostname
Unauthorized access to remote peers hs:// key required to connect; Noise mutual auth
Proxy accessible from other machines Loopback-only binding
Stale CA after reinstall causing cert errors Fingerprint verification on every CA install check
Multiple native host instances with no tunnels Process exits on EADDRINUSE; only one instance owns the ports
Custom TLD colliding with real internet domain TLD blocklist validation in dashboard before saving

Threats not mitigated

Threat Notes
Malicious code with local user access If an attacker has local user access, they can read the CA private key and the hs:// keys in state.json
Compromised remote peer Holesail authenticates the peer's public key but cannot verify what the peer does with your connection
Browser extension permissions The extension has proxy and nativeMessaging permissions; a compromised extension could redirect all browser traffic

SSH security

SSH sessions use the system ssh binary. Authentication is tried in order: public key, then password.

  • Public key auth — keys from ~/.ssh/ and ssh-agent are used automatically; no password prompt appears if a key is accepted
  • Password auth — passwords are optionally saved as passwordB64 (base64-encoded) in state.json and survive native host restarts; a saved password is delivered to SSH via SSH_ASKPASS and a named FIFO pipe (not via the process environment or command line); if no password is saved and key auth fails, an interactive password prompt appears in xterm.js and is delivered the same way
  • StrictHostKeyChecking=no / UserKnownHostsFile=/dev/null — host key verification is disabled for Holesail tunnels because the remote host's identity is already established by the hs:// public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for 127.0.0.1 would be misleading since the port changes per session
  • The Holesail tunnel provides transport; ssh's own encryption is layered on top, giving double encryption

Tunnel state storage

state.json contains hs:// keys for all configured tunnels. These keys are the public keys of remote peers — they are not secret. However, state.json also contains:

  • Server tunnel keypairs — the hs:// URL for each server tunnel is derived from a keypair that is preserved in state; anyone with this URL can connect to your exposed service
  • passwordB64 — SSH and RDP connection passwords are stored as base64-encoded strings under sshConnections[].passwordB64 and rdpConnections[].passwordB64; server tunnel label fields are also stored but are not sensitive

Protect ~/.holesail-browser/ accordingly.


Backup security

Backup archives contain state.json and all certificate files including the CA private key. Store backups securely. The archives are not encrypted.


Recommendations

  1. Keep ~/.holesail-browser/ private — it contains the CA private key, server tunnel keypairs, and any saved passwords
  2. Restart Chrome after CA installation — Chrome caches trust store state; a restart is required for the new CA to take effect
  3. Use readyTimeoutMs — setting a timeout (e.g. 30000ms) surfaces tunnel connection failures instead of hanging indefinitely
  4. Review hs:// keys before adding them — only add keys from sources you trust
  5. Use private TLDs only — the dashboard enforces this, but be aware that any TLD you add will have a locally-trusted wildcard certificate issued for it
  6. Encrypt backups if storing them in a shared location