The reported bug was the Hub being reachable from outside the local
network when Server + Desktop is active. c33457f guards the Caddy site
for sovransystemsos.local, but the Hub is an application that also
listens on a port of its own (8937), and a check in Caddy does nothing
for a client that never goes through Caddy. Whether a client could reach
the Hub depended on which door it used.
The Hub now asks the question itself. LanOnlyMiddleware is registered
outermost, so a client that is not on this computer or the local network
gets a bare 403 before authentication is considered; it never sees the
login page.
- Local means loopback, 10/8, 172.16/12, 192.168/16, 100.64/10
(Tailscale and other CGNAT/VPN ranges) and 169.254/16 over IPv4, and
::1, fc00::/7 and fe80::/10 over IPv6.
- IPv6 global addresses are not on the list. A global address belonging
to a laptop on the LAN cannot be told apart from a stranger's by the
address alone, and 2000::/3 is every public IPv6 address there is.
- ::ffff:a.b.c.d is read as the IPv4 address inside it.
- sovran_systemsOS.hub.extraLanNetworks adds networks (IPv4 or IPv6 CIDR)
for setups whose own devices use addresses outside those ranges. It is
checked at build time. The app ignores an entry it cannot parse and
refuses 0.0.0.0/0 and ::/0: it must never widen its policy by
guessing, and "everyone" is hub.lanOnly = false, asked for by name.
- sovran_systemsOS.hub.lanOnly (default true) turns the check off.
- The first refusal from each address is logged, naming the option to
change, so an operator whose own device is refused can find out why.
The list is capped so a scanner cannot fill the journal or memory.
Behind Caddy the policy applies to the real client, not to Caddy: uvicorn
takes the address from X-Forwarded-For only when the peer is 127.0.0.1.
Checked, not only reviewed:
- The real app with the config.json the module really generates (nix
eval on the nixpkgs revision flake.lock pins, read back from the
derivation), on a real socket with the source address chosen per
request: 127.0.0.1, 192.168/16, 100.64/10 and a declared extra
network are served; 203.0.113.9, 8.8.4.4 and an address just outside
the declared /28 get 403 on /login, / and /api/ping. /auto-login still
answers 303 to loopback and 403 to everyone else.
- With c33457f's Caddy guard in front, an IPv6 client at 2001:db8::9
passes Caddy (it is inside 2000::/3) and is refused by the Hub; an
IPv4 stranger has the connection closed by Caddy; a LAN client is
served.
- hub.extraLanNetworks accepts 203.0.113.0/28, 2001:db8:abcd::/48 and
bare hosts, and fails the build for /33, 300.1.1.1/8, 0.0.0.0/0, ::/0,
2001:db8::/129 and junk, with a message that says what to write.
Add tests/test_lan_policy.py and tests/test_hub_lan_only.py, and a note
in SECURITY.md.
6.1 KiB
Security Policy
Supported versions
| Release | Supported |
|---|---|
| Latest stable release | Yes |
main / staging-dev |
Development only |
Older than 1.0.0 |
No |
Install the newest stable point release to receive security fixes.
Report a vulnerability
Do not open a public issue or pull request. Report privately through:
Include the affected version, impact, reproduction steps, and a minimal proof of concept. Never send wallet recovery words, private keys, or live credentials.
We aim to acknowledge reports within two business days. Please allow reasonable time for a fix and coordinated disclosure.
Security model
Local-first operation
The Hub and core data run on operator-owned hardware. The Hub is intended for a trusted local network and must not be port-forwarded to the internet. Public services, DDNS, software updates, and optional third-party relays require external networks and are outside a “fully offline” model.
The local Hub currently uses HTTP. Authentication does not encrypt local network traffic, so use a trusted LAN and avoid public or guest Wi-Fi.
Caddy serves the Hub (sovransystemsos.local), Ride The Lightning (port 3051),
and Mempool (port 60847) only to this computer and to clients on your local
network (private, link-local, and VPN addresses), even when ports 80 and 443 are
forwarded to this computer for public services. Other IPv4 clients get the
connection closed. IPv6 global addresses are not filtered.
The Hub also checks every client itself, before it shows a login page. It runs
as root, so it answers only this computer and the local network (loopback,
private, VPN and link-local addresses) and turns everyone else away, however
they reached it. Global IPv6 addresses are turned away too: a laptop on your
network and a stranger on the internet look the same by address alone. If your
devices use addresses outside the local ranges, list their networks in
sovran_systemsOS.hub.extraLanNetworks in custom.nix;
sovran_systemsOS.hub.lanOnly = false turns the check off.
Public services and your home IP address
Server + Desktop publishes services under your own domain. The Dynamic DNS record at Njal.la then points at your home's public IP address, which anyone can look up (domain privacy does not hide it), and ports 80 and 443 are open to the whole internet. Public HTTPS certificates also list your service hostnames in Certificate Transparency logs. Desktop publishes nothing. Node publishes nothing unless Put BTCPay Server Online or Lightning Wallet Connections is on. See Server + Desktop and your home IP address for what this means and the alternatives.
Sovran_SystemsOS does not ask a STUN server, public DNS resolver, or “what is my IP” service for your address. The DDNS update asks Njal.la to use the address the request came from, and the address Njal.la reports back is the one Element calling and the Hub use.
Bitcoin stack
Bitcoin and Lightning modules are maintained in the standalone
Sovran_Bitcoin repository
and consumed as a flake input. OS-specific customizations (Second_Drive paths,
operator user, Hub integration) are bridged by
modules/sovran-bitcoin-integration.nix. The nix-bitcoin.* option namespace
and /etc/nix-bitcoin-secrets path remain only for upgrade compatibility.
Supply chain and integrity
flake.lock pins flake inputs, and fetched source archives use fixed hashes.
Builds still depend on pinned Nixpkgs, NixVim, btc-clients-nix, upstream source
archives, and any configured binary cache. Keeping the Bitcoin modules in this
repository reduces an external dependency; it does not remove supply-chain
risk.
The Hub integrity check verifies Nix store contents and compares the running
system with a build from local /etc/nixos. It does not authenticate the release
publisher or protect against an attacker who already controls root and can
change both the system and local configuration.
Access and service isolation
- Firewall enabled by default
- Public SSH and remote desktop disabled by default
- Separate service users and systemd sandboxing where supported
- Administrative service ports bound to loopback where practical
- Tor enforced for supported Bitcoin traffic and onion services
- Public web services exposed only when enabled by the operator (this makes your home IP address public)
Tor reduces network exposure for configured Bitcoin services. It is not a guarantee against every IP leak, application bug, or traffic-analysis attack.
Restricted support access
Support uses a per-session SSH key on the non-root sovran-support account.
Sessions expire after 24 hours and have a small allowlist of sudo commands.
Wallet paths receive deny ACLs unless the operator explicitly removes them.
Disabling support removes the key and reapplies the ACLs.
Support events are written to /var/log/sovran-support-audit.log. This is a
local audit log, not a cryptographically tamper-evident record.
Out of scope
Sovran_SystemsOS cannot protect against:
- Compromised root or administrator credentials
- Stolen recovery words, private keys, or backups
- Malicious or compromised hardware, firmware, or build infrastructure
- Services the operator deliberately exposes or weakens
- Physical access without appropriate disk and firmware protections
Operator basics
- Verify downloads and stop if the checksum does not match.
- Apply stable security updates promptly.
- Use unique passwords and keep SSH/RDP off when not needed.
- Prefer a well-reviewed hardware signer for meaningful Bitcoin balances.
- Keep tested, offline backups in separate secure locations.
- Never share recovery words or private keys with support.
- Disable support access when the session ends and review the audit log.
No software can provide absolute security. Review your configuration and threat model before storing important funds or data.