# 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 citizens** — `npub…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 (~150–200 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 | ~150–200 MB `libcef.so` | ~100–130 MB `libxul.so` | ~30–50 MB `servo` | ~30–40 MB `libwebkit2gtk-4.1.so` | | Binary size — full install | ~400–500 MB extracted (bundles Chromium runtime; full Chromium is ~684 MB) | ~200–300 MB (Firefox install) | ~30–50 MB | ~40–50 MB package (GTK deps usually already present on desktop) | | Binary size — compressed dist | ~100–150 MB tar.xz | ~80–100 MB tar.bz2 | ~15–25 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`](../poc/webkit_c99/FINDINGS.md) and [`poc/servo_rust/FINDINGS.md`](../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. --- ## Recommended path: dual-engine POC exploration 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 (~30–40 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 (~30–50 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) ```mermaid 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://…` or `.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:///` → 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 scheme** — `fips://:/path`, or reuse `http://.fips/…` and let FIPS's TUN+DNS handle it? The latter is less work but less explicit. (Note: FIPS already supports `http://.fips/` today per [`fips_setup/plans/FIPS_ADDRESSING.md`](../../lt/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).