- Scheme 62.8%
- Shell 18.4%
- Rust 12.2%
- Makefile 6.4%
- Common Lisp 0.2%
| .builds | ||
| .jerboa | ||
| docs | ||
| proton-bridge | ||
| proton-bridge-native | ||
| scripts | ||
| support | ||
| test | ||
| .gitignore | ||
| AGENTS.md | ||
| dependencies.lock | ||
| jpkg.sexp | ||
| LICENSE | ||
| main.ss | ||
| Makefile | ||
| NOTICE | ||
| plan.md | ||
| README.md | ||
| SECURITY.md | ||
| toolchains.lock | ||
jerboa-proton-bridge
YubiKey-gated Proton Mail client and Proton Bridge rewrite in Jerboa.
This project is a rewrite/reimplementation effort using Proton Mail Bridge as a GPL-3.0-or-later reference, but it is not intended to reproduce the same security boundary. The specific goal is to avoid a long-lived local IMAP password that allows mail access without a fresh YubiKey-gated authorization.
The Proton Bridge protocol and GPL source are the upstream reference. Companion
Jerboa libraries are resolved from sibling worktrees for development or from
the exact SourceHut commits in dependencies.lock in CI.
Security Model
The upstream Bridge authenticates to Proton, stores session/key material, then exposes local IMAP/SMTP with generated credentials. That is useful for normal mail clients, but it means local possession of Bridge IMAP credentials is enough to read decrypted mail while Bridge is running.
This rewrite starts with stricter rules:
- No password-authenticated local IMAP server in the default mode.
- No reusable Bridge password equivalent in the default mode.
- Every interactive mail-reading session must pass a YubiKey gate.
- The YubiKey gate should use Proton's actual FIDO2/WebAuthn challenge when authenticating to Proton.
- Any local cache must be encrypted and separately YubiKey-gated.
- SMTP/send remains out of scope until read-only access is correct.
Reference Findings
The upstream Bridge clone is Go and uses:
github.com/ProtonMail/go-proton-apigithub.com/ProtonMail/go-srpgithub.com/ProtonMail/gopenpgp/v2github.com/ProtonMail/gluongithub.com/ProtonMail/go-libfido2
Relevant upstream paths:
internal/bridge/user.go- login, 2FA, key unlock, refresh.internal/fido/- FIDO2/WebAuthn assertion flow.internal/vault/- stored auth/key material.internal/services/imapservice/- IMAP-facing service.pkg/message/andpkg/mime/- message decrypt/build helpers.
First Target
Build a read-only native Proton CLI:
jerboa-proton-bridge login
jerboa-proton-bridge folders
jerboa-proton-bridge list --folder INBOX
jerboa-proton-bridge show --folder INBOX --id MESSAGE_ID
Only after native auth/decrypt works should we decide whether to expose any local protocol. If we do, it should be a YubiKey-gated IPC protocol or single-user Unix socket, not a reusable IMAP password.
Development
make run ARGS='--help'
make native
make test
Current command support includes policy/status probes, FIDO2 payload probing, and fail-closed live-auth/read-only mail scaffolding:
make run ARGS='status'
make run ARGS='session-policy'
make run ARGS='login --username USER'
make run ARGS='folders --username USER'
make run ARGS='list --username USER --folder INBOX --limit 20'
make run ARGS='message-json --username USER --id MESSAGE_ID'
make run ARGS='show --username USER --id MESSAGE_ID'
make run ARGS='show --username USER --id MESSAGE_ID --body-file private-message.txt'
make run ARGS='login --auth-info auth-info.json --username USER'
make run ARGS='login --auth-options auth-options.json'
Interactive passwords and PINs are read directly from /dev/tty; the native
helper saves and restores the exact terminal attributes and fails if echo
cannot be disabled. Automation should pass an already-open non-terminal
descriptor with --password-fd, --mailbox-password-fd, or --pin-fd.
Environment-name options remain supported, but secrets are never accepted
directly in argv or from ordinary stdin.
Folder/message fields and the default show body are neutralized for ANSI,
OSC, C0/C1, and bidirectional controls before terminal output. Exact decrypted
body bytes are available only through --body-file, which creates a new 0600
file and refuses to follow or replace an existing path.
The live login path is intentionally disabled in the strict build before it
prompts for a Proton password. Proton SRP proof generation requires signed
modulus verification; the previous rPGP/RSA dependency path was removed until
an audit-clean verifier is available. The FIDO2 --auth-options probe remains
available because it does not require Proton credentials.
The read-only folders, list, message-json, and show commands are wired
for fresh per-invocation auth, but they currently fail closed at the SRP gate.
They do not store a reusable refresh token or local IMAP password.
The supported session policy is deliberately per-invocation and no-store;
there is no local protocol listener.
The release security model is documented in
docs/security-model.md. Current release gates are
make test, native loader attack regressions, source scanning, locked
dependency verification, Rust dependency audit, sibling dependency evidence,
SBOM output, and native helper reproducibility evidence.
Dynamic development runs load the Proton helper only from the canonical
absolute PROTON_BRIDGE_NATIVE_LIB selected by the Makefile. Bare filenames,
the current directory, relative paths, symlinks, untrusted owners, and
group/world-writable files are rejected. Production static binaries use
registered symbols and do not perform dynamic lookup.
make release-evidence writes the local release-evidence bundle under
dist/release-evidence/. It is allowed to fail after writing evidence; that
means a production gate is blocked. The former RustSec rsa blocker has been
removed from the native dependency graph by disabling the OpenPGP/SRP proof and
Drive crypto paths until a RustSec-clean backend exists.
The native helper currently derives Proton's salted mailbox key passphrase from
/core/v4/keys/salts. Proton SRP proofs, address-key token decryption,
OpenPGP message decryption/signing/encryption, and Proton Drive crypto symbols
remain present for ABI compatibility but return explicit unsupported errors.
show cannot decrypt mail until an audit-clean OpenPGP backend is integrated.
auth-info.json is the /auth/v4/info response. In this strict build,
login --auth-info exits before reading a password because SRP proof
generation is disabled until signed modulus verification is RustSec-clean.
auth-options.json may contain either the raw Proton
AuthenticationOptions object or the full auth JSON containing
2FA.FIDO2.AuthenticationOptions.