16 KiB
Reckless Browser — Implementation Options
Goal
A Linux x86 browser written in C99 that:
- Keeps traditional HTTP/IP — can still load normal web pages.
- Makes FIPS addresses first-class citizens —
npub…fips(or similar) is a resolvable URL scheme that routes over the FIPS mesh, not DNS/IP. - Has Nostr signing built in — the browser itself acts like the
nos2xextension: pages can requestgetPublicKey/signEventand the browser signs with the user's Nostr keypair, no extension install required. - 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.
nos2xand other Chrome extensions work via the standard extension mechanism, so we can study how they injectwindow.nostrand replicate it natively.- Mature, documented, large community.
- Chromium's networking stack is hackable: we can intercept requests at the
URLLoader/ResourceDispatcherHostlayer and reroute*.fipsto 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/SchemeHandlerFactoryforfips://andnostr://schemes. - Override certificate validation to accept self-signed / raw connections.
- Inject a
window.nostrobject from the browser process into every frame viaOnContextCreated, 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.
libxulis 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-stylewindow.nostrinjection 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
WebKitURISchemeRequestand theWebKitWebContext/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.
nos2xitself 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 | 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.
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.nostrwith 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
-
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 intab_manager.c, user preferences insettings.c, and session save/restore insession.c. -
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::/8mapping), 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.
-
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).
-
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 surfacenos2xexposes, so existing Nostr web apps work without an extension. -
FIPS integration. Link against
libfips(or shell out tofipsctl) so the browser can bring up / query the mesh and resolve.fipsnames. FIPS already maps npubs tofd00::/8IPv6 addresses and provides a.fipsDNS resolver, so the browser may not need its own resolver at all — it can lean on the FIPS TUN interface and just treat*.fipsas "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:
- FIPS URL scheme —
fips://<npub>:<port>/path, or reusehttp://<npub>.fips/…and let FIPS's TUN+DNS handle it? The latter is less work but less explicit. (Note: FIPS already supportshttp://<npub>.fips/today perfips_setup/plans/FIPS_ADDRESSING.md.) - Nostr content rendering — should
nostr://render as native UI, or synthesize an HTML document from events? Native is cleaner; HTML reuses the engine. - Key storage — plain file on disk, OS keyring, or a hardware signer (NIP-46 bunker / nos2x-style external signer)?
- 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)
Path A — WebKitGTK/C99 POC✅ Done — loadslaantungir.netcleanly.Path B — Servo/Rust POC⚠️ Done — OOM on custom build; servoshell tripped on real site.Compare and pick primary engine✅ Done — WebKitGTK.- Next: FIPS custom URI scheme. Register
fips://(or handle*.fips) viawebkit_web_context_register_uri_scheme()on the existing POC, proxying to the FIPS TUN interface. Verify a.fipsURL loads. - Nostr signing. Inject
window.nostr = { getPublicKey, signEvent, ... }viaWebKitUserContentManager+ JSC, marshalling calls to a C-side key store. Verify a Nostr web app signs without an extension. - Security stripping. Accept any cert via
webkit_web_context_allow_tls_certificate_for_host(); share a singleWebKitWebContextfor cross-origin identity. Spike CORS/same-origin stripping (may need aWebKitWebExtensionor source patch — the one open unknown from Path A). - Resolve the deferred open questions (FIPS URL scheme form, Nostr content rendering, key storage, how far to strip).