No description
  • Scheme 54%
  • C++ 19.4%
  • Shell 15.1%
  • C 5.5%
  • Makefile 4.6%
  • Other 1.4%
Find a file
2026-07-30 15:37:53 -06:00
.jerboa Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
docs Use Jerboa v0.2.8 toolchain 2026-07-30 13:03:27 -06:00
include Finish browser daily-driver hardening 2026-07-30 09:10:50 -06:00
notes Security hardening and release readiness 2026-06-23 10:49:02 -06:00
packaging Build browser FUSE helper without OpenSSL 2026-07-30 10:17:59 -06:00
qt-webengine Support Qt 6.4 WebEngine permissions 2026-07-30 13:56:04 -06:00
scheme Use Jerboa v0.2.8 toolchain 2026-07-30 13:03:27 -06:00
scripts Use Jerboa v0.2.8 toolchain 2026-07-30 13:03:27 -06:00
support build: update Jerboa 0.2.8 artifact digests 2026-07-30 13:19:36 -06:00
test Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
.build.yml Use Jerboa v0.2.8 toolchain 2026-07-30 13:03:27 -06:00
.dockerignore Use Ubuntu arm64 static Qt builder 2026-06-26 14:43:21 -06:00
.gitattributes Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
.gitignore jpkg: add package lock 2026-07-30 15:18:50 -06:00
.jerbuild Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
AGENTS.md Use Jerboa v0.2.8 toolchain 2026-07-30 13:03:27 -06:00
artifact-url.txt artifact-url.txt: record latest CI tarball URL (build #1769869) 2026-05-26 11:04:48 -06:00
build-binary.ss Finish browser daily-driver hardening 2026-07-30 09:10:50 -06:00
Dockerfile.qt Trim static Qt builder layer 2026-07-30 14:08:38 -06:00
final-kimi3.md Finish browser daily-driver hardening 2026-07-30 09:10:50 -06:00
jerboa-browser.md Security hardening and release readiness 2026-06-23 10:49:02 -06:00
jpkg.lock jpkg: add package lock 2026-07-30 15:18:50 -06:00
jpkg.policy.sexp Add jpkg package CI 2026-06-18 16:32:16 -06:00
jpkg.sexp Finish browser daily-driver hardening 2026-07-30 09:10:50 -06:00
LICENSE Switch to MIT license 2026-07-21 13:41:54 -06:00
Makefile Check Jerboa bundle digest per target 2026-07-30 13:50:48 -06:00
README.md Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
ROADMAP.md Stage 7: ad/tracker blocking via Brave's adblock-rust (C FFI) 2026-05-26 10:57:36 -06:00
rust-toolchain.toml Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
SECURITY.md Harden browser isolation and native dependencies 2026-07-11 17:22:54 -06:00
supply-chain.lock Check Jerboa bundle digest per target 2026-07-30 13:50:48 -06:00
WISHLIST.md Stage 7: ad/tracker blocking via Brave's adblock-rust (C FFI) 2026-05-26 10:57:36 -06:00

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.

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 iinit: 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 uunlock: prompts for the PIV PIN (masked) + a touch, then loads the bookmarks.
  • C-c y llock: 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:

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

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.

SourceHut CI 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)

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.