snowglobe

Networking

A guest has no network unless the site asks for one, and then its only way out is the visitor's own browser.

HTTP, HTTPS and WebSockets

With --network fetch (or snowglobe.network="fetch"), the guest gets a network card and DHCP, and every request it makes leaves as the page's own browser request. Its connections to ports 80 and 443 end inside the machine:

  • HTTP requests become a fetch().
  • HTTPS is terminated in the machine with a certificate signed by a CA made fresh for each build, which the guest trusts, and replayed with fetch("https://…"). Nothing else should trust that CA: its key ships with the site.
  • WebSockets open a browser WebSocket to the same URL.

The browser's rules still apply. HTTP(S) only reaches servers that allow CORS (others answer with a 502 that says so), UDP goes nowhere, and other TCP only through a tunnel.

Other TCP: tunnels

A page can't open raw TCP, so a connection to a database or any port but 80 and 443 is refused unless the site names it as a tunnel. --tunnel HOST:PORT=URL carries every connection to HOST:PORT over a WebSocket to URL, as binary messages: what websockify, websocat and Neon's wsproxy speak.

websocat --binary ws-l:0.0.0.0:6543 tcp:127.0.0.1:5432       # next to Postgres
snowglobe build ./my-app --network fetch --tunnel db.internal:5432=wss://db-proxy.example.com/

Credentials

Whatever the guest knows ships with the site: a password in its environment is in the snapshot, for every visitor.

Instead, put {token} in the tunnel URL and name a token endpoint with --tunnel-token URL. The page asks it for a fresh token on every connection (natively, once per boot), and a gateway behind the tunnel checks the token and holds the real credentials. A token only replaces the password: what it opens is whatever the gateway lets any visitor do.

Builds and snowglobe run refuse loopback and private addresses, so a guest can't reach the host's own services. --allow-private lets them, for a tunnel endpoint on your own machine while developing.