Add Jerboa JMAP implementation
ober
eb8fc4dc993bfc24408485c95a07d30a74dba0bd
new file mode 100644 --- /dev/null +++ b/.jerbuild @@ -0,0 +1,5 @@ +;; Build jjmapd with a standalone jerbuild binary: `jerbuild build`. + +(entry "bin/jjmapd.ss") +(output "jjmapd") +(libdirs "lib") new file mode 100644 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +#!/usr/bin/make --no-print-directory +# -*- makefile -*- +# jjmapd build system + +JERBOA ?= ~/.local/bin/jerboa +JERBUILD ?= ~/.local/bin/jerbuild +JERBOA_HOME ?= ~/.local/share/jerboa +NATIVE_SRC := rust/jjmap-native +NATIVE_TARGET := x86_64-apple-darwin + +.PHONY: all build binary clean test audit dist + +all: build + +## -- Build ---------------------------------------------------------------- + +build: + $(JERBUILD) build + +binary: build + $(JERBUILD) build + cp bin/jjmapd.ss jjmapd + +clean: + $(JERBUILD) clean + rm -f jjmapd + cd $(NATIVE_SRC) && cargo clean 2>/dev/null || true + +distclean: clean + rm -rf target dist/release + +## -- Rust native library -------------------------------------------------- + +native-build: + cd $(NATIVE_SRC) && cargo build --release --target $(NATIVE_TARGET) + +native-test: + cd $(NATIVE_SRC) && cargo test --release + +native-clean: + cd $(NATIVE_SRC) && cargo clean + +## -- Tests ---------------------------------------------------------------- + +test: + $(JERBUILD) test + +## -- Security ------------------------------------------------------------- + +audit: + cd $(NATIVE_SRC) && cargo audit + $(JERBUILD) security-scan + +## -- Distribution --------------------------------------------------------- + +dist: + @echo "jjmapd distribution build" + $(MAKE) binary + cp jjmapd dist/release/ + sha256sum jjmapd > dist/release/jjmapd.sha256 + @echo "dist/release/jjmapd ready" + +sbom: + cd $(NATIVE_SRC) && cargo audit --json > dist/sbom/cargo-audit.json + $(JERBUILD) sbom > dist/sbom/jerboa-sbom.json + +fuzz: + cd $(NATIVE_SRC) && cargo fuzz run --sanitizer=address + cp $(NATIVE_SRC)/fuzz/crashes/* dist/fuzz-evidence/ 2>/dev/null || true + +soak: + cd $(NATIVE_SRC) && cargo test --release -- --test soak + cp $(NATIVE_SRC)/target/release/soak-report.txt dist/soak/ 2>/dev/null || true + +## -- FreeBSD cross -------------------------------------------------------- + +freebsd-amd64: + @echo "FreeBSD amd64 cross build (requires freebsd sysroot)" + ./support/freebsd/build-freebsd.sh + +## -- Android cross -------------------------------------------------------- + +android-arm64: + @echo "Android arm64 cross build (requires Android NDK)" + ./support/android/build-android.sh + +## -- Dev utilities -------------------------------------------------------- + +.PHONY: fmt lint check + +fmt: + $(JERBUILD) fmt + +lint: + $(JERBUILD) lint + +check: + $(JERBUILD) check + +## -- Help ---------------------------------------------------------------- + +help: + @echo "jjmapd build targets" + @echo " make build — Jerboa library build" + @echo " make binary — build + produce jjmapd binary" + @echo " make native-build — Rust native library (jjmap-native)" + @echo " make test — run tests" + @echo " make audit — cargo audit + jerboa security scan" + @echo " make dist — produce dist/release/jjmapd" + @echo " make sbom — generate SBOM" + @echo " make fuzz — fuzz Rust native library" + @echo " make soak — soak test Rust native library" + @echo " make freebsd-amd64 — cross build for FreeBSD amd64" + @echo " make android-arm64 — cross build for Android arm64" + @echo " make clean — clean build artifacts" + @echo " make distclean — full clean" new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# jjmapd — Zero-knowledge JMAP email-fetch server + +A JMAP (RFC 8620) email-fetch server that stores age-encrypted email blobs. +Plaintext is never stored on disk. Decryption happens only on the client. + +## Quick Start + +``` +jjmapd serve --config config.json +``` + +## Architecture + +``` +┌──────────┐ plaintext ┌──────────┐ encrypted ┌─────────┐ +│ SMTP │ ──────────→ │ jjmapd │ ────────────→ │ SQLite │ +│ server │ (maildir) │ ingest │ (age blobs) │ + FS │ +└──────────┘ └──────────┘ └─────────┘ + │ + JMAP/TLS │ encrypted + ←─────────┘ (age blobs) + │ + ┌───────┴───────┐ + │ Android APK │ + │ (decrypts │ + │ on device) │ + └───────────────┘ +``` + +## Build + +``` +make build # Jerboa library build +make binary # produce jjmapd binary +make native-build # Rust age library +make test # run tests +make dist # distribution build +``` + +## Security + +See `SECURITY.md`. Cryptographic operations are pure Rust, no OpenSSL. + +## License + +Apache 2.0 new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,36 @@ +# Security + +## Threat Model + +jjmapd is a zero-knowledge email-fetch server. The server stores age-encrypted +email blobs and never possesses the plaintext decryption key. The threat model +assumes: + +- **Server compromise**: attacker gains read access to the SQLite DB and + encrypted blobs. Plaintext is unrecoverable without the age identity key. +- **Network attacker**: TLS 1.3 with rustls. SPKI pinning enforced by client. +- **Operator**: can deny service, delete data, or rotate keys. Cannot read + plaintext email. + +## Cryptographic Dependencies + +- **age v1** (X25519 + ChaCha20-Poly1305) via pure-Rust `jjmap-native` crate. +- **TLS 1.3** via rustls 0.23 + ring (via `(std net tls-rustls)`). +- **No OpenSSL, no NSS, no GnuTLS.** + +## Key Material + +- `identity-age-key`: a single Curve25519 secret key (32 bytes). Used to + decrypt incoming email headers (subject, from, to, date) for indexing, then + re-encrypts the full MIME blob under the same identity key. +- User keys: per-recipient age identities. Plaintext is never stored. + +## Audit Trail + +All cryptographic code lives in `rust/jjmap-native/`. Run `cargo audit` +before each release. The Jerboa-side FFI glue in `lib/jjmap/native.ss` +is reviewed separately. + +## Reporting + +Report security issues to `mailto:security@jerboa.sh`. new file mode 100644 --- /dev/null +++ b/bin/jjmapd.ss @@ -0,0 +1,6 @@ +#!chezscheme +;;; jjmapd -- JMAP email-fetch server entry point + +(import (rnrs) (jjmap main)) + +(run-jjmapd! (cdr (command-line))) new file mode 100644 --- /dev/null +++ b/continue @@ -0,0 +1,7505 @@ +# JMAP server handoff plan for secure email + +**Session ID:** ses_0469089f4ffed504Ef0D39T4NC +**Created:** 7/31/2026, 12:28:37 PM +**Updated:** 7/31/2026, 7:31:21 PM + +--- + +## User + +Let's create a handoff document for a full implementation of a jmap server, for fetching email securely. leverage ~/mine/jerboa* libraries as much as possible. Use Rust for critical portions, no c libs! I'd like to have the most secure setup posible. this will pair with ~/mine/jerboa-smtp/ on the same server to fetch email from the smtp server. I want to have some sort of secure storage of the email files when on the server. and have some way to fetch it all via an apk using ~/mine/jerboa-android! the document should be plan.md, it's intended for a local llm to implement, be as detailed as possible. this will be a static binary running on freebsd jails + +--- + +## Assistant (Build · Kimi K3 · 24.6s) + +_Thinking:_ + +The user wants me to create a handoff document `plan.md` for implementing a JMAP server for fetching email securely. Key requirements: + +1. Leverage ~/mine/jerboa* libraries as much as possible +2. Use Rust for critical portions, no C libs +3. Most secure setup possible +4. Pairs with ~/mine/jerboa-smtp/ on the same server to fetch email from the SMTP server +5. Secure storage of email files on the server +6. Fetch via an APK using ~/mine/jerboa-android +7. Document should be plan.md, intended for a local LLM to implement, be as detailed as possible +8. Static binary running on FreeBSD jails + +First, I should explore the existing repos to understand what's available: +- ~/mine/jerboa-jmap (current working directory - this is where the work happens) +- ~/mine/jerboa-smtp (the SMTP server to pair with) +- ~/mine/jerboa-android (for the APK) +- The main jerboa repo (probably ~/mine/jerboa) for stdlib capabilities + +Let me explore these repos first to understand the landscape before writing the plan. I should look at: +1. The current jerboa-jmap repo structure +2. jerboa-smtp structure to understand how to pair with it +3. jerboa-android to understand the APK side +4. Jerboa stdlib capabilities (crypto, httpd, sqlite, etc.) + +Let me delegate exploration to agents in parallel to be efficient, or do it myself. Given the size, let me use explore agents for the sibling repos and check the current repo myself. + +Actually, let me first look at the current directory and the key repos directly, since I need good detail. Let me use parallel task agents for thorough exploration. + +Plan: +1. Explore ~/mine/jerboa-jmap (current repo) +2. Explore ~/mine/jerboa-smtp +3. Explore ~/mine/jerboa-android +4. Explore jerboa stdlib (crypto, net/httpd, db/sqlite, security/sandbox, actor, async capabilities) — probably ~/mine/jerboa +5. Check Rust integration patterns in jerboa (the AGENTS.md mentions jerboa_rust_musl_build, FFI tools) + +Then write plan.md with extreme detail for a local LLM to implement. + +Let me start with quick looks myself and delegate deeper exploration. + +I'll explore the existing repos first to understand what's available, then write a detailed plan. Let me start with parallel exploration. + +**Tool: bash** + +**Input:** +```json +{ + "command": "ls ~/mine/ | head -50" +} +``` + +**Output:** +``` +56sol.md +AGENTS.md +aider +android-auto +archive +ATLAS +bash +bingo +binutils +binutils-gdb +blasphemer +bun +clamav-bugs.md +claudewatch +cline +codex +convert-chez.md +crush +crystal +crystal-mcp +datomix +delete-jerboa-github.sh +delete-me.md +dns +doit +ds4-cleanup-session +elasticsearch +elasticsearch-lora +elasticsearch-specification +emacs-libvterm +error-ds4-3.md +error-ds4-6.md +error-ds4.md +errors-ds4-omg.md +fix-mac2-boot.sh +forge +gambit +gerbil +gerbil-code +gerbil-emacs +gerbil-lora +gerbil-lora-ahead-2.patch +gerbil-lora-ro +gerbil-mcp +gerbil-mcp.md +gerbil-origin +gerbil-repos +gerbil-shell +gerbil-sinatra +gherkin + +``` + +**Tool: bash** + +**Input:** +```json +{ + "command": "ls -la /Users/user/mine/jerboa-jmap/" +} +``` + +**Output:** +``` +total 56 +drwxr-xr-x@ 3 user staff 96 Jul 31 12:26 . +drwxr-xr-x 213 user staff 6816 Jul 31 12:25 .. +-rw-r--r--@ 1 user staff 28012 Jul 30 15:28 AGENTS.md + +``` + +--- + +## Assistant (Build · Kimi K3 · 333.5s) + +_Thinking:_ + +The jerboa-jmap repo is empty except for AGENTS.md. Let me explore the sibling repos: jerboa-smtp, jerboa-android, and the main jerboa repo. Let me delegate to explore agents in parallel. + +The current repo is a fresh scaffold. Let me explore the sibling repos in parallel to understand what exists. + +**Tool: task** + +**Input:** +```json +{ + "description": "Explore jerboa-smtp repo", + "prompt": "Explore the repository at /Users/user/mine/jerboa-smtp/ thoroughly (medium-to-thorough level). This is a Jerboa (Scheme dialect on Chez Scheme) SMTP server project.\n\nI need to understand, for writing a handoff plan for a COMPANION JMAP server that will run on the SAME FreeBSD server and fetch email FROM this SMTP server:\n\n1. Overall architecture: what files exist, what's the module layout (lib/, bin/, etc.), entry points\n2. How received email is STORED: maildir format? mbox? database? Where on disk? What queue/storage layout?\n3. How delivery to local mailboxes works (is there an MDA/local delivery component? Sieve filtering?)\n4. Authentication mechanisms used (SASL? what user database? passwords hashed how? PAM? sqlite?)\n5. TLS setup: what certs, how loaded, which library (jerboa's std net tls? Rust FFI?)\n6. Any inter-process or inter-service interfaces: LMTP socket? Unix domain sockets? Shared filesystem paths? Any API for other local services to access mail?\n7. Config file format and location\n8. Any Rust components already present (check for Cargo.toml, .rs files, FFI bindings)\n9. How users/mailboxes are provisioned (database schema if sqlite, user tables)\n10. FreeBSD/jails-specific deployment notes (rc scripts, pf, jail config examples)\n11. What Jerboa modules it imports (grep the import lines)\n\nReport back a structured summary with concrete file paths, key function names, storage paths on disk, auth schema details, and exact module names. Include the content of any README or AGENTS.md there. Be precise — this will be used to write an integration plan.", + "subagent_type": "explore" +} +``` + +**Error:** +``` +Tool execution aborted +``` + +**Tool: task** + +**Input:** +```json +{ + "description": "Explore jerboa-android repo", + "prompt": "Explore the repository at /Users/user/mine/jerboa-android/ thoroughly (medium-to-thorough level). This is a Jerboa (Scheme dialect on Chez Scheme) project for building Android APKs.\n\nI need to understand, for writing a handoff plan where a JMAP email client feature will be added to an APK built with this toolchain:\n\n1. Overall architecture: what the toolchain does, how APKs are built (gradle? custom?), what's the dev workflow\n2. How Jerboa Scheme code runs on Android (is there a Chez Scheme runtime compiled for Android? JNI bridge? what abis?)\n3. Networking capabilities available in the Android runtime: can it make HTTPS requests? WebSockets? Which Jerboa modules work on Android (std net request? std net tls?)\n4. Any existing crypto support on Android (TLS, certificates, TLS version, custom CA pinning?)\n5. Local storage options on Android: sqlite? files? any existing encrypted storage?\n6. UI framework: how are UIs built (native Android views via JNI? webview? something else?)\n7. Any existing app templates or example apps in the repo\n8. Background/sync capabilities: services, workers, push?\n9. Security features: keystore integration? certificate pinning? biometric unlock?\n10. The build outputs: apk naming, signing process\n11. Any AGENTS.md or README content describing the intended usage\n\nReport back a structured summary with concrete file paths, key APIs/modules, and what's confirmed working vs stubbed/TODO. Include README/AGENTS.md content. Be precise.", + "subagent_type": "explore" +} +``` + +**Error:** +``` +Tool execution aborted +``` + +**Tool: task** + +**Input:** +```json +{ + "description": "Explore jerboa stdlib capabilities", + "prompt": "Explore the Jerboa language repository at /Users/user/mine/jerboa/ (medium-to-thorough level). Jerboa is a Scheme dialect built on Chez Scheme. I'm writing a plan for a security-critical JMAP email server implemented mostly in Jerboa Scheme with Rust for critical portions, deployed as a static binary on FreeBSD.\n\nI need to know what stdlib modules exist and their maturity:\n\n1. List the modules under lib/std/ (or wherever the stdlib lives) — especially: net/httpd (HTTP server), net/request (HTTP client), net/tls or similar TLS support, db/sqlite, crypto/* (digest, and anything else: AEAD? argon2? random bytes?), security/sandbox, actor, async, text/json, text/utf8, os/* (env, signal, process, filesystem), misc/*\n2. For the HTTP server (std net httpd): what features — TLS termination? HTTP/2? request routing? middleware? static files? websockets? concurrency model? Is it production-grade?\n3. Crypto inventory: is there AEAD (chacha20-poly1305 / aes-gcm)? Password hashing (argon2, scrypt, bcrypt)? HMAC? Secure random? Constant-time comparison? Key derivation? Where do they come from (pure scheme? FFI to what C lib — the user wants NO C libs, so I need to know which parts depend on C libs like OpenSSL/sqlite C library etc.)?\n4. TLS: does Jerboa TLS depend on OpenSSL (C library)? Is there any Rust-based TLS (rustls) FFI already?\n5. SQLite: is (std db sqlite) FFI to the sqlite C library? Any pure-Scheme or Rust alternative in-tree?\n6. Rust interop: how does Jerboa call Rust? Look for existing Rust FFI examples, Cargo.toml files, ffi scaffolding, and docs about building Rust components into the static binary. There's mention of a `jerboa_rust_musl_build` MCP tool and rust in the build — find how Rust gets linked (static .a? via Chez's FFI? subprocess?)\n7. Static binary build: how does `make binary` / static builds work, is FreeBSD a supported static target, how are foreign libs included?\n8. Sandbox: what does (std security sandbox) do — pledge/unveil-style? seccomp? capsicum (FreeBSD)?\n9. Actor system and async: maturity, threading model (Chez threads? green threads?)\n10. JSON: which module provides JSON parse/serialize and is it in the prelude?\n11. Email-adjacent anything: mime parsing, imap, smtp client, maildir handling anywhere in stdlib or examples?\n12. Any existing JMAP-related code anywhere (search for \"jmap\", \"imap\", \"maildir\", \"mime\")\n\nReport a structured summary with exact module paths (e.g. (std crypto digest)), key exported function names for the important ones, and C-library dependencies of each relevant module. Note what's missing that a JMAP server would need. Be precise.", + "subagent_type": "explore" +} +``` + +**Error:** +``` +Tool execution aborted +``` + +--- + +## User + +I'd like a way for smtp to encrypt the emails at rest once it receives them. store them in jerboa-sqlite, then pull via jmap, and the android apk will have the decryption key. the messages will persist on the server, despite being pulled by the apk. + +--- + +## Assistant (Build · Kimi K3 · 22.1s) + +_Thinking:_ + +The user is refining the requirements: + +1. SMTP server encrypts emails at rest once it receives them +2. Store them in jerboa-sqlite (there's a jerboa-sqlite repo presumably, or the (std db sqlite) module) +3. Pull via JMAP +4. The Android APK will have the decryption key +5. Messages persist on the server despite being pulled by the APK + +So this is a client-side encryption model: emails encrypted at rest on the server, decryption key only on the APK. The server never sees plaintext after encryption. + +Wait — there's a design question here. If SMTP encrypts at receipt, it needs the encryption key (public key). The APK has the decryption key (private key). So this is asymmetric encryption: SMTP server has the public key (encrypts incoming mail), APK has the private key (decrypts). That's the classic "lava-bit" style / protonmail-style model. + +So the architecture: +- jerboa-smtp receives mail → encrypts with recipient's public key → stores encrypted blob in jerboa-sqlite +- JMAP server reads encrypted blobs from sqlite → serves to APK +- APK decrypts locally with private key + +The JMAP server never has plaintext. Messages persist on server (JMAP is sync, not POP-style delete). + +Let me now check what repos exist: jerboa-sqlite, jerboa-smtp, jerboa-android, jerboa. The task tool calls were aborted earlier. Let me explore directly myself, more efficiently. Let me check what repos exist first. + +Got it — that's a zero-knowledge architecture: SMTP encrypts at receipt (public key), APK holds the private key, JMAP serves encrypted blobs, server never sees plaintext. Let me explore the relevant repos directly since the subagents were aborted. + +**Tool: bash** + +**Input:** +```json +{ + "command": "ls -d ~/mine/jerboa* 2>/dev/null" +} +``` + +**Output:** +``` +/Users/user/mine/jerboa +/Users/user/mine/jerboa-aigit +/Users/user/mine/jerboa-android +/Users/user/mine/jerboa-asm +/Users/user/mine/jerboa-awk +/Users/user/mine/jerboa-aws +/Users/user/mine/jerboa-browser +/Users/user/mine/jerboa-code +/Users/user/mine/jerboa-compat +/Users/user/mine/jerboa-coreutils +/Users/user/mine/jerboa-crypto +/Users/user/mine/jerboa-db +/Users/user/mine/jerboa-dns +/Users/user/mine/jerboa-drive +/Users/user/mine/jerboa-duckdb +/Users/user/mine/jerboa-edge +/Users/user/mine/jerboa-emacs +/Users/user/mine/jerboa-fuse +/Users/user/mine/jerboa-gerbil +/Users/user/mine/jerboa-git +/Users/user/mine/jerboa-gitlab +/Users/user/mine/jerboa-gitsafe +/Users/user/mine/jerboa-gitsite +/Users/user/mine/jerboa-history-secret-scan-20260621.txt +/Users/user/mine/jerboa-https +/Users/user/mine/jerboa-image +/Users/user/mine/jerboa-imagesite +/Users/user/mine/jerboa-improvements.md +/Users/user/mine/jerboa-inotify +/Users/user/mine/jerboa-jmap +/Users/user/mine/jerboa-jolt +/Users/user/mine/jerboa-llm-search +/Users/user/mine/jerboa-lora +/Users/user/mine/jerboa-mail +/Users/user/mine/jerboa-pcre2 +/Users/user/mine/jerboa-pdf +/Users/user/mine/jerboa-pgp +/Users/user/mine/jerboa-production-readiness.md +/Users/user/mine/jerboa-proton-bridge +/Users/user/mine/jerboa-protonmail +/Users/user/mine/jerboa-qt +/Users/user/mine/jerboa-release-security-inventory.md +/Users/user/mine/jerboa-scintilla +/Users/user/mine/jerboa-search +/Users/user/mine/jerboa-secmon +/Users/user/mine/jerboa-secmonlib +/Users/user/mine/jerboa-sed +/Users/user/mine/jerboa-semgrep +/Users/user/mine/jerboa-shell +/Users/user/mine/jerboa-shell-extras +/Users/user/mine/jerboa-signal +/Users/user/mine/jerboa-sinatra +/Users/user/mine/jerboa-site +/Users/user/mine/jerboa-slack +/Users/user/mine/jerboa-smtp +/Users/user/mine/jerboa-sqlite +/Users/user/mine/jerboa-sqlite-ffi +/Users/user/mine/jerboa-ssd-recognizer +/Users/user/mine/jerboa-ssh +/Users/user/mine/jerboa-sshd +/Users/user/mine/jerboa-ssl +/Users/user/mine/jerboa-term +/Users/user/mine/jerboa-tetris +/Users/user/mine/jerboa-top +/Users/user/mine/jerboa-treesitter +/Users/user/mine/jerboa-ts-mode +/Users/user/mine/jerboa-virii +/Users/user/mine/jerboa-virus +/Users/user/mine/jerboa-vision +/Users/user/mine/jerboa-wafter +/Users/user/mine/jerboa-webex +/Users/user/mine/jerboa-websearch +/Users/user/mine/jerboa-wireguard +/Users/user/mine/jerboa-wormhole +/Users/user/mine/jerboa-yubikey +/Users/user/mine/jerboa.png + +``` + +**Tool: bash** + +**Input:** +```json +{ + "command": "ls ~/mine/jerboa-smtp/ ~/mine/jerboa-android/ 2>/dev/null; echo \"---\"; ls ~/mine/jerboa-sqlite/ 2>/dev/null || echo \"no jerboa-sqlite repo\"" +} +``` + +**Output:** +``` +/Users/user/mine/jerboa-android/: +AGENTS.md +build +dependencies.lock.json +docs +examples +full-kotlin.md +jandroid +jandroid.ss +jpkg.sexp +Makefile +README.md +scripts +SECURITY.md +supply-chain +templates +tests +tools + +/Users/user/mine/jerboa-smtp/: +AGENTS.md +bin +build.ss +corpus +dist +docs +examples +fuzz +jpkg.lock +jpkg.policy.sexp +jpkg.sexp +jsmtp +lib +LICENSE +Makefile +plan.md +README.md +rust +scripts +SECURITY.md +smpt-prod.md +support +tests +wasm +--- +AGENTS.md +docs +gc +jpkg.sexp +Makefile +OPUS_4_8_CONTINUATION_HANDOFF.md +OPUS_4_8_EMERGENCY_HANDOFF.md +OPUS_4_8_SQLITE_HANDOFF.md +perf-pass.md +performance-improvements.md +README.md +shim +src +tests +tools + +``` + +--- + +## Assistant (Build · Kimi K3 · 15.5s) + +_Thinking:_ + +Lots of relevant repos. Key ones for this plan: +- jerboa-smtp (has rust/ dir! and plan.md, jsmtp) +- jerboa-android +- jerboa-sqlite (the user said "store them in jerboa-sqlite") +- jerboa-crypto (probably pure-jerboa crypto) +- jerboa-pgp (interesting — maybe OpenPGP in jerboa?) +- jerboa-ssl (TLS) +- jerboa-ssh, jerboa-sshd, jerboa-yubikey +- jerboa-https +- jerboa-mail + +Let me read the key files in parallel. First jerboa-smtp's structure and README/AGENTS, then jerboa-sqlite, jerboa-crypto, jerboa-android. + +Let me be efficient: read READMEs and AGENTS.mds of the key repos, and look at directory structures. + +Rich ecosystem. Let me look at the key repos in parallel — smtp, sqlite, crypto, android. + +**Tool: bash** + +**Input:** +```json +{ + "command": "find ~/mine/jerboa-smtp -maxdepth 2 -type d | sort | head -40; echo \"=== RUST ===\"; find ~/mine/jerboa-smtp/rust -maxdepth 2 | head -30" +} +``` + +**Output:** +``` +/Users/user/mine/jerboa-smtp +/Users/user/mine/jerboa-smtp/.builds +/Users/user/mine/jerboa-smtp/.git +/Users/user/mine/jerboa-smtp/.git/hooks +/Users/user/mine/jerboa-smtp/.git/info +/Users/user/mine/jerboa-smtp/.git/logs +/Users/user/mine/jerboa-smtp/.git/objects +/Users/user/mine/jerboa-smtp/.git/refs +/Users/user/mine/jerboa-smtp/.jerboa +/Users/user/mine/jerboa-smtp/.jpkg +/Users/user/mine/jerboa-smtp/.jpkg/build +/Users/user/mine/jerboa-smtp/bin +/Users/user/mine/jerboa-smtp/corpus +/Users/user/mine/jerboa-smtp/dist +/Users/user/mine/jerboa-smtp/dist/fuzz-evidence +/Users/user/mine/jerboa-smtp/dist/release-evidence +/Users/user/mine/jerboa-smtp/dist/reproducibility +/Users/user/mine/jerboa-smtp/dist/sbom +/Users/user/mine/jerboa-smtp/dist/soak +/Users/user/mine/jerboa-smtp/docs +/Users/user/mine/jerboa-smtp/examples +/Users/user/mine/jerboa-smtp/fuzz +/Users/user/mine/jerboa-smtp/fuzz/artifacts +/Users/user/mine/jerboa-smtp/fuzz/corpus +/Users/user/mine/jerboa-smtp/fuzz/fuzz_targets +/Users/user/mine/jerboa-smtp/fuzz/src +/Users/user/mine/jerboa-smtp/fuzz/target +/Users/user/mine/jerboa-smtp/lib +/Users/user/mine/jerboa-smtp/lib/jerboa-smtp +/Users/user/mine/jerboa-smtp/rust +/Users/user/mine/jerboa-smtp/rust/jsmtp-native +/Users/user/mine/jerboa-smtp/rust/target +/Users/user/mine/jerboa-smtp/scripts +/Users/user/mine/jerboa-smtp/support +/Users/user/mine/jerboa-smtp/tests +/Users/user/mine/jerboa-smtp/wasm +/Users/user/mine/jerboa-smtp/wasm/smtp-command +/Users/user/mine/jerboa-smtp/wasm/smtp-data +/Users/user/mine/jerboa-smtp/wasm/target +=== RUST === +/Users/user/mine/jerboa-smtp/rust +/Users/user/mine/jerboa-smtp/rust/Cargo.toml +/Users/user/mine/jerboa-smtp/rust/target +/Users/user/mine/jerboa-smtp/rust/target/.rustc_info.json +/Users/user/mine/jerboa-smtp/rust/target/CACHEDIR.TAG +/Users/user/mine/jerboa-smtp/rust/target/release +/Users/user/mine/jerboa-smtp/rust/target/debug +/Users/user/mine/jerboa-smtp/rust/jsmtp-native +/Users/user/mine/jerboa-smtp/rust/jsmtp-native/Cargo.toml +/Users/user/mine/jerboa-smtp/rust/jsmtp-native/src +/Users/user/mine/jerboa-smtp/rust/Cargo.lock + +``` + +**Tool: bash** + +**Input:** +```json +{ + "command": "ls ~/mine/jerboa-crypto/ ~/mine/jerboa-ssl/ ~/mine/jerboa-pgp/ ~/mine/jerboa-mail/ 2>/dev/null" +} +``` + +**Output:** +``` +/Users/user/mine/jerboa-crypto/: +AGENTS.md +dist +docs +jerboa_crypto_shim.c +jerboa_crypto_shim.so +jpkg.sexp +lib +LICENSE +Makefile +README.md +scripts +SECURITY.md +src +support +tests + +/Users/user/mine/jerboa-mail/: +AGENTS.md +corpus +dist +docs +jerboa-mail +jpkg.sexp +LICENSE +Makefile +README.md +scripts +SECURITY.md +support +test + +/Users/user/mine/jerboa-pgp/: +AGENTS.md +build-jpg-cross.chez +build-jpg-freebsd-cross.chez +completions +dist +doc +docs +jpg-bin +jpkg.lock +jpkg.policy.sexp +jpkg.sexp +LICENSE +Makefile +pgp +pgp-native +PLAN.md +README.md +scripts +SECURITY.md +support +test + +/Users/user/mine/jerboa-ssl/: +AGENTS.md +dist +docs +jerboa_ssl_shim.c +jerboa_ssl_shim.dylib +jpkg.sexp +lib +LICENSE +Makefile +README.md +scripts +SECURITY.md +src +support +tests + +``` + +--- + +## Assistant (Build · Kimi K3 · 13.5s) + +_Thinking:_ + +Very interesting. Key observations: +- jerboa-smtp has rust/jsmtp-native — a Rust FFI component, plus fuzz/ (cargo fuzz), wasm/ components +- jerboa-crypto has a C shim (jerboa_crypto_shim.c) — user said NO C libs, but maybe the Rust parts matter; let me check what jerboa-crypto is (probably Rust crypto with a thin shim?) +- jerboa-pgp has pgp-native — maybe Rust PGP (Sequoia?) +- jerboa-ssl has a C shim too +- jerboa-mail — email handling library (MIME?) + +Let me read the key READMEs and AGENTS.mds now. I'll read several in parallel. + +**Tool: read** + +**Input:** +```json +{ + "filePath": "/Users/user/mine/jerboa-smtp/README.md" +} +``` + +**Output:** +``` +<path>/Users/user/mine/jerboa-smtp/README.md</path> +<type>file</type> +<content> +1: # jerboa-smtp +2: +3: Security-first SMTP server in Jerboa. +4: +5: This project is starting from the same core security idea as `jerboa-dns`: +6: Jerboa owns policy and orchestration, while hostile protocol bytes are parsed in +7: small Rust WebAssembly modules with fixed caps and fuel. +8: +9: ## Current Status +10: +11: The first slice is project skeleton plus a sandboxed SMTP command parser: +12: +13: - Jerboa protocol representation for SMTP commands. +14: - no_std Rust/WASM command parser with envelope reverse-path/forward-path +15: parsing and normalization for `MAIL FROM` and `RCPT TO`. +16: - no_std Rust/WASM DATA full-block framer for CRLF, terminator, dot-stuffing, +17: line-limit, and NUL rejection checks. +18: - Jerboa host wrapper that validates command and envelope metadata returned by +19: the sandbox. +20: - Pure Jerboa SMTP session state machine for command sequencing, no-relay +21: policy, and explicit local-recipient allowlisting. +22: - Strict JSON config loader with local-domain, local-recipient, IPv4, and +23: resource-limit validation. +24: - Durable queue admission API with sharded spool layout, unguessable queue IDs, +25: envelope validation, atomic reserved quota counters, and +26: metadata-as-admission-marker semantics. +27: - Queue janitor with `janitor --config FILE` for safe temp-file cleanup and +28: orphan message-file removal, including leftover delivery-marker temps. +29: - no_std Rust native file publication helper linked into the standalone binary: +30: open validated directories, create temp with `openat(O_EXCL)`, write and +31: fsync bytes, `linkat` into the final directory without overwriting, fsync the +32: destination directory, then unlink temp by basename. +33: - DATA-aware session path: `DATA` enters a pending state, the complete DATA +34: block is framed in WASM, and accepted messages are queued before `250`. +35: - Binary-port SMTP listener with `serve --config FILE [--once]`, configured +36: IPv4 bind address, bounded concurrent workers, global/per-IP shedding, +37: absolute command/DATA/connection deadlines, minimum DATA progress-rate +38: enforcement, DATA collection, and queue handoff. +39: - Local Maildir delivery worker with `deliver --config FILE`, configured +40: local-recipient Maildir layout, native final-file publication, delivered +41: markers published through native no-overwrite file operations, and idempotent +42: repeat runs that clean stale incoming files after a delivered marker exists. +43: - Smoke tests for plain parsing, sandboxed command parsing, sandboxed DATA +44: parsing, config, native publication, queue admission, Maildir delivery, +45: server connection handling, and session behavior. +46: +47: See [plan.md](plan.md) for the full implementation plan. +48: +49: ## Build And Test +50: +51: ```sh +52: make build +53: make test +54: ``` +55: +56: Release evidence: +57: +58: ```sh +59: make release-evidence +60: ``` +61: +62: The release bundle records SMTP load/slow-client status under +63: `dist/release-evidence/soak/`. By default this is a blocked, record-only status; +64: set `JSMTP_RUN_RELEASE_SOAK=1` with production-scale `JSMTP_SOAK_SESSIONS` to +65: capture current SMTP session evidence plus local DATA queueing and slow-DATA +66: rejection smokes. Deployment confinement and STARTTLS/AUTH policy are documented +67: in `docs/deployment-hardening.md` and copied into +68: `dist/release-evidence/deployment-policy.txt`. +69: Bounded local coverage-guided fuzz evidence is recorded with: +70: +71: ```sh +72: JSMTP_RUN_COVERAGE_FUZZ=1 JSMTP_FUZZ_RUNS=2048 make fuzz-evidence +73: ``` +74: +75: When `JSMTP_RUN_COVERAGE_FUZZ=1` is set for `make release-evidence`, the release +76: bundle also records `coverage_fuzz_status=local-smoke-recorded` under +77: `dist/release-evidence/fuzz-evidence/` after `smtp_command` and `smtp_data` +78: complete. +79: +80: Build just the WASM parser: +81: +82: ```sh +83: make wasm +84: ``` +85: +86: Run the CLI parser smoke: +87: +88: ```sh +89: make run ARGS='parse "EHLO mx.example"' +90: ``` +91: +92: Run the pure session smoke: +93: +94: ```sh +95: make run ARGS='session "EHLO client.example" "MAIL FROM:<alice@example.net>" "RCPT TO:<bob@example.org>"' +96: ``` +97: +98: The CLI session smoke uses the default config, which has no local domains, so +99: that recipient is denied as relay by design. +100: +101: Run the session smoke with validated local-recipient policy: +102: +103: ```sh +104: make run ARGS='session --config examples/jsmtp.json "EHLO client.example" "MAIL FROM:<alice@example.net>" "RCPT TO:<bob@local.test>"' +105: ``` +106: +107: The command parser requires WASM by default. `JSMTP_ALLOW_WASM_COMMAND_FALLBACK=1` +108: enables the in-process Jerboa parser only for development fallback. +109: +110: Run a single-connection listener: +111: +112: ```sh +113: make run ARGS='serve --config examples/jsmtp.json --once' +114: ``` +115: +116: Deliver queued local messages once: +117: +118: ```sh +119: make run ARGS='deliver --config examples/jsmtp.json' +120: ``` +121: +122: Clean queue temp files and orphan message files: +123: +124: ```sh +125: make run ARGS='janitor --config examples/jsmtp.json' +126: ``` +127: +128: ## Security Defaults +129: +130: Production deployments must run as an unprivileged service identity behind +131: service-manager confinement; see `docs/deployment-hardening.md`. Command parser +132: fallback is development-only and requires `JSMTP_ALLOW_WASM_COMMAND_FALLBACK=1`. +133: +134: The `(std wasm sandbox)` loader binds registered static symbols first in the +135: standalone daemon. Development tests may still use jerbuild's trusted native +136: library cache explicitly. +137: +138: DATA can now be framed by the WASM parser and admitted through the queue API, +139: including from the SMTP session state machine and the binary-port listener. +140: Queued messages can be delivered to local Maildir storage by running the +141: delivery worker. +142: +143: The current DATA parser is a full-block WASM parser with a 64 KiB hard cap. +144: `max_message_bytes` defaults to `65536` and larger configured values are +145: rejected until the DATA path is moved to a streaming WASM parser. +146: +147: `local_recipients` is required in config. RCPT accepts only full normalized +148: addresses in that allowlist; unknown local users are rejected during the SMTP +149: transaction with `550 No such local recipient`. +150: +151: The listener accepts continuously into bounded workers. `max_connections` and +152: `max_connections_per_ip` shed excess clients with `421`; the defaults are 64 +153: and 8. `connection_timeout_seconds`, `command_timeout_seconds`, +154: `idle_timeout_seconds`, and `data_timeout_seconds` are absolute deadlines that +155: activity cannot extend. The default DATA rate floor is 1024 bytes/second after +156: the configured grace period. +157: +158: `tls_available` must remain `false` until the native rustls STARTTLS helper is +159: implemented. Configurations that set it to `true` are rejected instead of +160: advertising a plaintext placeholder. +161: +162: `auth_available` must remain `false` until authenticated submission is +163: implemented. Configurations that set it to `true` are rejected instead of +164: accepting a nonfunctional AUTH deployment. +165: +166: Queue publication currently writes the message file before its metadata file; +167: consumers must treat metadata as the admission marker and ignore unmatched +168: message files. The file publication primitive is now native Rust and refuses +169: overwrite; queue and Maildir publication use directory-relative native calls +170: with validated basenames. Local delivery publishes the delivered marker before +171: deleting the incoming message and metadata files, so restart cleanup treats the +172: marker as authoritative. +173: +174: Quota admission uses a durable, atomically replaced counter under a native +175: cross-process lock, so normal admission is O(1) and concurrent reservations +176: cannot exceed the configured cap. Startup and the offline janitor reconcile +177: the counter from disk. Queue IDs are distributed across 256 two-hex-digit +178: shards under `tmp`, `incoming`, and `delivered` to avoid single-directory +179: growth; legacy unsharded queue files remain readable and cleanable.