hermes-proton/docs/landscape.md
Templeton Face 0da56b1c7f
docs: landscape scan of 7 Proton third-party client implementations
Systematic study of existing open-source Proton client projects
to extract patterns, pitfalls, and reusable code for hermes-proton.

Projects analyzed:
- hydroxide (Go, SRP, CardDAV/IMAP/SMTP) — complete auth+session ref
- rclone protondrive (Go, Drive only) — encryption patterns
- roman-16/proton-cli (Go, all 5 products) — 3-tier architecture ref
- bscott/pm-cli (Go, Bridge mail) — agent-friendly gold standard
- StollD/proton-webdav-bridge (Go, Drive as WebDAV) — caching pattern
- cdump/proton-tui (Rust, VPN) — standalone SRP auth module
- rvacyber/openclaw-protonmail-skill (TS, Bridge mail) — skill pattern ref

Feeds into T1 (hannibal architecture).

Closes t_3c87cd08
2026-06-08 18:41:46 +02:00

561 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Proton Third-Party Client Landscape
> Landscape scan of 7 open-source Proton client implementations — patterns, pitfalls, and reusable code for hermes-proton.
> Compiled: 2026-06-08
> Analyst: Face (kanban t_3c87cd08)
---
## Overview
| # | Project | Lang | Stars | Auth | Scope | Key Value for Hermes-Proton |
|---|---------|------|-------|------|-------|----------------------------|
| 1 | **hydroxide** (emersion) | Go | 2.1k | SRP direct | Mail, Contacts, Calendar | Complete SRP+2FA+refresh implementation; bbolt caching; NaCl secretbox session persistence |
| 2 | **rclone protondrive** | Go | 57k | SRP direct | Drive only | Battle-tested Drive encryption (4MB blocks, X25519, gopenpgp v2); most-used third-party client |
| 3 | **proton-cli** (roman-16) | Go | 17 | SRP direct | All 5 products | Most architecturally mature; clean 3-tier layering; session cache; REF ID system; error classification |
| 4 | **pm-cli** (bscott) | Go | 14 | Bridge pass | Mail only | Agent-friendly gold standard: `--json` everywhere, `--help-json`, idempotency keys, semantic summaries |
| 5 | **proton-webdav-bridge** (StollD) | Go | 28 | SRP direct | Drive as WebDAV | In-memory tree cache + 5s event poll; Drive encryption via go-proton-api fork (archived) |
| 6 | **proton-tui** (cdump) | Rust | 12 | SRP direct | VPN only | Standalone SRP-6a auth module (reusable Rust crate); WireGuard key conversion; VPN lifecycle |
| 7 | **openclaw-protonmail-skill** (rvacyber) | TS | 16 | Bridge pass | Mail only | Closest analogue to Hermes skill pattern; IMAP/SMTP via Bridge; env-var config; search sanitization |
---
## 1. Project-by-Project Analysis
### 1.1 hydroxide (emersion/hydroxide)
**Location:** `github.com/emersion/hydroxide`
**Stars:** 2.1k | **License:** MIT | **Language:** Go 1.24
#### Auth Approach — Full SRP Direct (No Bridge Dependency)
```
AuthInfo(username) → POST /auth/info → {srpSession, Modulus (PGP-signed), ServerEphemeral, Salt}
srp(password, info) → bcrypt (cost 10) + expandHash (4×SHA-512) + generateProofs (2048-bit, LE)
Auth(username, password, info) → POST /auth → verify server proof (constant-time compare)
AuthTOTP(code) → POST /auth/2fa (bitmap check: Enabled != 0)
Unlock(auth, keySalts, passphrase) → decrypt keyring with password/token
```
- **SRP implementation location:** `protonmail/srp.go` — 2048-bit group, little-endian byte order matching Proton web client
- **Password hashing:** `protonmail/password.go``hashPassword()`: version 3/4 appends "proton" to salt, bcrypts (cost 10), runs `expandHash()` — 4 rounds SHA-512 with suffix bytes 0-3
- **Modulus trust:** Hardcoded `modulusPubkey` PGP key in source; modulus response is clearsigned and verified
- **2FA:** TOTP only; bitmap check (`Enabled != 0` not `Enabled == 1`); U2F/WebAuthn not implemented
- **Re-auth chain:** `AuthRefresh()` → POST `/auth/refresh`; on code 10013 (invalid refresh token) → fallback to full SRP Auth (fails if 2FA enabled)
#### Token Persistence — NaCl Secretbox
- **Storage:** `~/.config/hydroxide/auth.json`
- **Format:** JSON map `map[string]string` (username → base64-encrypted blob)
- **Encryption:** NaCl secretbox (XSalsa20-Poly1305) with random 24-byte nonce
- **Key:** Random 32-byte bridge password (base64), shown once at login, NOT stored by hydroxide
- **CachedAuth struct:** `{Auth (UID, AccessToken, RefreshToken, ExpiresIn), LoginPassword, MailboxPassword, KeySalts}`
#### Error Handling
| Pattern | Details |
|---------|---------|
| **401 auto-retry** | `Client.do()` — single retry with `ReAuth` callback |
| **APIError struct** | `{Code int, Message string}` — wrapped from `RawAPIError` |
| **Refresh failure** | Code 10013 → full re-auth via SRP (2FA blocks this) |
| **429 rate limit** | **Not handled** — no retry-wait logic (gap for production) |
#### Crypto
- `github.com/ProtonMail/go-crypto v1.4.1` (Proton's OpenPGP fork with SHA-512 support)
- Message encryption: `Message.Encrypt(to, signed)` → armored PGP via `openpgp.SymmetricallyEncrypt`
- Attachment crypto: `Attachment.GenerateKey()` → AES-256 key → symmetrically encrypted
- Contact crypto: `NewEncryptedContactCard`, `NewSignedContactCard` — dual-card scheme
- Key derivation: `computeKeyPassword()` → bcrypt, strip prefix (last 29 bytes)
#### Project Structure
```
protonmail/ # Core API client (18 files)
├── auth.go # Auth flows (AuthInfo, Auth, AuthTOTP, AuthRefresh, Unlock, Logout)
├── srp.go # SRP-6a protocol
├── password.go # Key derivation (bcrypt + SHA-512)
├── crypto.go # OpenPGP operations
├── messages.go # Message CRUD + Encrypt/Read
├── events.go # Event polling (GetEvent)
└── ...
auth/auth.go # Session persistence, Manager, re-auth, 2FA detection
imap/ # IMAP server backend + bbolt database
smtp/ # SMTP backend (MIME → PGP → send)
carddav/ # CardDAV handler
cmd/hydroxide/ # CLI entry (11 subcommands)
```
**Key takeaway:** Most complete SRP+session management reference. The NaCl secretbox pattern for encrypted token storage is directly adoptable.
---
### 1.2 rclone protondrive backend
**Location:** `github.com/rclone/rclone/tree/master/backend/protondrive`
**Stars:** 57k (rclone project) | **License:** MIT | **Language:** Go
#### Auth Approach — SRP Direct (via archived bridge library)
Uses `henrybear327/Proton-API-Bridge` (archived Feb 2026) and `henrybear327/go-proton-api` (archived Feb 2026). Both are single-contributor projects with **no upstream development**.
- SRP auth via `ProtonMail/go-srp v0.0.7`
- **Auth flow:** Login → API client creation → keyring unlock → Drive backend init
- **SaltedKeyPass caching:** After first login, the salted key pass is cached so subsequent operations don't need password
- **authHandler callback pattern:** Transparent token refresh middleware
#### Encryption — gopenpgp v2 (X25519)
- **Key type:** X25519 (Curve25519-based OpenPGP keys)
- **Block size:** 4MB blocks
- **Encryption:** OpenPGP CFB mode per block
- **Hashing:** SHA-256 block hashes + armored detached signatures per block; SHA-1 content digests (Proton legacy)
- **Bootstrap key:** HKDF from user key → address key → share key → node key → session key
- **Key library:** `github.com/ProtonMail/gopenpgp/v2 v2.10.0`
#### Token Management
- Config stores: UID + AccessToken + RefreshToken + SaltedKeyPass
- `configKey` constants hidden in config file
- Auto-refresh handled by bridge library (opaque to rclone backend)
#### Error Handling
| Pattern | Details |
|---------|---------|
| **shouldRetry** | Catches API Code=200501 (rate limit?) and 5xx server errors |
| **HTTP retry** | Deferred to `resty` (rclone's HTTP client) |
| **Sentinel errors** | 17 typed error constants in Proton-API-Bridge |
#### ⚠️ Critical Risk
Both `henrybear327/Proton-API-Bridge` and `henrybear327/go-proton-api` are **archived** (Feb 2026). rclone vendors them as-is. Any new project relying on this code path inherits the risk of unpatched bugs and API breakage.
---
### 1.3 proton-cli (roman-16/proton-cli)
**Location:** `github.com/roman-16/proton-cli`
**Stars:** 17 | **License:** MIT | **Language:** Go 1.26.1 | **Version:** v1.4.0 (May 2026)
#### Architecture — 3-Tier Layered (Best in Class)
```
cmd/ # Cobra CLI commands (per-product packages)
internal/
api/ # HTTP client, SRP auth, error types, HV resolver
services/ # Domain services (mail, drive, calendar, contacts, pass) — per-product packages
app/ # Wiring container (App struct holding 5 services + API + Renderer)
session/ # Session persistence (~/.config/proton-cli/sessions/<profile>.json)
config/ # TOML multi-profile config
keys/ # PGP key hierarchy unlock
pgp/ # Card encryption (clear/signed/encrypted/encrypted+signed)
aead/ # AES-256-GCM for Pass items/vaults
render/ # Output formatting (text/JSON/YAML/tables/progress)
idcache/ # Short-ID → full ID cache
hv/ # Embedded webview CAPTCHA helper
```
**The `App` wiring pattern is worth adopting directly:**
```go
type App struct {
Profile string
Creds Credentials
API *api.Client // Single session, shared across all 5 services
Mail *mail.Service
Drive *drive.Service
Calendar *calendar.Service
Contacts *contacts.Service
Pass *pass.Service
R *render.Renderer
IDCache *idcache.Cache
}
```
#### Auth Approach — Full SRP (Not Bridge)
- `POST /auth/v4/sessions` (creates unauth session)
- `POST /core/v4/auth/info``go-srp` proofs
- `POST /core/v4/auth` → verify server proof (MITM prevention)
- `POST /core/v4/auth/2fa` if bit 0 set in `TwoFA.Enabled`
- HV (CAPTCHA) via embedded webview helper binary
- **Dependencies:** `go-srp v0.0.7`, `gopenpgp/v2 v2.10.0`
#### Session Management
- **Storage:** `~/.config/proton-cli/sessions/<profile>.json` (0600)
- **SaltedKeyPass caching:** Stored in session file → subsequent invocations don't need password
- **Auto-refresh:** `api.Client.Do()` calls `POST /auth/v4/refresh` on 401, persists via `session.Save()`
- **Thread safety:** `sync.RWMutex` on client for token reads/writes
- **Multi-profile:** Each profile has independent session + config
#### Error Handling — Typed Hierarchy with Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | User error |
| 2 | Auth failure |
| 3 | Not found |
| 4 | Ambiguous/conflict |
| 5 | Network/server error |
| 130 | Cancelled (Ctrl+C) |
Hierarchy: `APIError``HumanVerificationError` (code 9001) → `ExitError``WrongTableError` (cross-table probing for message↔conversation confusion) → `ErrNotFound` / `AmbiguousError`
**Notable:** Cross-table probing on 422 errors — if a GET fails, it probes the opposite table (message↔conversation) and returns a precise diagnostic.
#### Key Design Decisions
1. **Single HTTP client** shared across all 5 products — no per-service auth
2. **App-in-context** pattern — `*app.App` stored in `context.Context` via `app.WithApp()`, avoids global state
3. **REF ID resolution** — prefix matching (8+ chars) from idcache; ambiguous → exit 4 with candidates
4. **Dashed ID protection** — auto-inserts `--` before Proton base64 IDs starting with `-`
5. **Render abstraction**`render.Renderer` handles text/JSON/YAML; commands never call `fmt.Println`
6. **Dry-run on mutations** — global `--dry-run` flag checked by all mutation commands
#### Notable Gaps
- No OAuth — env-var or config-file passwords only
- No structured logging — `slog` to stderr only; no log file/rotation
- CAPTCHA webview only — no stdin-based email/SMS verification
---
### 1.4 pm-cli (bscott/pm-cli)
**Location:** `github.com/bscott/pm-cli`
**Stars:** 14 | **License:** MIT | **Language:** Go | **Version:** v0.2.5 (April 2026)
#### Architecture — Bridge Protocol Only
- **Does NOT call Proton API directly** — connects to local Proton Bridge daemon
- **IMAP:** `127.0.0.1:1143` (STARTTLS) — read/search mail
- **SMTP:** `127.0.0.1:1025` (STARTTLS) — send mail
- **Auth:** Bridge-generated app password (not Proton password)
- **TLS:** `InsecureSkipVerify: true` (Bridge self-signed cert), v0.2.5 enforces **loopback-only**
#### Agent-Friendly Features (Gold Standard for Hermes)
| Feature | Implementation | Value for Agents |
|---------|---------------|------------------|
| **Universal `--json`** | `Formatter` struct threaded through all commands; `{"success": true, "data": {...}}` envelope | Always parseable, always on stdout |
| **`--help-json`** | Full typed command tree as JSON (flags, args, types, examples) | Zero-parsing schema introspection in 1 call |
| **Idempotency keys** | `--idempotency-key` on mail send/reply/forward; 24h TTL in `~/.config/pm-cli/idempotency.json` | Safe retry without double-send |
| **Semantic AI commands** | `mail summarize` (sentiment, priority, action_required, summary); `mail extract` (emails, URLs, dates, phone_numbers, action_items) | Pre-digested LLM input |
| **Consistent error envelope** | `{"success": false, "error": "..."}` on stdout | Always parseable even on failure |
| **Stable IDs** | Both seq numbers (`123`) and UIDs (`uid:456`) in output | Stable across invocations |
| **Watch + env vars** | `mail watch --exec` exposes metadata as `PM_MSG_*` env vars | Safe agent automation |
#### Error Handling
- Two-mode: human (stderr, color) vs JSON (stdout, structured)
- Descriptive messages with actionable guidance
- Exit code 1 on any error
- Wrapped errors via `%w`
#### Security Hardening (v0.2.5)
1. SMTP Header Injection — `SanitizeHeaderValue()` strips CR/LF
2. TLS Loopback Enforcement — `isLoopbackHost()` refuses non-localhost
3. Shell Injection — env vars replace string interpolation in watch mode
4. ANSI Escape Injection — `SanitizeForTerminal()` strips escapes
#### Gaps
- **Bridge required** — doesn't work without Bridge daemon
- **Mail only** — no Calendar, Drive, Pass, VPN
- **Polling only** — 30s poll interval, no IMAP IDLE
- **Single account** — one config at a time
- **Local-only** — can't be remote/multi-user
---
### 1.5 proton-webdav-bridge (StollD)
**Location:** `github.com/StollD/proton-webdav-bridge`
**Stars:** 28 | **License:** MIT | **Language:** Go
#### Architecture
- **Purpose:** Exposes Proton Drive as a local WebDAV filesystem
- **Daemon pattern:** Registers WebDAV handler on localhost:7984, proxies Drive operations
- **Auth:** SRP direct via `henrybear327/go-proton-api` (same archived fork as rclone)
#### Caching — Pure In-Memory
- Two `map[string]*Link` — one for ID lookups, one for path lookups
- **No disk persistence** — full re-fetch on restart
- **Event poll:** Background goroutine polls Proton share events every 5 seconds via `TriggerUpdate()`
- **Synchronous cache refresh** before every download/upload
#### Encryption
- Same gopenpgp/v2 stack as rclone (X25519)
- Full key hierarchy: User Key → Address Key → Share Key → Node Key → Session Key
- Reader decrypts blocks on-demand with SHA-256 hash verification
- Writer encrypts in 4MB blocks with SHA-1 content hashing (Proton API validation)
#### Critical Issues
1. **Archived dependencies** — both `henrybear327/go-proton-api` and `StollD/proton-drive` are single-contributor, archived
2. **`panic(err)` on init failures** — no graceful degradation
3. **Headless auth unsupported** — interactive `--login` only
4. **Token expiry = `os.Exit(1)`** — hard abort
---
### 1.6 proton-tui (cdump)
**Location:** `github.com/cdump/proton-tui`
**Stars:** ~12 | **License:** MIT | **Language:** Rust | **Version:** v0.3.0
#### Auth — Standalone Rust SRP-6a Implementation
The `auth.rs` module (~500 lines) is the single most reusable piece for Hermes-proton.
**Flow:**
1. `POST /auth/info` → extract modulus from PGP signed response body
2. `SrpClient::new(modulus)` → generate random 256-bit `a`
3. Password hashing: bcrypt (cost 10, `$2y$`) + `pm_hash()` (4×SHA-512 extend to 2048 bits)
4. SRP-6a math: `u = H(A,B)`, `S = (B - k*g^x)^(a+u*x) mod N`, client proof `M = H(A,B,K)`
5. `POST /auth` → verify, handle 2FA
**Reusable functions:**
- `pm_hash` — Proton's custom 2048-bit hash (4 SHA-512 rounds with suffixes 0x00-0x03)
- `hash_password` — bcrypt + PMHash combined
- `verify_server_proof` — constant-time comparison
- PGP modulus extraction — parse clearsigned response
**Dependencies:** `num-bigint`, `sha2`, `bcrypt`, `base64`, `reqwest` — all standard crates
#### VPN Connection — WireGuard Only
- **Key generation:** Ed25519 `SigningKey` → X25519 via SHA-512 + bit clamping
- **Certificate registration:** `POST /vpn/v1/certificate` with ED25519 public PEM
- **Connection:** `sudo wg-quick up <config>` with runtime/saved config targets
- **Disconnection:** `sudo wg-quick down <config>`
#### Token Management
- **Storage:** `~/.config/proton-tui/tokens.json` (0o600 on Unix)
- **Struct:** `StoredTokens { uid, access_token, refresh_token }`
- **Clear:** Menu option (deletes file)
- **Conversion:** `impl From<AuthResult> for StoredTokens`
---
### 1.7 openclaw-protonmail-skill (rvacyber)
**Location:** `github.com/rvacyber/openclaw-protonmail-skill`
**Stars:** 16 | **License:** MIT | **Language:** TypeScript | **Version:** v1.0.1 (2026-04)
#### Skill Structure — Direct Hermes Reference
```
SKILL.md # OpenClaw manifest (YAML frontmatter)
src/
index.ts # ProtonMailSkill class — orchestrator
imap.ts # IMAPClient — read/search operations
smtp.ts # SMTPClient — send/reply operations
tools.ts # OpenClaw tool registrations + TOOL_DEFINITIONS
bin/protonmail # CLI entry (Node.js shebang)
```
#### Bridge Connection Pattern (Directly Transferable)
```typescript
// IMAP (read path) — 127.0.0.1:1143, TLS, rejectUnauthorized: false
// SMTP (write path) — 127.0.0.1:1025, STARTTLS (secure: false), rejectUnauthorized: false
// Always enforce localhost-only host validation
```
#### Operations
| Method | What It Does | Key Implementation Detail |
|--------|-------------|---------------------------|
| `listInbox(limit, unreadOnly)` | List recent messages | Search ALL or UNSEEN, sort by UID desc, fetch headers |
| `search(query, limit)` | Search with syntax | Parse `from:`, `subject:`, `body:`, `newer_than:` filters |
| `readMessage(messageId)` | Full email content | UID fetch + mailparser; `messageFound` + `fetch.end` hang prevention |
| `send(to, subject, body, options)` | Send new email | Plain text + optional HTML/CC/BCC/attachments via nodemailer |
| `reply(originalMessage, body)` | Reply with threading | RFC 5322 `In-Reply-To`/`References` headers |
#### Search Sanitization (Security Pattern)
- Length limit: 200 chars
- Character allowlist: `[a-zA-Z0-9@._+\-\s:]`
- No CR/LF/control chars
- Unrecognized queries → safe subject keyword search fallback
#### Config & Env
```yaml
# SKILL.md metadata
requires:
env: [PROTONMAIL_ACCOUNT, PROTONMAIL_BRIDGE_PASSWORD]
```
```
PROTONMAIL_ACCOUNT=user@pm.me
PROTONMAIL_BRIDGE_PASSWORD=<bridge-app-password>
```
**Transferable config pattern:** env vars with config-override fallback; same naming scheme for Hermes (`HERMES_PROTON_ACCOUNT`, `HERMES_PROTON_BRIDGE_PASSWORD`).
#### Security Evolution (v0.1.0 → v0.1.1)
- v0.1.0: TLS bypass without localhost enforcement
- v0.1.1: Hosts validated against `['127.0.0.1', 'localhost', '::1']`
- **Hermes-proton must follow the hardened pattern**
---
## 2. Cross-Cutting Patterns
### 2.1 Auth Strategy Comparison
| Approach | Projects | Pros | Cons |
|----------|----------|------|------|
| **SRP Direct** | hydroxide, rclone, proton-cli, proton-webdav, proton-tui | No dependency; full API access; all products | Complex implementation; 2FA handling; CAPTCHA required; token refresh logic |
| **Bridge Protocol** | pm-cli, openclaw-skill | No crypto needed; no 2FA; no CAPTCHA; stable IMAP/SMTP | Mail only; Bridge dependency; self-signed TLS; local process only |
**Recommendation for Hermes-proton:** Support both paths — Bridge for mail (fastest path, proven), SRP for Calendar/Drive/Pass/VPN (via go-proton-api or proton-cli patterns).
### 2.2 SRP Implementation Details (Consensus Across Projects)
1. **Go standard:** `ProtonMail/go-srp` — official library, used by 4/4 Go projects
2. **Rust alternative:** `num-bigint` + `sha2` + `bcrypt` — standalone impl in proton-tui/auth.rs
3. **Key derivation:** bcrypt (cost 10, `$2y$`) → 4×SHA-512 (expand to 2048 bits) → modulus operations
4. **Modulus verification:** PGP clearsigned modulus response verified against Proton's hardcoded public key
5. **2FA:** Bitmap check (`Enabled & 1`); TOTP only in all projects
6. **CAPTCHA:** Only roman-16/proton-cli has a solution (embedded webview helper)
### 2.3 Token/Session Persistence Patterns
| Project | Location | Encryption | Format |
|---------|----------|------------|--------|
| hydroxide | `~/.config/hydroxide/auth.json` | NaCl secretbox (XSalsa20-Poly1305) | JSON map |
| proton-cli | `~/.config/proton-cli/sessions/<profile>.json` | Filesystem perms (0600) | JSON |
| proton-webdav | `$XDG_DATA_HOME/proton-webdav-bridge/tokens.json` | Filesystem perms (0600) | JSON |
| proton-tui | `~/.config/proton-tui/tokens.json` | Filesystem perms (0600) | JSON |
| pm-cli | OS keyring (libsecret/Keychain) | Platform keyring | N/A |
**Recommendation:** Use OS keyring for Bridge password (pm-cli pattern); encrypt cached tokens with NaCl secretbox if stored on disk (hydroxide pattern).
### 2.4 Error Handling Patterns
| Pattern | Found In | Details |
|---------|----------|---------|
| 401 auto-retry with refresh | hydroxide, proton-cli, rclone | Single retry with ReAuth callback |
| Typed error hierarchy | proton-cli, pm-cli, hydroxide | APIError, ExitError, WrongTableError |
| Cross-table probing | proton-cli | 422 → probe opposite table |
| Exit code mapping | proton-cli | 0-130 typed codes |
| Structured JSON errors | pm-cli | `{"success": false, "error": "..."}` |
| 429 not handled | All projects | **Gap for production** — no project implements 429 backoff |
### 2.5 Crypto Stack
| Component | Standard Library | Used By |
|-----------|-----------------|---------|
| OpenPGP (X25519) | `go-crypto` / `gopenpgp v2` | hydroxide, rclone, proton-cli, proton-webdav |
| Symmetric encryption | NaCl secretbox (XSalsa20-Poly1305) | hydroxide (session storage) |
| AES-256-GCM | Standard library | proton-cli (Pass items) |
| Card encryption | OpenPGP + signing | proton-cli, hydroxide |
| Key derivation | bcrypt + SHA-512 | All SRP-based projects |
---
## 3. Critical Risks
### 3.1 Archived Dependencies
Two projects that use SRP direct for Drive (`rclone protondrive`, `proton-webdav-bridge`) depend on archived libraries:
- `henrybear327/Proton-API-Bridge` — archived Feb 2026
- `henrybear327/go-proton-api` — archived Feb 2026
- `StollD/proton-drive` — single-contributor
**Mitigation:** Use `roman-16/proton-cli`'s approach instead (direct `go-proton-api` from ProtonMail, or use the official Proton Go SDK).
### 3.2 No 429 Handling
None of the 7 projects implement explicit rate-limit backoff. For a production agent making many API calls, this is a gap that must be filled.
### 3.3 Headless CAPTCHA
Only roman-16/proton-cli handles CAPTCHA (via embedded webview helper). For headless agent environments, CAPTCHA is a blocker unless using the Bridge path (which bypasses CAPTCHA entirely).
### 3.4 2FA Limitations
- Token refresh failure + 2FA enabled = cannot auto-re-auth (hydroxide)
- Only TOTP supported; U2F/WebAuthn unimplemented in all projects
- Bridge path avoids 2FA entirely
---
## 4. Recommendations for Hermes-Proton
### 4.1 Architecture
Adopt roman-16/proton-cli's **3-tier layered architecture**:
1. **Plugin layer** (Komodo plugin) — auth lifecycle, session management, shared state
2. **Service layer** (Hermes skills) — per-product tool collections
3. **Transport layer** — Bridge (mail) + SRP direct (Calendar/Drive/Pass/VPN)
### 4.2 Auth Paths
**Path A: Bridge (mail only)**
- OpenClaw-skill pattern: IMAP `127.0.0.1:1143` + SMTP `127.0.0.1:1025`
- Env vars: `HERMES_PROTON_ACCOUNT`, `HERMES_PROTON_BRIDGE_PASSWORD`
- Localhost-enforced TLS with self-signed cert acceptance
- OS keyring for credential storage (pm-cli pattern)
**Path B: SRP Direct (all products)**
- roman-16/proton-cli's auth flow as reference
- `go-srp` for Go-based components
- proton-tui's `auth.rs` for Rust-based components
- NaCl secretbox encrypted token cache (hydroxide pattern)
- Cross-table error probing for ID resolution
### 4.3 Agent-Friendly Output (pm-cli patterns)
- Universal `--json` flag with `{"success": true, "data": {...}}` envelope
- `--help-json` for tool introspection
- Idempotency keys for mutation operations
- Semantic subcommands (`summarize`, `extract`)
- Consistent exit codes
### 4.4 Must-Avoid
- Do NOT vendor archived `henrybear327` libraries (use official `ProtonMail/go-srp` + `go-proton-api`)
- Do NOT skip 429 handling
- Do NOT allow non-localhost TLS bypass (follow openclaw-skill's hardening)
- Do NOT hard-code single-protocol assumptions (support both Bridge and SRP)
### 4.5 Key File Locations to Reference
| Pattern | Source Project | File |
|---------|---------------|------|
| SRP auth (Go) | hydroxide | `protonmail/auth.go`, `protonmail/srp.go` |
| SRP auth (Go, idomatic) | proton-cli | `internal/api/auth.go` |
| SRP auth (Rust) | proton-tui | `src/auth.rs` |
| NaCl session storage | hydroxide | `auth/auth.go` |
| Bridge IMAP client | openclaw-skill | `src/imap.ts` |
| Bridge SMTP client | openclaw-skill | `src/smtp.ts` |
| Agent-friendly output | pm-cli | `internal/output/formatter.go` |
| Error classification | proton-cli | `internal/app/app.go` |
| Card encryption | proton-cli | `internal/pgp/cards.go` |
| Drive encryption | proton-webdav | entire `proton-drive` library |
| Session persistence | proton-tui | `src/tokens.rs` |
| Multi-profile config | proton-cli | `internal/config/` |
---
## 5. Dependency Matrix for Hermes-Proton
| Component | Recommended Library | Version | Source |
|-----------|-------------------|---------|--------|
| Go SRP | `github.com/ProtonMail/go-srp` | v0.0.7 | Official |
| Go API Client | `github.com/ProtonMail/go-proton-api` | latest | Official |
| OpenPGP/Crypto | `github.com/ProtonMail/go-crypto` | v1.4.1 | Official |
| Go OpenPGP (high-level) | `github.com/ProtonMail/gopenpgp/v2` | v2.10.0 | Official |
| Rust SRP | `num-bigint` + `sha2` + `bcrypt` | latest | proton-tui auth.rs pattern |
| Node IMAP | `imap` | latest | openclaw-skill pattern |
| Node SMTP | `nodemailer` | latest | openclaw-skill pattern |
| Node Mail Parse | `mailparser` | latest | openclaw-skill pattern |
| OS Keyring (Go) | `zalando/go-keyring` | latest | pm-cli pattern |
| Embedded DB | `go.etcd.io/bbolt` | v1.4.3 | hydroxide pattern |
| Webview (Go) | webview/webview | — | proton-cli HV pattern |