Files
sovereign_browser/plans/network-services-subprocesses.md

24 KiB

Network Services (Tor + FIPS) — Implementation Plan

Overview

sovereign_browser integrates two network services:

  1. 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.

  2. 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 .fips URLs 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:

  1. User-configured attach endpoint — if tor.attach_socks is set in settings, connect to that SOCKS port and verify it's a Tor proxy.
  2. System Tor control socket — check /run/tor/control (Unix) or 127.0.0.1:9051 (TCP). If responsive, attach.
  3. System Tor SOCKS port — check if 127.0.0.1:9050 is a SOCKS proxy. If so, attach (without control port, status is limited).
  4. Managed mode — if no existing Tor found and tor.binary_path is 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:

  1. Set WebKit proxy to a non-functional placeholder (e.g., socks5://127.0.0.1:1) to block all external traffic.
  2. 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:

  1. User-configured control socket — if fips.control_socket is set in settings, try connecting.

  2. 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().

  3. Managed mode — if no existing FIPS found and fips.binary_path is valid AND the binary has CAP_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 with getcap) OR user is root
  • .fips DNS 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:

  1. FIPS package installedfips-dns.service configures systemd-resolved to forward .fips queries to the daemon. This is the production path.
  2. Manual resolver configuration — the user has configured dnsmasq/systemd-resolved to forward .fips to [::1]:5354.
  3. Browser-level DNS override — the browser intercepts .fips lookups 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:

  1. Parse the npub from the URL.
  2. Redirect to http://<npub>.fips/path (preserving port, path, query, fragment).
  3. WebKit's normal HTTP stack handles the request.
  4. The .fips DNS resolver translates the hostname to an fd00::/8 IPv6 address.
  5. 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

  1. Create net_services.c — service ownership state machine, subprocess supervision (spawn, child-watch, graceful stop, stdout/stderr capture)
  2. Define the net_service_t struct and state transitions
  3. Add Tor/FIPS settings fields to settings.c / settings.h
  4. Create stub tor_control.c and fips_control.c (connect, send, receive)
  5. Wire net_services_init() and net_services_shutdown() into main.c

Phase 2: Attach-Only Tor Prototype

  1. Implement Tor control protocol client (authenticate, bootstrap status)
  2. Connect to an existing system Tor (attach mode only)
  3. Configure WebKitGTK SOCKS proxy with fail-closed behavior
  4. Test .fips bypass via ignore_hosts
  5. Verify no DNS leaks (check with https://check.torproject.org)
  6. Add Tor status UI to settings page

Phase 3: Managed Tor

  1. Generate private torrc (Unix sockets, cookie auth, private data dir)
  2. Spawn tor subprocess, capture stdout/stderr
  3. Monitor bootstrap progress via control socket
  4. Implement graceful stop (SIGTERM, timeout, SIGKILL)
  5. Handle crash detection and FAILED state
  6. "New Identity" button (SIGNAL NEWNYM)
  7. Test: full lifecycle, port conflict avoidance, crash recovery

Phase 4: Attach-Only FIPS

  1. Implement FIPS control protocol client (JSON line protocol)
  2. Auto-detect FIPS control socket (standard resolution order)
  3. Query show_status, show_peers, show_tree
  4. Render FIPS status in settings UI
  5. Verify http://<npub>.fips/ works through TUN
  6. Do NOT manage identity, config, or lifecycle

Phase 5: Managed FIPS

  1. Check prerequisites (binary found, CAP_NET_ADMIN, resolver integration)
  2. Generate minimal fips.yaml (FIPS owns its identity, separate key file)
  3. Spawn fips subprocess with private config
  4. Monitor via control socket
  5. Graceful stop via SIGTERM
  6. Show both browser npub and FIPS npub in settings UI
  7. Test: full lifecycle, privilege detection, resolver verification

Phase 6: URL Integration

  1. Support fips://<npub>/path as redirect to http://<npub>.fips/path
  2. Preserve port, path, query, fragment
  3. Handle nostr: link interception (NIP-21) — already planned in nostr-uri-scheme.md
  4. Show appropriate error when FIPS is not running

Phase 7: Packaging

  1. Debian package: Depends: tor (recommended), Recommends: fips
  2. Version compatibility checks (FIPS control protocol is pre-1.0)
  3. Document that FIPS package sets up TUN permissions and resolver
  4. No silent binary downloading at runtime
  5. Upgrade and uninstall behavior

Open Questions

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. How to verify .fips DNS bypass works with WebKitGTK's ignore_hosts? Must be tested empirically. If *.fips globbing doesn't work, may need to enumerate known .fips hostnames or use a different bypass mechanism.

  6. 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.