Compare commits
No commits in common. "29d83717124251941f6ff49201f262d2fe9f31f7" and "27592f710bb53f5e0608f18cf61f497fe6099f67" have entirely different histories.
29d8371712
...
27592f710b
2 changed files with 0 additions and 569 deletions
|
|
@ -1,11 +1,3 @@
|
||||||
---
|
|
||||||
title: hermes-proton
|
|
||||||
description: Hermes Agent runtime + Proton product suite integration — skills, plugins, and MCP tools for Proton Mail, Drive, Pass, VPN, Calendar
|
|
||||||
state: prototype
|
|
||||||
created: 2026-06-08
|
|
||||||
updated: 2026-06-08
|
|
||||||
---
|
|
||||||
|
|
||||||
# hermes-proton
|
# hermes-proton
|
||||||
|
|
||||||
> Hermes Agent runtime + Proton product suite integration — skills, plugins, and MCP tools for Proton Mail, Drive, Pass, VPN, Calendar.
|
> Hermes Agent runtime + Proton product suite integration — skills, plugins, and MCP tools for Proton Mail, Drive, Pass, VPN, Calendar.
|
||||||
|
|
|
||||||
|
|
@ -1,561 +0,0 @@
|
||||||
# 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 |
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue