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/andssh-agentare used automatically; no password prompt appears if a key is accepted - Password auth — passwords are optionally saved as
passwordB64(base64-encoded) instate.jsonand survive native host restarts; a saved password is delivered to SSH viaSSH_ASKPASSand 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 thehs://public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for127.0.0.1would 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 undersshConnections[].passwordB64andrdpConnections[].passwordB64; server tunnellabelfields 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
- Keep
~/.holesail-browser/private — it contains the CA private key, server tunnel keypairs, and any saved passwords - Restart Chrome after CA installation — Chrome caches trust store state; a restart is required for the new CA to take effect
- Use
readyTimeoutMs— setting a timeout (e.g. 30000ms) surfaces tunnel connection failures instead of hanging indefinitely - Review
hs://keys before adding them — only add keys from sources you trust - 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
- Encrypt backups if storing them in a shared location