349 lines
16 KiB
Markdown
349 lines
16 KiB
Markdown
# 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://<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).
|