Architecture
Tauri v2 desktop app. All crypto runs in Rust — no secrets touch JavaScript.
System Overview
Cargo.toml (workspace)
├── crates/core/ ← shared Rust library
│ ├── crypto AES-256-GCM + Argon2id
│ ├── scanner 3-layer Surgical Butler
│ ├── vault zip + SHA-256 hashing
│ ├── recovery BIP39 mnemonic
│ ├── meta project + config management
│ ├── supabase HTTP push/pull
│ ├── file_sync .envbutler file export/import + folder sync
│ ├── team invite token generation/parsing
│ └── ci_token CI/CD service tokens
├── crates/cli/ ← terminal binary (clap)
└── src-tauri/ ← desktop app (Tauri → core)
┌─────────────────┐ ┌──────────────┐
│ Desktop GUI │ │ CLI Binary │
│ React + Tauri │ │ clap │
└────────┬────────┘ └──────┬───────┘
│ invoke() │ direct call
└────────┬─────────┘
┌───────▼───────┐
│ env-butler │
│ -core │
└───┬───┬───┬───┘
│ │ │
┌────────┘ │ └────────┐
▼ ▼ ▼
Supabase Cloud Folder .envbutler
(HTTPS) (Drive/iCloud) (file)Rust Modules
crypto
AES-256-GCM encryption + Argon2id key derivation. All encryption/decryption happens here.
scanner
3-layer Surgical Butler — allowlist, content fingerprint, and push preview.
vault
Zips .env files into an archive, computes SHA-256 hash for conflict detection.
recovery
BIP39 mnemonic generation and Master Key derivation from 24-word phrase.
meta
Project and config management — projects.json and config.json in ~/.env-butler/.
supabase
HTTP client (reqwest + TLS) for uploading/downloading encrypted blobs.
file_sync
Export/import .envbutler files + folder-based sync (Google Drive, iCloud, Dropbox).
team
Invite token generation and parsing for team vault sharing.
ci_token
Service tokens for non-interactive CI/CD pulls via ENVBUTLER_TOKEN env var.
Push Flow
- Scan — Scanner finds
.env*files using allowlist, blocks SSH keys / certificates / binaries / files > 50KB - Preview — Non-skippable modal shows every file, variable count, and sensitive key warnings
- Package — Vault zips all allowed files and computes SHA-256 hash
- Encrypt — Crypto module derives a key from your Master Key via Argon2id, encrypts the zip with AES-256-GCM
- Upload — Supabase module upserts the encrypted blob + hash to your vault table
Pull Flow
- Fetch — Downloads encrypted blob + remote hash from Supabase
- Compare — Computes local hash and compares with remote to detect conflicts
- Resolve — Four states:
InSync,SafePull,PushReminder,Conflict - Diff — On conflict: decrypts remote, parses both sides, shows variable-level masked diff
- Write — User approves → files written to project directory
Tech Stack
| Layer | Technology |
|---|---|
| Desktop app | Tauri v2 + Rust |
| CLI | Rust + Clap |
| Frontend | React + TypeScript + Tailwind CSS |
| Encryption | AES-256-GCM + Argon2id |
| Recovery | BIP39 (tiny-bip39) |
| Storage | Supabase / Google Drive / iCloud / Dropbox |
| CI/CD | GitHub Actions + service tokens |
Supabase Schema
vault ( id UUID PRIMARY KEY project_slug TEXT UNIQUE NOT NULL encrypted_blob TEXT NOT NULL -- base64-encoded encrypted zip plaintext_hash TEXT NOT NULL -- SHA-256 of unencrypted zip metadata JSONB -- reserved for future use created_at TIMESTAMPTZ updated_at TIMESTAMPTZ -- auto-updated via trigger )
Row Level Security is enabled. The anon role is denied all access — only requests authenticated with the Service Role Key can read/write vault data. Safe for self-hosted single-user deployments.