Files
sovereign_browser/docs/architecture.md

16 KiB
Raw Permalink Blame History

Reckless Browser — Implementation Options

Goal

A Linux x86 browser written in C99 that:

  1. Keeps traditional HTTP/IP — can still load normal web pages.
  2. Makes FIPS addresses first-class citizensnpub…fips (or similar) is a resolvable URL scheme that routes over the FIPS mesh, not DNS/IP.
  3. Has Nostr signing built in — the browser itself acts like the nos2x extension: pages can request getPublicKey / signEvent and the browser signs with the user's Nostr keypair, no extension install required.
  4. Deprecates traditional web security where it gets in the way:
    • TLS/CA certificate system (replace with Nostr-identity auth or raw).
    • Same-origin policy / CORS (pages may call any endpoint freely).
    • Cookie/session sandboxing (shared Nostr identity across sites).
    • DNS/domain reliance (FIPS addresses and npubs resolve without DNS).

The name "reckless" is intentional: we are trading the browser's security sandbox for functionality, on the assumption that identity and transport are handled at a different layer (Nostr keys + FIPS mesh encryption).


The core decision: which engine to embed

A browser is really three things glued together: a rendering engine (HTML/ CSS/layout), a JS engine, and a networking stack. Writing all three from scratch in C99 is a multi-year effort and would never reach modern web compat. The realistic path is to embed an existing engine and write the "chrome" (URL bar, tabs, the FIPS/Nostr integration, the security-stripping layer) in C99.

There are four credible options.

Option A — Chromium Embedded Framework (CEF)

What it is. CEF is a third-party distribution that wraps Chromium (the same engine Chrome uses: Blink + V8 + the Chromium network stack) and exposes it through a C/C++ API. You link it into your own application and drive the browser from your own process. It is maintained by Marshall Bause and tracks Chromium releases closely.

Why it fits C99. CEF ships a C API wrapper (libcef_dll_wrapper plus the cef_app.h C interface). You can write the host application in C99 and only drop into C++ for the thin callback shims CEF requires. This is the standard way embedded-browser vendors (Spotify, Steam, many games) do it.

Pros.

  • Full modern web compat — it is Chromium, so every site works.
  • nos2x and other Chrome extensions work via the standard extension mechanism, so we can study how they inject window.nostr and replicate it natively.
  • Mature, documented, large community.
  • Chromium's networking stack is hackable: we can intercept requests at the URLLoader / ResourceDispatcherHost layer and reroute *.fips to the FIPS TUN interface, and strip CORS/same-origin checks there.

Cons.

  • Large binary (~150200 MB for the Chromium runtime it bundles).
  • Upstream is Google-controlled; CEF lags Chromium by a few weeks.
  • The C++ shim requirement means "pure C99" is aspirational — the glue is C++.
  • Building CEF from source is heavy; most users consume prebuilt binaries.

Security-stripping leverage. Chromium's security is layered (site isolation, CORS, mixed-content, certificate enforcement). In CEF we can:

  • Disable site isolation and the renderer sandbox via command-line switches.
  • Register a custom CefURLRequestHandler / SchemeHandlerFactory for fips:// and nostr:// schemes.
  • Override certificate validation to accept self-signed / raw connections.
  • Inject a window.nostr object from the browser process into every frame via OnContextCreated, before any page script runs.

Option B — Firefox / Gecko via Mozac or GeckoView

What it is. Mozilla's engine (Gecko) can be embedded. On Linux the relevant entry points are libxul (the Gecko shared library) and the newer Mozac components (Rust). There is no first-class "embed Gecko in C" story comparable to CEF; the supported embedding paths are Android (GeckoView) and the Firefox product itself.

Pros.

  • Mozilla upstream, non-Google.
  • WebExtensions API is the same one nos2x-style Firefox extensions use.
  • Gecko is more hackable at the protocol-handler level than Chromium.

Cons.

  • No supported C embedding story on Linux desktop. libxul is loadable but undocumented for embedders; you'd be reverse-engineering Firefox internals.
  • Mozac is Rust + Kotlin/Android-focused, not C99-friendly.
  • Smaller embedder community than CEF; most docs assume you're building Firefox.
  • Binary size comparable to CEF.

Verdict. Possible but uphill for a C99 project. Only choose this if there is a strong reason to avoid Chromium specifically.

Option C — Servo

What it is. Servo is the Rust-based browser engine originally spun out of Mozilla, now community-driven. It is designed to be embeddable and has a relatively clean API.

Pros.

  • Pure Rust — aligns with the FIPS stack (also Rust) and could eventually share crypto/networking code.
  • Hackable, small, modern architecture.
  • Embedding API is improving and intentionally minimal.

Cons.

  • No WebExtensions / extension system. We'd have to build the nos2x-style window.nostr injection ourselves (doable, but more work).
  • Web compat is incomplete — complex sites may not render correctly yet.
  • C API for embedding is nascent; you'd be calling Rust from C via FFI, which is fine but means the "C99" layer is thin.
  • Networking stack is less mature than Chromium's; intercepting/rerouting for FIPS is more work.

Verdict. Attractive long-term for a Rust-aligned stack, but risky for a first version that must load arbitrary HTTP/IP sites and behave like a real browser today.

Option D — WebKitGTK (via WPE or the GTK binding)

What it is. WebKitGTK exposes the WebKit engine (used by Safari/Epiphany) through a GObject/C API. WPE is the embedded-focused port. Both are C-callable.

Pros.

  • Genuine C API — no C++ shim required, best fit for "write it in C99."
  • Linux-native, packaged in distros, modest binary size.
  • WebKit is a real, full engine (Safari-class compat).
  • Custom URL schemes and request interception are supported via WebKitURISchemeRequest and the WebKitWebContext / WebsiteDataManager.
  • Extension model exists (WebKitWebExtension) for injecting window.nostr.

Cons.

  • Smaller community than Chromium; fewer Stack Overflow answers.
  • WebKit's process model and security flags are less documented to disable than Chromium's command-line switches.
  • Upstream is Apple-controlled; the GTK port tracks it with a lag.
  • nos2x itself is a Chrome/Firefox extension and won't run unchanged; we replicate its behavior natively regardless.

Verdict. The most C99-native option and a serious contender against CEF.


Comparison matrix

Criterion CEF (Chromium) Gecko/Firefox Servo WebKitGTK/WPE
Web compat (loads real sites) Excellent Excellent Partial Good
C99 friendliness C++ shim needed Poor FFI Excellent
Binary size — engine lib ~150200 MB libcef.so ~100130 MB libxul.so ~3050 MB servo ~3040 MB libwebkit2gtk-4.1.so
Binary size — full install ~400500 MB extracted (bundles Chromium runtime; full Chromium is ~684 MB) ~200300 MB (Firefox install) ~3050 MB ~4050 MB package (GTK deps usually already present on desktop)
Binary size — compressed dist ~100150 MB tar.xz ~80100 MB tar.bz2 ~1525 MB distro package
Extension / window.nostr Easy (Chrome ext) Easy (WebExt) Build it Build it
Request interception for FIPS Good Good Manual Good
Security flags easy to strip Best-documented Moderate N/A Moderate
Upstream control Google Mozilla Comm. Apple
Aligns with FIPS Rust stack No No Yes No

Decision: WebKitGTK + C99 (Path A)

POC results settled it. See poc/webkit_c99/FINDINGS.md and poc/servo_rust/FINDINGS.md.

Path Result
A — WebKitGTK/C99 Working in <1 min. 17 KB binary loaded laantungir.net cleanly.
B — Servo/Rust ⚠️ Prebuilt servoshell loaded pages but tripped on an SVG JS gap; custom embedder cargo build OOM-killed on this machine.

WebKitGTK is the primary engine. Servo is the documented fallback if Rust-alignment with FIPS later outweighs build-cost and web-compat concerns.

The rest of this section documents the POC exploration that led here.


We pursued two parallel proof-of-concept paths and compared them before committing to a primary engine:

Path A — WebKitGTK + C99 host

Why. WebKitGTK gives a genuine C API, honoring the "write it in C99" goal. It is Linux-native, distro-packaged, and the smallest practical footprint on a desktop (~3040 MB engine lib; GTK deps usually already present). Request interception and custom URI schemes are first-class — exactly what we need for fips:// / nostr:// and CORS stripping.

POC goal. A minimal C99 application that opens a WebKitGTK window and can load any normal web page (https://example.com, etc.). No FIPS, no Nostr, no security stripping yet — just prove the embedding path and feel out the friction.

What we're evaluating.

  • How clean is the C API in practice? Any forced C++?
  • Build complexity: pkg-config, GTK deps, WebKit version (4.0 vs 4.1).
  • Does it load heavy/real-world sites acceptably?
  • How hard is it to register a custom URI scheme handler (foreshadowing FIPS)?
  • How hard is it to inject a JS object before page scripts run (foreshadowing window.nostr)?

Path B — Servo + Rust host

Why. Servo is pure Rust, which aligns with the FIPS stack (also Rust). If this path wins, the browser and FIPS could eventually share crypto/networking code directly rather than via FFI. Servo is also the smallest (~3050 MB) and the most hackable.

POC goal. Same as Path A — a minimal application (Rust this time) that embeds Servo and loads any normal web page.

What we're evaluating.

  • How mature is Servo's embedding API? Is there a stable C ABI, or do we pin a specific commit?
  • Web compat: do real-world sites render, or do we hit missing-feature walls?
  • How hard is request interception / custom schemes (foreshadowing FIPS)?
  • How hard is injecting window.nostr with no extension system?
  • Can we link FIPS Rust crates directly into the host, or do we still need the TUN interface?

Comparison criteria for the POC phase

Criterion Path A (WebKitGTK/C99) Path B (Servo/Rust)
Time to first page load ? (measure) ? (measure)
Build friction ? ?
Real-site compat ? ?
Custom scheme handler cost ? ?
window.nostr injection ? ?
FIPS integration path FFI to libfips Direct crate link?
C99 purity High N/A (Rust host)

Both POCs are intentionally minimal — just "open a window, load a URL." The FIPS integration, Nostr signing, and security stripping come after we pick a primary engine based on the POC experience.


Proposed architecture (engine-agnostic)

flowchart LR
    subgraph Host[Reckless Browser Host - C99]
        UI[UI / URL bar / tabs]
        Router[Request Router]
        NostrKey[Nostr Key Store + Signer]
        SecStrip[Security Strip Layer]
    end

    subgraph Engine[Embedded Web Engine]
        Render[Renderer / JS]
        Net[Network Stack]
    end

    subgraph FIPS[FIPS Mesh]
        Tun[TUN fd00::/8]
    end

    subgraph Nostr[Nostr Relays]
        Relay[Relay pool]
    end

    UI --> Router
    Router -->|http/https| Net
    Router -->|fips:// npub.fips| Tun
    Router -->|nostr://| Relay
    Net --> SecStrip --> Render
    Render -->|window.nostr.signEvent| NostrKey
    NostrKey --> Relay

Layers

  1. Host application (C99). Owns the window, tab strip (GtkNotebook), per-tab toolbars (URL bar + hamburger menu), key store, and the request router. This is where the "reckless" policy lives. Tab management is in tab_manager.c, user preferences in settings.c, and session save/restore in session.c.

  2. Request router. Inspects every outgoing URL:

    • http:// / https:// → engine's normal network stack (with CORS / same-origin checks disabled at the engine level).
    • fips://<npub>… or <npub>.fips → resolve via FIPS TUN interface (fd00::/8 mapping), hand the raw TCP stream to the engine as a custom scheme handler. No TLS, no CA — FIPS provides Noise IK/XK encryption.
    • nostr://<npub>/<kind> → fetch the Nostr event set from relays, render natively or as a synthesized HTML document.
  3. Security strip layer. Engine configuration + request interception that:

    • Disables same-origin policy and CORS enforcement.
    • Accepts any certificate (or none) for raw connections.
    • Shares the Nostr identity across all origins (no per-site cookie sandbox).
  4. Nostr signer (built-in nos2x equivalent). Before page scripts run, inject window.nostr = { getPublicKey, signEvent, getRelays, … } into every frame. Calls are marshalled to the host's key store, which signs with the user's secp256k1 key and returns the signature. This is the same surface nos2x exposes, so existing Nostr web apps work without an extension.

  5. FIPS integration. Link against libfips (or shell out to fipsctl) so the browser can bring up / query the mesh and resolve .fips names. FIPS already maps npubs to fd00::/8 IPv6 addresses and provides a .fips DNS resolver, so the browser may not need its own resolver at all — it can lean on the FIPS TUN interface and just treat *.fips as "route to mesh."


Open questions to resolve (post-POC)

These are deferred until after the dual-path POC comparison, since the answers may depend on which engine wins:

  1. FIPS URL schemefips://<npub>:<port>/path, or reuse http://<npub>.fips/… and let FIPS's TUN+DNS handle it? The latter is less work but less explicit. (Note: FIPS already supports http://<npub>.fips/ today per fips_setup/plans/FIPS_ADDRESSING.md.)
  2. Nostr content rendering — should nostr:// render as native UI, or synthesize an HTML document from events? Native is cleaner; HTML reuses the engine.
  3. Key storage — plain file on disk, OS keyring, or a hardware signer (NIP-46 bunker / nos2x-style external signer)?
  4. How far to strip — do we keep any isolation (e.g. per-origin process separation) for stability, or go fully single-process reckless?

Suggested next steps (WebKitGTK chosen)

  1. Path A — WebKitGTK/C99 POC Done — loads laantungir.net cleanly.
  2. Path B — Servo/Rust POC ⚠️ Done — OOM on custom build; servoshell tripped on real site.
  3. Compare and pick primary engine Done — WebKitGTK.
  4. Next: FIPS custom URI scheme. Register fips:// (or handle *.fips) via webkit_web_context_register_uri_scheme() on the existing POC, proxying to the FIPS TUN interface. Verify a .fips URL loads.
  5. Nostr signing. Inject window.nostr = { getPublicKey, signEvent, ... } via WebKitUserContentManager + JSC, marshalling calls to a C-side key store. Verify a Nostr web app signs without an extension.
  6. Security stripping. Accept any cert via webkit_web_context_allow_tls_certificate_for_host(); share a single WebKitWebContext for cross-origin identity. Spike CORS/same-origin stripping (may need a WebKitWebExtension or source patch — the one open unknown from Path A).
  7. Resolve the deferred open questions (FIPS URL scheme form, Nostr content rendering, key storage, how far to strip).