Commit Graph
2 Commits
Author SHA1 Message Date
Security Fix ebcc17ae3c caddy: stop filtering the RTL and Mempool sites by client address
c33457f put an address check (sovran_lan_only) on the Hub, RTL and
Mempool sites. It was written for ports 80/443 being forwarded and a
Host header selecting a site, and that only ever applied to the Hub. RTL
and Mempool are sites on ports of their own (:3051, :60847): a request
on 80/443 cannot select them, whatever Host it carries.

Checked with Caddy 2.9.1 and the Caddyfile this module generates for
Server + Desktop with every domain configured, serving the public sites
on a stand-in port: Host: sovransystemsos.local, localhost:8937,
127.0.0.1 and x:3051 all get an empty 200, and Host: matrix.example.org
gets the Synapse stand-in. With the Hub off Caddy the guard has nothing
left to guard, and it could not be made right for the two sites that
remain:

- IPv6. A laptop's global address on the LAN looks exactly like a
  stranger's. The choice was between letting all of 2000::/3 through,
  which is the whole IPv6 internet and is what c33457f does, and
  refusing every LAN device that connects over a global address unless
  the operator copies the ISP's prefix into a Nix option.
- They do not need it. RTL has a random 20-character password
  (pwgen -s 20, about 119 bits) and, since 0.15.12, which Sovran_Bitcoin
  pins, a 30-minute lockout keyed on the client address. Mempool shows
  public chain data. If someone forwards 3051 or 60847 that is the same
  exposure as any other port on the machine, and SECURITY.md says not to.

Remove the snippet and its two imports, and tests/test_caddy_lan_only.py
with them. What is still worth pinning moves to test_hub_direct.py: no
address filter anywhere in caddy.nix, the two sites are plain proxies to
their loopback ports, and no option for a declared prefix is left
half-wired.

Behaviour change: RTL and Mempool answer any client that can reach :3051
or :60847, as they did before c33457f. In practice that is the local
network, because nothing asks you to forward those ports.

Checked with the real generator and Caddy 2.9.1: Node Only generates
`auto_https off` and the two plain sites and validates. Run live next to
the real Hub, a LAN client gets the Hub, RTL and Mempool; a stranger's
address gets RTL and Mempool (by design) and a 403 from the Hub; port 80
is not listening on Node Only.
2026-10-02 02:24:29 -05:00
Security Fix 78bfc5b408 hub: serve the Hub on its own port instead of through Caddy
Caddy fronted the Hub at http://sovransystemsos.local, but the Hub
already listens on 0.0.0.0:8937 itself, and nothing Caddy added is
something it needs:

- Not the name. That is avahi's: mDNS advertises a hostname, not a port,
  so the name resolves wherever the Hub listens.
- Not TLS (the site was plain http), not authentication, not cache
  headers. The header block duplicated NoCacheMiddleware, and its
  Clear-Site-Data ("cache") overrode the app's stronger ("cache",
  "storage").
- Not access control, and this is the point. With ports 80/443 forwarded
  for public services, a Host header on those ports reached the Hub. That
  second door is how the reported bug happened, and c33457f guards it
  with an address check instead of closing it.

The Hub is now served on port 8937 only, at
http://sovransystemsos.local:8937, and Caddy has no site for it. The
only thing Caddy answers on 80/443 is the public sites. Caddy keeps
Ride The Lightning (:3051) and Mempool (:60847), because those do need
it: Sovran_Bitcoin binds both to 127.0.0.1 and RTL's unit is sandboxed to
loopback besides, so Caddy is how the local network reaches them.

- caddy.nix: no Hub site. Caddy runs wherever RTL and Mempool do, which
  includes Bitcoin Node Only. There it did not run at all (enable was
  needsHttpsPorts || extraVhosts != ""), so :3051 and :60847 were open
  in the firewall with nothing listening. Ports 80/443 still follow
  needsHttpsPorts alone, so Node Only does not open them. The two sites
  are written only where their service exists; they were unconditional.
- sovran-hub.nix: 8937 follows the new hub.directPort, 60847 follows
  Mempool. It used to be `[ 8937 60847 ]` on every role, Desktop Only
  included.
- roles.nix: hub.directPort defaults to !roles.desktop: open on Server +
  Desktop and Bitcoin Node Only, closed on Desktop Only, where the Hub is
  reached from the machine itself through the desktop window on
  localhost.
- The bind stays 0.0.0.0, which is IPv4 only: with that bind [::1]:8937
  is refused and "localhost" falls back to 127.0.0.1. That is on purpose
  and is now said in the comment. An IPv6 listener would let in clients
  whose global addresses the Hub cannot tell from a stranger's, which is
  the question the previous commit declines to answer by guessing.
- README, SECURITY.md and two strings in index.html give the new URL.

Behaviour changes: the Hub's address gains :8937, and http://sovransystemsos.local
on port 80 no longer reaches it. Bitcoin Node Only now runs Caddy.

Evaluated with nix eval (nixpkgs as flake.lock pins it, Sovran_Bitcoin at
the locked revision), firewall TCP ports per role:

                      c33457f                      this commit
  Server + Desktop    22 80 443 3051 8937 60847    22 80 443 3051 8937
  Bitcoin Node Only   22 3051 8937 60847           22 3051 8937 60847   (Caddy now runs)
  Desktop Only        22 8937 60847                22

Port 22 is open on every role although sshd listens on loopback only;
the last commit of this series deals with that.

The Caddyfile the module really generates (the evaluated generator
script, run, then `caddy validate` with Caddy 2.9.1): Node Only gets the
two sites and nothing else; Server + Desktop with every domain
configured gets the seven domain sites plus :3051 and :60847 and no
mention of the Hub; with Bitcoin off there are no local-network sites;
Node Only with Bitcoin off and no domains leaves Caddy off.

Add tests/test_hub_direct.py and keep tests/test_caddy_lan_only.py for
the two sites it still covers.
2026-10-02 02:24:29 -05:00