-
Notifications
You must be signed in to change notification settings - Fork 0
Whitepaper
Zephyrus CLI is a decentralized, end-to-end encrypted file storage system that leverages GitHub as a distributed storage backend. This whitepaper provides a comprehensive technical overview of the architecture, cryptographic foundations, authentication mechanisms, and operational pipelines that enable secure, trustless file management in a cloud-agnostic manner.
Traditional cloud storage solutions present several fundamental security and privacy challenges:
- Vendor Lock-in: Proprietary encryption schemes create dependency on specific vendors
- Key Management Ambiguity: Users cannot verify where encryption keys are stored or how they're managed
- Zero-Knowledge Proof Limitations: Even "encrypted" services require trust in the service provider's implementation
- Cost Barriers: Reliable encrypted storage remains prohibitively expensive for individual users
- Infrastructure Dependency: Centralized systems create single points of failure
- Privacy Erosion: Metadata leakage through access patterns, file sizes, and timestamps reveals sensitive information
Zephyrus CLI addresses these challenges through:
- Client-Side Encryption: All encryption occurs on the user's machine before transmission
- Distributed Trust: Leverages GitHub's infrastructure as storage, eliminating specialized encryption service providers
- Cryptographic Transparency: Uses industry-standard AES-256-GCM and PBKDF2 with publicly auditable parameters
- Key Ownership: Users maintain exclusive control over all encryption keys
- Stateless Architecture: Supports both persistent and ephemeral session modes
- Fine-Grained Sharing: Per-file encryption enables selective sharing without exposing the entire vault
Zephyrus CLI protects against the following threats:
| Threat | Mitigation |
|---|---|
| GitHub repository compromise | All files encrypted with user-controlled keys; GitHub never sees plaintext |
| Network eavesdropping | SSH encryption for transport; all data encrypted at rest |
| Malware on client machine | Vault password never stored; requires interactive entry for authentication |
| Brute force key derivation | PBKDF2 with 100,000 iterations and random salt makes brute force computationally expensive |
| Chosen-plaintext attacks | Authenticated encryption (GCM) with per-message nonces prevents forgery |
| Replay attacks | Git commit hashes and timestamps in vault index prevent file replacement |
- Local security: User's machine is not compromised with keyloggers or screen capture malware
- Password strength: Vault password has sufficient entropy (minimum 12 characters recommended)
- SSH key security: SSH private key is protected by OS permissions and, ideally, a passphrase
- GitHub availability: GitHub remains available for repository access (mirrors can mitigate)
- Time synchronization: Local system clock is reasonably accurate (within a few minutes)
- Physical access to hardware
- Quantum computing attacks (applicable to all current encryption)
- GitHub infrastructure compromise at a level that alters stored data
Zephyrus CLI implements a layered encryption model:
┌─────────────────────────────────────┐
│ File Content Encryption │
│ (Per-File Key + AES-256-GCM) │
├─────────────────────────────────────┤
│ File Key Encryption │
│ (Vault Password + AES-256-GCM) │
├─────────────────────────────────────┤
│ Index & SSH Key Encryption │
│ (Vault Password + AES-256-GCM) │
├─────────────────────────────────────┤
│ Key Derivation │
│ (PBKDF2 with 100,000 iterations) │
└─────────────────────────────────────┘
Algorithm: PBKDF2-SHA256
Parameters:
- Iterations: 100,000
- Hash function: SHA-256
- Output size: 32 bytes (256 bits)
- Salt size: 16 bytes (128 bits), randomly generated per encryption
Security Justification:
- 100,000 iterations recommended by NIST SP 800-132 (as of 2010)
- Modern estimates suggest this provides ~2^65 computational complexity against brute force
- SHA-256 is cryptographically secure with no known vulnerabilities
Formula:
DK = PBKDF2(HMAC-SHA256, password, salt, 100000, 32)
Algorithm: AES-256 in Galois/Counter Mode (GCM)
Parameters:
- Key size: 256 bits (32 bytes)
- Nonce size: 96 bits (12 bytes), randomly generated per encryption
- Authentication tag: 128 bits (16 bytes)
Encryption Process:
Ciphertext = AES-GCM-Encrypt(key, nonce, plaintext, AAD)
Output = Salt || Nonce || Ciphertext || AuthTag
Why GCM?:
- Provides authenticated encryption with associated data (AEAD)
- Detects tampering or corruption automatically
- Nonce misuse-resistant when used with random nonces
- Hardware acceleration available on modern CPUs (AES-NI)
Each file has a unique 32-byte encryption key generated via:
fileKey = CryptoRand(32 bytes)Per-File Key Storage:
{
"documents": {
"type": "folder",
"contents": {
"report.pdf": {
"type": "file",
"realName": "a3f2e1c9d4b6f8e2",
"fileKey": "hex(AES-256-GCM(vaultPassword, randomSalt, fileKey))"
}
}
}
}Benefits of Per-File Keys:
- Selective sharing without exposing vault password
- File rotation without affecting other files
- Granular access control for multi-user scenarios
- Mitigation of attacks exploiting patterns across multiple files
The vault index is encrypted with the vault password:
IndexCiphertext = AES-256-GCM(DerivedKey, randomNonce, IndexJSON, NULL)
Where DerivedKey is derived from the vault password using PBKDF2.
Index Content:
{
"folder_name": {
"type": "folder",
"contents": {
"file_name": {
"type": "file",
"realName": "hex_storage_id",
"fileKey": "encrypted_file_key_hex"
}
}
}
}The index serves as the manifest for all vault contents, enabling:
- Directory traversal without downloading entire repository
- Fast file lookup
- Metadata organization (folder hierarchy)
- File deduplication through consistent storage IDs
During setup, the user's SSH private key is encrypted with the vault password:
EncryptedSSHKey = AES-256-GCM(DerivedKey, randomNonce, SSHPrivateKeyBytes, NULL)
This encrypted key is stored at .config/key in the vault repository, serving as a backup mechanism while ensuring GitHub cannot access the raw key.
sequenceDiagram
participant User
participant Client as Zephyrus CLI
participant GitHub as GitHub Repo
participant Remote as .config/key
User->>Client: setup username ssh_key_path
Client->>GitHub: Verify .zep repo exists
Note over Client: Generate vault password prompt
User->>Client: vault_password
Client->>Client: PBKDF2(vault_password, random_salt)
Note over Client: Derive 256-bit key
Client->>Client: Read SSH key from disk
Client->>Client: AES-256-GCM encrypt SSH key
Client->>Remote: Push encrypted SSH key
Client->>Client: Success, setup complete
Two operational modes are supported:
sequenceDiagram
participant User
participant Client as Zephyrus CLI
participant Disk as Local Disk
participant GitHub as GitHub
participant Index as .config/index
User->>Client: connect username
User->>Client: vault_password
Client->>Client: PBKDF2(vault_password)
Note over Client: Derive key K_vault
Client->>GitHub: Fetch .config/key (encrypted)
GitHub-->>Client: EncryptedKey
Client->>Client: AES-256-GCM decrypt SSH key
Note over Client: Using K_vault
Client->>GitHub: SSH clone, fetch .config/index (encrypted)
GitHub-->>Client: EncryptedIndex
Client->>Client: AES-256-GCM decrypt index using K_vault
Note over Client: Index now in memory
Client->>Disk: Write zep.conf (index + session data)
Note over Disk: zep.conf = JSON {Username, Password, RawKey, Index}
Client->>User: ✔ Connected
zep.conf Structure:
{
"username": "john",
"password": "vault_password_plaintext",
"raw_key": "ssh_private_key_bytes_base64",
"index": { /* encrypted index as JSON */ }
}zep.conf contains the vault password in plaintext. It should be treated as a sensitive session token and deleted after use.
graph LR
A["User Command<br/>-u username"] --> B["Prompt for Password"]
B --> C["Derive Key via PBKDF2"]
C --> D["Fetch & Decrypt SSH Key"]
D --> E["Single Operation"]
E --> F["Clear Memory"]
F --> G["No Persistent State"]
Advantages:
- No credentials written to disk
- Suitable for shared machines
- One-time operations without session overhead
Disadvantages:
- Index re-fetched for every operation
- Higher latency for bulk operations
- No caching benefits
Key Derivation Security:
The vault password protects three critical assets:
-
SSH private key (in
.config/key) -
Vault index (in
.config/index) - Per-file encryption keys (in index)
Attack complexity:
- Offline attack (password guessing): 100,000 PBKDF2 iterations = ~100ms per attempt; 10^6 attempts per day = 10 days to exhaust 1M passwords
- Online attack (repository compromise): All encrypted data remains secure; only risk is password guessing
graph TD
A["User: upload<br/>local_path vault_path"] --> B["Authenticate<br/>Derive K_vault"]
B --> C["Read file from disk"]
C --> D{File exists<br/>in index?}
D -->|Yes| E["Retrieve existing<br/>file key"]
D -->|No| F["Generate random<br/>32-byte file key"]
E --> G["Encrypt file with<br/>file key"]
F --> G
G --> H["Encrypt file key<br/>with K_vault"]
H --> I["Update index entry<br/>with encrypted key"]
I --> J["Encrypt entire index<br/>with K_vault"]
J --> K["Prepare git push:<br/>file + index"]
K --> L["SSH auth +<br/>git push"]
L --> M["Update local<br/>zephyrus.conf"]
M --> N["✔ Upload successful"]
Detailed Steps:
- File Reading: Load entire file into memory
-
Key Selection:
- Existing file: Decrypt stored file key using K_vault
- New file: Generate cryptographically secure random 32 bytes
-
File Encryption:
EncryptedFile = AES-256-GCM(FileKey, randomNonce, FileContent, NULL) Output = Nonce || EncryptedFile || AuthTag -
Key Encryption:
EncryptedFileKey = AES-256-GCM(K_vault, randomSalt, FileKey, NULL) Output = Salt || Nonce || EncryptedFileKey || AuthTag -
Index Update: Modify
VaultIndex[path].FileKey = hex(EncryptedFileKey) - Index Encryption: Encrypt entire updated index
-
Git Push: Two-file commit
- File:
a3f2e1c9d4b6f8e2(random hex storage ID) - Index:
.config/index
- File:
Data Flow:
PlaintextFile
↓
[Generate FileKey]
↓
AES-256-GCM(FileKey)
↓
EncryptedFile → GitHub
↓
[Encrypt FileKey with K_vault]
↓
Update Index[path].FileKey
↓
AES-256-GCM(Index, K_vault)
↓
EncryptedIndex → GitHub
Complexity: O(n) where n = file size + index size
graph TD
A["User: download<br/>vault_path output_path"] --> B["Authenticate<br/>Derive K_vault"]
B --> C["Parse vault_path"]
C --> D["Navigate index<br/>find entry"]
D --> E{Found?}
E -->|No| F["Error: file not found"]
E -->|Yes| G["Retrieve RealName<br/>& encrypted FileKey"]
G --> H["Decrypt FileKey<br/>using K_vault"]
H --> I["SSH fetch EncryptedFile<br/>from GitHub"]
I --> J["AES-256-GCM decrypt<br/>using FileKey"]
J --> K["Verify GCM auth tag"]
K --> L{Valid?}
L -->|No| M["Error: corrupted/invalid"]
L -->|Yes| N["Write to output_path"]
N --> O["✔ Download successful"]
Critical Security Check: GCM authentication tag verification prevents:
- Bit-flip attacks
- Replay attacks with old versions
- Files modified in transit or on server
Complexity: O(n) where n = file size
graph TD
A["User: delete vault_path"] --> B["Authenticate"]
B --> C["Parse vault_path"]
C --> D["Navigate index"]
D --> E{Type?}
E -->|File| F["Collect RealName"]
E -->|Folder| G["Recursive collection<br/>of all files"]
F --> H["Clone repo<br/>to memory"]
G --> H
H --> I["Remove files from<br/>git worktree"]
I --> J["Remove from index"]
J --> K["Encrypt updated index"]
K --> L["Commit & push"]
L --> M["✔ Deletion complete"]
Recursive Folder Deletion:
func collectIDs(entry Entry) {
if entry.Type == "file" {
idsToDelete.append(entry.RealName)
} else {
for _, subEntry := range entry.Contents {
collectIDs(subEntry) // Recursion
}
}
}Data Retention: Deleted files remain in git history but are inaccessible from the vault index. They can be permanently removed via purge command (force-push empty commit).
sequenceDiagram
participant Owner as File Owner
participant Client as Zephyrus CLI
participant Index as Vault Index
participant Recipient
Owner->>Client: share vault_path
Owner->>Client: vault_password
Client->>Index: Locate file entry
Index-->>Client: FileKey (encrypted)
Client->>Client: Decrypt FileKey with K_vault
Note over Client: Plaintext FileKey in memory
Client->>Client: Generate ShareString
Note over Client: username:RealName:hex(FileKey)
Client->>Owner: Display ShareString
Owner->>Recipient: Send ShareString via secure channel
Recipient->>Client: download _ output.pdf --shared "shareString"
Client->>Client: Parse ShareString
Note over Client: Extract username, RealName, FileKey
Client->>Client: Fetch file from vault
Client->>Client: Decrypt with extracted FileKey
Client->>Recipient: ✔ Download complete
Share String Format:
<username>:<storage_id>:<file_key_hex>
Example:
john:a3f2e1c9d4b6f8e2:abc123def456789abc123def456789ab
Key Properties:
- Single-file access only
- No access to index or vault structure
- Can be revoked by re-uploading file (new FileKey)
- No way to audit who accessed the file
.zep/
├── .config/
│ ├── key (encrypted SSH private key)
│ └── index (encrypted vault index)
├── a3f2e1c9d4b6f8e2 (encrypted file 1)
├── f1e2d3c4b5a6f7e8 (encrypted file 2)
├── c5d7a1f3e4b2f9e0 (encrypted file 3)
└── .git/ (git metadata)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"folder_name": {
"type": "object",
"properties": {
"type": { "enum": ["folder"] },
"contents": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
"properties": {
"type": { "enum": ["file"] },
"realName": { "type": "string" },
"fileKey": { "type": "string" }
},
"required": ["type", "realName", "fileKey"]
},
{ "$ref": "#" }
]
}
}
},
"required": ["type"]
}
}
}All encrypted data follows this structure:
[0:16] Salt (16 bytes)
[16:28] Nonce (12 bytes)
[28:end-16] Ciphertext (variable length)
[end-16:end] Authentication Tag (16 bytes)
Example for a 1KB file:
[0:16] 16 bytes salt
[16:28] 12 bytes nonce
[28:1044] 1020 bytes encrypted file content
[1044:1060] 16 bytes GCM auth tag
Total: 1060 bytes
Overhead: ~60 bytes per file (5.8% for 1KB file, decreases with larger files)
Each operation results in at most two git objects:
- File object: The encrypted file content (if new or updated)
-
Index object: The encrypted
.config/index
Commit Message Format:
Zephyrus: Updated Vault
Author: Zephyrus <bot@zephyrus.local>
Timestamp: 2026-02-04T15:30:45Z
- Audit Trail: Complete history of all operations
-
Rollback: Revert to previous vault state via
git reset --hard - Verification: Commit hashes ensure integrity
- Collaboration: Git's distributed nature enables multiple devices
For performance, Zephyrus uses shallow cloning:
git.Clone(
storer, fs,
&git.CloneOptions{
Depth: 1, // Only fetch latest commit
...
}
)Trade-off:
- Benefit: 95% reduction in bandwidth for large histories
- Cost: Cannot see file history (by design, for privacy)
Scenario: Individual stores financial records, medical documents, legal files
Workflow:
1. Setup vault with GitHub free account
2. Upload documents to vault
3. Encrypted at rest on GitHub (untrusted storage)
4. Download only when needed
5. No subscription costs; only GitHub storage (free tier: 1GB, adequate for documents)
Scenario: Consultant shares confidential reports with client
Workflow:
1. Consultant creates vault
2. Uploads report to vault/client_name/
3. Uses 'share' command to generate access token
4. Sends token via encrypted email or secure messaging
5. Client downloads using --shared flag
6. Consultant can revoke by deleting/re-uploading file
7. No third-party file sharing service needed
Scenario: Freelancer works across desktop, laptop, tablet
Workflow:
Device A:
- ./zep connect username
- ./zep upload project.docx projects/current.docx
Device B (same day):
- ./zep connect username (same vault password)
- Vault index already on GitHub, synced to Device B
- ./zep download projects/current.docx
All files encrypted end-to-end; GitHub sees only encrypted blobs
Scenario: Personal backup strategy
Automated daily backup:
crontab entry: 0 3 * * * /usr/local/bin/zep \
upload -u myusername ~/sensitive_daily.tar.gz \
backups/$(date +\%Y-\%m-\%d).tar.gz
Benefits:
- No recurring subscription fees
- Geographic distribution (GitHub's infrastructure)
- Immutable encryption: Cannot be breached to expose plaintext
- Auditable through git history
Architecture:
One vault per team
Shared vault password distributed via secure channel
SSH key managed by team lead or CI/CD system
Workflow:
Team member needs document:
- Uses 'share' to generate token for specific file
- Shares token with colleague
- Colleague uses --shared flag to access
- Only that file is accessible, not entire vault
Advantages:
- No single person holds all vault access
- Per-file sharing prevents over-exposure
- Cost is near-zero (GitHub storage)
Alternative to HashiCorp Vault:
# GitHub Actions workflow
env:
NEXUS_USER: ${{ secrets.NEXUS_USER }}
NEXUS_PASS: ${{ secrets.NEXUS_PASS }} # 32-char random password
NEXUS_SSH_KEY: ${{ secrets.NEXUS_SSH_KEY }}
steps:
- name: Download deployment key
run: |
echo "$NEXUS_SSH_KEY" > /tmp/key
zep download -u $NEXUS_USER \
secrets/deploy.key /tmp/deploy_key
chmod 600 /tmp/deploy_key
- name: Deploy with key
run: |
export SSH_KEY=/tmp/deploy_key
./deploy.shAdvantages over traditional secrets managers:
- No infrastructure to maintain
- Encrypted at GitHub's infrastructure
- Audit trail via git commits
- Cost: $0 to $5/month depending on GitHub tier
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Upload | O(n) file size | O(n) file in memory |
| Download | O(n) file size | O(n) file in memory |
| Delete | O(m) manifest size | O(m) index in memory |
| List | O(k) folder contents | O(k) directory entries |
| Search | O(total files) | O(1) during traversal |
| Share | O(1) lookup | O(1) string generation |
| Component | Latency |
|---|---|
| SSH authentication | 50-100ms |
| Git clone (shallow) | 200-500ms |
| File read from disk | 10-50ms |
| AES-256-GCM encryption | 5-20ms |
| Index encryption | 1-5ms |
| Git push | 100-300ms |
| Total | ~500-1000ms |
| Metric | Value |
|---|---|
| Overhead per file | ~60 bytes (encryption metadata) |
| Index size | ~100 bytes per file entry |
| Git overhead | ~1-2KB per commit |
| Example: 100 files, 10MB total | ~10.05MB stored |
Assumption: Attacker gains read access to .zep repository
What attacker can do:
- Download all encrypted files and index
- Observe file sizes and access patterns
- See git commit history and timestamps
What attacker cannot do:
- Decrypt files without vault password
- Modify files without detection (GCM auth tag)
- Access plaintext index
Mitigation: PBKDF2 protects against brute-force; 100,000 iterations × 100ms = 10M password attempts per day max
Assumption: Malware exists on user's machine
What malware can do:
-
Observe vault password during entry
-
Read SSH private key from memory
-
Modify files before encryption
-
Recover password after zep.conf is deleted
-
Decrypt archived files if malware installed after encryption
Mitigations:
- Use
--sharedflag for single-file operations (no persistent session) - Delete
zep.confafter sensitive operations - Use SSH key passphrase for additional protection
Assumption: Attacker monitors file sizes and access times
Vulnerability: File sizes leaked in storage IDs (but randomized by encryption)
Mitigation:
- File sizes partially obfuscated by encryption overhead
- No modification time data in index (git provides this separately)
- Symmetric encryption provides ciphertext indistinguishability
Assumption: Future cryptanalysis breaks AES-256 or PBKDF2
Protection Level: 256-bit symmetric encryption offers 2^256 security. To break:
- Classical: 2^128 operations (Grover's algorithm)
- Quantum: No known classical algorithm (quantum attacks not in scope)
func RotateVaultPassword(oldPassword, newPassword string) {
// 1. Decrypt all file keys with old password
// 2. Re-encrypt file keys with new password
// 3. Encrypt SSH key with new password
// 4. Push updated index
}{
"document.pdf": {
"type": "file",
"versions": [
{
"timestamp": "2026-02-04T15:30:45Z",
"realName": "a3f2e1c9d4b6f8e2",
"fileKey": "encrypted_key_v1"
},
{
"timestamp": "2026-02-03T10:15:30Z",
"realName": "f1e2d3c4b5a6f7e8",
"fileKey": "encrypted_key_v2"
}
]
}
}Owner: john
Readers: [alice, bob]
Writers: [alice]
Implemented via:
- Separate indexed file keys per user
- Share tokens with permission bits
Hash(FileContent) → RealName mapping
Enables:
- Multiple users storing same file (bandwidth savings)
- Unchanged files not re-uploaded
- Content-aware sharing
| Feature | Zephyrus CLI | SpiderOak | Google Drive | Sync.com |
|---|---|---|---|---|
| Client-side encryption | ✓ | ✓ | ✗ | ✓ |
| Source code auditable | ✓ (Open) | ✗ (Closed) | ✗ | ✗ |
| Cost | Free | $15/month | Free (2GB) | $7.99/month |
| Per-file sharing | ✓ | ✗ | ✓ | ✓ |
| Storage provider | GitHub | Private | Sync.com | |
| Key management | User | User | Sync.com | |
| Trusted infrastructure | GitHub | SpiderOak | Sync.com |
Zephyrus CLI demonstrates that decentralized encrypted file storage is achievable with existing infrastructure and cryptographic primitives. By leveraging GitHub's reliability and implementing transparent, industry-standard encryption, it provides a zero-cost, auditable alternative to proprietary encrypted storage services.
The architecture prioritizes:
- Security: AES-256-GCM per-file encryption with PBKDF2 key derivation
- Simplicity: No complex PKI or key distribution
- Transparency: Open-source, publicly auditable cryptography
- Usability: Simple CLI interface with interactive and stateless modes
- Cost: Free tier viable for most personal use cases
Future enhancements will address enterprise use cases while maintaining the core principle: users retain exclusive cryptographic control over their data.
- NIST SP 800-132: Password-Based Key Derivation
- FIPS 197: Advanced Encryption Standard (AES)
- NIST SP 800-38D: Recommendation for GCM Mode
- RFC 3394: AES Key Wrap Algorithm
- RFC 4251: The Secure Shell (SSH) Protocol Architecture
- Go Cryptography Packages Documentation: https://golang.org/pkg/crypto/
Document Version: 1.0
Last Updated: February 4, 2026
Status: Final