~ober/jerboa-browser
Imported from ~/mine/jerboa-browser
about
# Jerboa Browser
**Experimental.** A programmable browser pane controlled from the Jerboa REPL.
Qt WebEngine is the first backend (the logical surface is backend-neutral so a
future Servo backend could implement the same `jwb_*` C ABI). The pane loads
pages, navigates, evaluates JavaScript, and enforces a deny-by-default
capability policy on top of the Chromium sandbox.
On top of that pane sits a **keyboard-driven, nyxt-style browser**: buffers (not
tabs), a fuzzy minibuffer/prompt, an emacs-default keymap, link-hinting, and an
in-process (RAM-only) password manager. The pure-Scheme engine
(`scheme/browser/{keymap,fuzzy,minibuffer,commands,passwords,bookmarks}.ss`) is
the source of truth; Qt is only a display + input surface.
```sh
jerboa-browser https://example.com # opens the window, keyboard-driven
```
## Quick keys (TL;DR)
No address bar — Jerboa is keyboard-driven (`C-` = Ctrl, `M-` = Alt; `C-h b`
lists every binding):
| Key | Does |
|-----|------|
| `C-l` | **open a URL** — type in the prompt, press Enter |
| `M-l` | open a URL in a new buffer (tab) |
| `C-t` / `C-x C-k` | new buffer (tab) / close current |
| `M-n` / `M-p` | next / previous buffer |
| `C-r` · `C-b` / `C-f` | reload · history back / forward |
| `C-s` · `f` | find on page · follow a link by hint |
| `M-x` · `C-h b` | run a command by name · list all keys |
| `C-x C-c` | quit |
## Keyboard (emacs keyscheme)
There is no address bar: `C-l` opens a URL prompt in the minibuffer — type the
URL (scheme optional; bare hosts become `https://…`) and press Enter, or `C-g`
to cancel. The minibuffer line doubles as the echo area; the status line shows
`[modes] [i/n] title — url`. `C-g` aborts a half-typed chord, a prompt, or
hint-mode. `C-h b` opens a buffer listing every binding.
| Key | Command | |
|-----|---------|--|
| `C-l` / `M-l` | set URL (this / new buffer) | navigation |
| `C-r` | reload | |
| `C-b` / `C-f` | history back / forward | |
| `C-x b` / `C-x C-b` | switch buffer (fuzzy) | buffers |
| `M-n` / `M-p` / `` C-` `` | next / previous / last buffer | |
| `C-t` | new buffer | |
| `C-x k` / `C-x C-k` | delete buffer (pick / current) | |
| `C-n` / `C-p` | scroll line down / up | motion |
| `C-v` / `M-v` | scroll page down / up | |
| `M-<` / `M->` | scroll to top / bottom | |
| `C-+` `C-=` / `C--` / `C-0` | zoom in / out / reset | zoom |
| `C-s` / `C-M-s` / `C-M-r` | find / find-next / find-prev | find |
| `C-c u` / `C-c t` | copy URL / title | clipboard |
| `f` / `M-g` (or `; f`) | follow hint (this / new buffer) | hint-mode |
| `C-c p s` / `C-c p f` / `C-c p k` | save / fill / forget login (RAM only) | passwords |
| `C-x r m` / `C-x r b` | bookmark current / open bookmark | bookmarks |
| `C-c y i` / `C-c y u` / `C-c y l` | init / unlock / lock the YubiKey-encrypted store | YubiKey |
| `C-c b` | toggle ad / tracker blocking | blocking |
| `M-x` | run command by name (shows docs) | palette |
| `C-h b` | describe all bindings | help |
| `C-x C-c` | quit | |
Passwords are held **in RAM only** — never written to env, config, or disk —
XOR-masked in the heap with a per-session pad and zeroed immediately after each
use; that is unchanged. **Bookmarks** can optionally persist **encrypted at rest,
unlocked by a YubiKey** (see below); the password vault stays RAM-only regardless.
Ad / tracker blocking is **on by default**, powered by Brave's `adblock-rust`
engine (vendored, linked into `libjerboa_browser`) and consulted on every
network request. `C-c b` toggles it; the status line shows `block` while active
and the echo area reports the running count of blocked requests.
## YubiKey-encrypted bookmarks
Bookmarks (not passwords) can be saved **encrypted at rest** and unlocked with a
YubiKey's PIV applet — no `ykman` or `yubico-piv-tool` needed. The crypto lives
in `scheme/browser/secure-store.ss` and YubiKey support comes from the vendored
`vendor/jerboa-yubikey/lib` library. On macOS it uses Apple's built-in
PCSC.framework; Linux packages bundle `libyubikey_native.so` for direct USB CCID
access, with pcsc-lite as a fallback when available.
- `C-c y i` — **init**: provisions PIV slot 9D if empty (generates an RSA-2048
key — PIN-policy ALWAYS, touch CACHED — authenticating the default management
key), then writes the encrypted store.
- `C-c y u` — **unlock**: prompts for the PIV PIN (masked) + a touch, then loads
the bookmarks.
- `C-c y l` — **lock**: wipes the in-RAM data key and drops decrypted bookmarks.
A random 256-bit data key encrypts the store with AES-256-GCM; that key is
RSA-OAEP-SHA256-wrapped to slot 9D's public key, so only this YubiKey (with its
PIN + a touch) can open the file. Saving needs no card (it wraps to the public
key); only unlocking talks to the card. Store: `$XDG_DATA_HOME/jerboa-browser/
bookmarks.enc`. Needs a YubiKey 5 (firmware 5.3+); on Linux, install `pcsc-lite`
and run `pcscd`.
## Encrypted browser vault
By default, WebEngine still uses an off-record profile. To keep browser cache,
cookies, local storage, downloads, and other profile data in an encrypted FUSE
vault, launch with:
```sh
JERBOA_BROWSER_VAULT=1 jerboa-browser
```
The browser process creates and mounts its own vault before the WebEngine
profile is opened, then points Qt at directories inside that mount. The FUSE
access controller trusts only the browser PID and descendants such as
`QtWebEngineProcess`; a `jsh` `,vault mount` can be used at a different path but
is not reused by the browser. Defaults:
- Vault file: `$XDG_DATA_HOME/jerboa-browser/browser.vault`
- Mountpoint: a fresh 0700 CSPRNG-named directory directly under a validated
`$XDG_RUNTIME_DIR` (or the platform's canonical sticky temporary directory)
- Size: `JERBOA_BROWSER_VAULT_BLOCKS` blocks, default 65536 (256 MiB)
The production default is YubiKey PIV unlock. With `JERBOA_BROWSER_VAULT=1`, the
browser prompts for the YubiKey PIV PIN before the Qt profile opens and derives
the vault passphrase through the card operation, requiring the YubiKey, PIN, and
touch when the slot policy requires it. A stolen vault file plus browser binary
is not enough to open the profile.
For first use with a blank PIV slot 9D, launch once with
`JERBOA_BROWSER_VAULT_YUBIKEY_PROVISION=1`; the browser will generate an
RSA-2048 key in the key-management slot using the default PIV management key,
matching the bookmark-store provisioning path.
Explicit overrides are still available:
- `JERBOA_BROWSER_VAULT_PASSPHRASE_FILE` — 0600 file containing a recovery or
test passphrase.
- `JERBOA_BROWSER_VAULT_UNLOCK=prompt` — terminal passphrase prompt.
The browser intentionally does not accept a vault passphrase through environment
variables and does not embed a build-local fallback secret. Use the YubiKey PIV
default, a protected passphrase file, or the interactive prompt.
An explicit `JERBOA_BROWSER_VAULT_MOUNT` must be an absolute, new path beneath
a trusted parent; pre-existing paths and symlinks are rejected. The vault data
directory is owner-only, and an existing vault file must be a 0600 regular file
owned by the current user with exactly one link.
URL authority is scheme-specific. `network` permits only `http`, `https`, `ws`,
and `wss`; it never implies local content or files. Application-created `data`,
`blob`, `qrc`, and `about:blank` pages require `local-content`. File URLs require
`filesystem` plus an absolute `JERBOA_BROWSER_FILESYSTEM_ROOT`, and targets must
canonicalize beneath that root.
## Build / test
```sh
make binary # the self-contained native ./jerboa-browser
make static-qt # bundled Linux Qt/WebEngine tarball via Podman (linux/arm64)
make vendor-yubikey # verify/fetch the locked YubiKey commit and tree
make yubikey-native # build lib/libyubikey_native for direct USB YubiKey access
make test # (browser) suite + secure-store unit tests; also test-{keymap,…,polish}
make verify # full test set, security checks, and dependency audit evidence
make release-evidence # write dist/release-evidence/ for release review
make test-securestore # just the pure secure-store crypto/format unit tests
make test-gui # offline Qt snapshot harness (offscreen, headless)
```
`make static-qt` mirrors the jerboa-emacs container workflow through Podman and copies out
`jerboa-browser-static-qt-linux-arm64.tar.gz`. Because this backend is Qt
WebEngine, the artifact is a bundled Linux tarball rather than a literal
one-file static executable. It includes the native browser binary, the
`libjerboa_browser.so` backend, the browser vault FUSE helper, Qt
libraries/plugins, `QtWebEngineProcess`, and WebEngine resource files.
The `git.jerboa.sh` releases page publishes verified Linux tarballs under architecture-specific
artifact names.
The functional suites run offscreen (`QT_QPA_PLATFORM=offscreen`) and hermetic
(`JWB_TEST_NO_NETWORK=1`), modeled on the jerboa-emacs Qt test harness.
## Layout
```
include/jerboa_browser.h C ABI shared by every backend
qt-webengine/ Qt WebEngine backend (CMake; builds libjerboa_browser)
src/ C++ shim over QWebEngine{Profile,Page,View}
probe/ Ticket 0.1 standalone proof-of-host (kept for re-checks)
tests/ ABI/handle tests (ctest)
scheme/ Jerboa wrapper module + tests (.ss)
vendor/jerboa-fuse/ browser-owned encrypted FUSE vault
notes/ host pin, design watchpoints
jerboa-browser.md living plan / ticket log
```
## Build (macOS, Homebrew Qt)
```sh
cmake -S qt-webengine -B qt-webengine/build -DCMAKE_PREFIX_PATH=/opt/homebrew
cmake --build qt-webengine/build
./qt-webengine/build/jwb_demo
```
## Security model
Object-capability, deny-by-default: a context grants only the capabilities
named at creation (`JWB_CAP_NETWORK`, …); everything else is blocked. The
Chromium sandbox stays enabled — `QTWEBENGINE_DISABLE_SANDBOX` is never set for
normal runs. See `jerboa-browser.md`.
recent commits
- Document local mine repository authority ba58c37 Jaime Fournier
- Remove jerboa-emacs untouchable policy c02b4bf Jaime Fournier
- Update release links for Forgejo 1dd16c4 Jaime Fournier
- Update jpkg metadata for Forgejo f8f5c2e Jaime Fournier
- Remove GitHub workflow metadata b92ca7e Jaime Fournier
- jpkg: add package lock 0bdaae2 Jaime Fournier
- Trim static Qt builder layer 6aa4a37 Jaime Fournier
- Bundle Qt runtime in static container 860676e Jaime Fournier
- Support Qt 6.4 WebEngine permissions c303afc Jaime Fournier
- Check Jerboa bundle digest per target a8e7b67 Jaime Fournier