No description
  • Scheme 62.8%
  • Shell 18.4%
  • Rust 12.2%
  • Makefile 6.4%
  • Common Lisp 0.2%
Find a file
2026-07-30 15:47:00 -06:00
.builds ci: set umask 022 in package/verify tasks too (jerbuild creates group-writable cache dirs during pkg operations) 2026-07-22 13:10:57 -06:00
.jerboa Harden Proton Bridge release evidence privacy 2026-06-26 19:15:02 -06:00
docs Resolve security audit findings 2026-07-11 17:22:32 -06:00
proton-bridge fix: use 2FA response, selective FIDO2 error handling, request cleanup, https validation, constant-time SRP 2026-07-21 10:54:17 -06:00
proton-bridge-native Resolve security audit findings 2026-07-11 17:22:32 -06:00
scripts Resolve security audit findings 2026-07-11 17:22:32 -06:00
support Resolve security audit findings 2026-07-11 17:22:32 -06:00
test Resolve security audit findings 2026-07-11 17:22:32 -06:00
.gitignore Security hardening and release readiness 2026-06-23 10:50:50 -06:00
AGENTS.md docs: remove jerboa-emacs restriction 2026-07-30 15:47:00 -06:00
dependencies.lock Resolve security audit findings 2026-07-11 17:22:32 -06:00
jpkg.sexp Add jpkg packaging 2026-07-21 15:09:25 -06:00
LICENSE Switch to MIT license 2026-07-21 13:42:19 -06:00
main.ss Security hardening and release readiness 2026-06-23 10:50:50 -06:00
Makefile build: add lint target 2026-07-21 11:56:18 -06:00
NOTICE Security hardening and release readiness 2026-06-23 10:50:50 -06:00
plan.md Make no-store session policy explicit 2026-05-28 21:25:11 -06:00
README.md Resolve security audit findings 2026-07-11 17:22:32 -06:00
SECURITY.md Resolve security audit findings 2026-07-11 17:22:32 -06:00
toolchains.lock Resolve security audit findings 2026-07-11 17:22:32 -06:00

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-api
  • github.com/ProtonMail/go-srp
  • github.com/ProtonMail/gopenpgp/v2
  • github.com/ProtonMail/gluon
  • github.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/ and pkg/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.