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:

  1. Implement hidden input safely in a terminal.
  2. Encrypt and decrypt secrets at rest.
  3. Store secrets in a user-specific location with safe defaults.
  4. Avoid accidental secret exposure in logs or pipes.
  5. 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 (prints KEY=VALUE lines)

3.2 Functional Requirements

  1. Hidden Input: Prompt for secret value without echo.
  2. Encryption: Store secrets encrypted on disk.
  3. Key Store: Keep data in user config/data directory.
  4. Safe Output: get prints to STDOUT only when explicitly called.
  5. Shell Integration: export prints lines for eval usage.

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

  1. Derive key from passphrase or keychain.
  2. Generate nonce per secret.
  3. Encrypt plaintext and store nonce + ciphertext.
  4. Decrypt on get with 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

  1. Hidden input and terminal echo control
  2. Symmetric encryption basics (nonce, key, ciphertext)
  3. Key derivation (PBKDF2/Argon2)
  4. Safe output practices

5.5 Questions to Guide Your Design

  1. Where does the encryption key come from?
  2. How do you rotate keys or change passphrase?
  3. 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

  1. What is the difference between encoding and encryption?
  2. Why do you need a nonce for AES-GCM?
  3. 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

  1. Setting a secret does not echo input.
  2. Vault file contains no plaintext secrets.
  3. Wrong passphrase returns a clear error.

6.3 Test Data

Key: API_KEY
Value: sk_test_123

Expected:

  • env-vault get API_KEY returns sk_test_123
  • strings vault.json does 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 --clipboard support

8.3 Advanced Extensions

  • Integrate OS keychain
  • Add key rotation workflow

9. Real-World Connections

  • .env management in dev environments
  • Secure CLI workflows for deployment keys

10. Resources

  • libsodium or standard crypto docs
  • golang.org/x/term for 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