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.
This commit is contained in:
Security Fix
2026-10-02 02:24:29 -05:00
committed by naturallaw777
parent 78bfc5b408
commit ebcc17ae3c
4 changed files with 53 additions and 139 deletions
+8 -22
View File
@@ -32,11 +32,18 @@ let
# Sites for the local network, one per loopback-only service. Written after
# the public domain sites; each exists only where its service does.
#
# They do not filter by client address. A request can only reach them on
# their own ports, which no setup step asks you to forward, so forwarding
# 80/443 for public services does not expose them (a Host header on those
# ports cannot select a site that listens elsewhere). RTL has its own
# password and lockout, and Mempool shows public chain data. An address
# check here could not be made right for IPv6 anyway: a laptop's global
# address on the LAN looks exactly like a stranger's.
bitcoinUiSites =
lib.optionalString servesRtl ''
:3051 {
import sovran_lan_only
reverse_proxy :3050
encode gzip zstd
}
@@ -44,7 +51,6 @@ let
+ lib.optionalString servesMempool ''
:60847 {
import sovran_lan_only
reverse_proxy :60845
encode gzip zstd
}
@@ -124,26 +130,6 @@ EOF
EOF
''}
# ── LAN-only guard ──────────────────────────────
# The RTL and Mempool sites below are meant for this home network
# only. Forwarding ports 80/443 on the router also lets other clients
# reach Caddy, so these sites check where a request comes from, not just
# which Host it asks for. Anyone else gets the connection closed.
# private_ranges 10/8, 172.16/12, 192.168/16, 127/8, fd00::/8, ::1
# 100.64.0.0/10 Tailscale and other VPN addresses
# 169.254.0.0/16, fe80::/10, fc00::/7 link-local and unique-local
# 2000::/3 IPv6 global addresses. Computers on this network
# often connect over their own global address, which
# looks the same as one from the internet, so IPv6
# global addresses are not filtered.
cat >> /run/caddy/Caddyfile <<'EOF'
(sovran_lan_only) {
@outside not remote_ip private_ranges 100.64.0.0/10 169.254.0.0/16 fe80::/10 fc00::/7 2000::/3
abort @outside
}
EOF
# ── Matrix ──────────────────────────────────────
if [ -n "$MATRIX" ]; then
if [ -f /run/caddy/element-calling.snippet ]; then