24 KiB
Network Services (Tor + FIPS) — Implementation Plan
Overview
sovereign_browser integrates two network services:
-
Tor — SOCKS proxy for Tor-routed transport. Naturally browser-managed: unprivileged subprocess, no system-level access. The browser points WebKitGTK's proxy at Tor's SOCKS port.
-
FIPS — Mesh networking daemon for Nostr-identity-based peer-to-peer connectivity. System networking infrastructure: TUN interface, DNS resolver integration, capabilities, persistent identity. Best treated as a system service the browser attaches to, with browser-managed mode added only after privilege and resolver handling are proven.
Both services can be either browser-managed (spawned as child processes) or attached (connected to an already-running system instance). The browser detects which mode is available and never stops or reconfigures a service it doesn't own.
sovereign_browser (C99/GTK main loop)
│
├── Tor: managed subprocess OR attached to system Tor
│ └── WebKitGTK SOCKS proxy → tor → Tor network → internet
│ (fail-closed: no direct fallback while Tor mode is on)
│
├── FIPS: attached to system fips.service OR managed subprocess
│ └── http://<npub>.fips/ → TUN → FIPS mesh → remote node
│ (fips:// is a redirect shorthand, not a custom scheme handler)
│
└── Settings UI: enable/disable, view status, manage lifecycle
Important: Tor-routed ≠ Tor Browser
WebKit routed through Tor is not Tor Browser. It lacks Tor Browser's anti-fingerprinting, site isolation policy, first-party isolation, WebRTC hardening, and uniform browser fingerprint. This feature provides Tor-routed transport, not guaranteed anonymity. The settings UI should make this distinction clear.
Service Ownership Model
Each service has an explicit ownership state:
| State | Description |
|---|---|
DISABLED |
User has not enabled this service |
DISCOVERING |
Checking for an existing system instance |
ATTACHING |
Connecting to an already-running service's control socket |
STARTING |
Spawning a managed subprocess |
BOOTSTRAPPING |
Subprocess is running but not yet ready (Tor < 100%, FIPS initializing) |
READY |
Service is operational |
STOPPING |
Graceful shutdown in progress |
EXITED |
Service stopped (managed) or disconnected (attached) |
FAILED |
Service crashed or failed to start |
Critical rules:
- Attached services are read-only. The browser may query status and observe, but must never stop, restart, or reconfigure a service it didn't start.
- Managed services are owned. The browser starts, stops, monitors, and may reconfigure them.
- Fail-closed for Tor. If Tor mode is enabled and Tor is not READY, network requests are blocked or show a waiting page. Never silently fall back to direct access.
- Graceful degradation for FIPS. If FIPS is not running,
fips://and.fipsURLs show an error. Normal HTTP/HTTPS traffic is unaffected.
Architecture
Net Services Module (net_services.c / net_services.h)
typedef enum {
NET_SERVICE_TOR,
NET_SERVICE_FIPS,
} net_service_type_t;
typedef enum {
SERVICE_DISABLED,
SERVICE_DISCOVERING,
SERVICE_ATTACHING,
SERVICE_STARTING,
SERVICE_BOOTSTRAPPING,
SERVICE_READY,
SERVICE_STOPPING,
SERVICE_EXITED,
SERVICE_FAILED,
} service_state_t;
typedef enum {
OWNERSHIP_NONE, /* not yet determined */
OWNERSHIP_ATTACHED, /* connected to existing system service */
OWNERSHIP_MANAGED, /* spawned and owned by the browser */
} service_ownership_t;
typedef struct {
net_service_type_t type;
service_state_t state;
service_ownership_t ownership;
GPid pid; /* valid only when OWNERSHIP_MANAGED */
char *binary_path;
char *data_dir;
int control_fd; /* socket to control interface */
char *error_msg;
guint child_watch; /* g_child_watch_add source */
guint poll_timer; /* periodic status poll */
/* Service-specific status (updated via control protocol) */
union {
struct {
int bootstrap_progress; /* 0-100 */
char circuit_info[256];
} tor;
struct {
int peer_count;
char node_npub[80]; /* FIPS node's own npub */
char tree_state[32]; /* root/leaf/branch */
gboolean tun_active;
char tun_name[16];
} fips;
} status;
} net_service_t;
API
/* Initialize the net services module (called on startup) */
void net_services_init(void);
/* Enable and start/attach a service */
int net_service_enable(net_service_type_t type);
/* Disable and stop/detach a service */
int net_service_disable(net_service_type_t type);
/* Restart a managed service (no-op for attached) */
int net_service_restart(net_service_type_t type);
/* Get current state and status */
const net_service_t *net_service_get_status(net_service_type_t type);
/* Check if a service is enabled in settings */
gboolean net_service_is_enabled(net_service_type_t type);
/* Check if a service is READY */
gboolean net_service_is_ready(net_service_type_t type);
/* Called on browser exit — stop managed services, detach from attached */
void net_services_shutdown(void);
Subprocess Supervision
For managed children:
- Spawn with
g_spawn_async()+G_SPAWN_DO_NOT_REAP_CHILD+ child-watch - Capture stdout/stderr via pipes → log to browser log file
g_child_watch_add()for exit notification- Graceful stop: send SIGTERM, wait with timeout (5s), then SIGKILL
- Prevent duplicate starts (check state before spawning)
- Use per-instance data directories and control endpoints to avoid collisions with system instances
- On crash: transition to
FAILED, notify UI, do not auto-restart (user must explicitly re-enable)
Tor Integration
Discovery: Attach vs Manage
On enable, the browser checks:
- User-configured attach endpoint — if
tor.attach_socksis set in settings, connect to that SOCKS port and verify it's a Tor proxy. - System Tor control socket — check
/run/tor/control(Unix) or127.0.0.1:9051(TCP). If responsive, attach. - System Tor SOCKS port — check if
127.0.0.1:9050is a SOCKS proxy. If so, attach (without control port, status is limited). - Managed mode — if no existing Tor found and
tor.binary_pathis valid, spawn a private Tor instance.
Managed Tor Configuration
The browser generates a private torrc under the browser profile:
DataDirectory ~/.sovereign_browser/tor/data
SocksPort unix:/home/user/.sovereign_browser/tor/socks.sock
ControlPort unix:/home/user/.sovereign_browser/tor/control.sock
CookieAuthentication 1
CookieAuthFileGroupReadable 1
Log notice file ~/.sovereign_browser/tor/tor.log
AvoidDiskWrites 1
Key choices:
- Unix sockets for SOCKS and control (not TCP ports) — avoids port conflicts and is more secure (filesystem permissions).
- Private data directory — no collision with system Tor.
- Cookie authentication — standard, no passwords in config files.
- Data directory mode 0700.
If Unix socket support is unavailable (unlikely on Linux), fall back to private TCP ports allocated dynamically (bind to port 0, read assigned port).
Tor Control Protocol
Connect to the control socket. Authenticate with cookie (read from
~/.sovereign_browser/tor/control.authcookie).
Key commands:
AUTHENTICATE <hex-cookie>
GETINFO status/bootstrap-phase
→ 250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=85 TAG=handshake
GETINFO signal/newnym
SIGNAL NEWNYM
→ 250 OK
Poll bootstrap progress every 2 seconds. When PROGRESS=100, transition
to READY.
Fail-Closed Proxy Configuration
Before Tor is READY:
- Set WebKit proxy to a non-functional placeholder (e.g.,
socks5://127.0.0.1:1) to block all external traffic. - Show a "Connecting to Tor… (85%)" waiting page for any navigation attempt.
When Tor is READY:
WebKitNetworkProxySettings *proxy = webkit_network_proxy_settings_new(
"socks5://unix:/home/user/.sovereign_browser/tor/socks.sock",
ignore_hosts);
/* ignore_hosts must include .fips domains when FIPS is active */
webkit_website_data_manager_set_network_proxy_settings(dm,
WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxy);
When Tor exits or is disabled:
- If user explicitly disabled Tor: revert to
WEBKIT_NETWORK_PROXY_MODE_NO_PROXY. - If Tor crashed while enabled: keep proxy blocked, show error, do NOT fall back to direct.
DNS
Tor SOCKS5 supports hostname resolution — WebKitGTK sends the hostname to the SOCKS proxy, Tor resolves it via the Tor network. No DNS leak for proxied traffic.
.fips bypass: When FIPS is active, .fips hostnames must bypass
the Tor proxy. webkit_network_proxy_settings_new() accepts an
ignore_hosts array. Add *.fips to this list. This must be tested
empirically — WebKitGTK's ignore-host matching for custom TLDs needs
verification.
Tor Settings
[tor]
enabled=false
mode=auto ; auto|attach|manage
binary_path=tor ; for managed mode
attach_socks= ; for attach mode (e.g. socks5://127.0.0.1:9050)
attach_control= ; for attach mode (e.g. unix:/run/tor/control)
data_dir=~/.sovereign_browser/tor
Tor Status UI
- Toggle: Enable Tor
- Mode: Auto-detected (Attached to system Tor / Managed)
- Status: Disabled / Connecting (85%) / Ready / Failed (error message)
- "New Identity" button (sends
SIGNAL NEWNYM) - Warning: "Tor-routed transport. This is not Tor Browser and does not provide the same fingerprinting protections."
- Advanced: custom entry/exit nodes (future)
FIPS Integration
Discovery: Attach vs Manage
On enable, the browser checks:
-
User-configured control socket — if
fips.control_socketis set in settings, try connecting. -
Default FIPS control socket — check the standard resolution order:
/run/fips/control.sock$XDG_RUNTIME_DIR/fips/control.sock/tmp/fips-control.sock
See FIPS's
default_control_path(). -
Managed mode — if no existing FIPS found and
fips.binary_pathis valid AND the binary hasCAP_NET_ADMIN(or user has root), spawn a managed instance.
Attached FIPS (Production Mode)
Connect to the control socket. Query status:
{"command":"show_status"}
Response (per control-socket.md):
{
"status": "ok",
"data": {
"version": "0.4.0",
"npub": "npub1...",
"node_addr": "...",
"ipv6_addr": "fd97:...",
"state": "running",
"peer_count": 3,
"tun_state": "active",
"tun_name": "fips0",
...
}
}
The browser renders this in the settings UI. It does NOT stop, restart, or reconfigure the daemon.
Managed FIPS (Development/Portable Mode)
Prerequisites:
- FIPS binary found at
fips.binary_path - Binary has
CAP_NET_ADMIN(check withgetcap) OR user is root .fipsDNS resolution is available (either system resolver integration or browser-level DNS override — see below)
Config generation:
The browser generates a minimal fips.yaml matching the actual FIPS
config schema (see configuration.md):
node:
identity:
persistent: true # FIPS manages its own key file
control:
enabled: true
socket_path: ~/.sovereign_browser/fips/control.sock
tun:
device: fips0
dns:
enabled: true
bind_addr: "::1"
port: 5354
transports:
- type: udp
bind_addr: "0.0.0.0:4242"
- type: tcp
listen: "0.0.0.0:4243"
peers: [] # populated from settings or auto-discovery
Identity: FIPS owns a separate persistent identity.
The browser does NOT write its own nsec into the FIPS config. FIPS
generates and persists its own keypair (via persistent: true), as
documented in IdentityConfig.
Rationale:
- Read-only browser login has no private key to share.
- NIP-46 and n_signer identities keep the private key outside the browser — there is no nsec to write.
- Writing a local nsec to YAML creates a new persistent secret-at-rest path that bypasses the signer model.
- FIPS's identity is a node identity, not a user identity — it doesn't need to match the browser's Nostr npub.
The settings UI shows both npubs:
- "Browser identity: npub1abc… (local key / read-only / n_signer)"
- "FIPS node identity: npub1def… (managed by FIPS)"
Optional future: explicit identity import. A user could choose to import a local nsec into FIPS config, behind an explicit warning: "Writing your private key to FIPS config creates a copy on disk. This is not recommended for NIP-46 or hardware signer users."
Shutdown:
FIPS does not have a control-socket shutdown command. The only mutating
commands are connect and disconnect (see
commands.rs). Stop the daemon
with SIGTERM, which it handles gracefully via
foreground_shutdown_signal().
.fips DNS Resolution
FIPS's DNS responder listens on [::1]:5354 by default. For .fips
names to resolve system-wide, one of these must be true:
- FIPS package installed —
fips-dns.serviceconfigures systemd-resolved to forward.fipsqueries to the daemon. This is the production path. - Manual resolver configuration — the user has configured
dnsmasq/systemd-resolved to forward
.fipsto[::1]:5354. - Browser-level DNS override — the browser intercepts
.fipslookups and resolves them by querying FIPS's DNS port directly. This is more complex and not yet implemented.
For the initial implementation, require that FIPS is installed as a system package (which sets up resolver integration). Browser-managed FIPS without resolver integration is a future enhancement.
fips:// URL Handling
fips:// is NOT a custom URI scheme handler. It's a redirect shorthand.
When the user enters fips://<npub>/path or clicks such a link:
- Parse the npub from the URL.
- Redirect to
http://<npub>.fips/path(preserving port, path, query, fragment). - WebKit's normal HTTP stack handles the request.
- The
.fipsDNS resolver translates the hostname to anfd00::/8IPv6 address. - The TUN interface routes the traffic through the FIPS mesh.
This reuses FIPS's existing IPv6 adapter and DNS resolver entirely — no custom scheme handler needed.
When FIPS is not running, .fips DNS resolution fails and the user
sees a standard "server not found" error.
FIPS Settings
[fips]
enabled=false
mode=auto ; auto|attach|manage
binary_path=fips ; for managed mode
control_socket= ; for attach mode (auto-detected if empty)
config_dir=~/.sovereign_browser/fips
FIPS Status UI
- Toggle: Enable FIPS
- Mode: Auto-detected (Attached to system fips / Managed)
- Status: Disabled / Connecting / Ready / Failed
- FIPS node identity: npub1def…
- Peers: 3 connected
- Tree state: Root / Leaf / Branch
- TUN: fips0 (active)
- Peer list (expandable): npub, transport, link metrics
- "Connect to peer" input: enter npub + address + transport
(sends
{"command":"connect","params":{...}})
Traffic Routing
When Tor is enabled (fail-closed):
| URL | Route |
|---|---|
http:// / https:// |
Tor SOCKS proxy → Tor network → internet |
http://<npub>.fips/ |
Direct (bypasses Tor via ignore_hosts) → TUN → FIPS mesh |
nostr:// / nostr: |
Tor SOCKS proxy → Nostr relays |
sovereign:// |
Direct (internal, no proxy) |
file:// |
Direct (local, no proxy) |
about: |
Direct (internal, no proxy) |
When Tor is disabled:
| URL | Route |
|---|---|
http:// / https:// |
Direct (no proxy) |
http://<npub>.fips/ |
Direct → TUN → FIPS mesh (if FIPS active) |
| All others | Same as above |
Proxy scope: WebKitGTK's proxy settings apply to WebKit's network stack (HTTP/HTTPS via libsoup). They do NOT automatically proxy:
- C-side relay fetches (
relay_fetch.c) - Agent LLM API calls
- NIP-05 verification HTTP requests
- FIPS's own Nostr relay connections
These C-side network calls would need separate proxy configuration
(e.g., setting SOCKS_PROXY env var, or configuring libsoup's proxy
directly). This is a known limitation and should be documented. A
future phase could route all browser-originated traffic through Tor.
FIPS + Tor interaction: FIPS has its own Tor transport
(TorConfig) for FIPS's
outbound relay connections. If the browser manages a Tor instance,
FIPS could be configured to use it as its SOCKS proxy. This requires
coordination: the browser's Tor SOCKS socket path must be passed to
FIPS's config. This is a future enhancement, not Phase 1.
Settings UI
A "Network" section in the Settings page (sovereign://settings):
┌─────────────────────────────────────────────────────────────┐
│ Network Services │
│ │
│ ┌─ Tor ───────────────────────────────────────────────────┐ │
│ │ [✓] Enable Tor-routed transport │ │
│ │ Mode: Attached to system Tor │ │
│ │ Status: Ready │ │
│ │ ⚠ Tor-routed transport is not Tor Browser. It does not │ │
│ │ provide the same fingerprinting protections. │ │
│ │ [New Identity] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ FIPS Mesh ─────────────────────────────────────────────┐ │
│ │ [✓] Enable FIPS │ │
│ │ Mode: Attached to system fips.service │ │
│ │ Status: Ready │ │
│ │ FIPS node: npub1def…xyz │ │
│ │ Peers: 3 connected │ │
│ │ TUN: fips0 (active) │ │
│ │ [Connect to peer…] │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Files
src/
├── net_services.c # Service ownership, state machine, supervision
├── net_services.h # Public API
├── tor_control.c # Tor control protocol client
├── tor_control.h # Tor control API
├── fips_control.c # FIPS JSON control protocol client
├── fips_control.h # FIPS control API
├── settings.c # Extended: tor/fips settings
├── settings.h # Extended: tor/fips config fields
├── main.c # Extended: init services, shutdown
├── tab_manager.c # Extended: fips:// redirect, fail-closed page
└── nostr_bridge.c # Extended: settings page network section
www/
├── settings.html # Extended: network services UI
├── settings.js # Extended: network services logic
└── settings.css # Extended: network services styling
Implementation Phases
Phase 1: Foundation
- Create
net_services.c— service ownership state machine, subprocess supervision (spawn, child-watch, graceful stop, stdout/stderr capture) - Define the
net_service_tstruct and state transitions - Add Tor/FIPS settings fields to
settings.c/settings.h - Create stub
tor_control.candfips_control.c(connect, send, receive) - Wire
net_services_init()andnet_services_shutdown()intomain.c
Phase 2: Attach-Only Tor Prototype
- Implement Tor control protocol client (authenticate, bootstrap status)
- Connect to an existing system Tor (attach mode only)
- Configure WebKitGTK SOCKS proxy with fail-closed behavior
- Test
.fipsbypass via ignore_hosts - Verify no DNS leaks (check with https://check.torproject.org)
- Add Tor status UI to settings page
Phase 3: Managed Tor
- Generate private torrc (Unix sockets, cookie auth, private data dir)
- Spawn tor subprocess, capture stdout/stderr
- Monitor bootstrap progress via control socket
- Implement graceful stop (SIGTERM, timeout, SIGKILL)
- Handle crash detection and FAILED state
- "New Identity" button (SIGNAL NEWNYM)
- Test: full lifecycle, port conflict avoidance, crash recovery
Phase 4: Attach-Only FIPS
- Implement FIPS control protocol client (JSON line protocol)
- Auto-detect FIPS control socket (standard resolution order)
- Query
show_status,show_peers,show_tree - Render FIPS status in settings UI
- Verify
http://<npub>.fips/works through TUN - Do NOT manage identity, config, or lifecycle
Phase 5: Managed FIPS
- Check prerequisites (binary found, CAP_NET_ADMIN, resolver integration)
- Generate minimal
fips.yaml(FIPS owns its identity, separate key file) - Spawn fips subprocess with private config
- Monitor via control socket
- Graceful stop via SIGTERM
- Show both browser npub and FIPS npub in settings UI
- Test: full lifecycle, privilege detection, resolver verification
Phase 6: URL Integration
- Support
fips://<npub>/pathas redirect tohttp://<npub>.fips/path - Preserve port, path, query, fragment
- Handle
nostr:link interception (NIP-21) — already planned innostr-uri-scheme.md - Show appropriate error when FIPS is not running
Phase 7: Packaging
- Debian package:
Depends: tor(recommended),Recommends: fips - Version compatibility checks (FIPS control protocol is pre-1.0)
- Document that FIPS package sets up TUN permissions and resolver
- No silent binary downloading at runtime
- Upgrade and uninstall behavior
Open Questions
-
Should C-side network calls (relay fetch, LLM API, NIP-05) also route through Tor? Currently they use libsoup directly, not WebKit's proxy. This needs separate proxy configuration. Phase 1 scope: WebKit traffic only. Future: route all browser traffic.
-
Should FIPS use the browser-managed Tor instance for its relay connections? FIPS has its own Tor transport config. Coordinating the browser's Tor SOCKS socket with FIPS's config is a future enhancement.
-
What happens when Tor mode is toggled at runtime? Existing WebKit connections may need to be closed or re-established. The proxy change applies to new requests; in-flight requests may complete on the old path. This needs testing.
-
Should the browser support multiple Tor instances? No — one Tor instance per browser. If the user needs multiple, they should use system Tor with multiple SOCKS ports.
-
How to verify
.fipsDNS bypass works with WebKitGTK's ignore_hosts? Must be tested empirically. If*.fipsglobbing doesn't work, may need to enumerate known.fipshostnames or use a different bypass mechanism. -
Should managed FIPS auto-generate peers or wait for user input? Default: no auto-peering. User must explicitly add peers via the "Connect to peer" UI. Auto-discovery (open policy) is a future option.