Project 5: env-vault
Build a CLI for managing secrets with hidden input and secure local storage.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2 (Intermediate) |
| Time Estimate | 1 week |
| Language | Go (Alternatives: Rust, Python) |
| Prerequisites | Project 2 or basic config/persistence |
| Key Topics | Hidden input, encryption, key storage |
1. Learning Objectives
By completing this project, you will:
- Implement hidden input safely in a terminal.
- Encrypt and decrypt secrets at rest.
- Store secrets in a user-specific location with safe defaults.
- Avoid accidental secret exposure in logs or pipes.
- Build a minimal secrets workflow for shell usage.
2. Theoretical Foundation
2.1 Core Concepts
- Hidden Input: Password prompts should disable echo so secrets are not visible or stored in shell history.
- Encryption at Rest: Secrets must be encrypted before writing to disk. Encoding (base64) is not encryption.
- Key Management: The encryption key can come from a passphrase, env var, or OS keychain. Each has tradeoffs.
- Least Exposure: Secrets should only be printed on explicit commands and should default to STDERR for warnings.
2.2 Why This Matters
Secrets are routinely leaked via logs, terminals, or dotfiles. A secure CLI helps prevent common mistakes without making workflows unusable.
2.3 Historical Context / Background
Tools like pass, gopass, and aws-vault exist because .env files and shell exports are risky. Your tool introduces the same safety mindset in a smaller scope.
2.4 Common Misconceptions
- “Base64 is encryption”: It is reversible encoding only.
- “Local files are safe”: Backups and sync tools can spread secrets.
3. Project Specification
3.1 What You Will Build
A CLI tool named env-vault that can:
- Add secrets:
env-vault set API_KEY - Retrieve secrets:
env-vault get API_KEY - List keys:
env-vault list - Export:
env-vault export(printsKEY=VALUElines)
3.2 Functional Requirements
- Hidden Input: Prompt for secret value without echo.
- Encryption: Store secrets encrypted on disk.
- Key Store: Keep data in user config/data directory.
- Safe Output:
getprints to STDOUT only when explicitly called. - Shell Integration:
exportprints lines forevalusage.
3.3 Non-Functional Requirements
- Security: No plaintext secrets in storage.
- Reliability: Corrupt file does not crash the tool.
- Usability: Clear warnings and confirmations.
3.4 Example Usage / Output
$ env-vault set API_KEY
Enter value (hidden):
Saved API_KEY
3.5 Real World Outcome
A typical flow looks like:
$ env-vault set API_KEY
Enter value (hidden):
Saved API_KEY
$ env-vault list
API_KEY
$ env-vault get API_KEY
sk_live_1234567890
When you run env-vault export, the output is suitable for eval:
$ env-vault export
API_KEY=sk_live_1234567890
4. Solution Architecture
4.1 High-Level Design
+--------------+ +-----------------+ +------------------+
| CLI Parser | --> | Crypto Layer | --> | Encrypted Store |
+--------------+ +-----------------+ +------------------+
| |
+-------------------+-- Key derivation
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Prompt | Hidden input | Terminal echo control |
| Crypto | Encrypt/decrypt | AES-GCM vs ChaCha20 |
| Store | Persist secrets | File format and location |
| Export | Emit env lines | STDOUT only on request |
4.3 Data Structures
type Vault struct {
Version int
Entries map[string]EncryptedEntry
}
type EncryptedEntry struct {
Nonce string
Ciphertext string
}
4.4 Algorithm Overview
Key Algorithm: Encrypt on write
- Derive key from passphrase or keychain.
- Generate nonce per secret.
- Encrypt plaintext and store nonce + ciphertext.
- Decrypt on
getwith the same key.
Complexity Analysis:
- Time: O(1) per secret
- Space: O(N) for N secrets
5. Implementation Guide
5.1 Development Environment Setup
mkdir env-vault && cd env-vault
go mod init env-vault
5.2 Project Structure
env-vault/
├── cmd/
│ ├── set.go
│ ├── get.go
│ └── list.go
├── internal/
│ ├── crypto/
│ └── store/
└── README.md
5.3 The Core Question You Are Answering
“How can a CLI handle secrets without leaking them by default?”
5.4 Concepts You Must Understand First
- Hidden input and terminal echo control
- Symmetric encryption basics (nonce, key, ciphertext)
- Key derivation (PBKDF2/Argon2)
- Safe output practices
5.5 Questions to Guide Your Design
- Where does the encryption key come from?
- How do you rotate keys or change passphrase?
- What happens if the vault file is corrupted?
5.6 Thinking Exercise
Imagine a user runs env-vault get API_KEY | pbcopy. Where should warnings be printed, and to which stream?
5.7 The Interview Questions They Will Ask
- What is the difference between encoding and encryption?
- Why do you need a nonce for AES-GCM?
- How do you avoid leaking secrets in logs?
5.8 Hints in Layers
Hint 1: Start with plaintext storage to validate CLI flow.
Hint 2: Add encryption using a well-tested library.
Hint 3: Store metadata (salt, nonce) alongside ciphertext.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Practical crypto | “Serious Cryptography” | Ch. 4-6 |
| Secure shell tools | “Effective Shell” | Security sections |
5.10 Implementation Phases
Phase 1: Foundation (2-3 days)
Goals:
- CLI flow for set/get
- Hidden input prompt
Checkpoint: Secrets saved and retrieved (plaintext).
Phase 2: Security (2-3 days)
Goals:
- Encrypt on write
- Decrypt on read
Checkpoint: Vault file contains no plaintext secrets.
Phase 3: Polish (1-2 days)
Goals:
- Export command
- Safe error handling
Checkpoint: env-vault export prints KEY=VALUE lines.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Key source | passphrase vs keychain | passphrase + env override | Portable |
| Crypto | AES-GCM vs ChaCha20 | AES-GCM | Widely available |
| Storage | single JSON file | JSON | Simple and inspectable |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Crypto round-trip | encrypt/decrypt |
| Integration Tests | CLI | set/get/list |
| Edge Cases | Corrupt vault | error handling |
6.2 Critical Test Cases
- Setting a secret does not echo input.
- Vault file contains no plaintext secrets.
- Wrong passphrase returns a clear error.
6.3 Test Data
Key: API_KEY
Value: sk_test_123
Expected:
env-vault get API_KEYreturnssk_test_123strings vault.jsondoes not reveal the secret
7. Common Pitfalls and Debugging
| Pitfall | Symptom | Solution |
|---|---|---|
| Secrets in logs | Sensitive output | Print warnings to STDERR |
| Missing nonce | Decrypt fails | Store nonce per entry |
| Weak key derivation | Brute force risk | Use PBKDF2/Argon2 |
8. Extensions and Challenges
8.1 Beginner Extensions
- Add
env-vault delete KEY - Add
env-vault rename OLD NEW
8.2 Intermediate Extensions
- Add TTL expiration for secrets
- Add
--clipboardsupport
8.3 Advanced Extensions
- Integrate OS keychain
- Add key rotation workflow
9. Real-World Connections
.envmanagement in dev environments- Secure CLI workflows for deployment keys
10. Resources
- libsodium or standard crypto docs
golang.org/x/termfor hidden input
11. Self-Assessment Checklist
- I can explain how the secret is encrypted
- I can describe how the key is derived
12. Submission / Completion Criteria
Minimum Viable Completion:
- Hidden input works
- Encrypted storage works
Full Completion:
- Export and list commands
Excellence (Going Above and Beyond):
- Key rotation and keychain integration