- Scheme 54%
- C++ 19.4%
- Shell 15.1%
- C 5.5%
- Makefile 4.6%
- Other 1.4%
| .jerboa | ||
| docs | ||
| include | ||
| notes | ||
| packaging | ||
| qt-webengine | ||
| scheme | ||
| scripts | ||
| support | ||
| test | ||
| .build.yml | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| .jerbuild | ||
| AGENTS.md | ||
| artifact-url.txt | ||
| build-binary.ss | ||
| Dockerfile.qt | ||
| final-kimi3.md | ||
| jerboa-browser.md | ||
| jpkg.lock | ||
| jpkg.policy.sexp | ||
| jpkg.sexp | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| ROADMAP.md | ||
| rust-toolchain.toml | ||
| SECURITY.md | ||
| supply-chain.lock | ||
| WISHLIST.md | ||
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 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:
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_BLOCKSblocks, 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.