Files
sovereign_browser/docs/architecture.md
T

349 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (~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`](../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 (~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)
```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://<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 scheme**`fips://<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`](../../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).