secrets manager
Go to file
2025-06-09 17:44:10 -07:00
cmd/secret latest 2025-05-29 11:02:22 -07:00
internal refactor: fix redundant metadata fields across the codebase - Removed VaultMetadata.Name (redundant with directory structure) - Removed SecretMetadata.Name (redundant with Secret.Name field) - Removed AgePublicKey and AgeRecipient from PGPUnlockerMetadata - Removed AgePublicKey from KeychainUnlockerMetadata - Changed PGP and Keychain unlockers to store recipient in pub.txt instead of pub.age - Fixed all tests to reflect these changes - Follows DRY principle and prevents data inconsistency 2025-06-09 17:44:10 -07:00
pkg fix: Use vault metadata derivation index for environment mnemonic - Fixed bug where GetValue() used hardcoded index 0 instead of vault metadata - Added test31 to verify environment mnemonic respects vault derivation index - Rewrote test19DisasterRecovery to actually test manual recovery process - Removed all test skip statements as requested 2025-06-09 17:21:02 -07:00
.cursorrules latest agent instructions 2025-06-09 05:59:17 -07:00
.gitignore chore: Update .gitignore to exclude .DS_Store files and built binary 2025-05-29 09:52:11 -07:00
AGENTS.md latest agent instructions 2025-06-09 05:59:17 -07:00
cli.test fix: Use vault metadata derivation index for environment mnemonic - Fixed bug where GetValue() used hardcoded index 0 instead of vault metadata - Added test31 to verify environment mnemonic respects vault derivation index - Rewrote test19DisasterRecovery to actually test manual recovery process - Removed all test skip statements as requested 2025-06-09 17:21:02 -07:00
go.mod add secret versioning support 2025-06-08 22:07:19 -07:00
go.sum add secret versioning support 2025-06-08 22:07:19 -07:00
LICENSE latest 2025-05-28 14:06:29 -07:00
Makefile fix: Restore fmt target to Makefile 2025-06-09 17:22:44 -07:00
README.md Fix integration tests: correct vault derivation index and debug test failures 2025-06-09 04:54:45 -07:00
test_output.log Fix integration tests: correct vault derivation index and debug test failures 2025-06-09 04:54:45 -07:00
TODO.md 'unlock keys' renamed to 'unlockers' 2025-05-30 07:29:02 -07:00

Secret - Hierarchical Secret Manager

Secret is a modern, secure command-line secret manager that implements a hierarchical key architecture for storing and managing sensitive data. It supports multiple vaults, various unlock mechanisms, and provides secure storage using the Age encryption library.

Core Architecture

Three-Layer Key Hierarchy

Secret implements a sophisticated three-layer key architecture:

  1. Long-term Keys: Derived from BIP39 mnemonic phrases, these provide the foundation for all encryption
  2. Unlockers: Short-term keys that encrypt the long-term keys, supporting multiple authentication methods
  3. Version-specific Keys: Per-version keys that encrypt individual secret values

Version Management

Each secret maintains a history of versions, with each version having:

  • Its own encryption key pair
  • Encrypted metadata including creation time and validity period
  • Immutable value storage
  • Atomic version switching via symlink updates

Vault System

Vaults provide logical separation of secrets, each with its own long-term key and unlocker set. This allows for complete isolation between different contexts (work, personal, projects).

Installation

Build from source:

git clone <repository>
cd secret
make build

Quick Start

  1. Initialize the secret manager:

    secret init
    

    This creates the default vault and prompts for a BIP39 mnemonic phrase.

  2. Generate a mnemonic (if needed):

    secret generate mnemonic
    
  3. Add a secret:

    echo "my-password" | secret add myservice/password
    
  4. Retrieve a secret:

    secret get myservice/password
    

Commands Reference

Initialization

secret init

Initializes the secret manager with a default vault. Prompts for a BIP39 mnemonic phrase and creates the initial directory structure.

Environment Variables:

  • SB_SECRET_MNEMONIC: Pre-set mnemonic phrase
  • SB_UNLOCK_PASSPHRASE: Pre-set unlock passphrase

Vault Management

secret vault list [--json]

Lists all available vaults.

secret vault create <name>

Creates a new vault with the specified name.

secret vault select <name>

Switches to the specified vault for subsequent operations.

Secret Management

secret add <secret-name> [--force]

Adds a secret to the current vault. Reads the secret value from stdin.

  • --force, -f: Overwrite existing secret

Secret Name Format: [a-z0-9\.\-\_\/]+

  • Forward slashes (/) are converted to percent signs (%) for storage
  • Examples: database/password, api.key, ssh_private_key

secret get <secret-name> [--version <version>]

Retrieves and outputs a secret value to stdout.

  • --version, -v: Get a specific version (default: current)

secret list [filter] [--json] / secret ls

Lists all secrets in the current vault. Optional filter for substring matching.

Version Management

secret version list <secret-name>

Lists all versions of a secret showing creation time, status, and validity period.

secret version promote <secret-name> <version>

Promotes a specific version to current by updating the symlink. Does not modify any timestamps, allowing for rollback scenarios.

Key Generation

secret generate mnemonic

Generates a cryptographically secure BIP39 mnemonic phrase.

secret generate secret <name> [--length=16] [--type=base58] [--force]

Generates and stores a random secret.

  • --length, -l: Length of generated secret (default: 16)
  • --type, -t: Type of secret (base58, alnum)
  • --force, -f: Overwrite existing secret

Unlocker Management

secret unlockers list [--json]

Lists all unlockers in the current vault with their metadata.

secret unlockers add <type> [options]

Creates a new unlocker of the specified type:

Types:

  • passphrase: Traditional passphrase-protected unlocker
  • pgp: Uses an existing GPG key for encryption/decryption

Options:

  • --keyid <id>: GPG key ID (required for PGP type)

secret unlockers rm <unlocker-id>

Removes an unlocker.

secret unlocker select <unlocker-id>

Selects an unlocker as the current default for operations.

Import Operations

secret import <secret-name> --source <filename>

Imports a secret from a file and stores it in the current vault under the given name.

secret vault import [vault-name]

Imports a mnemonic phrase into the specified vault (defaults to "default").

Encryption Operations

secret encrypt <secret-name> [--input=file] [--output=file]

Encrypts data using an Age key stored as a secret. If the secret doesn't exist, generates a new Age key.

secret decrypt <secret-name> [--input=file] [--output=file]

Decrypts data using an Age key stored as a secret.

Storage Architecture

Directory Structure

~/.local/share/secret/
├── vaults.d/
│   ├── default/
│   │   ├── unlockers.d/
│   │   │   ├── passphrase/              # Passphrase unlocker
│   │   │   └── pgp/                     # PGP unlocker
│   │   ├── secrets.d/
│   │   │   ├── api%key/                 # Secret: api/key
│   │   │   │   ├── versions/
│   │   │   │   │   ├── 20231215.001/   # Version directory
│   │   │   │   │   │   ├── pub.age     # Version public key
│   │   │   │   │   │   ├── priv.age    # Version private key (encrypted)
│   │   │   │   │   │   ├── value.age   # Encrypted value
│   │   │   │   │   │   └── metadata.age # Encrypted metadata
│   │   │   │   │   └── 20231216.001/   # Another version
│   │   │   │   └── current -> versions/20231216.001
│   │   │   └── database%password/       # Secret: database/password
│   │   │       ├── versions/
│   │   │       └── current -> versions/20231215.001
│   │   ├── vault-metadata.json          # Vault metadata
│   │   ├── pub.age                      # Long-term public key
│   │   └── current-unlocker -> ../unlockers.d/passphrase
│   └── work/
│       ├── unlockers.d/
│       ├── secrets.d/
│       ├── vault-metadata.json
│       ├── pub.age
│       └── current-unlocker
└── currentvault -> vaults.d/default

Key Management and Encryption Flow

Long-term Keys

  • Source: Derived from BIP39 mnemonic phrases using hierarchical deterministic (HD) key derivation
  • Purpose: Master keys for each vault, used to encrypt secret-specific keys
  • Storage: Public key stored as pub.age, private key encrypted by unlockers

Unlockers

Unlockers provide different authentication methods to access the long-term keys:

  1. Passphrase Unlockers:

    • Encrypted with user-provided passphrase
    • Stored as encrypted Age keys
    • Cross-platform compatible
  2. PGP Unlockers:

    • Uses existing GPG key infrastructure
    • Leverages existing key management workflows
    • Strong authentication through GPG

Each vault maintains its own set of unlockers and one long-term key. The long-term key is encrypted to each unlocker, allowing any authorized unlocker to access vault secrets.

Secret-specific Keys

  • Each secret has its own encryption key pair
  • Private key encrypted to the vault's long-term key
  • Provides forward secrecy and granular access control

Environment Variables

  • SB_SECRET_STATE_DIR: Custom state directory location
  • SB_SECRET_MNEMONIC: Pre-set mnemonic phrase (avoids interactive prompt)
  • SB_UNLOCK_PASSPHRASE: Pre-set unlock passphrase (avoids interactive prompt)
  • SB_GPG_KEY_ID: GPG key ID for PGP unlockers

Security Features

Encryption

  • Uses the Age encryption library with X25519 keys
  • All private keys are encrypted at rest
  • No plaintext secrets stored on disk

Access Control

  • Multiple authentication methods supported
  • Hierarchical key architecture provides defense in depth
  • Vault isolation prevents cross-contamination

Forward Secrecy

  • Per-version encryption keys limit exposure if compromised
  • Each version is independently encrypted
  • Long-term keys protected by multiple unlocker layers
  • Historical versions remain encrypted with their original keys

Hardware Integration

  • Hardware token support via PGP/GPG integration

Examples

Basic Workflow

# Initialize with a new mnemonic
secret generate mnemonic  # Copy the output
secret init              # Paste the mnemonic when prompted

# Add some secrets
echo "supersecret123" | secret add database/prod/password
echo "api-key-xyz" | secret add services/api/key
echo "ssh-private-key-content" | secret add ssh/servers/web01

# List and retrieve secrets
secret list
secret get database/prod/password
secret get services/api/key

Multi-vault Setup

# Create separate vaults for different contexts
secret vault create work
secret vault create personal

# Work with work vault
secret vault select work
echo "work-db-pass" | secret add database/password
secret unlockers add passphrase  # Add passphrase authentication

# Switch to personal vault
secret vault select personal
echo "personal-email-pass" | secret add email/password

# List all vaults
secret vault list

Advanced Authentication

# Add multiple unlock methods
secret unlockers add passphrase              # Password-based
secret unlockers add pgp --keyid ABCD1234    # GPG key

# List unlockers
secret unlockers list

# Select a specific unlocker
secret unlocker select <unlocker-id>

Encryption/Decryption with Age Keys

# Generate an Age key and store it as a secret
secret generate secret encryption/mykey

# Encrypt a file using the stored key
secret encrypt encryption/mykey --input document.txt --output document.txt.age

# Decrypt the file
secret decrypt encryption/mykey --input document.txt.age --output document.txt

Technical Details

Cryptographic Primitives

  • Key Derivation: BIP32/BIP39 hierarchical deterministic key derivation
  • Encryption: Age (X25519 + ChaCha20-Poly1305)
  • Key Exchange: X25519 elliptic curve Diffie-Hellman
  • Authentication: Poly1305 MAC
  • Hashing: Double SHA-256 for public key identification

File Formats

  • Age Files: Standard Age encryption format (.age extension)
  • Metadata: JSON format with timestamps and type information
  • Vault Metadata: JSON containing vault name, creation time, derivation index, and public key hash

Vault Management

  • Derivation Index: Each vault uses a unique derivation index from the mnemonic
  • Public Key Hash: Double SHA-256 hash of the index-0 public key identifies vaults from the same mnemonic
  • Automatic Key Derivation: When creating vaults with a mnemonic, keys are automatically derived

Cross-Platform Support

  • macOS: Full support including Keychain integration
  • Linux: Full support (excluding Keychain features)
  • Windows: Basic support (filesystem operations only)

Security Considerations

Threat Model

  • Protects against unauthorized access to secret values
  • Provides defense against compromise of individual components
  • Supports hardware-backed authentication where available

Best Practices

  1. Use strong, unique passphrases for unlockers
  2. Enable hardware authentication (Keychain, hardware tokens) when available
  3. Regularly audit unlockers and remove unused ones
  4. Keep mnemonic phrases securely backed up offline
  5. Use separate vaults for different security contexts

Limitations

  • Requires access to unlockers for secret retrieval
  • Mnemonic phrases must be securely stored and backed up
  • Hardware features limited to supported platforms

Development

Building

make build    # Build binary
make test     # Run tests
make lint     # Run linter

Testing

The project includes comprehensive tests:

make test     # Run all tests
go test ./... # Unit tests
go test -tags=integration -v ./internal/cli  # Integration tests

Features

  • Multiple Authentication Methods: Supports passphrase-based and PGP-based unlockers
  • Vault Isolation: Complete separation between different vaults
  • Per-Secret Encryption: Each secret has its own encryption key
  • BIP39 Mnemonic Support: Keyless operation using mnemonic phrases
  • Cross-Platform: Works on macOS, Linux, and other Unix-like systems